Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jon R. Humphrey 164 posts 455 karma points c-trib
    Jan 07, 2016 @ 01:50
    Jon R. Humphrey
    0

    Using ContentService.Saving event to remove Allowed child node types

    Even though Jeroen pointed me in the right direction earlier to control the depth of nodes in the content tree: "Home>Child>Child" but stop the creation of "Home>Child>Child>Child" I can't seem to hook into the right event to have this happen?

    private static void ContentService_Saving(IContentService sender, SaveEventArgs<IContent> e)
    {
      foreach (var node in e.SavedEntities.Where(
            node =>
              node.ContentType.Alias.InvariantEquals("NG_USK_StandardPage") ||
              node.ContentType.Alias.InvariantEquals("NG_USK_NewsArticle") ||
              node.ContentType.Alias.InvariantEquals("NG_USK_Downloads")
            )
         )
      {
        try
        {
          if (node.Level == 3)
          {
            RemoveAllowedContentTypes(node);
          }
        }
        catch (Exception err)
        {
          // Get stack trace for the exception with source file information
          var st = new StackTrace(err, true);
          // Get the top stack frame
          var frame = st.GetFrame(0);
          // Get the line number from the stack frame
          var line = frame.GetFileLineNumber();
          LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: ContentService_Publishing - " + node.ContentType.Alias + " ~ " + err.Message + "[" + line + "]:" + st);
        }
      }
    }
    
    private static void RemoveAllowedContentTypes(IContent node)
    {
      try
      {
        List<ContentTypeSort> allowedTypes = new List<ContentTypeSort>(node.ContentType.AllowedContentTypes);
    
        foreach (var allowedChild in allowedTypes.Where(allowedChild => allowedChild.Alias.Equals("NG_USK_StandardPage")).Reverse())
        {
          allowedTypes.Remove(allowedChild);
        }
    
        node.ContentType.AllowedContentTypes = allowedTypes;
    
      }
      catch (Exception oops)
      {
        // Get stack trace for the exception with source file information
        var st = new StackTrace(oops, true);
        // Get the top stack frame
        var frame = st.GetFrame(0);
        // Get the line number from the stack frame
        var line = frame.GetFileLineNumber();
        LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: RemoveAllowedContentTypes - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
      }
    }
    

    In this code example, I loop through the allowed types and use a reversed list to modify the types on the fly - this works according to VS when I set a breakpoint on "node.ContentType.AllowedContentTypes = allowedTypes;" the count has dropped by 1.

    As you can see, I've attempted to use the saving event like this so it will still save the node even though the allowedChildTypes list has been modified?

    It's not though as when I click the node in the content tree after the save event has completed and the page has refreshed the doctype I want to restrict is still showing in the allowed child listing.

    As always, any and all help is greatly appreciated!

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Jan 07, 2016 @ 08:24
    Jeroen Breuer
    0

    Hello,

    I was thinking more about using the content_new event or something like that. In there get the parent id and if that path is too deep cancel the event.

    A complete different approach would be to hijack the web api call that returns the allowed child nodes. In AngularJS everything is done with ajax. For the Hybrid Framework I hijack the web api call which returns the content so I can change the media picker start node. You can find an example here: https://github.com/jbreuer/Hybrid-Framework-for-Umbraco-v7-Best-Practises/blob/master/Umbraco.Extensions/Utilities/WebApiHandler.cs

    Try to find the web api call that returns the allowed child nodes and see if you can do it in there.

    Jeroen

  • Jon R. Humphrey 164 posts 455 karma points c-trib
    Jan 07, 2016 @ 11:59
    Jon R. Humphrey
    0

    Jeroen,

    I was going the CS Saving route as the 3rd level node will always be created before a deeper level is and this approach removes the allowed child type before any editor can try to add anything down the road.

    The code above works up to the point of save for the new node - here's the desired process:

    1. create 2nd level standard page node
    2. click to add 3rd level child and am presented with both child allowed child types
    3. on-saving event fires and removes the allowed child node type discretely
    4. click to add 4th level and am only presented with the single allowed type <- this isn't happening and both child types are still there!

    Does this help clarify the issue?

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Jan 07, 2016 @ 12:13
    Jeroen Breuer
    0

    Hello,

    I don't think you can change the allowed child nodes per node. It's only per document type so your event probably won't work. You could try the second suggestion I wrote about hijacking the web api.

    Jeroen

  • Jon R. Humphrey 164 posts 455 karma points c-trib
    Jan 07, 2016 @ 17:28
    Jon R. Humphrey
    100

    Jeroen,

    I took a slightly different approach as I was slightly perplexed by the examples you linked to, it wasn't your code but my level of understanding!

    With that said, and after all the research I had done on the content service api, I persevered with my attack route and was successful this afternoon with this code:

    using System;
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    using Umbraco.Core;
    using Umbraco.Core.Events;
    using Umbraco.Core.Logging;
    using Umbraco.Core.Models;
    using Umbraco.Core.Models.EntityBase;
    using Umbraco.Core.Services;
    
    namespace NG.USK.Helpers
    {
      public class CreateKitExtensions : ApplicationEventHandler
      {
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
         //Changed all events to Saved due to Infinite Looping
          ContentService.Saved += CreateControlPanel;
          ContentService.Saved += CheckDocumentTypes;
          //ContentService.Publishing += ContentService_Publishing;
        }
    
        /// <summary>
        /// Check to see if the node is one that we want to control - these are the only doctypes that can be below level 2
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void CheckDocumentTypes(IContentService sender, SaveEventArgs<IContent> e)
        {
          //
          foreach (var node in e.SavedEntities.Where(
                node =>
                  node.ContentType.Alias.InvariantEquals("NG_USK_StandardPage") ||
                  node.ContentType.Alias.InvariantEquals("NG_USK_NewsArticle") ||
                  node.ContentType.Alias.InvariantEquals("NG_USK_Downloads")
                )
             )
          {
            try
            {
              //Check to make sure the level is deep enough
              if (node.Level == 3)
              {
                SwapDocumentType(node);
              }
            }
            catch (Exception err)
            {
              // Get stack trace for the exception with source file information
              var st = new StackTrace(err, true);
              // Get the top stack frame
              var frame = st.GetFrame(0);
              // Get the line number from the stack frame
              var line = frame.GetFileLineNumber();
              LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: ContentService_Publishing - " + node.ContentType.Alias + " ~ " + err.Message + "[" + line + "]:" + st);
            }
          }
        }
    
        /// <summary>
        /// Attempt to remove the AllowedContentTypes from the specific content node
        /// </summary>
        /// <param name="node"></param>
        /// <remarks>
        /// This failed due to the lock on the AllowedContentTypes being per document type and not per document node 
        /// </remarks>
        private static void RemoveAllowedContentTypes(IContent node)
        {
          try
          {
            //Create a listing of all the current Allowed Content Types
            List<ContentTypeSort> allowedTypes = new List<ContentTypeSort>(node.ContentType.AllowedContentTypes);
    
            //Loop thought the listing and find the flagged AllowedContentType
            //use a reverse to copy the listing into a temporary collection and prevent the dreaded
            //"System.InvalidOperationException: Collection was modified; enumeration operation may not execute" error
            foreach (var allowedChild in allowedTypes.Where(allowedChild => allowedChild.Alias.Equals("NG_USK_StandardPage")).Reverse())
            {
              //Remove the flagged AllowedContentType
              allowedTypes.Remove(allowedChild);
            }
            //Reload the AllowedContentTypes inthe the node
            node.ContentType.AllowedContentTypes = allowedTypes;
    
          }
          catch (Exception oops)
          {
            // Get stack trace for the exception with source file information
            var st = new StackTrace(oops, true);
            // Get the top stack frame
            var frame = st.GetFrame(0);
            // Get the line number from the stack frame
            var line = frame.GetFileLineNumber();
            LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: RemoveAllowedContentTypes - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
          }
        }
    
        /// <summary>
        /// Since we can't modify the AllowedContentTypes of a node you can change the 
        /// content type of the node to another with different AllowedContentTypes instead!
        /// </summary>
        /// <param name="node"></param>
        private static void SwapDocumentType(IContent node)
        {
          try
          {
    
            var contentService = ApplicationContext.Current.Services.ContentService;
            var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
    
            //Check is the node is dirty or not
            if (node.HasIdentity)
            {
    
              //Given a 'ContentTypeService' object get the ContentType that we're changing to,
              var contentType = contentTypeService.GetContentType("NG_USK_StandardPageChild");
              //get the Content from the 'ContentService' for which we want to change ContentType for,
              var content = contentService.GetById(node.Id);
              //swap the ContentType over for the content node
              content.ChangeContentType(contentType);
              //and then saveand publish with status the Content throught the ContentService.
              contentService.SaveAndPublishWithStatus(content);
            }
            else
            {
              //Save the node so it is dirty and loop back through the content service events
              contentService.Save(node);
            }
          }
          catch (Exception oops)
          {
            // Get stack trace for the exception with source file information
            var st = new StackTrace(oops, true);
            // Get the top stack frame
            var frame = st.GetFrame(0);
            // Get the line number from the stack frame
            var line = frame.GetFileLineNumber();
            LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: SwapDocumentType - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
          }
        }
    
        /// <summary>
        /// On Starter Kit installation - create a control panel node to the root level 
        /// then publish the homepage and children
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void CreateControlPanel(IContentService sender, SaveEventArgs<IContent> e)
        {
          //If the event level is not at the root leave
          if (e.SavedEntities.All(x => x.Level != 1)) return;
          //Create a ContentService Object
          var service = ApplicationContext.Current.Services.ContentService;
          //Use the CSO to get all the children at the root
          var rootNodeChildren = service.GetChildren(-1);
    
          // check if the root node's children has a node with the Control Panel's doctype
          if (!rootNodeChildren.Any(x => x.ContentType.Alias.InvariantEquals("NG_Global__Options")))
          {
            // if not, call the method to create a Control Panel node 
            MakeControlPanel();
          }
    
          //Find the root node
          var homeNode = service.GetRootContent().FirstOrDefault(x => x.ContentType.Alias.InvariantEquals("NG_USK_Homepage"));
          //Add everything to the Content Tree - even the unpublished nodes!
          service.PublishWithChildrenWithStatus(homeNode, GetCurrentUserId(), true);
        }
    
        /// <summary>
        /// 
        /// </summary>
        private static void MakeControlPanel()
        {
          try
          {
            //Create a ContentService Object
            var service = ApplicationContext.Current.Services.ContentService;
            //Create a Control Panel node
            var controlPanel = service.CreateContent("Control Panel", -1, "NG_Global__Options");
            //Add it to the content tree
            service.SaveAndPublishWithStatus(controlPanel);
    
            // once saved/published, check that it has an ID...
            if (controlPanel.HasIdentity)
            {
              // ... then create the Site Settings node
              MakeSiteSettings(controlPanel);
            }
          }
          catch (Exception ex)
          {
            LogHelper.Error<CreateKitExtensions>("MakeControlPanel", ex);
          }
        }
    
        private static void MakeSiteSettings(IEntity node)
        {
          try
          {
            //Create a ContentService Object
            var service = ApplicationContext.Current.Services.ContentService;
            //Create the Site Settings node
            var siteSettings = service.CreateContent("Site Settings", node.Id, "NG_Global__SiteSettings");
            //Add it to the Content Tree
            service.SaveAndPublishWithStatus(siteSettings);
          }
          catch (Exception ex)
          {
            LogHelper.Error<CreateKitExtensions>("MakeSiteSettings", ex);
          }
        }
    
        /// <summary>
        /// Get the Current User
        /// </summary>
        private static int GetCurrentUserId()
        {
          //Create a UserService Object
          var userService = ApplicationContext.Current.Services.UserService;
          //Get the id of the current user
          return userService.GetByUsername(HttpContext.Current.User.Identity.Name).Id;
        }
    
        /// <summary>
        /// This is based on code from Dan Lister (@dan_lister)
        /// https://our.umbraco.org/member/46642
        /// and can be found on the Our Umbraco forum post:
        /// https://our.umbraco.org/forum/developers/api-questions/60263-Get-allowed-child-node-types-by-code
        /// </summary>
        /// <param name="alias"></param>
        /// <returns></returns>
        public IEnumerable<IContentType> GetAllowedContentTypes(string alias)
        {
          var retval = new List<IContentType>();
    
          if (string.IsNullOrWhiteSpace(alias))
            return retval;
    
          // Use the content type service to find
          // the content type passed to the method
          var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
          var contentType = contentTypeService.GetContentType(alias);
    
          if (contentType == null)
            return retval;
    
          // If we have found the content type, loop through 
          // each of it's allowed content types and select
          // the content type
          retval.AddRange(
              contentType.AllowedContentTypes.Select(
                  ct => contentTypeService.GetContentType(ct.Id.Value)));
    
          return retval;
        }
    
      }
    }
    

    I've left the wrong code in in case anyone stumbles on this later!

  • Dominik 1 post 71 karma points
    Feb 22, 2016 @ 14:49
    Dominik
    0

    i searched internet error message path is too deep and found this one, its really helpful, also i solved my issue "Long Path Tool". thanks guys for your awesome article, you can try with Long Path Tool it may help you

Please Sign in or register to post replies

Write your reply to:

Draft