Merge remote-tracking branch 'origin/6.2.0' into 7.0.0-member-editor-wip

Conflicts:
	src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadContent.cs
This commit is contained in:
Shannon
2013-09-30 13:52:01 +10:00
14 changed files with 452 additions and 131 deletions

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Core
{
/// <summary>
/// Any class implementing this interface that is added to the httpcontext.items keys or values will be disposed of at the end of the request.
/// </summary>
public interface IDisposeOnRequestEnd : IDisposable
{
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Umbraco.Core.CodeAnnotations;
@@ -9,7 +10,8 @@ namespace Umbraco.Core.Models
/// </summary>
public static class UmbracoObjectTypesExtensions
{
private static readonly Dictionary<UmbracoObjectTypes, Guid> UmbracoObjectTypeCache = new Dictionary<UmbracoObjectTypes,Guid>();
//MUST be concurrent to avoid thread collisions!
private static readonly ConcurrentDictionary<UmbracoObjectTypes, Guid> UmbracoObjectTypeCache = new ConcurrentDictionary<UmbracoObjectTypes, Guid>();
/// <summary>
/// Get an UmbracoObjectTypes value from it's name
@@ -48,24 +50,22 @@ namespace Umbraco.Core.Models
/// <returns>a GUID value of the UmbracoObjectTypes</returns>
public static Guid GetGuid(this UmbracoObjectTypes umbracoObjectType)
{
if (UmbracoObjectTypeCache.ContainsKey(umbracoObjectType))
return UmbracoObjectTypeCache[umbracoObjectType];
return UmbracoObjectTypeCache.GetOrAdd(umbracoObjectType, types =>
{
var type = typeof(UmbracoObjectTypes);
var memInfo = type.GetMember(umbracoObjectType.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute),
false);
var type = typeof(UmbracoObjectTypes);
var memInfo = type.GetMember(umbracoObjectType.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute),
false);
if (attributes.Length == 0)
return Guid.Empty;
if (attributes.Length == 0)
return Guid.Empty;
var attribute = ((UmbracoObjectTypeAttribute)attributes[0]);
if (attribute == null)
return Guid.Empty;
var attribute = ((UmbracoObjectTypeAttribute)attributes[0]);
if (attribute == null)
return Guid.Empty;
UmbracoObjectTypeCache.Add(umbracoObjectType, attribute.ObjectId);
return attribute.ObjectId;
return attribute.ObjectId;
});
}
/// <summary>

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence
/// can then override any additional execution (such as additional loggging, functionality, etc...) that we need to without breaking compatibility since we'll always be exposing
/// this object instead of the base PetaPoco database object.
/// </remarks>
public class UmbracoDatabase : Database
public class UmbracoDatabase : Database, IDisposeOnRequestEnd
{
private readonly Guid _instanceId = Guid.NewGuid();
/// <summary>

View File

@@ -16,6 +16,16 @@ namespace Umbraco.Core
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache = new ConcurrentDictionary<Type, FieldInfo[]>();
private static readonly ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]>();
/// <summary>
/// Checks if the method is actually overriding a base method
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
public static bool IsOverride(MethodInfo m)
{
return m.GetBaseDefinition().DeclaringType != m.DeclaringType;
}
/// <summary>
/// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList
/// </summary>

View File

@@ -274,6 +274,7 @@
<Compile Include="Events\SaveEventArgs.cs" />
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="IApplicationEventHandler.cs" />
<Compile Include="IDisposeOnRequestEnd.cs" />
<Compile Include="IO\ResizedImage.cs" />
<Compile Include="IO\UmbracoMediaFile.cs" />
<Compile Include="Macros\XsltExtension.cs" />

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using umbraco.cms.presentation.Trees;
namespace Umbraco.Tests.Trees
{
[TestFixture]
public class BaseContentTreeTests
{
[TearDown]
public void TestTearDown()
{
BaseTree.AfterTreeRender -= EventHandler;
BaseTree.BeforeTreeRender -= EventHandler;
}
[Test]
public void Run_Optimized()
{
var tree1 = new MyOptimizedContentTree1("content");
var tree2 = new MyOptimizedContentTree2("content");
Assert.IsTrue(tree1.UseOptimizedRendering);
Assert.IsTrue(tree2.UseOptimizedRendering);
}
[Test]
public void Not_Optimized_Events_AfterRender()
{
var tree = new MyOptimizedContentTree1("content");
BaseTree.AfterTreeRender += EventHandler;
Assert.IsFalse(tree.UseOptimizedRendering);
}
[Test]
public void Not_Optimized_Events_BeforeRender()
{
var tree = new MyOptimizedContentTree1("content");
BaseTree.BeforeTreeRender += EventHandler;
Assert.IsFalse(tree.UseOptimizedRendering);
}
[Test]
public void Not_Optimized_Overriden_Method()
{
var tree = new MyNotOptimizedContentTree("content");
Assert.IsFalse(tree.UseOptimizedRendering);
}
private void EventHandler(object sender, TreeEventArgs treeEventArgs)
{
}
//optimized because we are not overriding OnRenderNode
public class MyOptimizedContentTree1 : BaseContentTree
{
public MyOptimizedContentTree1(string application)
: base(application)
{
}
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
}
}
public class MyOptimizedContentTree2 : BaseContentTree
{
public MyOptimizedContentTree2(string application)
: base(application)
{
}
protected override bool LoadMinimalDocument
{
get { return true; }
}
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
}
//even if we override it will still be optimized because of the LoadMinimalDocument flag
protected override void OnRenderNode(ref XmlTreeNode xNode, umbraco.cms.businesslogic.web.Document doc)
{
base.OnRenderNode(ref xNode, doc);
}
}
public class MyNotOptimizedContentTree : BaseContentTree
{
public MyNotOptimizedContentTree(string application)
: base(application)
{
}
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
}
protected override bool LoadMinimalDocument
{
get { return false; }
}
protected override void OnRenderNode(ref XmlTreeNode xNode, umbraco.cms.businesslogic.web.Document doc)
{
base.OnRenderNode(ref xNode, doc);
}
}
}
}

