patrickdemooij9 / SeoToolkit.Umbraco

SeoToolkit is a SEO package for Umbraco 9, 10, 11, 12 & 13. This package features most functionalities needed for your SEO needs like meta fields, sitemap, robots.txt and much more.
MIT License
33 stars 28 forks source link

robots.txt with multiple domains #211

Open erikjanwestendorp opened 1 year ago

erikjanwestendorp commented 1 year ago

Is there a way to configure robots.txt per domain? Sometimes we run multiple sites in one Umbraco instance and it would be great if we can configure the robots.txt separately. 😄

patrickdemooij9 commented 1 year ago

Hi @erikjanwestendorp It currently isn't possible to configure multiple robots.txt per domain, but it is something that I am planning to add (along with other domain specific settings). I'll let you know when I have more, but you can always give it a start if you want!

erikjanwestendorp commented 10 months ago

@patrickdemooij9 Thanks for your reply! I want to work on this one but before I start I want to discuss a possible solution.

Of coure you don't want to introduce a breaking change (at least not in a minor version), so I was thinking maybe it's a good idea to add a setting, called MultiDomainSupport or so, and if this is set to true than it's possible to configure a robots.txt per domain. If it's false the behaviour will be the same as before.

The RobotsTxtTreeController can be updated like so:

public class RobotsTxtTreeController : TreeController
    {
        public const string TreeGroupAlias = TreeControllerConstants.SeoToolkitTreeGroupAlias;

        private readonly IDomainService _domainService;
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
        private readonly bool _multiDomainSupport;

        public RobotsTxtTreeController(
            ILocalizedTextService localizedTextService,
            UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
            IEventAggregator eventAggregator,
            IDomainService domainService,
            IUmbracoContextAccessor umbracoContextAccessor,
            IOptionsMonitor<GlobalAppSettingsModel> config)
            : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator)
        {
            _domainService = domainService;
            _umbracoContextAccessor = umbracoContextAccessor;
            _multiDomainSupport = config.CurrentValue.MultiDomainSupport;
        }

        protected override ActionResult<TreeNode> CreateRootNode(FormCollection queryStrings)
        {
            var root = base.CreateRootNode(queryStrings);

            root.Value.Icon = "icon-cloud";
            root.Value.HasChildren = _multiDomainSupport;
            root.Value.RoutePath = $"{SectionAlias}/{TreeAlias}/detail";
            root.Value.MenuUrl = null;

            return root.Value;
        }

        protected override ActionResult<MenuItemCollection> GetMenuForNode(string id, FormCollection queryStrings)
        {
            return null;
        }

        protected override ActionResult<TreeNodeCollection> GetTreeNodes(string id, FormCollection queryStrings)
        {
            if (!_multiDomainSupport)
            {
                return null;
            }

            var nodes = new TreeNodeCollection();
            _umbracoContextAccessor.TryGetUmbracoContext(out var context);

            if (id != Constants.System.Root.ToInvariantString())
            {
                return nodes;
            }

            foreach (var domain in _domainService.GetAll(false).GroupBy(x => x.RootContentId).Select(x => x.First()))
            {
                var node = context?.Content?.GetById(domain.RootContentId ?? -1);
                if (node is null)
                {
                    continue;
                }
                var newTreeItem = CreateTreeNode(node.Id.ToString(), "-1", queryStrings, node.Name(domain.LanguageIsoCode), "icon-globe", false);
                nodes.Add(newTreeItem);
            }

            if (nodes.Any())
            {
                return nodes;
            }

            var firstRootNode = context?.Content?.GetAtRoot().FirstOrDefault(x => x.TemplateId > 0);
            if (firstRootNode is null)
            {
                return nodes;
            }

            var firstTreeItem = CreateTreeNode(firstRootNode.Id.ToString(), "-1", queryStrings, firstRootNode?.Name, "icon-globe", false);
            nodes.Add(firstTreeItem);

            return nodes;
        }
    }

image

What do you think about this?

patrickdemooij9 commented 10 months ago

Hi @erikjanwestendorp Sorry for the late reply on this. I had wanted to write my thoughts about this earlier, but then totally forgot about the issue. So let me add them now.

I think what you have here is great, but I think it is currently a bit limited. I see this website-specific functionality being very handy for other functionalities like ScriptManager or future features. Therefore, I think we should have a tree with websites where you can see the website-specific settings like this: image

The code for that is quite simple as of now. Probably needs some changes:

namespace SeoToolkit.Umbraco.ScriptManager.Core.Controllers
{
    [Tree("SeoToolkit", "ScriptManager", TreeTitle = "Script Manager", TreeGroup = "SeoToolkit", SortOrder = 2)]
    [PluginController("SeoToolkit")]
    public class ScriptManagerTreeController : TreeController
    {
        private readonly IMenuItemCollectionFactory _menuItemCollectionFactory;

        public ScriptManagerTreeController(
            ILocalizedTextService localizedTextService,
            UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
            IEventAggregator eventAggregator,
            IMenuItemCollectionFactory menuItemCollectionFactory)
            : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator)
        {
            _menuItemCollectionFactory = menuItemCollectionFactory;
        }

        protected override ActionResult<TreeNode> CreateRootNode(FormCollection queryStrings)
        {
            var root = base.CreateRootNode(queryStrings);

            root.Value.Icon = "icon-script";
            root.Value.HasChildren = false;
            root.Value.RoutePath = $"{SectionAlias}/{TreeAlias}/list";

            return root.Value;
        }

        protected override ActionResult<MenuItemCollection> GetMenuForNode(string id, FormCollection queryStrings)
        {
            if (id == UmbConstants.System.RootString)
            {
                var menuItemCollection = _menuItemCollectionFactory.Create();

                var item = menuItemCollection.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, hasSeparator: false);
                item.NavigateToRoute($"{SectionAlias}/{TreeAlias}/edit");

                return menuItemCollection;
            }

            return null;
        }

        protected override ActionResult<TreeNodeCollection> GetTreeNodes(string id, FormCollection queryStrings)
        {
            return null;
        }
    }
}

And I think we can also introduce this without it being a breaking change this way. Instead of the websites being generated by domain, I think they should be added manually by the user. This way, we still keep the old items as a fallback functionality, but it also makes it clearer for users who only have a single website. If you then want specific settings for a website, you can add it to the website list and customize it the way you want to.

The root items could then be used as follows:

But this is mostly coming from my experiences. I would love to hear what you think of this and if this would also fit your websites. It will be a bit more work, but I could also help work on this one.

JohanReitsma83 commented 5 months ago

@patrickdemooij9 is there an update for this? Will it become an option in the next release?