From 6c172702be06770566bb1271003f40903a2c7e11 Mon Sep 17 00:00:00 2001 From: Kevin Giszewski Date: Wed, 27 Sep 2017 11:32:36 -0400 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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 e808360653b6c3fb2af18bd915d53341d0940266 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Jan 2018 10:46:02 +0100 Subject: [PATCH 05/41] Can navigate trough global search results --- .../src/controllers/search.controller.js | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 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..7f4af335a8 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -36,22 +36,33 @@ function SearchController($scope, searchService, $log, $location, navigationServ var group = undefined; + var groupNames = []; var groupIndex = -1; var itemIndex = -1; $scope.selectedItem = undefined; + function iterateResults(up) { + //default group + if (!group) { + + for (var g in $scope.groups) { + if ($scope.groups.hasOwnProperty(g)) { + groupNames.push(g); - function iterateResults(up) { - //default group - if (!group) { - group = $scope.groups[0]; + } + } + + //Sorting to match the groups order + groupNames.sort(); + + group = $scope.groups[groupNames[0]]; groupIndex = 0; } if (up) { if (itemIndex === 0) { if (groupIndex === 0) { - gotoGroup($scope.groups.length - 1, true); + gotoGroup(Object.keys($scope.groups).length - 1, true); } else { gotoGroup(groupIndex - 1, true); } @@ -62,7 +73,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ if (itemIndex < group.results.length - 1) { gotoItem(itemIndex + 1); } else { - if (groupIndex === $scope.groups.length - 1) { + if (groupIndex === Object.keys($scope.groups).length - 1) { gotoGroup(0); } else { gotoGroup(groupIndex + 1); @@ -73,7 +84,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ function gotoGroup(index, up) { groupIndex = index; - group = $scope.groups[groupIndex]; + group = $scope.groups[groupNames[groupIndex]]; if (up) { gotoItem(group.results.length - 1); @@ -94,7 +105,14 @@ function SearchController($scope, searchService, $log, $location, navigationServ $scope.$apply(function () { $scope.hasResults = false; if ($scope.searchTerm) { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + + //Resetting for brand new search + group = undefined; + groupNames = []; + groupIndex = -1; + itemIndex = -1; + $scope.isSearching = true; navigationService.showSearch(); $scope.selectedItem = undefined; @@ -114,7 +132,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ var filtered = {}; _.each(result, function (value, key) { if (value.results.length > 0) { - filtered[key] = value; + filtered[key] = value; } }); $scope.groups = filtered; From ab9e029525a20c8fd87200425c51e400e4ceb830 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 23 Jan 2018 13:04:57 +0100 Subject: [PATCH 06/41] U4-10853 - fix (scheduled) unpublishing issues --- .../Repositories/ContentRepository.cs | 18 +----------------- src/Umbraco.Core/Services/ContentService.cs | 4 ++-- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 6c00964c15..a10e8c62ab 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -693,27 +693,11 @@ namespace Umbraco.Core.Persistence.Repositories // else if unpublished then clear published version infos if (entity.Published) { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; ((Content) entity).PublishedVersionGuid = dto.VersionId; ((Content) entity).PublishedDate = dto.UpdateDate; } - else if (publishedStateChanged) + else { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default (Guid), - VersionDate = default (DateTime), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; ((Content) entity).PublishedVersionGuid = default(Guid); ((Content) entity).PublishedDate = default (DateTime); } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3cbfd77045..faa48eb711 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -884,8 +884,8 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published && x.ExpireDate <= DateTime.Now); - return repository.GetByQuery(query); + var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); + return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); } } From 7032e137b88c95057480ee4972f9a11cdb265a4c Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 23 Jan 2018 15:02:12 +0100 Subject: [PATCH 07/41] U4-10853 - refactor --- .../Repositories/ContentRepository.cs | 18 +++++++++++++++++- .../Publishing/PublishingStrategy.cs | 7 ++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index a10e8c62ab..6c00964c15 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -693,11 +693,27 @@ namespace Umbraco.Core.Persistence.Repositories // else if unpublished then clear published version infos if (entity.Published) { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + VersionDate = dto.UpdateDate, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; ((Content) entity).PublishedVersionGuid = dto.VersionId; ((Content) entity).PublishedDate = dto.UpdateDate; } - else + else if (publishedStateChanged) { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = default (Guid), + VersionDate = default (DateTime), + Newest = false, + NodeId = dto.NodeId, + Published = false + }; ((Content) entity).PublishedVersionGuid = default(Guid); ((Content) entity).PublishedDate = default (DateTime); } diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index 2c603a40ed..c4e57c5122 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -401,9 +401,10 @@ namespace Umbraco.Core.Publishing content.Name, content.Id)); } - // if newest is published, unpublish - if (content.Published) - content.ChangePublishedState(PublishedState.Unpublished); + // make sure we dirty .Published and always unpublish + // the version we have here could be the newest, !Published + content.ChangePublishedState(PublishedState.Published); + content.ChangePublishedState(PublishedState.Unpublished); _logger.Info( string.Format("Content '{0}' with Id '{1}' has been unpublished.", From ca8638526938a734fe226bdf184cd40da8483b7e Mon Sep 17 00:00:00 2001 From: Pete Duncanson Date: Tue, 23 Jan 2018 15:10:09 +0000 Subject: [PATCH 08/41] 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 09/41] 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 b49db0e941f121262dcf582d0794c3749e4f4c11 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Jan 2018 13:01:31 -0700 Subject: [PATCH 10/41] Initial commit for member type sensitive data, this adds the db and model requirements and updates the normal property editor for a member type which saves the isSensitive data to the db. --- src/Umbraco.Core/Models/IMemberType.cs | 16 +- src/Umbraco.Core/Models/MemberType.cs | 41 +++- .../Models/MemberTypePropertyProfileAccess.cs | 6 +- .../Models/Rdbms/MemberTypeDto.cs | 6 +- .../Models/Rdbms/PropertyTypeReadOnlyDto.cs | 8 +- .../Factories/ContentTypeFactory.cs | 5 +- .../Factories/MemberTypeReadOnlyFactory.cs | 10 +- .../AddTourDataUserColumn.cs | 36 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Mapping/ContentTypeModelMappingTests.cs | 8 +- .../services/umbdataformatter.service.js | 4 +- .../less/components/umb-group-builder.less | 193 +++++++++--------- .../propertysettings/propertysettings.html | 9 +- .../views/components/umb-groups-builder.html | 5 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 + .../umbraco/config/lang/en_us.xml | 2 + .../ContentEditing/MemberPropertyTypeBasic.cs | 5 +- .../MemberPropertyTypeDisplay.cs | 5 +- .../Models/Mapping/ContentTypeModelMapper.cs | 8 +- 19 files changed, 252 insertions(+), 118 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddTourDataUserColumn.cs diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index 878cc24334..9596d88cca 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -19,6 +19,13 @@ /// bool MemberCanViewProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); + /// /// Sets a boolean indicating whether a Property is editable by the Member. /// @@ -32,5 +39,12 @@ /// PropertyType Alias of the Property to set /// Boolean value, true or false void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 4f79e1d231..939c4ca8b5 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. /// [DataMember] internal IDictionary MemberTypePropertyTypes { get; private set; } @@ -99,6 +99,21 @@ namespace Umbraco.Core.Models return false; } + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool IsSensitiveProperty(string propertyTypeAlias) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + return MemberTypePropertyTypes[propertyTypeAlias].IsSensitive; + } + + return false; + } + /// /// Sets a boolean indicating whether a Property is editable by the Member. /// @@ -112,7 +127,7 @@ namespace Umbraco.Core.Models } else { - var tuple = new MemberTypePropertyProfileAccess(false, value); + var tuple = new MemberTypePropertyProfileAccess(false, value, false); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } @@ -130,9 +145,27 @@ namespace Umbraco.Core.Models } else { - var tuple = new MemberTypePropertyProfileAccess(value, false); + var tuple = new MemberTypePropertyProfileAccess(value, false, false); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + MemberTypePropertyTypes[propertyTypeAlias].IsSensitive = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(false, false, true); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index fa9e0b7307..db483191a0 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -5,13 +5,15 @@ namespace Umbraco.Core.Models /// internal class MemberTypePropertyProfileAccess { - public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable) + public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive) { IsVisible = isVisible; IsEditable = isEditable; + IsSensitive = isSenstive; } public bool IsVisible { get; set; } public bool IsEditable { get; set; } + public bool IsSensitive { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs index c9432652af..861ff1383e 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs @@ -27,5 +27,9 @@ namespace Umbraco.Core.Models.Rdbms [Column("viewOnProfile")] [Constraint(Default = "0")] public bool ViewOnProfile { get; set; } + + [Column("isSensitive")] + [Constraint(Default = "0")] + public bool IsSensitive { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index d85baa2884..07c2bc421c 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -45,6 +45,9 @@ namespace Umbraco.Core.Models.Rdbms [Column("viewOnProfile")] public bool ViewOnProfile { get; set; } + [Column("isSensitive")] + public bool IsSensitive { get; set; } + /* cmsDataType */ [Column("propertyEditorAlias")] public string PropertyEditorAlias { get; set; } @@ -52,7 +55,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("dbType")] public string DbType { get; set; } - [Column("UniqueID")] + [Column("UniqueID")] public Guid UniqueId { get; set; } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 3672f80873..4186d00774 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, PropertyTypeId = x.Id, CanEdit = memberType.MemberCanEditProperty(x.Alias), - ViewOnProfile = memberType.MemberCanViewProperty(x.Alias) + ViewOnProfile = memberType.MemberCanViewProperty(x.Alias), + IsSensitive = memberType.IsSensitiveProperty(x.Alias) }).ToList(); return dtos; } @@ -159,4 +160,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index ba7a47165b..7588309fce 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -56,10 +56,10 @@ namespace Umbraco.Core.Persistence.Factories //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); - + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false)); + new MemberTypePropertyProfileAccess(false, false, false)); } memberType.NoGroupPropertyTypes = propertyTypes; @@ -102,7 +102,7 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); var tempGroupDto = groupDto; @@ -157,7 +157,7 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); //ensures that any built-in membership properties have their correct dbtype assigned no matter //what the underlying data type is @@ -198,4 +198,4 @@ namespace Umbraco.Core.Persistence.Factories } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddTourDataUserColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddTourDataUserColumn.cs new file mode 100644 index 0000000000..1e948a7c86 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddTourDataUserColumn.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero +{ + [Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)] + public class AddIsSensitiveMemberTypeColumn : MigrationBase + { + public AddIsSensitiveMemberTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Don't exeucte if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsMemberType") && x.ColumnName.InvariantEquals("isSensitive")) == false) + { + Create.Column("isSensitive").OnTable("cmsMemberType").AsBoolean(); + } + } + + public override void Down() + { + } + } + +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4a44ef2dcc..37eead17c3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -552,6 +552,7 @@ + diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 1ff3447539..a976c8ad8c 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -120,6 +120,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeDefinitionId); Assert.AreEqual(propTypes.ElementAt(j).MemberCanViewProperty, result.MemberCanViewProperty(result.PropertyTypes.ElementAt(j).Alias)); Assert.AreEqual(propTypes.ElementAt(j).MemberCanEditProperty, result.MemberCanEditProperty(result.PropertyTypes.ElementAt(j).Alias)); + Assert.AreEqual(propTypes.ElementAt(j).IsSensitiveData, result.IsSensitiveProperty(result.PropertyTypes.ElementAt(j).Alias)); } } @@ -335,7 +336,7 @@ namespace Umbraco.Tests.Models.Mapping .Returns(new[] { new TextboxPropertyEditor() }); var memberType = MockedContentTypes.CreateSimpleMemberType(); - memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true); + memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true, true); MockedContentTypes.EnsureAllIds(memberType, 8888); @@ -539,6 +540,7 @@ namespace Umbraco.Tests.Models.Mapping { MemberCanEditProperty = true, MemberCanViewProperty = true, + IsSensitiveData = true, Id = 33, SortOrder = 1, Alias = "prop1", @@ -556,6 +558,7 @@ namespace Umbraco.Tests.Models.Mapping { MemberCanViewProperty = false, MemberCanEditProperty = false, + IsSensitiveData = false, Id = 34, SortOrder = 2, Alias = "prop2", @@ -944,6 +947,7 @@ namespace Umbraco.Tests.Models.Mapping Label = "Prop 1", MemberCanViewProperty = true, MemberCanEditProperty = true, + IsSensitiveData = true, Validation = new PropertyTypeValidation() { Mandatory = true, @@ -963,6 +967,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation, result.Validation); Assert.AreEqual(basic.MemberCanViewProperty, result.MemberCanViewProperty); Assert.AreEqual(basic.MemberCanEditProperty, result.MemberCanEditProperty); + Assert.AreEqual(basic.IsSensitiveData, result.IsSensitiveData); } [Test] @@ -1029,6 +1034,7 @@ namespace Umbraco.Tests.Models.Mapping { MemberCanEditProperty = true, MemberCanViewProperty = true, + IsSensitiveData = true, Alias = "property1", Description = "this is property 1", Inherited = false, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index f09e31924f..714b95d873 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -56,7 +56,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData'); return saveProperty; }); @@ -338,4 +338,4 @@ } angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter); -})(); \ No newline at end of file +})(); 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 c05d33db04..eecbf51ec5 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 @@ -426,103 +426,114 @@ input.umb-group-builder__group-title-input { .content-type-editor-dialog.edit-property-settings { - .validation-wrapper { - position: relative; - } - - .validation-label { - position: absolute; - top: 50%; - right: 0; - font-size: 12px; - color: @red; - transform: translate(0, -50%); - } - - textarea.editor-label { - border-color:transparent; - box-shadow: none; - width: 100%; - box-sizing: border-box; - margin-bottom: 0; - font-size: 16px; - font-weight: bold; - resize: none; - line-height: 1.5em; - padding-left: 0; - border: none; - &:focus { - outline: none; - box-shadow: none !important; + .validation-wrapper { + position: relative; } - } - .editor-placeholder { - border: 1px dashed @gray-8; - width: 100%; - height: 80px; - line-height: 80px; - text-align: center; - display: block; - border-radius: 5px; - color: @gray-3; - font-weight: bold; - font-size: 14px; - color: @turquoise-d1; - &:hover { - text-decoration: none; - } - } - - .editor { - margin-bottom: 10px; - .editor-icon-wrapper { - border: 1px solid @gray-8; - width: 60px; - height: 60px; - text-align: center; - line-height: 60px; - border-radius: 5px; - float: left; - margin-right: 20px; - .icon { - font-size: 26px; - } - } - .editor-details { - float: left; - margin-top: 10px; - .editor-name { - display: block; - font-weight: bold; - } - .editor-editor { - display: block; + .validation-label { + position: absolute; + top: 50%; + right: 0; font-size: 12px; - } + color: @red; + transform: translate(0, -50%); } - .editor-settings-icon { - font-size: 18px; - margin-top: 8px; - } - } - .checkbox { - margin-bottom: 20px; - } + textarea.editor-label { + border-color: transparent; + box-shadow: none; + width: 100%; + box-sizing: border-box; + margin-bottom: 0; + font-size: 16px; + font-weight: bold; + resize: none; + line-height: 1.5em; + padding-left: 0; + border: none; - .editor-description, - .editor-validation-pattern { - min-width: 100%; - min-height: 25px; - resize: none; - box-sizing: border-box; - border: none; - overflow: hidden; - } + &:focus { + outline: none; + box-shadow: none !important; + } + } - .umb-dropdown { - width: 100%; - } + .editor-placeholder { + border: 1px dashed @gray-8; + width: 100%; + height: 80px; + line-height: 80px; + text-align: center; + display: block; + border-radius: 5px; + color: @gray-3; + font-weight: bold; + font-size: 14px; + color: @turquoise-d1; + &:hover { + text-decoration: none; + } + } + + .editor { + margin-bottom: 10px; + + .editor-icon-wrapper { + border: 1px solid @gray-8; + width: 60px; + height: 60px; + text-align: center; + line-height: 60px; + border-radius: 5px; + float: left; + margin-right: 20px; + + .icon { + font-size: 26px; + } + } + + .editor-details { + float: left; + margin-top: 10px; + + .editor-name { + display: block; + font-weight: bold; + } + + .editor-editor { + display: block; + font-size: 12px; + } + } + + .editor-settings-icon { + font-size: 18px; + margin-top: 8px; + } + } + + .checkbox { + margin-bottom: 20px; + } + + .editor-description, + .editor-validation-pattern { + min-width: 100%; + min-height: 25px; + resize: none; + box-sizing: border-box; + border: none; + overflow: hidden; + } + + .umb-dropdown { + width: 100%; + } + + label.checkbox.no-indent { + width: 100%; + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 50e5e29d1e..05da7e1ded 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -90,8 +90,8 @@
    -
    - +
    + + +
    +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 1bd8673b43..61d00b4a49 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -510,6 +510,7 @@ There is a configuration error with the data type used for this property, please check the data type + Options About Action Actions @@ -1609,6 +1610,7 @@ To manage your website, simply open the Umbraco back office and start adding con using this editor will get updated with the new settings Member can edit + Is sensitive data Show on member profile tab has no sort order 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 b9375420bd..dbf20229ef 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -516,6 +516,7 @@ There is a configuration error with the data type used for this property, please check the data type + Options About Action Actions @@ -1613,6 +1614,7 @@ To manage your website, simply open the Umbraco back office and start adding con using this editor will get updated with the new settings Member can edit + Is sensitive data Show on member profile tab has no sort order diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs index dfe98461dd..a1f5af645b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs @@ -13,5 +13,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs index 8f7d4be310..bdbf19e68e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs @@ -10,5 +10,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index 3daf22f172..7ab2e63dbc 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -88,7 +88,7 @@ namespace Umbraco.Web.Models.Mapping { ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); - //map the MemberCanEditProperty,MemberCanViewProperty + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) { var localCopy = propertyType; @@ -97,6 +97,7 @@ namespace Umbraco.Web.Models.Mapping { dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); + dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); } } }); @@ -108,7 +109,7 @@ namespace Umbraco.Web.Models.Mapping .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) .AfterMap((memberType, display) => { - //map the MemberCanEditProperty,MemberCanViewProperty + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData foreach (var propertyType in memberType.PropertyTypes) { var localCopy = propertyType; @@ -117,6 +118,7 @@ namespace Umbraco.Web.Models.Mapping { displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias); + displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias); } } }); @@ -284,4 +286,4 @@ namespace Umbraco.Web.Models.Mapping } -} \ 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 11/41] 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 12/41] 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 13/41] 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 14/41] 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 15/41] 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 16/41] 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 17/41] 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 18/41] 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 19/41] 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 20/41] 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 21/41] 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 22/41] 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 6f81b7fe2474fd6a2e40ea83b7e257eed1259f21 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 Jan 2018 12:10:34 +0100 Subject: [PATCH 23/41] 'getting-started' tour only available to admins --- src/Umbraco.Web/Editors/TourController.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 152879bf5a..0833d26cb2 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -17,13 +17,16 @@ namespace Umbraco.Web.Editors { public IEnumerable GetTours() { + //Check if it has admin group + var isAdmin = UmbracoContext.Current.Security.CurrentUser.Groups.Any(x => x.Alias == "admin"); + var result = new List(); if (UmbracoConfig.For.UmbracoSettings().BackOffice.Tours.EnableTours == false) return result; var filters = TourFilterResolver.Current.Filters.ToList(); - + //get all filters that will be applied to all tour aliases var aliasOnlyFilters = filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList(); @@ -36,6 +39,10 @@ namespace Umbraco.Web.Editors { foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json")) { + var tourFileName = Path.GetFileName(tourFile.TrimEnd('\\')); + //We brake if isAdmin is false as we don't want to show getting-started tour to non-admins + if (tourFileName.Equals("getting-started.json") && isAdmin == false) break; + TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters); } } @@ -79,7 +86,7 @@ namespace Umbraco.Web.Editors //get the filters specific to this file var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList(); - + //If there is any filter applied to match the file only (no tour alias) then ignore the file entirely var isFileFiltered = fileFilters.Any(x => x.TourAlias == null); if (isFileFiltered) return; @@ -117,4 +124,4 @@ namespace Umbraco.Web.Editors } } } -} \ No newline at end of file +} From 64628c0a9d92d5e76a00957119f5c83a09210fe0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Jan 2018 14:14:12 -0700 Subject: [PATCH 24/41] Gets the IsSensitive property mapped to the display object and changes the property output to show that the field contains sensitive data. Refactors the TabsAndPropertiesResolver and simplifies it a lot which also allows us to pass in an UmbracoContext which we'll need in order to determine a user's rights, etc... Refactors how the TreeNodeUrls are mapped and they now use a proper mapper. This now means we do much less in AfterMap --- src/Umbraco.Core/Models/MemberType.cs | 33 +- .../Factories/MemberTypeReadOnlyFactory.cs | 2 +- .../Repositories/MemberTypeRepository.cs | 5 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + .../umbraco/config/lang/en_us.xml | 1 + .../ContentEditing/ContentPropertyDisplay.cs | 7 +- .../Models/Mapping/AutoMapperExtensions.cs | 49 +++ .../Models/Mapping/ContentModelMapper.cs | 21 +- .../Mapping/ContentPropertyBasicConverter.cs | 8 +- .../ContentPropertyDisplayConverter.cs | 14 +- .../Mapping/ContentPropertyDtoConverter.cs | 6 +- .../Mapping/ContentPropertyModelMapper.cs | 10 +- .../Mapping/ContentTreeNodeUrlResolver.cs | 34 ++ .../Models/Mapping/DataTypeModelMapper.cs | 10 +- .../Models/Mapping/MediaModelMapper.cs | 19 +- .../Models/Mapping/MemberModelMapper.cs | 334 +++++++++++------- .../Mapping/MemberTreeNodeUrlResolver.cs | 32 ++ .../Models/Mapping/PreValueDisplayResolver.cs | 8 +- .../Mapping/TabsAndPropertiesResolver.cs | 152 ++++---- 19 files changed, 458 insertions(+), 288 deletions(-) create mode 100644 src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs create mode 100644 src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs create mode 100644 src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 939c4ca8b5..4e70b4157f 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -76,11 +76,11 @@ namespace Umbraco.Core.Models /// public bool MemberCanEditProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsEditable; + return propertyProfile.IsEditable; } - return false; } @@ -91,11 +91,11 @@ namespace Umbraco.Core.Models /// public bool MemberCanViewProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsVisible; + return propertyProfile.IsVisible; } - return false; } @@ -106,11 +106,11 @@ namespace Umbraco.Core.Models /// public bool IsSensitiveProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsSensitive; + return propertyProfile.IsSensitive; } - return false; } @@ -121,9 +121,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value; + propertyProfile.IsEditable = value; } else { @@ -139,9 +140,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value; + propertyProfile.IsVisible = value; } else { @@ -157,9 +159,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsSensitive = value; + propertyProfile.IsSensitive = value; } else { diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 7588309fce..439ac68360 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core.Persistence.Factories //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, new MemberTypePropertyProfileAccess(false, false, false)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 5865878aa9..d473e19485 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -90,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", - "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", + "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", + "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") @@ -362,4 +363,4 @@ namespace Umbraco.Core.Persistence.Repositories return Attempt.Fail(propertyEditor); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 61d00b4a49..7a8cb6da50 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -221,6 +221,7 @@ Add another text box Remove this text box Content root + Your user permissions do not allow access to this property Create a new Content Template from '%0%' 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 dbf20229ef..92ec93ab71 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -227,6 +227,7 @@ Add another text box Remove this text box Content root + Your user permissions do not allow access to this property Create a new Content Template from '%0%' diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 343b018000..14793ace5e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -34,6 +34,9 @@ namespace Umbraco.Web.Models.ContentEditing public bool HideLabel { get; set; } [DataMember(Name = "validation")] - public PropertyTypeValidation Validation { get; set; } + public PropertyTypeValidation Validation { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitive { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs new file mode 100644 index 0000000000..78fc71dbc0 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal static class AutoMapperExtensions + { + /// + /// This maps an object and passes in the current so the mapping logic can use it + /// + /// + /// + /// + /// + /// + public static TOut MapWithUmbracoContext(TIn obj, UmbracoContext umbCtx) + { + return Mapper.Map(obj, opt => opt.Items["UmbracoContext"] = umbCtx); + } + + /// + /// Returns an from the mapping options + /// + /// + /// + /// + /// If an UmbracoContext is not found in the mapping options, it will try to retrieve it from the singleton + /// + public static UmbracoContext GetUmbracoContext(this ResolutionResult res) + { + //get the context from the mapping options set during a mapping operation + object umbCtx; + if (res.Context.Options.Items.TryGetValue("UmbracoContext", out umbCtx)) + { + var umbracoContext = umbCtx as UmbracoContext; + if (umbracoContext != null) return umbracoContext; + } + + //return the singleton (this could be null) + return UmbracoContext.Current; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 2fa919716c..9d5975b564 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Models.Mapping : content.GetContentUrls(UmbracoContext.Current))) .ForMember(display => display.Properties, expression => expression.Ignore()) .ForMember(display => display.AllowPreview, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) @@ -101,16 +101,9 @@ namespace Umbraco.Web.Models.Mapping ILocalizedTextService localizedText, IContentTypeService contentTypeService) { // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = content.Parent(); - display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } + display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); //set default template if template isn't set if (string.IsNullOrEmpty(display.TemplateAlias)) @@ -122,8 +115,6 @@ namespace Umbraco.Web.Models.Mapping { TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } - - TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText); } /// @@ -134,9 +125,7 @@ namespace Umbraco.Web.Models.Mapping { protected override ContentTypeBasic ResolveCore(IContent source) { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will + //TODO: We can resolve the UmbracoContext from the IValueResolver options! if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { @@ -194,4 +183,4 @@ namespace Umbraco.Web.Models.Mapping } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index f61d9adf77..e7104f2939 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -16,9 +16,9 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : TypeConverter where T : ContentPropertyBasic, new() { - protected Lazy DataTypeService { get; private set; } + protected IDataTypeService DataTypeService { get; private set; } - public ContentPropertyBasicConverter(Lazy dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService) { DataTypeService = dataTypeService; } @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping var result = new T { Id = property.Id, - Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService.Value), + Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService), Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias @@ -51,4 +51,4 @@ namespace Umbraco.Web.Models.Mapping return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 20ff209371..8267e46e25 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -13,17 +13,19 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter { - public ContentPropertyDisplayConverter(Lazy dataTypeService) + private readonly ILocalizedTextService _textService; + + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService) : base(dataTypeService) { - + _textService = textService; } protected override ContentPropertyDisplay ConvertCore(Property originalProp) { var display = base.ConvertCore(originalProp); - var dataTypeService = DataTypeService.Value; + var dataTypeService = DataTypeService; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); //configure the editor for display with the pre-values @@ -54,7 +56,11 @@ namespace Umbraco.Web.Models.Mapping display.View = valEditor.View; } + //Translate + display.Label = _textService.UmbracoDictionaryTranslate(display.Label); + display.Description = _textService.UmbracoDictionaryTranslate(display.Description); + return display; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 3a6e199f96..f3f9fbe9d5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter { - public ContentPropertyDtoConverter(Lazy dataTypeService) + public ContentPropertyDtoConverter(IDataTypeService dataTypeService) : base(dataTypeService) { } @@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping { var propertyDto = base.ConvertCore(originalProperty); - var dataTypeService = DataTypeService.Value; + var dataTypeService = DataTypeService; propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; @@ -35,4 +35,4 @@ namespace Umbraco.Web.Models.Mapping return propertyDto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs index ba51eb1790..5e29bcf125 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -16,8 +16,6 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - //FROM Property TO ContentPropertyBasic config.CreateMap>() .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) @@ -27,15 +25,15 @@ namespace Umbraco.Web.Models.Mapping //FROM Property TO ContentPropertyBasic config.CreateMap() - .ConvertUsing(new ContentPropertyBasicConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService)); //FROM Property TO ContentPropertyDto config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService)); //FROM Property TO ContentPropertyDisplay config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..dfea70a968 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs @@ -0,0 +1,34 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the content or media + /// + internal class ContentTreeNodeUrlResolver : IValueResolver + where TSource : IContentBase + where TController : ContentTreeControllerBase + { + + public ResolutionResult Resolve(ResolutionResult source) + { + return source.New(ResolveCore(source, (TSource)source.Value), typeof(string)); + } + + private string ResolveCore(ResolutionResult res, TSource source) + { + var umbCtx = res.GetUmbracoContext(); + //map the tree node url + if (umbCtx != null) + { + var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext); + var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + return url; + } + return null; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index 32d25028e4..60d9eb7d3b 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -19,9 +19,7 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - - config.CreateMap(); + config.CreateMap(); //just maps the standard properties, does not map the value! config.CreateMap() @@ -67,7 +65,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( - new PreValueDisplayResolver(lazyDataTypeService))) + new PreValueDisplayResolver(applicationContext.Services.DataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias)) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) @@ -90,7 +88,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap>() .ConvertUsing(definition => { - var resolver = new PreValueDisplayResolver(lazyDataTypeService); + var resolver = new PreValueDisplayResolver(applicationContext.Services.DataTypeService); return resolver.Convert(definition); }); @@ -124,4 +122,4 @@ namespace Umbraco.Web.Models.Mapping }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 157205eb51..9a0480f771 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Published, expression => expression.Ignore()) @@ -74,23 +74,14 @@ namespace Umbraco.Web.Models.Mapping { // Adapted from ContentModelMapper //map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = media.Parent(); display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } - + if (media.ContentType.IsContainer) { TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); } - - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText); } /// @@ -101,9 +92,7 @@ namespace Umbraco.Web.Models.Mapping { protected override ContentTypeBasic ResolveCore(IMedia source) { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will + //TODO: We can resolve the UmbracoContext from the IValueResolver options! if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index e3c82cf782..5f5c42f9dc 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Web; -using System.Web.Mvc; using System.Web.Routing; using System.Web.Security; using AutoMapper; @@ -14,7 +13,6 @@ using Umbraco.Web.Models.ContentEditing; using System.Linq; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; -using Umbraco.Web.Trees; using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping @@ -56,9 +54,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(member => member.SortOrder, expression => expression.Ignore()) .ForMember(member => member.AdditionalData, expression => expression.Ignore()) .ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore()) - .ForMember(member => member.DeletedDate, expression => expression.Ignore()) - //TODO: Support these eventually - .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) + .ForMember(member => member.DeletedDate, expression => expression.Ignore()) + //TODO: Support these eventually + .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) .ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore()); //FROM IMember TO MediaItemDisplay @@ -69,10 +67,10 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService, applicationContext.Services.MemberService, applicationContext.Services.UserService))) .ForMember(display => display.MemberProviderFieldMapping, expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) .ForMember(display => display.MembershipScenario, - expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy(() => applicationContext.Services.MemberTypeService)))) + expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(applicationContext.Services.MemberTypeService))) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Published, expression => expression.Ignore()) @@ -81,9 +79,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) .ForMember(display => display.Trashed, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService)); + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new MemberTreeNodeUrlResolver())) + .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()); + //.AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService)); //FROM IMember TO MemberBasic config.CreateMap() @@ -100,14 +98,14 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic - config.CreateMap() + config.CreateMap() //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) .ForMember(display => display.Udi, expression => expression.Ignore()) .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - .ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile {Name = "Admin", UserId = 0})) + .ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile { Name = "Admin", UserId = 0 })) .ForMember(member => member.Icon, expression => expression.UseValue("icon-user")) .ForMember(member => member.Name, expression => expression.MapFrom(user => user.UserName)) .ForMember(member => member.Email, expression => expression.MapFrom(content => content.Email)) @@ -137,119 +135,11 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver())); } - /// - /// Maps the generic tab with custom properties for content - /// - /// - /// - /// - /// - /// - /// - /// If this is a new entity and there is an approved field then we'll set it to true by default. - /// - private static void MapGenericCustomProperties(IMemberService memberService, IUserService userService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) - { - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Key.ToString("N"), null)); - display.TreeNodeUrl = url; - } - - var genericProperties = new List - { - new ContentPropertyDisplay - { - Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membertype"), - Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View - }, - GetLoginProperty(memberService, member, display, localizedText), - new ContentPropertyDisplay - { - Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("general/email"), - Value = display.Email, - View = "email", - Validation = {Mandatory = true} - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("password"), - //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists - // only when creating a new member and we want to have a generated password pre-filled. - Value = new Dictionary - { - {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, - }, - //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor - View = "changepassword", - //initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.GetConfiguration(userService)) - { - //the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - } - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membergroup"), - Value = GetMemberGroupValue(display.Username), - View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} - } - }; - - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => - { - if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doctype property - var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProperty.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = memberTypeLink, - target = "_self", - icon = "icon-item-arrangement" - } - }; - docTypeProperty.View = "urllist"; - } - }); - - //check if there's an approval field - var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; - if (member.HasIdentity == false && provider != null) - { - var approvedField = provider.ApprovedPropertyTypeAlias; - var prop = display.Properties.FirstOrDefault(x => x.Alias == approvedField); - if (prop != null) - { - prop.Value = 1; - } - } - } - /// /// Returns the login property display field /// /// /// - /// /// /// /// @@ -257,13 +147,13 @@ namespace Umbraco.Web.Models.Mapping /// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively /// allow that. /// - internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) + internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, ILocalizedTextService localizedText) { var prop = new ContentPropertyDisplay { Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("login"), - Value = display.Username + Value = member.Username }; var scenario = memberService.GetMembershipScenario(); @@ -329,24 +219,60 @@ namespace Umbraco.Web.Models.Mapping /// /// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because /// an admin cannot actually set isLockedOut = true, they can only unlock. + /// + /// This also ensures that the IsSensitive property display value is set based on the configured IMemberType property type /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver + internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver { private readonly ILocalizedTextService _localizedTextService; + private readonly IMemberService _memberService; + private readonly IUserService _userService; - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService) : base(localizedTextService) { _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; } public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, - IEnumerable ignoreProperties) : base(localizedTextService, ignoreProperties) + IEnumerable ignoreProperties, IMemberService memberService, IUserService userService) : base(localizedTextService, ignoreProperties) { _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; } - protected override IEnumerable> ResolveCore(IContentBase content) + /// + /// Explicitly implement to override behavior + /// + /// + /// + /// + /// This is required to get access to the object which allows us access to the mapping options + /// + ResolutionResult IValueResolver.Resolve(ResolutionResult source) + { + //call the base class logic + var result = base.Resolve(source); + + var member = (IMember)source.Value; + //now we can customize the result and use the ResolutionResult options to get the UmbracoContext + var tabs = (List>) result.Value; + + //now we can customize the result with the current context, we can get the UmbracoContext from the options + CustomizeProperties(source.GetUmbracoContext(), member, tabs); + + return result; + } + + /// + /// Overridden to deal with custom member properties + /// + /// + /// + protected override List> ResolveCore(IContentBase content) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); @@ -355,7 +281,7 @@ namespace Umbraco.Web.Models.Mapping .Select(x => x.Alias) .ToArray(); - var result = base.ResolveCore(content).ToArray(); + var result = base.ResolveCore(content); if (provider.IsUmbracoMembershipProvider() == false) { @@ -366,12 +292,10 @@ namespace Umbraco.Web.Models.Mapping isLockedOutProperty.View = "readonlyvalue"; isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); } - - return result; } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier // if we just had all of the membeship provider fields on the member table :( @@ -382,17 +306,150 @@ namespace Umbraco.Web.Models.Mapping isLockedOutProperty.View = "readonlyvalue"; isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); } - - return result; } + + return result; + } + + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember) content; + var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + var genericProperties = new List + { + new ContentPropertyDisplay + { + Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("content/membertype"), + //Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), + Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name), + View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + }, + GetLoginProperty(_memberService, member, _localizedTextService), + new ContentPropertyDisplay + { + Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("general/email"), + Value = member.Email, + View = "email", + Validation = {Mandatory = true} + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("password"), + //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists + // only when creating a new member and we want to have a generated password pre-filled. + Value = new Dictionary + { + {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, + {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, + }, + //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor + View = "changepassword", + //initialize the dictionary with the configuration from the default membership provider + Config = new Dictionary(membersProvider.GetConfiguration(_userService)) + { + //the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + } + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("content/membergroup"), + Value = GetMemberGroupValue(member.Username), + View = "membergroups", + Config = new Dictionary {{"IsRequired", true}} + } + }; + + return genericProperties; + } + + /// + /// Performs some customizations for the properties based on the current context + /// + /// + /// + /// The current tab collection + /// + /// If this is a new entity and there is an approved field then we'll set it to true by default. + /// + private void CustomizeProperties(UmbracoContext umbracoContext, IMember member, List> tabs) + { + var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + if (umbracoContext != null && umbracoContext.Security.CurrentUser != null + && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); + + //Replace the doctype property + var docTypeProperty = tabs.SelectMany(x => x.Properties) + .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new + { + linkText = member.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; + } + + //check if there's an approval field + var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; + if (member.HasIdentity == false && provider != null) + { + var approvedField = provider.ApprovedPropertyTypeAlias; + var prop = tabs.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); + if (prop != null) + { + prop.Value = 1; + } + } + } + + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + protected override List MapProperties(IContentBase content, List properties) + { + var result = base.MapProperties(content, properties); + var member = (IMember)content; + var memberType = member.ContentType; + var labelPropEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; + //now update the IsSensitive value + foreach (var prop in result) + { + prop.IsSensitive = memberType.IsSensitiveProperty(prop.Alias); + //check permissions for viewing sensitive data + + if (prop.IsSensitive) + { + //replace this editor with a label + prop.View = labelPropEditor; + //replace the value + prop.Value = string.Format("{0}", _localizedTextService.Localize("content/isSensitiveValue")); + } + } + return result; } } internal class MembershipScenarioMappingResolver : ValueResolver { - private readonly Lazy _memberTypeService; + private readonly IMemberTypeService _memberTypeService; - public MembershipScenarioMappingResolver(Lazy memberTypeService) + public MembershipScenarioMappingResolver(IMemberTypeService memberTypeService) { _memberTypeService = memberTypeService; } @@ -405,7 +462,7 @@ namespace Umbraco.Web.Models.Mapping { return MembershipScenario.NativeUmbraco; } - var memberType = _memberTypeService.Value.Get(Constants.Conventions.MemberTypes.DefaultAlias); + var memberType = _memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); return memberType != null ? MembershipScenario.CustomProviderWithUmbracoLink : MembershipScenario.StandaloneCustomProvider; @@ -432,7 +489,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; return new Dictionary { @@ -444,4 +501,7 @@ namespace Umbraco.Web.Models.Mapping } } } -} \ No newline at end of file +} + + + diff --git a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..a0b99252d2 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs @@ -0,0 +1,32 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the IMember + /// + internal class MemberTreeNodeUrlResolver : IValueResolver + { + + public ResolutionResult Resolve(ResolutionResult source) + { + return source.New(ResolveCore(source, (IMember)source.Value), typeof(string)); + } + + private string ResolveCore(ResolutionResult res, IMember source) + { + var umbCtx = res.GetUmbracoContext(); + //map the tree node url + if (umbCtx != null) + { + var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext); + var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + return url; + } + return null; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 295a5df63d..f4e7a78504 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -13,9 +13,9 @@ namespace Umbraco.Web.Models.Mapping { internal class PreValueDisplayResolver : ValueResolver> { - private readonly Lazy _dataTypeService; + private readonly IDataTypeService _dataTypeService; - public PreValueDisplayResolver(Lazy dataTypeService) + public PreValueDisplayResolver(IDataTypeService dataTypeService) { _dataTypeService = dataTypeService; } @@ -55,7 +55,7 @@ namespace Umbraco.Web.Models.Mapping } //set up the defaults - var dataTypeService = _dataTypeService.Value; + var dataTypeService = _dataTypeService; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); var result = Enumerable.Empty().ToArray(); @@ -77,4 +77,4 @@ namespace Umbraco.Web.Models.Mapping return Convert(source); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 4ac35b48f8..55a72cb704 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates the tabs collection with properties assigned for display models /// - internal class TabsAndPropertiesResolver : ValueResolver>> + internal class TabsAndPropertiesResolver : ValueResolver>> { private readonly ILocalizedTextService _localizedTextService; protected IEnumerable IgnoreProperties { get; set; } @@ -35,60 +35,6 @@ namespace Umbraco.Web.Models.Mapping IgnoreProperties = ignoreProperties; } - /// - /// Maps properties on to the generic properties tab - /// - /// - /// - /// - /// - /// Any additional custom properties to assign to the generic properties tab. - /// - /// - /// - /// The generic properties tab is mapped during AfterMap and is responsible for - /// setting up the properties such as Created date, updated date, template selected, etc... - /// - public static void MapGenericProperties( - TPersisted content, - ContentItemDisplayBase display, - ILocalizedTextService localizedTextService, - IEnumerable customProperties = null, - Action> onGenericPropertiesMapped = null) - where TPersisted : IContentBase - { - var genericProps = display.Tabs.Single(x => x.Id == 0); - - //store the current props to append to the newly inserted ones - var currProps = genericProps.Properties.ToArray(); - - var contentProps = new List(); - - if (customProperties != null) - { - //add the custom ones - contentProps.AddRange(customProperties); - } - - //now add the user props - contentProps.AddRange(currProps); - - //callback - if (onGenericPropertiesMapped != null) - { - onGenericPropertiesMapped(contentProps); - } - - //re-assign - genericProps.Properties = contentProps; - - //Show or hide properties tab based on wether it has or not any properties - if (genericProps.Properties.Any() == false) - { - display.Tabs = display.Tabs.Where(x => x.Id != 0); - } - } - /// /// Adds the container (listview) tab to the document /// @@ -199,7 +145,7 @@ namespace Umbraco.Web.Models.Mapping display.Tabs = tabs; } - protected override IEnumerable> ResolveCore(IContentBase content) + protected override List> ResolveCore(IContentBase content) { var tabs = new List>(); @@ -224,10 +170,8 @@ namespace Umbraco.Web.Models.Mapping if (properties.Count == 0) continue; - // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. - var mappedProperties = Mapper.Map, IEnumerable>(properties.OrderBy(prop => prop.PropertyType.SortOrder)); - - TranslateProperties(mappedProperties); + //map the properties + var mappedProperties = MapProperties(content, properties); // add the tab // we need to pick an identifier... there is no "right" way... @@ -245,12 +189,40 @@ namespace Umbraco.Web.Models.Mapping }); } + MapGenericProperties(content, tabs); + + // activate the first tab + tabs[0].IsActive = true; + + return tabs; + } + + /// + /// Returns a collection of custom generic properties that exist on the generic properties tab + /// + /// + protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) + { + return Enumerable.Empty(); + } + + /// + /// Maps properties on to the generic properties tab + /// + /// + /// + /// + /// The generic properties tab is responsible for + /// setting up the properties such as Created date, updated date, template selected, etc... + /// + protected virtual void MapGenericProperties(IContentBase content, List> tabs) + { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab var noGroupProperties = content.GetNonGroupedProperties() - .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored - var genericproperties = Mapper.Map, IEnumerable>(noGroupProperties).ToList(); - TranslateProperties(genericproperties); + .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored + .ToList(); + var genericproperties = MapProperties(content, noGroupProperties); tabs.Add(new Tab { @@ -260,20 +232,54 @@ namespace Umbraco.Web.Models.Mapping Properties = genericproperties }); - // activate the first tab - tabs.First().IsActive = true; + var genericProps = tabs.Single(x => x.Id == 0); - return tabs; - } + //store the current props to append to the newly inserted ones + var currProps = genericProps.Properties.ToArray(); - private void TranslateProperties(IEnumerable properties) - { - // Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper - foreach (var prop in properties) + var contentProps = new List(); + + var customProperties = GetCustomGenericProperties(content); + if (customProperties != null) { - prop.Label = _localizedTextService.UmbracoDictionaryTranslate(prop.Label); - prop.Description = _localizedTextService.UmbracoDictionaryTranslate(prop.Description); + //add the custom ones + contentProps.AddRange(customProperties); + } + + //now add the user props + contentProps.AddRange(currProps); + + //re-assign + genericProps.Properties = contentProps; + + //Show or hide properties tab based on wether it has or not any properties + if (genericProps.Properties.Any() == false) + { + //loop throug the tabs, remove the one with the id of zero and exit the loop + for (var i = 0; i < tabs.Count; i++) + { + if (tabs[i].Id != 0) continue; + tabs.RemoveAt(i); + break; + } } } + + /// + /// Maps a list of to a list of + /// + /// + /// + /// + protected virtual List MapProperties(IContentBase content, List properties) + { + var result = Mapper.Map, IEnumerable>( + // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. + properties.OrderBy(prop => prop.PropertyType.SortOrder)) + .ToList(); + + return result; + } + } } From 54cc80f4e8bbcbe79c541b0cd5c0e1aa66104f72 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Jan 2018 14:41:28 -0700 Subject: [PATCH 25/41] Updates all required mappings to use AutoMapperExtensions.MapWithUmbracoContext where appropriate --- src/Umbraco.Web/Editors/ContentController.cs | 18 +++++++------- src/Umbraco.Web/Editors/MediaController.cs | 14 +++++------ src/Umbraco.Web/Editors/MemberController.cs | 12 +++++----- .../Models/Mapping/AutoMapperExtensions.cs | 6 ++--- .../Mapping/ContentTreeNodeUrlResolver.cs | 2 +- .../Models/Mapping/MemberModelMapper.cs | 24 +++++++++++++------ .../Mapping/MemberTreeNodeUrlResolver.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 3 +++ 8 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a61ca7e249..45107d3f21 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(Mapper.Map); + return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); } /// @@ -232,7 +232,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = Mapper.Map(foundContent); + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); SetupBlueprint(content, foundContent); @@ -270,7 +270,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = Mapper.Map(foundContent); + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); return content; } @@ -283,7 +283,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = Mapper.Map(foundContent); + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); return content; } @@ -306,7 +306,7 @@ namespace Umbraco.Web.Editors } var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); @@ -563,7 +563,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a validation message - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); @@ -605,7 +605,7 @@ namespace Umbraco.Web.Editors } //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -858,7 +858,7 @@ namespace Umbraco.Web.Editors var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - var content = Mapper.Map(foundContent); + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); if (unpublishResult == false) { @@ -1092,4 +1092,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 72af1c4c70..0b2044dbe5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -89,7 +89,7 @@ namespace Umbraco.Web.Editors } var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); @@ -137,7 +137,7 @@ namespace Umbraco.Web.Editors //HandleContentNotFound will throw an exception return null; } - return Mapper.Map(foundContent); + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); } /// @@ -157,7 +157,7 @@ namespace Umbraco.Web.Editors //HandleContentNotFound will throw an exception return null; } - return Mapper.Map(foundContent); + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); } /// @@ -186,7 +186,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(Mapper.Map); + return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); } /// @@ -488,7 +488,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw validation response - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -498,7 +498,7 @@ namespace Umbraco.Web.Editors var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -607,7 +607,7 @@ namespace Umbraco.Web.Editors var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); mediaService.Save(f, Security.CurrentUser.Id); - return Mapper.Map(f); + return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); } /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 5aad40aa70..841b2df3d5 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -178,7 +178,7 @@ namespace Umbraco.Web.Editors { HandleContentNotFound(key); } - return Mapper.Map(foundMember); + return AutoMapperExtensions.MapWithUmbracoContext(foundMember, UmbracoContext); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. @@ -238,7 +238,7 @@ namespace Umbraco.Web.Editors emptyContent = new Member(contentType); emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); + return AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. @@ -247,7 +247,7 @@ namespace Umbraco.Web.Editors //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); + return AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); } } @@ -287,7 +287,7 @@ namespace Umbraco.Web.Editors //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors if (ModelState.IsValid == false) { - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -329,7 +329,7 @@ namespace Umbraco.Web.Editors //If we've had problems creating/updating the user with the provider then return the error if (ModelState.IsValid == false) { - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -367,7 +367,7 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); diff --git a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs index 78fc71dbc0..e5850b2689 100644 --- a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Models.Mapping { return Mapper.Map(obj, opt => opt.Items["UmbracoContext"] = umbCtx); } - + /// /// Returns an from the mapping options /// @@ -32,11 +32,11 @@ namespace Umbraco.Web.Models.Mapping /// /// If an UmbracoContext is not found in the mapping options, it will try to retrieve it from the singleton /// - public static UmbracoContext GetUmbracoContext(this ResolutionResult res) + public static UmbracoContext GetUmbracoContext(this ResolutionContext res) { //get the context from the mapping options set during a mapping operation object umbCtx; - if (res.Context.Options.Items.TryGetValue("UmbracoContext", out umbCtx)) + if (res.Options.Items.TryGetValue("UmbracoContext", out umbCtx)) { var umbracoContext = umbCtx as UmbracoContext; if (umbracoContext != null) return umbracoContext; diff --git a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs index dfea70a968..63d5b9b7e3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Models.Mapping private string ResolveCore(ResolutionResult res, TSource source) { - var umbCtx = res.GetUmbracoContext(); + var umbCtx = res.Context.GetUmbracoContext(); //map the tree node url if (umbCtx != null) { diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 5f5c42f9dc..7f5bc731b1 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -25,12 +25,7 @@ namespace Umbraco.Web.Models.Mapping public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { //FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider - config.CreateMap() - .ConvertUsing(user => - { - var member = Mapper.Map(user); - return Mapper.Map(member); - }); + config.CreateMap().ConvertUsing(); //FROM MembershipUser TO IMember - used when using a non-umbraco membership provider config.CreateMap() @@ -262,7 +257,7 @@ namespace Umbraco.Web.Models.Mapping var tabs = (List>) result.Value; //now we can customize the result with the current context, we can get the UmbracoContext from the options - CustomizeProperties(source.GetUmbracoContext(), member, tabs); + CustomizeProperties(source.Context.GetUmbracoContext(), member, tabs); return result; } @@ -500,6 +495,21 @@ namespace Umbraco.Web.Models.Mapping } } } + + /// + /// A converter to go from a to a + /// + internal class MembershipUserTypeConverter : ITypeConverter + { + public MemberDisplay Convert(ResolutionContext context) + { + var source = (MembershipUser)context.SourceValue; + //first convert to IMember + var member = Mapper.Map(source); + //then convert to MemberDisplay + return AutoMapperExtensions.MapWithUmbracoContext(member, context.GetUmbracoContext()); + } + } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs index a0b99252d2..a6e6472e16 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Models.Mapping private string ResolveCore(ResolutionResult res, IMember source) { - var umbCtx = res.GetUmbracoContext(); + var umbCtx = res.Context.GetUmbracoContext(); //map the tree node url if (umbCtx != null) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2a164b6ea1..e71441a06a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -330,6 +330,9 @@ + + + From 469d6f9ca30f2f33e5ac07aff5ed0ccbfe8de021 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Jan 2018 15:06:23 -0700 Subject: [PATCH 26/41] Now checks for the sensitiveData group to view sensitive data --- src/Umbraco.Core/Constants-Security.cs | 3 +- src/Umbraco.Core/Models/UserExtensions.cs | 12 +- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 2 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- .../Models/Mapping/ContentModelMapper.cs | 6 +- .../Models/Mapping/MediaModelMapper.cs | 5 +- .../Models/Mapping/MemberModelMapper.cs | 126 +++++++----------- .../Mapping/TabsAndPropertiesResolver.cs | 41 +++++- 9 files changed, 102 insertions(+), 97 deletions(-) diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0ada375163..9aa7a907dd 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -9,6 +9,7 @@ namespace Umbraco.Core { public const string AdminGroupAlias = "admin"; + public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; @@ -36,4 +37,4 @@ namespace Umbraco.Core } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index a65b307f24..5db36d16ed 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -261,6 +261,16 @@ namespace Umbraco.Core.Models return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } + /// + /// Determines whether this user has access to view sensitive data + /// + /// + public static bool HasAccessToSensitiveData(this IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + } + // calc. start nodes, combining groups' and user's, and excluding what's in the bin public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) { @@ -413,4 +423,4 @@ namespace Umbraco.Core.Models return lsn.ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 45107d3f21..be78638a9e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -219,7 +219,7 @@ namespace Umbraco.Web.Editors Path = "-1," + Constants.System.RecycleBinContent }; - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 0b2044dbe5..e22c83cbeb 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -115,7 +115,7 @@ namespace Umbraco.Web.Editors Path = "-1," + Constants.System.RecycleBinMedia }; - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 841b2df3d5..2440ad0691 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -154,7 +154,7 @@ namespace Umbraco.Web.Editors ParentId = -1 }; - TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 9d5975b564..6df9c40f6a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.Mapping expression.MapFrom(content => content.ContentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) .ToDictionary(t => t.Alias, t => t.Name))) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService), new Lazy(() => applicationContext.Services.ContentService)))) .AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, @@ -100,6 +100,8 @@ namespace Umbraco.Web.Models.Mapping private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService) { + //TODO: All of this logic should be moved to the TabsAndPropertiesResolver and not in AfterMap + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = content.Parent(); @@ -113,7 +115,7 @@ namespace Umbraco.Web.Models.Mapping if (content.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); + TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 9a0480f771..7069bea2e2 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Alias, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .ForMember(display => display.ContentType, expression => expression.ResolveUsing()) .ForMember(display => display.MediaLink, expression => expression.ResolveUsing( content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, applicationContext.ProfilingLogger.Logger)))) @@ -70,6 +70,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); } + //TODO: All of this logic should be moved to the TabsAndPropertiesResolver and not in AfterMap private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService, ILogger logger) { // Adapted from ContentModelMapper @@ -80,7 +81,7 @@ namespace Umbraco.Web.Models.Mapping if (media.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); + TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 7f5bc731b1..147c074747 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -217,7 +217,7 @@ namespace Umbraco.Web.Models.Mapping /// /// This also ensures that the IsSensitive property display value is set based on the configured IMemberType property type /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver + internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver { private readonly ILocalizedTextService _localizedTextService; private readonly IMemberService _memberService; @@ -238,45 +238,23 @@ namespace Umbraco.Web.Models.Mapping _memberService = memberService; _userService = userService; } - + /// - /// Explicitly implement to override behavior - /// - /// - /// - /// - /// This is required to get access to the object which allows us access to the mapping options - /// - ResolutionResult IValueResolver.Resolve(ResolutionResult source) - { - //call the base class logic - var result = base.Resolve(source); - - var member = (IMember)source.Value; - //now we can customize the result and use the ResolutionResult options to get the UmbracoContext - var tabs = (List>) result.Value; - - //now we can customize the result with the current context, we can get the UmbracoContext from the options - CustomizeProperties(source.Context.GetUmbracoContext(), member, tabs); - - return result; - } - - /// - /// Overridden to deal with custom member properties + /// Overridden to deal with custom member properties and permissions /// + /// /// /// - protected override List> ResolveCore(IContentBase content) + protected override List> ResolveCore(UmbracoContext umbracoContext, IMember content) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - + IgnoreProperties = content.PropertyTypes .Where(x => x.HasIdentity == false) .Select(x => x.Alias) .ToArray(); - var result = base.ResolveCore(content); + var result = base.ResolveCore(umbracoContext, content); if (provider.IsUmbracoMembershipProvider() == false) { @@ -303,6 +281,39 @@ namespace Umbraco.Web.Models.Mapping } } + if (umbracoContext != null && umbracoContext.Security.CurrentUser != null + && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", content.ContentTypeId); + + //Replace the doctype property + var docTypeProperty = result.SelectMany(x => x.Properties) + .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new + { + linkText = content.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; + } + + //check if there's an approval field + var legacyProvider = provider as global::umbraco.providers.members.UmbracoMembershipProvider; + if (content.HasIdentity == false && legacyProvider != null) + { + var approvedField = legacyProvider.ApprovedPropertyTypeAlias; + var prop = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); + if (prop != null) + { + prop.Value = 1; + } + } + return result; } @@ -362,63 +373,17 @@ namespace Umbraco.Web.Models.Mapping return genericProperties; } - - /// - /// Performs some customizations for the properties based on the current context - /// - /// - /// - /// The current tab collection - /// - /// If this is a new entity and there is an approved field then we'll set it to true by default. - /// - private void CustomizeProperties(UmbracoContext umbracoContext, IMember member, List> tabs) - { - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (umbracoContext != null && umbracoContext.Security.CurrentUser != null - && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doctype property - var docTypeProperty = tabs.SelectMany(x => x.Properties) - .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProperty.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = memberTypeLink, - target = "_self", - icon = "icon-item-arrangement" - } - }; - docTypeProperty.View = "urllist"; - } - - //check if there's an approval field - var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; - if (member.HasIdentity == false && provider != null) - { - var approvedField = provider.ApprovedPropertyTypeAlias; - var prop = tabs.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); - if (prop != null) - { - prop.Value = 1; - } - } - } - + /// /// Overridden to assign the IsSensitive property values /// + /// /// /// /// - protected override List MapProperties(IContentBase content, List properties) + protected override List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) { - var result = base.MapProperties(content, properties); + var result = base.MapProperties(umbracoContext, content, properties); var member = (IMember)content; var memberType = member.ContentType; var labelPropEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; @@ -427,8 +392,7 @@ namespace Umbraco.Web.Models.Mapping { prop.IsSensitive = memberType.IsSensitiveProperty(prop.Alias); //check permissions for viewing sensitive data - - if (prop.IsSensitive) + if (prop.IsSensitive && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) { //replace this editor with a label prop.View = labelPropEditor; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 55a72cb704..3792ba39ec 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -16,7 +16,8 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates the tabs collection with properties assigned for display models /// - internal class TabsAndPropertiesResolver : ValueResolver>> + internal class TabsAndPropertiesResolver : IValueResolver + where TSource : IContentBase { private readonly ILocalizedTextService _localizedTextService; protected IEnumerable IgnoreProperties { get; set; } @@ -35,6 +36,24 @@ namespace Umbraco.Web.Models.Mapping IgnoreProperties = ignoreProperties; } + /// + /// Implements the + /// + /// + /// + public ResolutionResult Resolve(ResolutionResult source) + { + if (source.Value != null && (source.Value is TSource) == false) + throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", new object[] + { + source.Value.GetType(), + typeof (TSource) + })); + return source.New( + //perform the mapping with the current umbraco context + ResolveCore(source.Context.GetUmbracoContext(), (TSource)source.Value), typeof(List>)); + } + /// /// Adds the container (listview) tab to the document /// @@ -145,7 +164,13 @@ namespace Umbraco.Web.Models.Mapping display.Tabs = tabs; } - protected override List> ResolveCore(IContentBase content) + /// + /// Create the list of tabs for the + /// + /// + /// Source value + /// Destination + protected virtual List> ResolveCore(UmbracoContext umbracoContext, TSource content) { var tabs = new List>(); @@ -171,7 +196,7 @@ namespace Umbraco.Web.Models.Mapping continue; //map the properties - var mappedProperties = MapProperties(content, properties); + var mappedProperties = MapProperties(umbracoContext, content, properties); // add the tab // we need to pick an identifier... there is no "right" way... @@ -189,7 +214,7 @@ namespace Umbraco.Web.Models.Mapping }); } - MapGenericProperties(content, tabs); + MapGenericProperties(umbracoContext, content, tabs); // activate the first tab tabs[0].IsActive = true; @@ -209,20 +234,21 @@ namespace Umbraco.Web.Models.Mapping /// /// Maps properties on to the generic properties tab /// + /// /// /// /// /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// - protected virtual void MapGenericProperties(IContentBase content, List> tabs) + protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab var noGroupProperties = content.GetNonGroupedProperties() .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored .ToList(); - var genericproperties = MapProperties(content, noGroupProperties); + var genericproperties = MapProperties(umbracoContext, content, noGroupProperties); tabs.Add(new Tab { @@ -268,10 +294,11 @@ namespace Umbraco.Web.Models.Mapping /// /// Maps a list of to a list of /// + /// /// /// /// - protected virtual List MapProperties(IContentBase content, List properties) + protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) { var result = Mapper.Map, IEnumerable>( // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. From f08192a4ab76ac1dcaf519130a929cae894df9f5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 Jan 2018 15:52:12 -0700 Subject: [PATCH 27/41] Changes copy for hidden values and makes the rendering for hidden values it's own property editor view so we can more easily update it. --- .../propertyeditors/sensitivevalue/sensitivevalue.html | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs | 8 ++++---- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/sensitivevalue/sensitivevalue.html diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/sensitivevalue/sensitivevalue.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/sensitivevalue/sensitivevalue.html new file mode 100644 index 0000000000..234ee4548e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/sensitivevalue/sensitivevalue.html @@ -0,0 +1,3 @@ +
    + {{model.value}} +
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 7a8cb6da50..ed30a4a997 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -221,7 +221,7 @@ Add another text box Remove this text box Content root - Your user permissions do not allow access to this property + This value is hidden. If you need access to view this value please contact your website administrator. Create a new Content Template from '%0%' 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 92ec93ab71..abbfcad4b2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -227,7 +227,7 @@ Add another text box Remove this text box Content root - Your user permissions do not allow access to this property + This value is hidden. If you need access to view this value please contact your website administrator. Create a new Content Template from '%0%' diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 147c074747..6fdf32bcb4 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -386,7 +386,7 @@ namespace Umbraco.Web.Models.Mapping var result = base.MapProperties(umbracoContext, content, properties); var member = (IMember)content; var memberType = member.ContentType; - var labelPropEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; + //now update the IsSensitive value foreach (var prop in result) { @@ -394,10 +394,10 @@ namespace Umbraco.Web.Models.Mapping //check permissions for viewing sensitive data if (prop.IsSensitive && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) { - //replace this editor with a label - prop.View = labelPropEditor; + //replace this editor with a sensitivevalue + prop.View = "sensitivevalue"; //replace the value - prop.Value = string.Format("{0}", _localizedTextService.Localize("content/isSensitiveValue")); + prop.Value = _localizedTextService.Localize("content/isSensitiveValue"); } } return result; From e039265fab959122119381502eb3b590af339f5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 Jan 2018 16:11:14 -0700 Subject: [PATCH 28/41] adds descriptions to the member type options --- .../propertysettings/propertysettings.html | 15 +++++++++------ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 05da7e1ded..9a5008da2d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -93,18 +93,21 @@
    - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index ed30a4a997..64eef18fd1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1611,8 +1611,12 @@ To manage your website, simply open the Umbraco back office and start adding con using this editor will get updated with the new settings Member can edit + Allow this property value to be edited by the member on their profile page Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information Show on member profile + Allow this property value to be displayed on the member profile page + tab has no sort order 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 abbfcad4b2..d2b6ca6f82 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1615,8 +1615,12 @@ To manage your website, simply open the Umbraco back office and start adding con using this editor will get updated with the new settings Member can edit + Allow this property value to be edited by the member on their profile page Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information Show on member profile + Allow this property value to be displayed on the member profile page + tab has no sort order From f7eec8dafbefec7b4c17d56bbc2d5dea46ce689a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 26 Jan 2018 16:24:40 -0700 Subject: [PATCH 29/41] Allows editing the property metadata even if the property type is locked --- .../directives/components/umbgroupsbuilder.directive.js | 3 ++- .../propertysettings/propertysettings.html | 6 +++--- .../src/views/components/umb-groups-builder.html | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index caa79439be..c0be05addf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -489,7 +489,7 @@ scope.editPropertyTypeSettings = function(property, group) { - if (!property.inherited && !property.locked) { + if (!property.inherited) { scope.propertySettingsDialogModel = {}; scope.propertySettingsDialogModel.title = "Property settings"; @@ -547,6 +547,7 @@ property.validation.pattern = oldModel.property.validation.pattern; property.showOnMemberProfile = oldModel.property.showOnMemberProfile; property.memberCanEdit = oldModel.property.memberCanEdit; + property.isSensitiveValue = oldModel.property.isSensitiveValue; // because we set state to active, to show a preview, we have to check if has been filled out // label is required so if it is not filled we know it is a placeholder diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 9a5008da2d..50baede6f6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -1,6 +1,6 @@
    -
    +