Try our conversational search powered by Generative AI!

Restrict Block so it can only be created under "For this Page"

Vote:
 

Hi,

We are working on a solution that requires us to ensure that any given instance of a particular block can only be used on a single page. We therefore need to prevent this block type from being created underneath the "For All Sites" folder, but still allow it to be created underneath the "For This Page" folder on the relevant page. 

We have tried using the AvailableContentTypes attribute to restrict where the block can be created through the Include, IncludeOn, Exclude and ExcludeOn properties, but this does not seem to work as expected. Does anyone have any suggestions as to how we can achieve this?

#275812
Edited, Mar 07, 2022 13:39
Ted
Vote:
 

Perhaps you could implement your own ContentTypeAvailabilityService: https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2015/9/hide-blocktype-from-editmode-but-available-when-combined-with-allowedtypes/

Possibly in combination with the CreatingContent event through IContentEvents, to trigger a validation error if a block is being created somewhere where it shouldn't be.

Another option is a custom MetadataExtender or EditorDescriptor to override the AllowedTypes property, depending on the requirements. Some inspiration here: https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2019/11/hiding-properties-programtically-in-edit-mode/ 

#275820
Mar 07, 2022 14:27
Vote:
 

Hi James,

I'd probably go with a ContentTypeAvailabilityService as per Ted's first suggestion.

As a very quick and simple example (with plenty of opportunity for clean-up), if we implement our own we only need to modify the ListAvailable(IContent content, bool contentFolder, IPrincipal user) method. Here I'm restricting the TextBlock in Foundation to only be available on the HomePage:

public class CustomContentTypeAvailabilityService : ContentTypeAvailabilityService
{
    private readonly ContentTypeAvailabilityService _defaultContentTypeAvailabilityService;
    private readonly IContentLoader _contentLoader;

    public CustomContentTypeAvailabilityService(ContentTypeAvailabilityService defaultContentTypeAvailabilityService, IContentLoader contentLoader)
    {
        _defaultContentTypeAvailabilityService = defaultContentTypeAvailabilityService;
        _contentLoader = contentLoader;
    }
    public override AvailableSetting GetSetting(string contentTypeName)
    {
        return _defaultContentTypeAvailabilityService.GetSetting(contentTypeName);
    }

    public override bool IsAllowed(string parentContentTypeName, string childContentTypeName)
    {
        return _defaultContentTypeAvailabilityService.IsAllowed(parentContentTypeName, childContentTypeName);
    }

    public override IList<ContentType> ListAvailable(string contentTypeName, IPrincipal user)
    {
        return _defaultContentTypeAvailabilityService.ListAvailable(contentTypeName, user);
    }

    public override IList<ContentType> ListAvailable(IContent content, bool contentFolder, IPrincipal user)
    {
        var availableTypes = _defaultContentTypeAvailabilityService.ListAvailable(content, contentFolder, user);

        var contentType = availableTypes.SingleOrDefault(x => x.ModelType == typeof(TextBlock));

        if (contentType == null)
        {
            return availableTypes;
        }

        availableTypes.Remove(contentType);

        // Only available on a homepage
        if (content is HomePage)
        {
            availableTypes.Add(contentType);
            return availableTypes;
        }

        // We also need to validate whether this is a content asset folder for the home page
        if (content is not ContentAssetFolder folder)
        {
            return availableTypes;
        }

        var ownerGuid = folder.ContentOwnerID;

        if (!_contentLoader.TryGet(ownerGuid, ContentLanguage.PreferredCulture, out HomePage ownerContent))
        {
            return availableTypes;
        }

        availableTypes.Add(contentType);
        return availableTypes;
    }
}

To register it you just need to create an initializable module where you also pass in an IContentLoader:

public class ContentTypeAvailabilityInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Intercept<ContentTypeAvailabilityService>(
            (locator, defaultContentTypeAvailabilityService) => new CustomContentTypeAvailabilityService(defaultContentTypeAvailabilityService, locator.GetService<IContentLoader>()));
    }

    public void Initialize(InitializationEngine context) { }
    public void Uninitialize(InitializationEngine context) { }
}

I've only tested this runs and not dug into it, regardless, I hope it gets you started.

#275825
Mar 07, 2022 16:42
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.