From 9a57f463ceb4aff0b72a27006dbe893741f80e91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 14 Mar 2017 15:31:30 +1100 Subject: [PATCH] Updates LocalLinks to be UDI, this will support both INT and UDI as LocalLink but when a new LocalLink is picked, it will use the UDI so eventually the INT will be gone --- src/Umbraco.Core/Constants-ObjectTypes.cs | 10 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Web/TemplateUtilitiesTests.cs | 69 +++++++++++++++++++ .../tinymce/plugins/umbracolink/plugin.min.js | 21 +++++- .../src/common/services/tinymce.service.js | 22 +++++- .../linkpicker/linkpicker.controller.js | 18 +++-- src/Umbraco.Web/Editors/ContentController.cs | 44 +++++++++++- src/Umbraco.Web/Editors/EntityController.cs | 33 ++++++++- src/Umbraco.Web/Routing/UrlProvider.cs | 68 ++++++++++++++++++ .../Templates/TemplateUtilities.cs | 65 ++++++++++++----- .../Trees/ContentTreeController.cs | 7 +- .../Trees/ContentTypeTreeController.cs | 2 +- .../Trees/DataTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 7 +- .../Trees/MediaTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/MemberTreeController.cs | 4 +- .../Trees/MemberTypeTreeController.cs | 2 +- .../Trees/TemplatesTreeController.cs | 9 +-- src/Umbraco.Web/Trees/TreeControllerBase.cs | 57 +++++++++++++++ 19 files changed, 395 insertions(+), 48 deletions(-) create mode 100644 src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 229bd1b7d7..97213ae935 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -89,6 +89,11 @@ namespace Umbraco.Core /// public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + /// + /// Guid for a Document object. + /// + public static readonly Guid MediaGuid = new Guid(Media); + /// /// Guid for the Media Recycle Bin. /// @@ -148,6 +153,11 @@ namespace Umbraco.Core /// public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; + /// + /// Guid for a Template object. + /// + public static readonly Guid TemplateTypeGuid = new Guid(Template); + /// /// Guid for a Lock object. /// diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index eb126eb68e..31317aae51 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -594,6 +594,7 @@ + diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs new file mode 100644 index 0000000000..5c0880ee6e --- /dev/null +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Web; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Profiling; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; +using Umbraco.Web.Templates; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class TemplateUtilitiesTests + { + [TestCase("", "")] + [TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] + [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] + //this one has an invalid char so won't match + [TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] + public void ParseLocalLinks(string input, string result) + { + var serviceCtxMock = MockHelper.GetMockedServiceContext(); + + //setup a mock entity service from the service context to return an integer for a GUID + var entityService = Mock.Get(serviceCtxMock.EntityService); + entityService.Setup(x => x.GetIdForKey(It.IsAny(), It.IsAny())) + .Returns((Guid id, UmbracoObjectTypes objType) => + { + return Attempt.Succeed(1234); + }); + + //setup a mock url provider which we'll use fo rtesting + var testUrlProvider = new Mock(); + testUrlProvider.Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((UmbracoContext umbCtx, int id, Uri url, UrlProviderMode mode) => + { + return "/my-test-url"; + }); + + using (var appCtx = new ApplicationContext(new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"), + serviceCtxMock, + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of()))) + using (var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), appCtx, new Mock(null, null).Object, + //setup a quick mock of the WebRouting section + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), + //pass in the custom url provider + new[]{ testUrlProvider.Object }, + false)) + { + var output = TemplateUtilities.ParseInternalLinks(input, umbCtx.UrlProvider); + + Assert.AreEqual(result, output); + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js index deb0a3cf3e..3f936b0577 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js @@ -172,8 +172,17 @@ tinymce.PluginManager.add('umbracolink', function(editor) { //locallink detection, we do this here, to avoid poluting the dialogservice //so the dialog service can just expect to get a node-like structure - if(currentTarget.url.indexOf("localLink:") > 0){ - currentTarget.id = currentTarget.url.substring(currentTarget.url.indexOf(":")+1,currentTarget.url.length-1); + if (currentTarget.url.indexOf("localLink:") > 0) { + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.length - 1); + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } + else { + currentTarget.id = linkId; + } } } @@ -213,7 +222,13 @@ tinymce.PluginManager.add('umbracolink', function(editor) { //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set if(data.id && (angular.isUndefined(data.isMedia) || !data.isMedia)){ - href = "/{localLink:" + data.id + "}"; + if (target.udi) { + href = "/{localLink:" + target.udi + "}"; + } + else { + //This shouldn't happen! but just in case we'll leave this here + href = "/{localLink:" + target.id + "}"; + } insertLink(); return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index cdc7d77ca4..8676ac5b34 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -672,8 +672,17 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro //locallink detection, we do this here, to avoid poluting the dialogservice //so the dialog service can just expect to get a node-like structure - if(currentTarget.url.indexOf("localLink:") > 0){ - currentTarget.id = currentTarget.url.substring(currentTarget.url.indexOf(":")+1,currentTarget.url.length-1); + if (currentTarget.url.indexOf("localLink:") > 0) { + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.length - 1); + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } + else { + currentTarget.id = linkId; + } } } @@ -747,7 +756,14 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set if(target.id && (angular.isUndefined(target.isMedia) || !target.isMedia)){ - href = "/{localLink:" + target.id + "}"; + if (target.udi) { + href = "/{localLink:" + target.udi + "}"; + } + else { + //This shouldn't happen! but just in case we'll leave this here + href = "/{localLink:" + target.id + "}"; + } + insertLink(); return; } 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 5d638444c7..e2f95b0478 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 @@ -26,17 +26,21 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $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) { + if ($scope.model.target.id || $scope.model.target.udi) { - if (!$scope.model.target.path) { - entityResource.getPath($scope.model.target.id, "Document").then(function (path) { + //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) { + + entityResource.getPath(id, "Document").then(function (path) { $scope.model.target.path = path; //now sync the tree to this path $scope.dialogTreeEventHandler.syncTree({ path: $scope.model.target.path, tree: "content" }); }); } - contentResource.getNiceUrl($scope.model.target.id).then(function (url) { + contentResource.getNiceUrl(id).then(function (url) { $scope.model.target.url = url; }); } @@ -63,7 +67,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.currentNode = args.node; $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; + $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) { @@ -116,7 +121,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", submit: function(model) { var media = model.selectedImages[0]; - $scope.model.target.id = media.id; + $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); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ed0ac95a2b..da5e0c3a3b 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http; using System.Text; using System.Web.Http; +using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; using AutoMapper; using Umbraco.Core; @@ -35,7 +36,8 @@ namespace Umbraco.Web.Editors /// access to ALL of the methods on this controller will need access to the content application. /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [ContentControllerConfiguration] public class ContentController : ContentControllerBase { /// @@ -55,6 +57,18 @@ namespace Umbraco.Web.Editors { } + /// + /// Configures this controller with a custom action selector + /// + private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)))); + } + } + /// /// Return content for the specified ids /// @@ -162,6 +176,34 @@ namespace Umbraco.Web.Editors return response; } + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Guid id) + { + var url = Umbraco.UrlProvider.GetUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetNiceUrl(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + /// /// Gets the children for the content id passed in /// diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 1bb0cdfdf6..48da53e263 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" //id is passed in eventually we'll probably want to support GUID + Udi too new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), - + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); } @@ -153,7 +153,36 @@ namespace Umbraco.Web.Editors return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } - + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) + { + var foundContent = GetResultForKey(id, type); + + return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetPath(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + /// /// Gets the url of an entity /// diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 2bb012d207..f237a7d886 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Web.PublishedCache; using Umbraco.Core; +using Umbraco.Core.Models; namespace Umbraco.Web.Routing { @@ -24,6 +25,7 @@ namespace Umbraco.Web.Routing public UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders) { if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (routingSettings == null) throw new ArgumentNullException("routingSettings"); _umbracoContext = umbracoContext; _urlProviders = urlProviders; @@ -65,6 +67,72 @@ namespace Umbraco.Web.Routing #region GetUrl + /// + /// Gets the url of a published content. + /// + /// The published content identifier. + /// The url for the published content. + /// + /// The url is absolute or relative depending on Mode and on the current url. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(Guid id) + { + var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + return GetUrl(intId.Success ? intId.Result : -1); + } + + /// + /// Gets the nice url of a published content. + /// + /// The published content identifier. + /// A value indicating whether the url should be absolute in any case. + /// The url for the published content. + /// + /// The url is absolute or relative depending on Mode and on current, unless + /// absolute is true, in which case the url is always absolute. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(Guid id, bool absolute) + { + var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + return GetUrl(intId.Success ? intId.Result : -1, absolute); + } + + /// + /// Gets the nice url of a published content. + /// + /// The published content id. + /// The current absolute url. + /// A value indicating whether the url should be absolute in any case. + /// The url for the published content. + /// + /// The url is absolute or relative depending on Mode and on current, unless + /// absolute is true, in which case the url is always absolute. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(Guid id, Uri current, bool absolute) + { + var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + return GetUrl(intId.Success ? intId.Result : -1, current, absolute); + } + + /// + /// Gets the nice url of a published content. + /// + /// The published content identifier. + /// The url mode. + /// The url for the published content. + /// + /// The url is absolute or relative depending on mode and on the current url. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(Guid id, UrlProviderMode mode) + { + var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + return GetUrl(intId.Success ? intId.Result : -1, mode); + } + /// /// Gets the url of a published content. /// diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index c56d7b5b8a..a7e6738374 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { @@ -39,11 +40,51 @@ namespace Umbraco.Web.Templates /// Parses the string looking for the {localLink} syntax and updates them to their correct links. /// /// + /// /// - public static string ParseInternalLinks(string text) + public static string ParseInternalLinks(string text, UrlProvider urlProvider) { - //TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable, better yet, pass in urlprovider, routing context, separately + if (urlProvider == null) throw new ArgumentNullException("urlProvider"); + // Parse internal links + var tags = LocalLinkPattern.Matches(text); + foreach (Match tag in tags) + { + if (tag.Groups.Count > 0) + { + var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + + //The id could be an int or a UDI + Udi udi; + if (Udi.TryParse(id, out udi)) + { + var guidUdi = udi as GuidUdi; + if (guidUdi != null) + { + var newLink = urlProvider.GetUrl(guidUdi.Guid); + text = text.Replace(tag.Value, "href=\"" + newLink); + } + } + int intId; + if (int.TryParse(id, out intId)) + { + var newLink = urlProvider.GetUrl(intId); + text = text.Replace(tag.Value, "href=\"" + newLink); + } + } + } + + return text; + } + + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + [Obsolete("Use the overload specifying all dependencies instead")] + public static string ParseInternalLinks(string text) + { //don't attempt to proceed without a context as we cannot lookup urls without one if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null) { @@ -51,22 +92,14 @@ namespace Umbraco.Web.Templates } var urlProvider = UmbracoContext.Current.UrlProvider; - - // Parse internal links - var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - foreach (Match tag in tags) - if (tag.Groups.Count > 0) - { - var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); - var newLink = urlProvider.GetUrl(int.Parse(id)); - text = text.Replace(tag.Value, "href=\"" + newLink); - } - - return text; + return ParseInternalLinks(text, urlProvider); } - + // static compiled regex for faster performance - private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", + private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); /// diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9a5f90ff89..b0f5d59508 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -84,11 +84,10 @@ namespace Umbraco.Web.Trees var isContainer = e.IsContainer(); // && (queryStrings.Get("isDialog") != "true"); var node = CreateTreeNode( - e.Id.ToInvariantString(), + entity, + Constants.ObjectTypes.DocumentGuid, parentId, - queryStrings, - e.Name, - entity.ContentTypeIcon, + queryStrings, entity.HasChildren && (isContainer == false)); node.AdditionalData.Add("contentType", entity.ContentTypeAlias); diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 7de352de79..6df71341cd 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", + var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentTypeGuid, id, queryStrings, "icon-item-arrangement", //NOTE: Since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. //We need this check to keep supporting sites where childs have already been created. dt.HasChildren()); diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 35ae7cabfd..0baadaf6ca 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -37,7 +37,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DataTypeGuid, id, queryStrings, "icon-folder", dt.HasChildren()); node.Path = dt.Path; node.NodeType = "container"; //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index b08db4cacd..528080c35a 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -73,11 +73,10 @@ namespace Umbraco.Web.Trees var isContainer = e.IsContainer(); // && (queryStrings.Get("isDialog") != "true"); var node = CreateTreeNode( - e.Id.ToInvariantString(), + entity, + Constants.ObjectTypes.MediaGuid, parentId, - queryStrings, - e.Name, - entity.ContentTypeIcon, + queryStrings, entity.HasChildren && (isContainer == false)); node.AdditionalData.Add("contentType", entity.ContentTypeAlias); diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 31fb899f81..e116e43825 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaTypeGuid, id, queryStrings, "icon-item-arrangement", //NOTE: Since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. //We need this check to keep supporting sites where childs have already been created. dt.HasChildren()); diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index fa6eb64571..fe7aa8a689 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -79,7 +79,9 @@ namespace Umbraco.Web.Trees queryStrings, member.Name, "icon-user", - false); + false, + "", + Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(Constants.ObjectTypes.MemberGuid), member.Key)); node.AdditionalData.Add("contentType", member.ContentTypeAlias); node.AdditionalData.Add("isContainer", true); diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index d739c2a661..1e0c32a4b4 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Trees nodes.AddRange( Services.MemberTypeService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false))); + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberTypeGuid, id, queryStrings, "icon-item-arrangement", false))); return nodes; } diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 04458cbc20..b92e79fe7b 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -48,13 +48,14 @@ namespace Umbraco.Web.Trees nodes.AddRange(found.Select(template => CreateTreeNode( template.Id.ToString(CultureInfo.InvariantCulture), //TODO: Fix parent ID stuff for templates - "-1", + "-1", queryStrings, template.Name, template.IsMasterTemplate ? "icon-newspaper" : "icon-newspaper-alt", - template.IsMasterTemplate, - GetEditorPath(template, queryStrings) - ))); + template.IsMasterTemplate, + GetEditorPath(template, queryStrings), + Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(Constants.ObjectTypes.TemplateTypeGuid), template.Key) + ))); return nodes; } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 91191a165d..751f83db16 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -8,6 +8,7 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Web.Trees { @@ -229,6 +230,41 @@ namespace Umbraco.Web.Trees return node; } + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + /// /// Helper method to create tree nodes and automatically generate the json url /// @@ -265,6 +301,27 @@ namespace Umbraco.Web.Trees return treeNode; } + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + treeNode.Udi = udi; + return treeNode; + } + #endregion ///