Mark Hall
May 16, 2018
visibility 6886
star star star star star star
(6 votes)

Hide Sites in Page Tree for editors without Create or Edit Access rights

Recently I was asked by a customer how they could hide sites in the page tree for users without edit access.  I thought this was easily done using access rights, but I was wroing as the content still shows but cannot be edited since everyone has read access.  After some googling I found this article which was for CMS 6R2.  I tried removing the everyone role read access, but that led to the none of the sites showing in the page tree. 

Next I tried the next forum post which led to this article.  The code below is a continuation of the code found on the blog post by Thomas Krantz.  Please note that this code only cares about pages so editors will still have access to commerce content, blocks, media, etc.  It is also required either Edit or Create access to show the tree.

using EPiServer.Cms.Shell.UI.Rest.ContentQuery;
using EPiServer.Core;
using EPiServer.Data.Dynamic;
using EPiServer.Filters;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Shell.ContentQuery;
using System.Collections.Generic;
using System.Linq;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    [ServiceConfiguration(typeof(IContentQuery))]
    public class MyGetChildrenQuery : GetChildrenQuery
    {
        private readonly IContentRepository _contentRepository;
        private readonly IContentQueryHelper _queryHelper;
        private readonly LanguageSelectorFactory _languageSelectorFactory;

        public MyGetChildrenQuery(IContentQueryHelper queryHelper, IContentRepository contentRepository, LanguageSelectorFactory languageSelectorFactory) 
            : base(queryHelper, contentRepository, languageSelectorFactory)
        {
            _contentRepository = contentRepository;
            _queryHelper = queryHelper;
            _languageSelectorFactory = languageSelectorFactory;
        }

        public override int Rank => 100; 

        protected override IEnumerable<IContent> GetContent(ContentQueryParameters parameters)
        {
            if (((parameters.References == null) || !parameters.References.Any()) && ContentReference.IsNullOrEmpty(parameters.ReferenceId))
            {
                return Enumerable.Empty<IContent>();
            }

            var selector = _languageSelectorFactory.AutoDetect(true);
            IContent content = null;

            if (!ContentReference.IsNullOrEmpty(parameters.ReferenceId))
            {
                content = _contentRepository.Get<IContent>(parameters.ReferenceId) as PageData;
                if (content == null)
                {
                    return base.GetContent(parameters);
                }
                if (content.ContentLink.ID == 1 || (FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Create)))
                {
                    return GetChildren(parameters, parameters.ReferenceId, selector);
                }
                return Enumerable.Empty<IContent>();
            }

            var filteredContent = new List<IContent>();
            foreach(var link in parameters.References.ToList())
            {
                content = _contentRepository.Get<IContent>(link) as PageData;
                if (content == null)
                {
                    filteredContent.AddRange(base.GetContent(parameters));
                    continue;
                }
                if (!FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Edit) || !FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Create))
                {
                    continue;
                }
                filteredContent.AddRange(GetChildren(parameters, parameters.ReferenceId, selector));
            }
            return filteredContent;
        }

        private IEnumerable<IContent> GetChildren(ContentQueryParameters parameters, ContentReference parentId, LanguageSelector selector)
        {
            IEnumerable<IContent> children = null;

            if (parameters.TypeIdentifiers != null && parameters.TypeIdentifiers.Any())
            {
                // Get by type and concatenate into one list
                children =
                    parameters.TypeIdentifiers.Select(i => TypeResolver.GetType(i, false))
                        .Where(type => type != null)
                        .SelectMany(type => GetChildrenByType(type, parentId, selector))
                        .Distinct()
                        .Where(x => FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Create));
            }
            else
            {
                children = _contentRepository.GetChildren<IContent>(parentId, selector)
                    .Where(x => FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Create)); ;
            }

            return (children ?? Enumerable.Empty<IContent>());
        }
    }
}

Here is how the access rights or setup for the site in questions.

Image AccessRights.png

Here is how the user permissions has been setup.

Image UserPermissions.png

Here is the site with administrator access.

Image Adminsitator.png

Here is the site with just site1 access.

Image Site1.png

May 16, 2018

Comments

Niklas Lord
Niklas Lord May 30, 2018 10:09 AM

Nice addition to the older solution. Great job!

Though it still have a problem I've been trying to solve before. What happens if you have no creat/edit access on a page. But you have that access on a child or a child deeper down in the tree.

Example:

Start = RCU

--Page A = R

----Page B = RCU

In that case A and B should be visible and A should be read only. With this solution A and B is hidden :-S

I guess you could query access down the tree but how will that affect the performance?

Andrea Sedlacek
Andrea Sedlacek Jun 6, 2018 11:40 PM

This is great!

error Please login to comment.
Latest blogs
Automated Search & Navigation to Graph Migration with Claude Code

A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command. CMS ...

Connor Fortin | Jun 24, 2026

Migrating from Find to Graph: Lessons Learned from a Real CMS 13 Project

While migrating a search solution from Optimizely Search & Navigation (Find) to Optimizely Graph in CMS 13, I encountered several issues that were...

Binh Nguyen Thi | Jun 24, 2026

Optimizely: Upgrade Opti-ID and .NET 10 in CMS 12

Many Optimizely customers are planning their roadmap around a future migration to Optimizely CMS 13. As a result, upgrades such as Opti ID adoption...

Madhu | Jun 23, 2026 |

Understanding Optimizely Graph: Caching, Webhooks & Avoiding Stale Content (Optimizely SaaS CMS)

📌 Scope: This post covers Optimizely CMS (SaaS) only — using the official @optimizely/cms-sdk and @optimizely/cms-cli packages with Next.js 15. If...

Kiran Patil | Jun 23, 2026 |