Custom Site Settings Area

Topics: Customizing Orchard
Feb 4, 2015 at 8:34 PM
Edited Feb 4, 2015 at 8:35 PM
I'm trying to create my own area for site settings that requires a different level of permissions than normal site settings. I have copied the code for settings from the core into my own module and have it working but it only displays settings that are currently part of the site settings. I would like to create my own content item, something like ExtendedSite, and use that to attach settings parts that I need users to have access to but do not want them to have access to the other site settings. I have attempted to alter the SiteService.cs file to grab from my new content item called ExtendedSite however it fails miserably at every turn with null exceptions and whatnot. I altered:
public ISite GetSiteSettings() {
            var siteId = _cacheManager.Get("SiteId", ctx => {
                var site = _contentManager.Query("Site")
                    .List()
                    .FirstOrDefault();

                if (site == null) {
                    site = _contentManager.Create<ExtendedSiteSettingsPart>("Site", item => {
                        item.SiteSalt = Guid.NewGuid().ToString("N");
                        item.SiteName = "My Orchard Project Application";
                        item.PageTitleSeparator = " - ";
                        item.SiteTimeZone = TimeZoneInfo.Local.Id;
                    }).ContentItem;
                }

                return site.Id;
            });

            return _contentManager.Get<ISite>(siteId, VersionOptions.Published);
        }
to
public ISite GetSiteSettings() {
            var siteId = _cacheManager.Get("SiteId", ctx => {
                var site = _contentManager.Query("ExtendedSite")
                    .List()
                    .FirstOrDefault();

                if (site == null) {
                    site = _contentManager.Create<ExtendedSiteSettingsPart>("Site", item => {
                        item.SiteSalt = Guid.NewGuid().ToString("N");
                        item.SiteName = "My Orchard Project Application";
                        item.PageTitleSeparator = " - ";
                        item.SiteTimeZone = TimeZoneInfo.Local.Id;
                    }).ContentItem;
                }

                return site.Id;
            });

            return _contentManager.Get<ISite>(siteId, VersionOptions.Published);
        }
But this caused problems so I tried creating my own ISite interface and ISiteService interface and I think this helped but when I queried for my ExtendedSite content item it returns null which causes an exception. How do I create a new content item to be used for site settings and create an instance of it? I set it to not be creatable or draftable like the Site content item which is probably why I am getting a null exception since an instance of this item has not been created. How is this done for the Site content item. Thank you.

Edit: this is spurned off of the following discussion https://orchard.codeplex.com/discussions/578255
Feb 5, 2015 at 4:14 PM
Edited Feb 5, 2015 at 4:34 PM
I think I got a little farther by making my own ISite.cs and ISiteService.cs files (I named them differently of course). It's working for the most part except the SiteService.cs file which is returning null with the following code:
public IExtendedSite GetSiteSettings()
        {
            var extendedSiteId = _cacheManager.Get("SiteId", ctx => {
                var site = _contentManager.Query("ExtendedSite")
                    .List()
                    .FirstOrDefault();

                if (site == null) {
                    site = _contentManager.Create<ExtendedSiteSettingsPart>("ExtendedSite", item => {
                        item.SiteSalt = Guid.NewGuid().ToString("N");
                        item.SiteName = "My Orchard Project Application";
                        item.PageTitleSeparator = " - ";
                        item.SiteTimeZone = TimeZoneInfo.Local.Id;
                    }).ContentItem;
                }

                return site.Id;
            });

            return _contentManager.Get<IExtendedSite>(extendedSiteId, VersionOptions.Published);
        }
If I debug and look at the value for _contentManager.Get<IExtendedSite>(extendedSiteId, VersionOptions.Published) it is null but if I look at the value for _contentManager.Get(extendedSiteId, VersionOptions.Published) it appears to have grabbed the ExtendedSite content item but this doesn't work as the return type must be IExtendedSite. Any ideas why the first method returns null but the second doesn't? Do I need to somehow incorporate IExtendedSite with my ExtendedSite content item? Thank you.

