diff --git a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs index b68aef3c3d..6b55a4af7e 100644 --- a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs +++ b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Composing.CompositionRoots { public void Compose(IServiceRegistry container) { - container.Register(); + container.Register(); } } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs similarity index 90% rename from src/Umbraco.Core/Models/Identity/IdentityProfile.cs rename to src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs index f44003b62a..81069bd74c 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityProfile.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Models.Identity { - public class IdentityProfile : Profile + public class IdentityMapperProfile : Profile { - public IdentityProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) + public IdentityMapperProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) { CreateMap() .BeforeMap((src, dest) => @@ -19,6 +19,7 @@ namespace Umbraco.Core.Models.Identity }) .ConstructUsing(src => new BackOfficeIdentityUser(src.Id, src.Groups)) .ForMember(dest => dest.LastLoginDateUtc, opt => opt.MapFrom(src => src.LastLoginDate.ToUniversalTime())) + .ForMember(user => user.LastPasswordChangeDateUtc, expression => expression.MapFrom(user => user.LastPasswordChangeDate.ToUniversalTime())) .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) .ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue)) .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index e953a6073e..30e76468a6 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1841,7 +1841,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs()); foreach (var x in copies) scope.Events.Dispatch(Copied, this, new CopyEventArgs(x.Item1, x.Item2, false, x.Item2.ParentId, relateToOriginal)); - Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id); + Audit(AuditType.Copy, "Copy Content performed by user", userId, content.Id); scope.Complete(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4c2dd2df3f..1689be9b85 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -355,6 +355,7 @@ + @@ -663,7 +664,6 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 072f1b9b5e..720edc2114 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,170 +1,170 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.model; + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.model; - var anchorPattern = //gi; + var anchorPattern = //gi; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); - } + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); + } - $scope.dialogTreeApi = {}; - $scope.model.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + $scope.dialogTreeApi = {}; + $scope.model.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; - $scope.showTarget = $scope.model.hideTarget !== true; + $scope.showTarget = $scope.model.hideTarget !== true; - if (dialogOptions.currentTarget) { - $scope.model.target = dialogOptions.currentTarget; - //if we have a node ID, we fetch the current node to build the form data - if ($scope.model.target.id || $scope.model.target.udi) { + if (dialogOptions.currentTarget) { + $scope.model.target = dialogOptions.currentTarget; + //if we have a node ID, we fetch the current node to build the form data + if ($scope.model.target.id || $scope.model.target.udi) { - //will be either a udi or an int - var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; + //will be either a udi or an int + var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; - if (!$scope.model.target.path) { + if (!$scope.model.target.path) { - entityResource.getPath(id, "Document").then(function (path) { - $scope.model.target.path = path; - //now sync the tree to this path - $scope.dialogTreeApi.syncTree({ path: $scope.model.target.path, tree: "content" }); - path: $scope.model.target.path, - tree: "content" - }); - }); - } + entityResource.getPath(id, "Document").then(function (path) { + $scope.model.target.path = path; + //now sync the tree to this path + $scope.dialogTreeApi.syncTree({ + path: $scope.model.target.path, + tree: "content" + }); + }); + } - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0]; - }); - } else if ($scope.model.target.url.length) { - // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs - $scope.model.target.url = $scope.model.target.url.substring(0, $scope.model.target.url.search(/(#|\?)/)); - } - } else if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.model.target.url = resp.urls[0]; + }); + } else if ($scope.model.target.url.length) { + // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs + $scope.model.target.url = $scope.model.target.url.substring(0, $scope.model.target.url.search(/(#|\?)/)); + } + } else if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } - function nodeSelectHandler(args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - eventsService.emit("dialogs.linkPicker.select", args); + eventsService.emit("dialogs.linkPicker.select", args); - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; - $scope.model.target.udi = args.node.udi; - $scope.model.target.name = args.node.name; + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.udi = args.node.udi; + $scope.model.target.name = args.node.name; - if (args.node.id < 0) { - $scope.model.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0]; - }); - } + if (args.node.id < 0) { + $scope.model.target.url = "/"; + } else { + contentResource.getById(args.node.id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.model.target.url = resp.urls[0]; + }); + } - if (!angular.isUndefined($scope.model.target.isMedia)) { - delete $scope.model.target.isMedia; - } - } + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } - function nodeExpandedHandler(args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, - show: true, - submit: function (model) { - var media = model.selectedImages[0]; + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + startNodeIsVirtual: userData.startMediaIds.length !== 1, + show: true, + submit: function (model) { + var media = model.selectedImages[0]; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); - - debugger; + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - }); - }; + debugger; - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }); + }; - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; - $scope.onTreeInit = function () { - $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); - $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - } - - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { - node: node - }); - }; + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); + } - function openMiniListView(node) { - $scope.miniListView = node; - } + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { + node: node + }); + }; - }); + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs b/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs index 43544c5af0..e865019790 100644 --- a/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs +++ b/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs @@ -1,5 +1,8 @@ using LightInject; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Trees; namespace Umbraco.Web.Composing.CompositionRoots { @@ -7,6 +10,7 @@ namespace Umbraco.Web.Composing.CompositionRoots { public void Compose(IServiceRegistry container) { + //register the profiles container.Register(); container.Register(); container.Register(); @@ -25,6 +29,16 @@ namespace Umbraco.Web.Composing.CompositionRoots container.Register(); container.Register(); container.Register(); + + //register any resolvers, etc.. that the profiles use + container.Register(); + container.Register>(); + container.Register>(); + container.Register>(); + container.Register>(); + container.Register(); + container.Register(); + container.Register(); } } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 801eb84d5d..650f0d082b 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1322,7 +1322,7 @@ namespace Umbraco.Web.Editors culture = Services.LocalizationService.GetDefaultLanguageIsoCode(); } - var display = ContextMapper.Map(content, UmbracoContext, + var display = ContextMapper.Map(content, new Dictionary { { ContextMapper.CultureKey, culture } }); return display; diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 059dd32499..a133705e15 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -14,18 +14,25 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentMapperProfile : Profile { - public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, ILocalizationService localizationService, ILogger logger) + public ContentMapperProfile( + ContentUrlResolver contentUrlResolver, + ContentTreeNodeUrlResolver contentTreeNodeUrlResolver, + TabsAndPropertiesResolver tabsAndPropertiesResolver, + IUserService userService, + ILocalizedTextService textService, + IContentService contentService, + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILogger logger) { // create, capture, cache var contentOwnerResolver = new OwnerResolver(userService); var creatorResolver = new CreatorResolver(userService); var actionButtonsResolver = new ActionButtonsResolver(userService, contentService); - var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService); var contentTypeBasicResolver = new ContentTypeBasicResolver(); - var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var defaultTemplateResolver = new DefaultTemplateResolver(); - var contentUrlResolver = new ContentUrlResolver(textService, contentService, logger); var variantResolver = new ContentItemDisplayVariationResolver(localizationService); //FROM IContent TO ContentItemDisplay diff --git a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs index 48400f51f6..91420fbe21 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs @@ -12,9 +12,16 @@ namespace Umbraco.Web.Models.Mapping where TSource : IContentBase where TController : ContentTreeControllerBase { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public ContentTreeNodeUrlResolver(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + } + public string Resolve(TSource source, object destination, string destMember, ResolutionContext context) { - var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); + var umbracoContext = _umbracoContextAccessor.UmbracoContext; if (umbracoContext == null) return null; var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); diff --git a/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs index a278a4a8c5..dd235bda75 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs @@ -10,24 +10,36 @@ namespace Umbraco.Web.Models.Mapping { internal class ContentUrlResolver : IValueResolver { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly PublishedRouter _publishedRouter; + private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _textService; private readonly IContentService _contentService; private readonly ILogger _logger; - public ContentUrlResolver(ILocalizedTextService textService, IContentService contentService, ILogger logger) + public ContentUrlResolver( + IUmbracoContextAccessor umbracoContextAccessor, + PublishedRouter publishedRouter, + ILocalizationService localizationService, + ILocalizedTextService textService, + IContentService contentService, + ILogger logger) { - _textService = textService; - _contentService = contentService; - _logger = logger; + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _publishedRouter = publishedRouter ?? throw new System.ArgumentNullException(nameof(publishedRouter)); + _localizationService = localizationService ?? throw new System.ArgumentNullException(nameof(localizationService)); + _textService = textService ?? throw new System.ArgumentNullException(nameof(textService)); + _contentService = contentService ?? throw new System.ArgumentNullException(nameof(contentService)); + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } public UrlInfo[] Resolve(IContent source, ContentItemDisplay destination, UrlInfo[] destMember, ResolutionContext context) { - var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); + var umbracoContext = _umbracoContextAccessor.UmbracoContext; var urls = umbracoContext == null ? new[] { UrlInfo.Message("Cannot generate urls without a current Umbraco Context") } - : source.GetContentUrls(umbracoContext.UrlProvider, _textService, _contentService, _logger).ToArray(); + : source.GetContentUrls(_publishedRouter, umbracoContext, _localizationService, _textService, _contentService, _logger).ToArray(); return urls; } diff --git a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs index 5aa8af7668..6897a8ae62 100644 --- a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs @@ -10,42 +10,9 @@ namespace Umbraco.Web.Models.Mapping /// internal static class ContextMapper { - public const string UmbracoContextKey = "ContextMapper.UmbracoContext"; + //public const string UmbracoContextKey = "ContextMapper.UmbracoContext"; public const string CultureKey = "ContextMapper.Culture"; - - public static TDestination Map(TSource obj, UmbracoContext umbracoContext) - => Mapper.Map(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext); - - public static TDestination Map(TSource obj, UmbracoContext umbracoContext, IDictionary contextVals) - => Mapper.Map(obj, opt => - { - //set the umb ctx - opt.Items[UmbracoContextKey] = umbracoContext; - //set other supplied context vals - if (contextVals != null) - { - foreach (var contextVal in contextVals) - { - opt.Items[contextVal.Key] = contextVal.Value; - } - } - }); - - public static TDestination Map(TSource obj, UmbracoContext umbracoContext, object contextVals) - => Mapper.Map(obj, opt => - { - //set the umb ctx - opt.Items[UmbracoContextKey] = umbracoContext; - //set other supplied context vals - if (contextVals != null) - { - foreach (var contextVal in contextVals.ToDictionary()) - { - opt.Items[contextVal.Key] = contextVal.Value; - } - } - }); - + public static TDestination Map(TSource obj, IDictionary contextVals) => Mapper.Map(obj, opt => { @@ -86,27 +53,6 @@ namespace Umbraco.Web.Models.Mapping return null; } - - /// - /// Returns the in the mapping context if one is found - /// - /// - /// - /// - public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true) - { - if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext) - return umbracoContext; - - // better fail fast - if (throwIfMissing) - throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext."); - - // fixme - not a good idea at all - // because this falls back to magic singletons - // so really we should remove this line, but then some tests+app breaks ;( - return Umbraco.Web.Composing.Current.UmbracoContext; - } } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index 61419281c4..b7b38dfcb1 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -14,13 +14,19 @@ namespace Umbraco.Web.Models.Mapping /// internal class MediaMapperProfile : Profile { - public MediaMapperProfile(IUserService userService, ILocalizedTextService textService, IDataTypeService dataTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, ILogger logger) + public MediaMapperProfile( + TabsAndPropertiesResolver tabsAndPropertiesResolver, + ContentTreeNodeUrlResolver contentTreeNodeUrlResolver, + IUserService userService, + ILocalizedTextService textService, + IDataTypeService dataTypeService, + IMediaService mediaService, + IMediaTypeService mediaTypeService, + ILogger logger) { // create, capture, cache var mediaOwnerResolver = new OwnerResolver(userService); - var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService); - var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var mediaTypeBasicResolver = new ContentTypeBasicResolver(); //FROM IMedia TO MediaItemDisplay diff --git a/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs index 09d0657530..fbd14876ea 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AutoMapper; using Umbraco.Core.Models; @@ -11,9 +12,17 @@ namespace Umbraco.Web.Models.Mapping /// internal class MemberBasicPropertiesResolver : IValueResolver> { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public MemberBasicPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + } + public IEnumerable Resolve(IMember source, MemberBasic destination, IEnumerable destMember, ResolutionContext context) { - var umbracoContext = context.GetUmbracoContext(); + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) throw new InvalidOperationException("Cannot resolve value without an UmbracoContext available"); var result = Mapper.Map, IEnumerable>( // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. @@ -39,4 +48,4 @@ namespace Umbraco.Web.Models.Mapping return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs index d0ab4099a9..ae4fc19d54 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -14,16 +14,19 @@ namespace Umbraco.Web.Models.Mapping /// internal class MemberMapperProfile : Profile { - public MemberMapperProfile(IUserService userService, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService) + public MemberMapperProfile( + MemberTabsAndPropertiesResolver tabsAndPropertiesResolver, + MemberTreeNodeUrlResolver memberTreeNodeUrlResolver, + MemberBasicPropertiesResolver memberBasicPropertiesResolver, + IUserService userService, + IMemberTypeService memberTypeService, + IMemberService memberService) { // create, capture, cache var memberOwnerResolver = new OwnerResolver(userService); - var tabsAndPropertiesResolver = new MemberTabsAndPropertiesResolver(textService, memberService, userService); var memberProfiderFieldMappingResolver = new MemberProviderFieldResolver(); var membershipScenarioMappingResolver = new MembershipScenarioResolver(memberTypeService); var memberDtoPropertiesResolver = new MemberDtoPropertiesResolver(); - var memberTreeNodeUrlResolver = new MemberTreeNodeUrlResolver(); - var memberBasicPropertiesResolver = new MemberBasicPropertiesResolver(); //FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider CreateMap().ConvertUsing(); diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs index 66fb3619cf..c268af2be7 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs @@ -22,24 +22,27 @@ namespace Umbraco.Web.Models.Mapping /// internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILocalizedTextService _localizedTextService; private readonly IMemberService _memberService; private readonly IUserService _userService; - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService) - : base(localizedTextService) + public MemberTabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService) + : base(umbracoContextAccessor, localizedTextService) { - _localizedTextService = localizedTextService; - _memberService = memberService; - _userService = userService; + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _localizedTextService = localizedTextService ?? throw new System.ArgumentNullException(nameof(localizedTextService)); + _memberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService)); + _userService = userService ?? throw new System.ArgumentNullException(nameof(userService)); } - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties, IMemberService memberService, IUserService userService) - : base(localizedTextService, ignoreProperties) + public MemberTabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IEnumerable ignoreProperties, IMemberService memberService, IUserService userService) + : base(umbracoContextAccessor, localizedTextService, ignoreProperties) { - _localizedTextService = localizedTextService; - _memberService = memberService; - _userService = userService; + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _localizedTextService = localizedTextService ?? throw new System.ArgumentNullException(nameof(localizedTextService)); + _memberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService)); + _userService = userService ?? throw new System.ArgumentNullException(nameof(userService)); } /// @@ -80,7 +83,7 @@ namespace Umbraco.Web.Models.Mapping } } - var umbracoContext = context.GetUmbracoContext(); + var umbracoContext = _umbracoContextAccessor.UmbracoContext; if (umbracoContext != null && umbracoContext.Security.CurrentUser != null && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) diff --git a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs index 864fd18ab2..c4655294d7 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs @@ -11,9 +11,16 @@ namespace Umbraco.Web.Models.Mapping /// internal class MemberTreeNodeUrlResolver : IValueResolver { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public MemberTreeNodeUrlResolver(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + } + public string Resolve(IMember source, MemberDisplay destination, string destMember, ResolutionContext context) { - var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); + var umbracoContext = _umbracoContextAccessor.UmbracoContext; if (umbracoContext == null) return null; var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); diff --git a/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs b/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs index dbbf6f69df..38482cca49 100644 --- a/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs @@ -13,9 +13,9 @@ namespace Umbraco.Web.Models.Mapping public MemberDisplay Convert(MembershipUser source, MemberDisplay destination, ResolutionContext context) { //first convert to IMember - var member = Mapper.Map(source); + var member = Mapper.Map(source); //then convert to MemberDisplay - return ContextMapper.Map(member, context.GetUmbracoContext()); + return Mapper.Map(member); } } -} \ 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 1f8cfa920e..44d69ad32e 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -250,17 +250,25 @@ namespace Umbraco.Web.Models.Mapping internal class TabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver>> where TSource : IContentBase { - public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) - : base(localizedTextService) - { } + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) + public TabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService) + : base(localizedTextService) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } + + public TabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) : base(localizedTextService, ignoreProperties) - { } + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } public virtual IEnumerable> Resolve(TSource source, TDestination destination, IEnumerable> destMember, ResolutionContext context) { - var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); // fixme + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) throw new InvalidOperationException("Cannot resolve value without an UmbracoContext available"); + var tabs = new List>(); // add the tabs, for properties that belong to a tab diff --git a/src/Umbraco.Web/Routing/PublishedRequest.cs b/src/Umbraco.Web/Routing/PublishedRequest.cs index 492837104d..17a9cc89e1 100644 --- a/src/Umbraco.Web/Routing/PublishedRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedRequest.cs @@ -78,6 +78,11 @@ namespace Umbraco.Web.Routing _publishedRouter.PrepareRequest(this); } + /// + /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// + public bool IgnorePublishedContentCollisions { get; set; } + #region Events /// diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 9f1d42d6b1..ce09bdc645 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -18,10 +18,18 @@ namespace Umbraco.Web.Routing /// Use when displaying Urls. If errors occur when generating the Urls, they will show in the list. /// Contains all the Urls that we can figure out (based upon domains, etc). /// - public static IEnumerable GetContentUrls(this IContent content, UrlProvider urlProvider, ILocalizedTextService textService, IContentService contentService, ILogger logger) + public static IEnumerable GetContentUrls(this IContent content, + PublishedRouter publishedRouter, + UmbracoContext umbracoContext, + ILocalizationService localizationService, + ILocalizedTextService textService, + IContentService contentService, + ILogger logger) { if (content == null) throw new ArgumentNullException(nameof(content)); - if (urlProvider == null) throw new ArgumentNullException(nameof(urlProvider)); + if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (localizationService == null) throw new ArgumentNullException(nameof(localizationService)); if (textService == null) throw new ArgumentNullException(nameof(textService)); if (contentService == null) throw new ArgumentNullException(nameof(contentService)); if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -33,12 +41,7 @@ namespace Umbraco.Web.Routing urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished"))); return urls; } - - // fixme inject - // fixme PublishedRouter is stateless and should be a singleton! - var localizationService = Core.Composing.Current.Services.LocalizationService; - var publishedRouter = Core.Composing.Current.Container.GetInstance(); - + // build a list of urls, for the back-office // which will contain // - the 'main' urls, which is what .Url would return, for each culture @@ -61,7 +64,7 @@ namespace Umbraco.Web.Routing string url; try { - url = urlProvider.GetUrl(content.Id, culture); + url = umbracoContext.UrlProvider.GetUrl(content.Id, culture); } catch (Exception e) { @@ -83,7 +86,7 @@ namespace Umbraco.Web.Routing // got a url, deal with collisions, add url default: - if (!DetectCollision(content, url, urls, culture, publishedRouter, textService)) // detect collisions, etc + if (!DetectCollision(content, url, urls, culture, umbracoContext, publishedRouter, textService)) // detect collisions, etc urls.Add(UrlInfo.Url(url, culture)); break; } @@ -137,13 +140,13 @@ namespace Umbraco.Web.Routing urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); } - private static bool DetectCollision(IContent content, string url, List urls, string culture, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, List urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); - if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl); + if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); uri = UriUtility.UriToUmbraco(uri); - var pcr = publishedRouter.CreateRequest(UmbracoContext.Current, uri); + var pcr = publishedRouter.CreateRequest(umbracoContext, uri); publishedRouter.TryRouteRequest(pcr); if (pcr.HasPublishedContent == false) @@ -152,6 +155,9 @@ namespace Umbraco.Web.Routing return true; } + if (pcr.IgnorePublishedContentCollisions) + return false; + if (pcr.PublishedContent.Id != content.Id) { var o = pcr.PublishedContent; diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index a4e5db0767..03ba763527 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -192,7 +192,7 @@ namespace Umbraco.Web.Runtime composition.Container.RegisterAuto(typeof(UmbracoViewPage<>)); // register published router - composition.Container.Register(); + composition.Container.RegisterSingleton(); composition.Container.Register(_ => UmbracoConfig.For.UmbracoSettings().WebRouting); // register preview SignalR hub