diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index b789698704..7783331952 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -225,42 +225,5 @@ namespace Umbraco.Core.Models return clone; } - /// - /// A struction that can be contained in the additional data of an UmbracoEntity representing - /// a user defined property - /// - public class EntityProperty : IDeepCloneable - { - public string PropertyEditorAlias { get; set; } - public object Value { get; set; } - public object DeepClone() - { - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = MemberwiseClone(); - return clone; - } - - protected bool Equals(EntityProperty other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EntityProperty) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 495c0109ad..7b10b19e1e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -20,27 +20,22 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class EntityRepository : DisposableObjectSlim, IEntityRepository { - private readonly IDatabaseUnitOfWork _work; - public EntityRepository(IDatabaseUnitOfWork work) { - _work = work; + UnitOfWork = work; } /// /// Returns the Unit of Work added to the repository /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } + protected internal IDatabaseUnitOfWork UnitOfWork { get; } /// /// Internal for testing purposes /// internal Guid UnitKey { - get { return (Guid)_work.Key; } + get { return (Guid)UnitOfWork.Key; } } #region Query Methods @@ -69,75 +64,8 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable result; - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new { ids = idGroup }); - propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } + var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. @@ -159,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories var translatorCount = new SqlTranslator(sqlCountClause, query); var countSql = translatorCount.Translate(); - totalRecords = _work.Database.ExecuteScalar(countSql); + totalRecords = UnitOfWork.Database.ExecuteScalar(countSql); return result; } @@ -167,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories public IUmbracoEntity GetByKey(Guid key) { var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -187,7 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -202,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id) { var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -222,7 +150,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -256,7 +184,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -283,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); if (filter != null) filter(sql); - return _work.Database.Fetch(sql); + return UnitOfWork.Database.Fetch(sql); } public virtual IEnumerable GetByQuery(IQuery query) @@ -292,7 +220,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); - var dtos = _work.Database.Fetch(sql); + var dtos = UnitOfWork.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); @@ -306,10 +234,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// - /// Note that this will also fetch all property data for media items, which can cause performance problems - /// when used without paging, in sites with large amounts of data in cmsPropertyData. - /// public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; @@ -323,27 +247,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetByQueryInternal(entitySql, isContent, isMedia); } - /// - /// Gets entities by query without fetching property data. - /// - /// - /// - /// - /// - /// This is supposed to be internal and can be used when getting all entities without paging, without causing - /// performance issues. - /// - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - return GetByQueryInternal(entitySql, false, true); - } - - internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) + private IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) { var factory = new UmbracoEntityFactory(); @@ -351,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories var finalSql = entitySql.Append(GetGroupBy(isContent, isMedia)); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); + var dtos = UnitOfWork.Database.Query(finalSql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -392,22 +296,6 @@ namespace Umbraco.Core.Persistence.Repositories return entitySql.Append(GetGroupBy(isContent, true, false)); } - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .InnerJoin() - .On(dto => dto.Id, dto => dto.PropertyTypeId) - .InnerJoin() - .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); - - return sql; - } - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { return GetBase(isContent, isMedia, customFilter, false); @@ -638,13 +526,13 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(Guid key) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } public bool Exists(int id) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } #region private classes diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 906ece1081..f4b1b71732 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -234,27 +234,6 @@ namespace Umbraco.Core.Services } } - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); - } - } - /// /// Returns a paged collection of children /// diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 7186474999..1ab6bbea31 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -177,18 +177,7 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var clone = (UmbracoEntity)item.DeepClone(); Assert.AreNotSame(clone, item); @@ -250,20 +239,10 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); Debug.Print(json); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 54adb24b74..e7fb0b8d84 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -207,9 +207,6 @@ namespace Umbraco.Web.Trees return HasPathAccess(entity, queryStrings); } - internal override IEnumerable GetChildrenFromEntityService(int entityId) - => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); - /// /// Returns a collection of all menu items that can be on a content node /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2624c89b56..4e4086a3c6 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -205,12 +205,8 @@ namespace Umbraco.Web.Trees return GetChildrenFromEntityService(entityId); } - /// - /// Abstract method to fetch the entities from the entity service - /// - /// - /// - internal abstract IEnumerable GetChildrenFromEntityService(int entityId); + private IEnumerable GetChildrenFromEntityService(int entityId) + => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); /// /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index f2f59b6bd7..50336dbc10 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -177,12 +177,6 @@ namespace Umbraco.Web.Trees { return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } - - internal override IEnumerable GetChildrenFromEntityService(int entityId) - // Not pretty having to cast the service, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - => ((EntityService)Services.EntityService).GetMediaChildrenWithoutPropertyData(entityId).ToList(); + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d4fd27c323..32eaae1077 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1812,7 +1812,6 @@ - diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs deleted file mode 100644 index 15a6c10880..0000000000 --- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Script.Serialization; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web.Media.ThumbnailProviders; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.Tags; -using Umbraco.Web.BaseRest; -using Tag = umbraco.cms.businesslogic.Tags.Tag; - -namespace Umbraco.Web.WebServices -{ - //TODO: Can we convert this to MVC please instead of /base? - [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - [RestExtension("FolderBrowserService")] - public class FolderBrowserService - { - [RestExtensionMethod(ReturnXml = false)] - public static string GetChildren(int parentId) - { - var currentUser = GetCurrentUser(); - AuthorizeAccess(parentId, currentUser); - - // Get children and filter - var data = new List(); - var service = ApplicationContext.Current.Services.EntityService; - - var entities = service.GetChildren(parentId, UmbracoObjectTypes.Media); - foreach (UmbracoEntity entity in entities) - { - var uploadFieldProperty = entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null) - .FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - //var uploadFieldProperty = entity.UmbracoProperties.FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - var thumbnailUrl = uploadFieldProperty == null ? "" : ThumbnailProvidersResolver.Current.GetThumbnailUrl((string)uploadFieldProperty.Value); - - var item = new - { - Id = entity.Id, - Path = entity.Path, - Name = entity.Name, - Tags = string.Join(",", Tag.GetTags(entity.Id).Select(x => x.TagCaption)), - MediaTypeAlias = entity.ContentTypeAlias, - EditUrl = string.Format("editMedia.aspx?id={0}", entity.Id), - FileUrl = uploadFieldProperty == null - ? "" - : uploadFieldProperty.Value, - ThumbnailUrl = string.IsNullOrEmpty(thumbnailUrl) - ? IOHelper.ResolveUrl(string.Format("{0}/images/thumbnails/{1}", SystemDirectories.Umbraco, entity.ContentTypeThumbnail)) - : thumbnailUrl - }; - - data.Add(item); - - } - - return new JavaScriptSerializer().Serialize(data); - } - - [RestExtensionMethod(ReturnXml = false)] - public static string Delete(string nodeIds) - { - var currentUser = GetCurrentUser(); - - var nodeIdParts = nodeIds.Split(','); - - foreach (var nodeIdPart in nodeIdParts.Where(x => string.IsNullOrEmpty(x) == false)) - { - int nodeId; - if (Int32.TryParse(nodeIdPart, out nodeId) == false) - continue; - - var node = new global::umbraco.cms.businesslogic.media.Media(nodeId); - AuthorizeAccess(node, currentUser); - - node.delete(("," + node.Path + ",").Contains(",-21,")); - } - - return new JavaScriptSerializer().Serialize(new { success = true }); - } - - private static User GetCurrentUser() - { - var currentUser = User.GetCurrent(); - if (currentUser == null) - throw new UnauthorizedAccessException("You must be logged in to use this service"); - - return currentUser; - } - - private static void AuthorizeAccess(global::umbraco.cms.businesslogic.media.Media mediaItem, User currentUser) - { - if (("," + mediaItem.Path + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - - private static void AuthorizeAccess(int parentId, User currentUser) - { - var service = ApplicationContext.Current.Services.EntityService; - var parentMedia = service.Get(parentId, UmbracoObjectTypes.Media); - var mediaPath = parentMedia == null ? parentId.ToString(CultureInfo.InvariantCulture) : parentMedia.Path; - - if (("," + mediaPath + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs index f72457d867..f7412f6915 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs @@ -189,35 +189,8 @@ function openMedia(id) { return ""; } - /// - /// NOTE: New implementation of the legacy GetLinkValue. This is however a bit quirky as a media item can have multiple "Linkable DataTypes". - /// 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 - /// list on application startup. - /// - /// - /// - internal virtual string GetLinkValue(UmbracoEntity entity) - { - foreach (var property in entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null)) - { - - - //required for backwards compatibility with v7 with changing the GUID -> alias - var controlId = LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias(property.PropertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.ReturnNull); - if (controlId != null) - { - if (LinkableMediaDataTypes.Contains(controlId.Value) - && string.IsNullOrEmpty((string)property.Value) == false) - - return property.Value.ToString(); - } - } - return ""; - } + [Obsolete("Just like this class is and is not used whatsoever for a very long time, this method will return an empty string")] + internal virtual string GetLinkValue(UmbracoEntity entity) => string.Empty; /// /// By default, any media type that is to be "linkable" in the WYSIWYG editor must contain diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs index 49d8c7214d..ab18a1316e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs @@ -652,4 +652,4 @@ namespace umbraco.cms.presentation.Trees } -} \ No newline at end of file +}