From a4c9c3bfd9f3985981aea3393ce1fc94043e2614 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 3 Apr 2013 05:25:26 -0200 Subject: [PATCH] Updating EntityService and repository to add content and media specific properties to the UmbracoEntity for performance enhancements. Adding sample implementation in the BaseMediaTree but keeping it commented out as it won't work with the legacy events. And a bit of code cleanup --- src/Umbraco.Core/Models/UmbracoEntity.cs | 45 +++++++ .../Factories/UmbracoEntityFactory.cs | 5 +- .../Repositories/EntityRepository.cs | 123 +++++++++++------- src/Umbraco.Core/Services/EntityService.cs | 12 +- src/Umbraco.Tests/Services/BaseServiceTest.cs | 5 +- .../Services/EntityServiceTests.cs | 45 +++++++ .../umbraco/Trees/BaseMediaTree.cs | 95 +++++++++++++- .../umbraco/Trees/loadMedia.cs | 19 --- 8 files changed, 263 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 4791606d2c..d87ec768f9 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -20,6 +20,8 @@ namespace Umbraco.Core.Models private bool _isPublished; private bool _isDraft; private bool _hasPendingChanges; + private string _contentTypeAlias; + private string _umbracoFile; private Guid _nodeObjectTypeId; private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); @@ -33,7 +35,11 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); private static readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); private static readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); + private static readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + private static readonly PropertyInfo ContentTypeIconUrlSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); + private static readonly PropertyInfo UmbracoFileSelector = ExpressionHelper.GetPropertyInfo(x => x.UmbracoFile); private static readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); + private string _contentTypeIcon; public UmbracoEntity() { @@ -187,6 +193,45 @@ namespace Umbraco.Core.Models } } + public string ContentTypeAlias + { + get { return _contentTypeAlias; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _contentTypeAlias = value; + return _contentTypeAlias; + }, _contentTypeAlias, ContentTypeAliasSelector); + } + } + + public string ContentTypeIcon + { + get { return _contentTypeIcon; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _contentTypeIcon = value; + return _contentTypeIcon; + }, _contentTypeIcon, ContentTypeIconUrlSelector); + } + } + + public string UmbracoFile + { + get { return _umbracoFile; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _umbracoFile = value; + return _umbracoFile; + }, _umbracoFile, UmbracoFileSelector); + } + } + public Guid NodeObjectTypeId { get { return _nodeObjectTypeId; } diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index e3fec16cf4..e2e319829b 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -21,7 +21,10 @@ namespace Umbraco.Core.Persistence.Factories ParentId = dto.ParentId, Path = dto.Path, SortOrder = dto.SortOrder, - HasChildren = dto.Children > 0 + HasChildren = dto.Children > 0, + ContentTypeAlias = dto.Alias ?? string.Empty, + ContentTypeIcon = dto.IconUrl ?? string.Empty, + UmbracoFile = dto.UmbracoFile ?? string.Empty }; entity.IsPublished = dto.PublishedVersion != default(Guid) || diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index a4a0e08e5d..7fdec8702f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -58,8 +58,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id, Guid objectTypeId) { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var sql = GetBaseWhere(GetBase, isContent, objectTypeId, id).Append(GetGroupBy(isContent)); + bool isContentOrMedia = objectTypeId == new Guid(Constants.ObjectTypes.Document) || objectTypeId == new Guid(Constants.ObjectTypes.Media); + var sql = GetBaseWhere(GetBase, isContentOrMedia, objectTypeId, id).Append(GetGroupBy(isContentOrMedia)); var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -81,8 +81,8 @@ namespace Umbraco.Core.Persistence.Repositories } else { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var sql = GetBaseWhere(GetBase, isContent, objectTypeId).Append(GetGroupBy(isContent)); + bool isContentOrMedia = objectTypeId == new Guid(Constants.ObjectTypes.Document) || objectTypeId == new Guid(Constants.ObjectTypes.Media); + var sql = GetBaseWhere(GetBase, isContentOrMedia, objectTypeId).Append(GetGroupBy(isContentOrMedia)); var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); @@ -111,10 +111,10 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var sqlClause = GetBaseWhere(GetBase, isContent, objectTypeId); + bool isContentOrMedia = objectTypeId == new Guid(Constants.ObjectTypes.Document) || objectTypeId == new Guid(Constants.ObjectTypes.Media); + var sqlClause = GetBaseWhere(GetBase, isContentOrMedia, objectTypeId); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy(isContent)); + var sql = translator.Translate().Append(GetGroupBy(isContentOrMedia)); var dtos = _work.Database.Fetch(sql); @@ -128,94 +128,110 @@ namespace Umbraco.Core.Persistence.Repositories #region Sql Statements - protected virtual Sql GetBase(bool isContent) + protected virtual Sql GetBase(bool isContentOrMedia) { var columns = new List { - "main.id", - "main.trashed", - "main.parentID", - "main.nodeUser", - "main.level", - "main.path", - "main.sortOrder", - "main.uniqueID", - "main.text", - "main.nodeObjectType", - "main.createDate", + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate", "COUNT(parent.parentID) as children" }; - if (isContent) + if (isContentOrMedia) { columns.Add("published.versionId as publishedVerison"); columns.Add("latest.versionId as newestVersion"); + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("property.dataNvarchar as umbracoFile"); } var sql = new Sql() .Select(columns.ToArray()) - .From("umbracoNode main") - .LeftJoin("umbracoNode parent").On("parent.parentID = main.id"); + .From("umbracoNode umbracoNode") + .LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); - if (isContent) + if (isContentOrMedia) { - sql.LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published").On("main.id = published.nodeId"); - sql.LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE newest = 1 GROUP BY nodeId, versionId) as latest").On("main.id = latest.nodeId"); + sql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id") + .LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType") + .LeftJoin( + "(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published") + .On("umbracoNode.id = published.nodeId") + .LeftJoin( + "(SELECT nodeId, versionId FROM cmsDocument WHERE newest = 1 GROUP BY nodeId, versionId) as latest") + .On("umbracoNode.id = latest.nodeId") + .LeftJoin( + "(SELECT contentNodeId, dataNvarchar FROM cmsPropertyData INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid"+ + " INNER JOIN cmsDataType ON cmsPropertyType.dataTypeId = cmsDataType.nodeId WHERE cmsDataType.controlId = '"+ Constants.PropertyEditors.UploadField +"') as property") + .On("umbracoNode.id = property.contentNodeId"); } return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, Guid id) + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContentOrMedia, Guid id) { - var sql = baseQuery(isContent) - .Where("main.nodeObjectType = @NodeObjectType", new {NodeObjectType = id}); + var sql = baseQuery(isContentOrMedia) + .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = id }); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, int id) + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContentOrMedia, int id) { - var sql = baseQuery(isContent) - .Where("main.id = @Id", new {Id = id}) - .Append(GetGroupBy(isContent)); + var sql = baseQuery(isContentOrMedia) + .Where("umbracoNode.id = @Id", new { Id = id }) + .Append(GetGroupBy(isContentOrMedia)); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, Guid objectId, int id) + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContentOrMedia, Guid objectId, int id) { - var sql = baseQuery(isContent) - .Where("main.id = @Id AND main.nodeObjectType = @NodeObjectType", + var sql = baseQuery(isContentOrMedia) + .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", new {Id = id, NodeObjectType = objectId}); return sql; } - protected virtual Sql GetGroupBy(bool isContent) + protected virtual Sql GetGroupBy(bool isContentOrMedia) { var columns = new List { - "main.id", - "main.trashed", - "main.parentID", - "main.nodeUser", - "main.level", - "main.path", - "main.sortOrder", - "main.uniqueID", - "main.text", - "main.nodeObjectType", - "main.createDate" + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate" }; - if (isContent) + if (isContentOrMedia) { columns.Add("published.versionId"); columns.Add("latest.versionId"); + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("property.dataNvarchar"); } var sql = new Sql() .GroupBy(columns.ToArray()) - .OrderBy("main.sortOrder"); + .OrderBy("umbracoNode.sortOrder"); return sql; } @@ -246,6 +262,15 @@ namespace Umbraco.Core.Persistence.Repositories [Column("newestVerison")] public Guid NewestVersion { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("icon")] + public string IconUrl { get; set; } + + [Column("umbracoFile")] + public string UmbracoFile { get; set; } } #endregion } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 5e0b68b239..e8bd53e9c6 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -164,13 +164,13 @@ namespace Umbraco.Core.Services /// /// Gets a collection of children by the parents Id /// - /// Id of the parent to retrieve children for + /// Id of the parent to retrieve children for /// An enumerable list of objects - public virtual IEnumerable GetChildren(int id) + public virtual IEnumerable GetChildren(int parentId) { using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.ParentId == id); + var query = Query.Builder.Where(x => x.ParentId == parentId); var contents = repository.GetByQuery(query); return contents; @@ -180,15 +180,15 @@ namespace Umbraco.Core.Services /// /// Gets a collection of children by the parents Id and UmbracoObjectType /// - /// Id of the parent to retrieve children for + /// Id of the parent to retrieve children for /// UmbracoObjectType of the children to retrieve /// An enumerable list of objects - public virtual IEnumerable GetChildren(int id, UmbracoObjectTypes umbracoObjectType) + public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.ParentId == id); + var query = Query.Builder.Where(x => x.ParentId == parentId); var contents = repository.GetByQuery(query, objectTypeId); return contents; diff --git a/src/Umbraco.Tests/Services/BaseServiceTest.cs b/src/Umbraco.Tests/Services/BaseServiceTest.cs index 56b94dcbf1..cd4318b5a6 100644 --- a/src/Umbraco.Tests/Services/BaseServiceTest.cs +++ b/src/Umbraco.Tests/Services/BaseServiceTest.cs @@ -1,11 +1,8 @@ using System; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; -using umbraco.editorControls.tinyMCE3; -using umbraco.interfaces; namespace Umbraco.Tests.Services { @@ -26,7 +23,7 @@ namespace Umbraco.Tests.Services base.TearDown(); } - public void CreateTestData() + public virtual void CreateTestData() { //NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested. diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index abf541883f..dce19cb80a 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { @@ -61,6 +62,18 @@ namespace Umbraco.Tests.Services Assert.That(entities.Any(x => x.Trashed), Is.True); } + [Test] + public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType() + { + var service = ServiceContext.EntityService; + + var entities = service.GetChildren(-1, UmbracoObjectTypes.Document); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + [Test] public void EntityService_Throws_When_Getting_All_With_Invalid_Type() { @@ -105,5 +118,37 @@ namespace Umbraco.Tests.Services Assert.That(entities.Any(), Is.True); Assert.That(entities.Count(), Is.EqualTo(1)); } + + [Test] + public void EntityService_Can_Find_All_Media_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Media); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(3)); + Assert.That(entities.Any(x => ((UmbracoEntity)x).UmbracoFile != string.Empty), Is.True); + } + + public override void CreateTestData() + { + base.CreateTestData(); + + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); + + //Create and Save image-Media -> 1051 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> 1052 + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs index be85585f8e..98569162a3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs @@ -1,26 +1,30 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; using umbraco.BasePages; using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; -using umbraco.cms.businesslogic.media; -using umbraco.cms.businesslogic.property; using umbraco.interfaces; using Umbraco.Core; +using Media = umbraco.cms.businesslogic.media.Media; +using Property = umbraco.cms.businesslogic.property.Property; namespace umbraco.cms.presentation.Trees { public abstract class BaseMediaTree : BaseTree { + private DisposableTimer _timer; + private User _user; + public BaseMediaTree(string application) : base(application) { } - private User m_user; - /// /// Returns the current User. This ensures that we don't instantiate a new User object /// each time. @@ -29,11 +33,10 @@ namespace umbraco.cms.presentation.Trees { get { - return (m_user == null ? (m_user = UmbracoEnsuredPage.CurrentUser) : m_user); + return (_user == null ? (_user = UmbracoEnsuredPage.CurrentUser) : _user); } } - public override void RenderJS(ref StringBuilder Javascript) { if (!string.IsNullOrEmpty(this.FunctionToCall)) @@ -53,8 +56,83 @@ function openMedia(id) { } } + //Updated Render method for improved performance, but currently not usable because of backwards compatibility + //with the OnBeforeTreeRender/OnAfterTreeRender events, which sends an array for legacy Media items. + /*public override void Render(ref XmlTree tree) + { + _timer = DisposableTimer.Start(x => LogHelper.Info("Media tree loaded" + " (took " + x + "ms)")); + + var service = base.Services.EntityService; + var entities = service.GetChildren(m_id, UmbracoObjectTypes.Media); + + var args = new TreeEventArgs(tree); + OnBeforeTreeRender(entities, args); + + foreach (UmbracoEntity entity in entities) + { + XmlTreeNode xNode = XmlTreeNode.Create(this); + xNode.NodeID = entity.Id.ToString(CultureInfo.InvariantCulture); + xNode.Text = entity.Name; + + xNode.HasChildren = entity.HasChildren; + xNode.Source = this.IsDialog ? GetTreeDialogUrl(entity.Id) : GetTreeServiceUrl(entity.Id); + + xNode.Icon = entity.ContentTypeIconUrl; + xNode.OpenIcon = entity.ContentTypeIconUrl; + + xNode.Menu = this.ShowContextMenu ? new List(new IAction[] { ActionRefresh.Instance }) : null; + + if (IsDialog == false) + { + xNode.Action = "javascript:openMedia(" + entity.Id + ");"; + } + else + { + if (this.DialogMode == TreeDialogModes.fulllink) + { + if (string.IsNullOrEmpty(entity.UmbracoFile) == false) + { + xNode.Action = "javascript:openMedia('" + entity.UmbracoFile + "');"; + } + else + { + if (string.Equals(entity.ContentTypeAlias, Constants.Conventions.MediaTypes.Folder, StringComparison.OrdinalIgnoreCase)) + { + xNode.Action = "javascript:jQuery('.umbTree #" + entity.Id.ToString(CultureInfo.InvariantCulture) + "').click();"; + } + else + { + xNode.Action = null; + xNode.Style.DimNode(); + } + } + } + else + { + xNode.Action = "javascript:openMedia('" + entity.Id.ToString(CultureInfo.InvariantCulture) + "');"; + } + } + + OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); + if (xNode != null) + { + tree.Add(xNode); + OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); + } + } + + //stop the timer and log the output + _timer.Dispose(); + + OnAfterTreeRender(entities, args); + }*/ + public override void Render(ref XmlTree tree) { +#if Debug + _timer = DisposableTimer.Start(x => LogHelper.Info("Media tree loaded" + " (took " + x + "ms)")); +#endif + Media[] docs = new Media(m_id).Children; var args = new TreeEventArgs(tree); @@ -124,10 +202,13 @@ function openMedia(id) { OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); } } +#if Debug + _timer.Dispose(); +#endif OnAfterTreeRender(docs, args); } - /// + /// /// Returns the value for a link in WYSIWYG mode, by default only media items that have a /// DataTypeUploadField are linkable, however, a custom tree can be created which overrides /// this method, or another GUID for a custom data type can be added to the LinkableMediaDataTypes diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMedia.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMedia.cs index 90135d9d15..80a1e4d307 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMedia.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMedia.cs @@ -1,28 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Text; -using System.Web; -using System.Xml; -using System.Configuration; using umbraco.BasePages; -using umbraco.BusinessLogic; using umbraco.businesslogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.cache; -using umbraco.cms.businesslogic.contentitem; -using umbraco.cms.businesslogic.datatype; -using umbraco.cms.businesslogic.language; -using umbraco.cms.businesslogic.media; -using umbraco.cms.businesslogic.member; -using umbraco.cms.businesslogic.property; -using umbraco.cms.businesslogic.web; using umbraco.interfaces; -using umbraco.DataLayer; using umbraco.BusinessLogic.Actions; -using umbraco.BusinessLogic.Utils; using umbraco.cms.presentation.Trees; using Umbraco.Core;