Edit: I managed to get that fixed. I just needed to change something in the model. I ran into a new problem however. Everything in the menu is populated and looks good but when I click on one of the links in the dropdown it says the page can't be found. I'm guessing there is something wrong with the way my routes and menu are set up.

Routes.cs
namespace Orchard.ExtendedSettings
{
    public class Routes : IRouteProvider {
        public void GetRoutes(ICollection<RouteDescriptor> routes) {
            foreach (var routeDescriptor in GetRoutes())
                routes.Add(routeDescriptor);
        }

        public IEnumerable<RouteDescriptor> GetRoutes() {
            return new[] {
                new RouteDescriptor {
                    Route = new Route(
                        "Admin/ExtendedSettings/{groupInfoId}",
                        new RouteValueDictionary {
                            {"area", "Orchard.ExtendedSettings"},
                            {"controller", "Admin"},
                            {"action", "Index"}
                        },
                        new RouteValueDictionary {
                            {"groupInfoId",  new SettingsActionConstraint()}
                        },
                        new RouteValueDictionary {
                            {"area", "Orchard.ExtendedSettings"},
                            {"groupInfoId", ""}
                        },
                        new MvcRouteHandler())
                }
            };
        }

   }

    public class SettingsActionConstraint : IRouteConstraint {
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
            if (routeDirection == RouteDirection.UrlGeneration)
                return true;

            if (!values.ContainsKey(parameterName))
                return false;
            
            // just hard-coding to know action name strings for now
            var potentialActionName = values[parameterName] as string;
            return !string.IsNullOrWhiteSpace(potentialActionName)
                   && !potentialActionName.Equals("Index", StringComparison.OrdinalIgnoreCase)
                   && !potentialActionName.Equals("Culture", StringComparison.OrdinalIgnoreCase)
                   && !potentialActionName.Equals("AddCulture", StringComparison.OrdinalIgnoreCase)
                   && !potentialActionName.Equals("DeleteCulture", StringComparison.OrdinalIgnoreCase)
                ;
        }
    }
}
AdminMenu.cs
namespace Orchard.ExtendedSettings
{
    public class AdminMenu : INavigationProvider {
        private readonly IExtendedSiteService _siteService;

        public AdminMenu(IExtendedSiteService siteService, IOrchardServices orchardServices)
        {
            _siteService = siteService;
            Services = orchardServices;
        }

        public Localizer T { get; set; }
        public string MenuName { get { return "admin"; } }
        public IOrchardServices Services { get; private set; }

        public void GetNavigation(NavigationBuilder builder) {
            builder.AddImageSet("extendedsettings")
                .Add(T("Bank Settings"), "98",
                    menu => menu.Add(T("General"), "0", item => item.Action("Index", "Admin", new { area = "Orchard.ExtendedSettings", groupInfoId = "Index" })
                        .Permission(Permissions.ManageExtendedSettings)), new [] {"collapsed"});

            var site = _siteService.GetSiteSettings();
            if (site == null)
                return;

            foreach (var groupInfo in Services.ContentManager.GetEditorGroupInfos(site.ContentItem)) {
                GroupInfo info = groupInfo;
                builder.Add(T("Bank Settings"),
                    menu => menu.Add(info.Name, info.Position, item => item.Action("Index", "Admin", new { area = "Orchard.ExtendedSettings", groupInfoId = info.Id })
                        .Permission(Permissions.ManageExtendedSettings)));
            }
        }
    }
}
Feb 5, 2015 at 5:49 PM
Got this working too, I just needed to change the controller to use my new IExtendedSiteService instead of the old ISiteService. However, I am forced to use Orchard.ExtendedSettings for all my routes instead of ExtendedSettings. I would like the url to appear as Admin/ExtendedSettings/{groupInfoId} like in the standard site settings, but when I do this it fails to find the Admin/ExtendedSettings/Index page. It doesn't even seem to realize it should be part of the admin as it leaves to the front end and says the page didn't exist. If I change my route to be "Admin/Orchard.ExtendedSettings/{groupInfoId} then it works.