View File

@@ -0,0 +1,65 @@
using NUnit.Framework;
using umbraco.cms.presentation.Trees;
namespace Umbraco.Tests.Trees
{
[TestFixture]
public class BaseMediaTreeTests
{
[TearDown]
public void TestTearDown()
{
BaseTree.AfterTreeRender -= EventHandler;
BaseTree.BeforeTreeRender -= EventHandler;
}
[Test]
public void Run_Optimized()
{
var tree = new MyOptimizedMediaTree("media");
Assert.IsTrue(tree.UseOptimizedRendering);
}
[Test]
public void Not_Optimized_Events_AfterRender()
{
var tree = new MyOptimizedMediaTree("media");
BaseTree.AfterTreeRender += EventHandler;
Assert.IsFalse(tree.UseOptimizedRendering);
}
[Test]
public void Not_Optimized_Events_BeforeRender()
{
var tree = new MyOptimizedMediaTree("media");
BaseTree.BeforeTreeRender += EventHandler;
Assert.IsFalse(tree.UseOptimizedRendering);
}
private void EventHandler(object sender, TreeEventArgs treeEventArgs)
{
}
public class MyOptimizedMediaTree : BaseMediaTree
{
public MyOptimizedMediaTree(string application)
: base(application)
{
}
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
}
}
}
}

View File

@@ -427,6 +427,8 @@
<Compile Include="TestHelpers\Entities\MockedContentTypes.cs" />
<Compile Include="TestHelpers\Entities\MockedEntity.cs" />
<Compile Include="TestHelpers\Entities\MockedMedia.cs" />
<Compile Include="Trees\BaseContentTreeTests.cs" />
<Compile Include="Trees\BaseMediaTreeTests.cs" />
<Compile Include="UmbracoExamine\ContentServiceTest.cs" />
<Compile Include="UmbracoExamine\EventsTest.cs" />
<Compile Include="TypeHelperTests.cs" />

View File

@@ -28,11 +28,13 @@ namespace Umbraco.Web.Routing
/// <param name="pcr">The content request.</param>
public PublishedContentRequestEngine(PublishedContentRequest pcr)
{
if (pcr == null) throw new ArgumentException("pcr is null.");
_pcr = pcr;
_routingContext = pcr.RoutingContext;
var umbracoContext = _routingContext.UmbracoContext;
if (_routingContext == null) throw new ArgumentException("pcr.RoutingContext is null.");
var umbracoContext = _routingContext.UmbracoContext;
if (umbracoContext == null) throw new ArgumentException("pcr.RoutingContext.UmbracoContext is null.");
if (umbracoContext.RoutingContext != _routingContext) throw new ArgumentException("RoutingContext confusion.");
// no! not set yet.

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Web
/// <summary>
/// Class that encapsulates Umbraco information of a specific HTTP request
/// </summary>
public class UmbracoContext : DisposableObject
public class UmbracoContext : DisposableObject, IDisposeOnRequestEnd
{
private const string HttpContextItemName = "Umbraco.Web.UmbracoContext";
private static readonly object Locker = new object();

View File

@@ -527,11 +527,15 @@ namespace Umbraco.Web
/// <param name="http"></param>
private static void DisposeHttpContextItems(HttpContext http)
{
// do not process if client-side request
if (http.Request.Url.IsClientSideRequest())
return;
//get a list of keys to dispose
var keys = new HashSet<object>();
foreach (DictionaryEntry i in http.Items)
{
if (i.Value is IDisposable || i.Key is IDisposable)
if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd)
{
keys.Add(i.Key);
}

View File

@@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Models;
using umbraco.BasePages;
using umbraco.BusinessLogic;
@@ -25,14 +27,7 @@ namespace umbraco.cms.presentation.Trees
public BaseContentTree(string application) : base(application) { }
private User _user;
/// <summary>
/// Determines whether the (legacy) Document object passed to the OnRenderNode-method
/// should be initialized with a full set of properties.
/// By default the Document will be initialized, so setting the boolean to True will
/// ensure that the Document object is loaded with a minimum set of properties to
/// improve performance.
/// </summary>
protected virtual bool LoadMinimalDocument { get; set; }
/// <summary>
@@ -74,61 +69,69 @@ function openContent(id) {
/// Renders the specified tree item.
/// </summary>
/// <param name="Tree">The tree.</param>
/*public override void Render(ref XmlTree Tree)
{
//get documents to render
Document[] docs = Document.GetChildrenForTree(m_id);
var args = new TreeEventArgs(Tree);
OnBeforeTreeRender(docs, args);
foreach (Document dd in docs)
{
List<IAction> allowedUserOptions = GetUserActionsForNode(dd);
if (CanUserAccessNode(dd, allowedUserOptions))
{
XmlTreeNode node = CreateNode(dd, allowedUserOptions);
OnRenderNode(ref node, dd);
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
if (node != null)
{
Tree.Add(node);
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
}
}
}
OnAfterTreeRender(docs, args);
}*/
public override void Render(ref XmlTree Tree)
{
//get documents to render
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Document).ToArray();
var args = new TreeEventArgs(Tree);
OnBeforeTreeRender(entities, args, true);
foreach (var entity in entities)
if (UseOptimizedRendering == false)
{
var e = entity as UmbracoEntity;
List<IAction> allowedUserOptions = GetUserActionsForNode(e);
if (CanUserAccessNode(e, allowedUserOptions))
//We cannot run optimized mode since there are subscribers to events/methods that require document instances
// so we'll render the original way by looking up the docs.
//get documents to render
var docs = Document.GetChildrenForTree(m_id);
var args = new TreeEventArgs(Tree);
OnBeforeTreeRender(docs, args);
foreach (var dd in docs)
{
XmlTreeNode node = CreateNode(e, allowedUserOptions);
OnRenderNode(ref node, new Document(entity, LoadMinimalDocument));
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
if (node != null)
var allowedUserOptions = GetUserActionsForNode(dd);
if (CanUserAccessNode(dd, allowedUserOptions))
{
Tree.Add(node);
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
var node = CreateNode(dd, allowedUserOptions);
OnRenderNode(ref node, dd);
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
if (node != null)
{
Tree.Add(node);
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
}
}
}
OnAfterTreeRender(docs, args);
}
else
{
//We ARE running in optmized mode, this means we will NOT be raising the BeforeTreeRender or AfterTreeRender
// events and NOT calling the OnRenderNode method - we've already detected that there are not subscribers or implementations
// to call so that is fine.
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Document).ToArray();
foreach (var entity in entities)
{
var e = entity as UmbracoEntity;
var allowedUserOptions = GetUserActionsForNode(e);
if (CanUserAccessNode(e, allowedUserOptions))
{
var node = CreateNode(e, allowedUserOptions);
//in optimized mode the LoadMinimalDocument will ALWAYS be true, if it is not true then we will
// be rendering in non-optimized mode and this code will not get executed so we don't need to worry
// about performance here.
OnRenderNode(ref node, new Document(e, LoadMinimalDocument));
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
if (node != null)
{
Tree.Add(node);
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
}
}
}
}
OnAfterTreeRender(entities, args, true);
}
#region Tree Create-node-helper Methods - Legacy
@@ -476,5 +479,35 @@ function openContent(id) {
return umbraco.BusinessLogic.Actions.Action.FromString(fullMenu);
}
/// <summary>
/// Returns true if we can use the EntityService to render the tree or revert to the original way
/// using normal documents
/// </summary>
/// <remarks>
/// We determine this by:
/// * If there are any subscribers to the events: BeforeTreeRender or AfterTreeRender - then we cannot run optimized
/// * If there are any overrides of the method: OnRenderNode - then we cannot run optimized
/// </remarks>
internal bool UseOptimizedRendering
{
get
{
if (HasEntityBasedEventSubscribers)
{
return false;
}
//now we need to check if the current tree type has OnRenderNode overridden with a custom implementation
//Strangely - this even works in med trust!
var method = this.GetType().GetMethod("OnRenderNode", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(XmlTreeNode).MakeByRefType(), typeof(Document) }, null);
if (TypeHelper.IsOverride(method) && LoadMinimalDocument == false)
{
return false;
}
return true;
}
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -10,6 +11,7 @@ using Umbraco.Core.PropertyEditors;
using umbraco.BasePages;
using umbraco.BusinessLogic;
using umbraco.BusinessLogic.Actions;
using umbraco.cms.businesslogic.web;
using umbraco.interfaces;
using Umbraco.Core;
using Media = umbraco.cms.businesslogic.media.Media;
@@ -21,7 +23,6 @@ namespace umbraco.cms.presentation.Trees
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
public abstract class BaseMediaTree : BaseTree
{
private DisposableTimer _timer;
private User _user;
public BaseMediaTree(string application)
@@ -61,78 +62,109 @@ 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.Debug<BaseMediaTree>("Media tree loaded" + " (took " + x + "ms)"));
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Media).ToArray();
var args = new TreeEventArgs(tree);
OnBeforeTreeRender(entities, args, false);
foreach (UmbracoEntity entity in entities)
if (UseOptimizedRendering == false)
{
XmlTreeNode xNode = XmlTreeNode.Create(this);
xNode.NodeID = entity.Id.ToString(CultureInfo.InvariantCulture);
xNode.Text = entity.Name;
//We cannot run optimized mode since there are subscribers to events/methods that require document instances
// so we'll render the original way by looking up the docs.
xNode.HasChildren = entity.HasChildren;
xNode.Source = this.IsDialog ? GetTreeDialogUrl(entity.Id) : GetTreeServiceUrl(entity.Id);
var docs = new Media(m_id).Children;
xNode.Icon = entity.ContentTypeIcon;
xNode.OpenIcon = entity.ContentTypeIcon;
if (IsDialog == false)
var args = new TreeEventArgs(tree);
OnBeforeTreeRender(docs, args);
foreach (var dd in docs)
{
if(this.ShowContextMenu == false)
xNode.Menu = null;
xNode.Action = "javascript:openMedia(" + entity.Id + ");";
}
else
{
xNode.Menu = this.ShowContextMenu ? new List<IAction>(new IAction[] { ActionRefresh.Instance }) : null;
if (this.DialogMode == TreeDialogModes.fulllink)
var e = dd;
var xNode = PerformNodeRender(e.Id, e.Text, e.HasChildren, e.ContentType.IconUrl, e.ContentType.Alias, () => GetLinkValue(e, e.Id.ToString(CultureInfo.InvariantCulture)));
OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty);
if (xNode != null)
{
string nodeLink = GetLinkValue(entity);
if (string.IsNullOrEmpty(nodeLink) == false)
{
xNode.Action = "javascript:openMedia('" + nodeLink + "');";
}
else
{
if (string.Equals(entity.ContentTypeAlias, Constants.Conventions.MediaTypes.Folder, StringComparison.OrdinalIgnoreCase))
{
//#U4-2254 - Inspiration to use void from here: http://stackoverflow.com/questions/4924383/jquery-object-object-error
xNode.Action = "javascript:void jQuery('.umbTree #" + entity.Id.ToString(CultureInfo.InvariantCulture) + "').click();";
}
else
{
xNode.Action = null;
xNode.Style.DimNode();
}
}
tree.Add(xNode);
OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty);
}
}
OnAfterTreeRender(docs, args);
}
else
{
//We ARE running in optmized mode, this means we will NOT be raising the BeforeTreeRender or AfterTreeRender
// events - we've already detected that there are not subscribers or implementations
// to call so that is fine.
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Media).ToArray();
foreach (UmbracoEntity entity in entities)
{
var e = entity;
var xNode = PerformNodeRender(e.Id, entity.Name, e.HasChildren, e.ContentTypeIcon, e.ContentTypeAlias, () => GetLinkValue(e));
OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty);
if (xNode != null)
{
tree.Add(xNode);
OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty);
}
}
}
}
private XmlTreeNode PerformNodeRender(int nodeId, string nodeName, bool hasChildren, string icon, string contentTypeAlias, Func<string> getLinkValue)
{
var xNode = XmlTreeNode.Create(this);
xNode.NodeID = nodeId.ToString(CultureInfo.InvariantCulture);
xNode.Text = nodeName;
xNode.HasChildren = hasChildren;
xNode.Source = this.IsDialog ? GetTreeDialogUrl(nodeId) : GetTreeServiceUrl(nodeId);
xNode.Icon = icon;
xNode.OpenIcon = icon;
if (IsDialog == false)
{
if (this.ShowContextMenu == false)
xNode.Menu = null;
xNode.Action = "javascript:openMedia(" + nodeId + ");";
}
else
{
xNode.Menu = this.ShowContextMenu ? new List<IAction>(new IAction[] { ActionRefresh.Instance }) : null;
if (this.DialogMode == TreeDialogModes.fulllink)
{
string nodeLink = getLinkValue();
if (string.IsNullOrEmpty(nodeLink) == false)
{
xNode.Action = "javascript:openMedia('" + nodeLink + "');";
}
else
{
xNode.Action = "javascript:openMedia('" + entity.Id.ToString(CultureInfo.InvariantCulture) + "');";
if (string.Equals(contentTypeAlias, Constants.Conventions.MediaTypes.Folder, StringComparison.OrdinalIgnoreCase))
{
//#U4-2254 - Inspiration to use void from here: http://stackoverflow.com/questions/4924383/jquery-object-object-error
xNode.Action = "javascript:void jQuery('.umbTree #" + nodeId.ToString(CultureInfo.InvariantCulture) + "').click();";
}
else
{
xNode.Action = null;
xNode.Style.DimNode();
}
}
}
OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty);
if (xNode != null)
else
{
tree.Add(xNode);
OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty);
xNode.Action = "javascript:openMedia('" + nodeId.ToString(CultureInfo.InvariantCulture) + "');";
}
}
//stop the timer and log the output
//_timer.Dispose();
OnAfterTreeRender(entities, args, false);
return xNode;
}
/// <summary>
/// Returns the value for a link in WYSIWYG mode, by default only media items that have a
@@ -192,5 +224,26 @@ function openMedia(id) {
/// </summary>
public static List<Guid> LinkableMediaDataTypes { get; protected set; }
/// <summary>
/// Returns true if we can use the EntityService to render the tree or revert to the original way
/// using normal documents
/// </summary>
/// <remarks>
/// We determine this by:
/// * If there are any subscribers to the events: BeforeTreeRender or AfterTreeRender - then we cannot run optimized
/// </remarks>
internal bool UseOptimizedRendering
{
get
{
if (HasEntityBasedEventSubscribers)
{
return false;
}
return true;
}
}
}
}

View File

@@ -528,6 +528,7 @@ namespace umbraco.cms.presentation.Trees
AfterTreeRender(sender, e);
}
[Obsolete("Do not use this method to raise events, it is no longer used and will cause very high performance spikes!")]
protected internal virtual void OnBeforeTreeRender(IEnumerable<IUmbracoEntity> sender, TreeEventArgs e, bool isContent)
{
if (BeforeTreeRender != null)
@@ -543,6 +544,7 @@ namespace umbraco.cms.presentation.Trees
}
}
[Obsolete("Do not use this method to raise events, it is no longer used and will cause very high performance spikes!")]
protected internal virtual void OnAfterTreeRender(IEnumerable<IUmbracoEntity> sender, TreeEventArgs e, bool isContent)
{
if (AfterTreeRender != null)
@@ -558,6 +560,14 @@ namespace umbraco.cms.presentation.Trees
}
}
/// <summary>
/// Returns true if there are subscribers to either BeforeTreeRender or AfterTreeRender
/// </summary>
internal bool HasEntityBasedEventSubscribers
{
get { return BeforeTreeRender != null || AfterTreeRender != null; }
}
/// <summary>
/// Event that is raised once actions are assigned to nodes
/// </summary>