From 963c159743a82a470bcaee950f1385ef8a18c51c Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 28 Apr 2010 11:36:27 -0700 Subject: [PATCH 1/8] Adding prefix adjustment in route matches Complications with login make this impractical for the time being, but mechanically it does work to some extent. --HG-- branch : dev --- src/Orchard.Specs/MultiTenancy.feature | 2 +- src/Orchard.Specs/MultiTenancy.feature.cs | 2 +- .../Mvc/Routes/ShellRouteTests.cs | 95 ++++- .../Mvc/Routes/UrlPrefixTests.cs | 71 ++++ .../Orchard.Framework.Tests.csproj | 1 + src/Orchard.Tests/Stubs/StubHttpContext.cs | 18 +- src/Orchard/Mvc/Routes/ShellRoute.cs | 26 +- src/Orchard/Mvc/Routes/UrlPrefix.cs | 56 +++ .../Routes/UrlPrefixAdjustedHttpContext.cs | 36 ++ .../Mvc/Wrappers/HttpContextBaseWrapper.cs | 211 +++++++++++ .../Mvc/Wrappers/HttpRequestBaseWrapper.cs | 287 ++++++++++++++ .../Mvc/Wrappers/HttpResponseBaseWrapper.cs | 358 ++++++++++++++++++ src/Orchard/Orchard.Framework.csproj | 5 + 13 files changed, 1154 insertions(+), 14 deletions(-) create mode 100644 src/Orchard.Tests/Mvc/Routes/UrlPrefixTests.cs create mode 100644 src/Orchard/Mvc/Routes/UrlPrefix.cs create mode 100644 src/Orchard/Mvc/Routes/UrlPrefixAdjustedHttpContext.cs create mode 100644 src/Orchard/Mvc/Wrappers/HttpContextBaseWrapper.cs create mode 100644 src/Orchard/Mvc/Wrappers/HttpRequestBaseWrapper.cs create mode 100644 src/Orchard/Mvc/Wrappers/HttpResponseBaseWrapper.cs diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature index ed5bbed0b..b91693135 100644 --- a/src/Orchard.Specs/MultiTenancy.feature +++ b/src/Orchard.Specs/MultiTenancy.feature @@ -79,6 +79,6 @@ Scenario: Listing tenants from command line Given I have installed Orchard And I have installed "Orchard.MultiTenancy" And I have tenant "Alpha" on "example.org" as "New-site-name" - When I execute >orchard tenant list + When I execute >tenant list Then I should see "Name: Alpha" And I should see "Request Url Host: example.org" diff --git a/src/Orchard.Specs/MultiTenancy.feature.cs b/src/Orchard.Specs/MultiTenancy.feature.cs index 701a66ee0..ec7bf1d9f 100644 --- a/src/Orchard.Specs/MultiTenancy.feature.cs +++ b/src/Orchard.Specs/MultiTenancy.feature.cs @@ -8,7 +8,7 @@ // the code is regenerated. // // ------------------------------------------------------------------------------ -namespace Orchard.Specs +namespace Orchard.Tests { using TechTalk.SpecFlow; diff --git a/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs b/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs index 886845203..dea1069c2 100644 --- a/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs +++ b/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs @@ -7,6 +7,7 @@ using System.Web.Mvc; using System.Web.Routing; using Autofac; using Autofac.Integration.Web; +using Moq; using NUnit.Framework; using Orchard.Environment; using Orchard.Environment.Configuration; @@ -87,15 +88,15 @@ namespace Orchard.Tests.Mvc.Routes { var routeC = new Route("quux", new MvcRouteHandler()); _containerA.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeA}}); + new[] { new RouteDescriptor { Priority = 0, Route = routeA } }); _containerB.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeB}}); + new[] { new RouteDescriptor { Priority = 0, Route = routeB } }); Assert.That(_routes.Count(), Is.EqualTo(2)); _containerA.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeC}}); + new[] { new RouteDescriptor { Priority = 0, Route = routeC } }); Assert.That(_routes.Count(), Is.EqualTo(2)); @@ -110,17 +111,17 @@ namespace Orchard.Tests.Mvc.Routes { [Test] public void MatchingRouteToActiveShellTableWillLimitTheAbilityToMatchRoutes() { - + var routeFoo = new Route("foo", new MvcRouteHandler()); _settingsA.RequestUrlHost = "a.example.com"; _containerA.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}}); + new[] { new RouteDescriptor { Priority = 0, Route = routeFoo } }); _rootContainer.Resolve().Add(_settingsA); _settingsB.RequestUrlHost = "b.example.com"; _containerB.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}}); + new[] { new RouteDescriptor { Priority = 0, Route = routeFoo } }); _rootContainer.Resolve().Add(_settingsB); var httpContext = new StubHttpContext("~/foo"); @@ -143,6 +144,86 @@ namespace Orchard.Tests.Mvc.Routes { Assert.That(routeContainerProviderB.ApplicationContainer.Resolve(), Is.SameAs(_containerB.Resolve())); Assert.That(routeContainerProviderB.ApplicationContainer.Resolve(), Is.Not.SameAs(_containerA.Resolve())); } - + + [Test] + public void RequestUrlPrefixAdjustsMatchingAndPathGeneration() { + var settings = new ShellSettings { RequestUrlPrefix = "~/foo" }; + + var builder = new ContainerBuilder(); + builder.RegisterType().InstancePerDependency(); + builder.RegisterAutoMocking(); + builder.Register(ctx => settings); + + var container = builder.Build(); + container.Mock() + .Setup(x => x.Match(It.IsAny())) + .Returns(settings); + + + var shellRouteFactory = container.Resolve>(); + + var helloRoute = shellRouteFactory(new Route( + "hello", + new RouteValueDictionary { { "controller", "foo" }, { "action", "bar" } }, + new MvcRouteHandler())); + + var tagsRoute = shellRouteFactory(new Route( + "tags/{tagName}", + new RouteValueDictionary { { "controller", "tags" }, { "action", "show" } }, + new MvcRouteHandler())); + + var defaultRoute = shellRouteFactory(new Route( + "{controller}/{action}", + new RouteValueDictionary { { "controller", "home" }, { "action", "index" } }, + new MvcRouteHandler())); + + var routes = new RouteCollection { helloRoute, tagsRoute, defaultRoute }; + + var helloRouteData = routes.GetRouteData(new StubHttpContext("~/Foo/Hello")); + Assert.That(helloRouteData, Is.Not.Null); + Assert.That(helloRouteData.Values.Count(), Is.EqualTo(2)); + Assert.That(helloRouteData.GetRequiredString("controller"), Is.EqualTo("foo")); + Assert.That(helloRouteData.GetRequiredString("action"), Is.EqualTo("bar")); + + var tagsRouteData = routes.GetRouteData(new StubHttpContext("~/Foo/Tags/my-tag-name")); + Assert.That(tagsRouteData, Is.Not.Null); + Assert.That(tagsRouteData.Values.Count(), Is.EqualTo(3)); + Assert.That(tagsRouteData.GetRequiredString("controller"), Is.EqualTo("tags")); + Assert.That(tagsRouteData.GetRequiredString("action"), Is.EqualTo("show")); + Assert.That(tagsRouteData.GetRequiredString("tagName"), Is.EqualTo("my-tag-name")); + + var defaultRouteData = routes.GetRouteData(new StubHttpContext("~/Foo/Alpha/Beta")); + Assert.That(defaultRouteData, Is.Not.Null); + Assert.That(defaultRouteData.Values.Count(), Is.EqualTo(2)); + Assert.That(defaultRouteData.GetRequiredString("controller"), Is.EqualTo("Alpha")); + Assert.That(defaultRouteData.GetRequiredString("action"), Is.EqualTo("Beta")); + + var defaultRouteData2 = routes.GetRouteData(new StubHttpContext("~/Foo/Alpha")); + Assert.That(defaultRouteData2, Is.Not.Null); + Assert.That(defaultRouteData2.Values.Count(), Is.EqualTo(2)); + Assert.That(defaultRouteData2.GetRequiredString("controller"), Is.EqualTo("Alpha")); + Assert.That(defaultRouteData2.GetRequiredString("action"), Is.EqualTo("index")); + + var defaultRouteData3 = routes.GetRouteData(new StubHttpContext("~/Foo/")); + Assert.That(defaultRouteData3, Is.Not.Null); + Assert.That(defaultRouteData3.Values.Count(), Is.EqualTo(2)); + Assert.That(defaultRouteData3.GetRequiredString("controller"), Is.EqualTo("home")); + Assert.That(defaultRouteData3.GetRequiredString("action"), Is.EqualTo("index")); + + var defaultRouteData4 = routes.GetRouteData(new StubHttpContext("~/Foo")); + Assert.That(defaultRouteData4, Is.Not.Null); + Assert.That(defaultRouteData4.Values.Count(), Is.EqualTo(2)); + Assert.That(defaultRouteData4.GetRequiredString("controller"), Is.EqualTo("home")); + Assert.That(defaultRouteData4.GetRequiredString("action"), Is.EqualTo("index")); + + var requestContext = new RequestContext(new StubHttpContext("~/Foo/Alpha/Beta"), defaultRouteData); + var helloVirtualPath = routes.GetVirtualPath(requestContext, helloRouteData.Values); + Assert.That(helloVirtualPath, Is.Not.Null); + Assert.That(helloVirtualPath.VirtualPath, Is.EqualTo("~/foo/hello")); + + var defaultVirtualPath4 = routes.GetVirtualPath(requestContext, defaultRouteData4.Values); + Assert.That(defaultVirtualPath4, Is.Not.Null); + Assert.That(defaultVirtualPath4.VirtualPath, Is.EqualTo("~/foo/")); + } } } diff --git a/src/Orchard.Tests/Mvc/Routes/UrlPrefixTests.cs b/src/Orchard.Tests/Mvc/Routes/UrlPrefixTests.cs new file mode 100644 index 000000000..37255f345 --- /dev/null +++ b/src/Orchard.Tests/Mvc/Routes/UrlPrefixTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; +using Orchard.Mvc.Routes; + +namespace Orchard.Tests.Mvc.Routes { + [TestFixture] + public class UrlPrefixTests { + [Test] + public void RemoveLeadingSegmentsOnlyMatchesFullSegment() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/bar")); + Assert.That(prefix.RemoveLeadingSegments("~/fooo/bar"), Is.EqualTo("~/fooo/bar")); + Assert.That(prefix.RemoveLeadingSegments("~/fo/bar"), Is.EqualTo("~/fo/bar")); + } + + [Test] + public void RemoveLeadingSegmentsMayContainSlash() { + var prefix = new UrlPrefix("foo/quux"); + Assert.That(prefix.RemoveLeadingSegments("~/foo/quux/bar"), Is.EqualTo("~/bar")); + Assert.That(prefix.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/foo/bar")); + Assert.That(prefix.RemoveLeadingSegments("~/quux/bar"), Is.EqualTo("~/quux/bar")); + } + + [Test] + public void RemoveLeadingSegmentsCanMatchEntireUrl() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.RemoveLeadingSegments("~/foo/"), Is.EqualTo("~/")); + Assert.That(prefix.RemoveLeadingSegments("~/foo"), Is.EqualTo("~/")); + } + + [Test] + public void RemoveLeadingSegmentsIsCaseInsensitive() { + var prefix = new UrlPrefix("Foo"); + Assert.That(prefix.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/bar")); + Assert.That(prefix.RemoveLeadingSegments("~/FOO/BAR"), Is.EqualTo("~/BAR")); + } + + [Test] + public void RemoveLeadingSegmentsIgnoreLeadingAndTrailingCharactersOnInput() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/bar")); + var prefix2 = new UrlPrefix("~/foo"); + Assert.That(prefix2.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/bar")); + var prefix3 = new UrlPrefix("foo/"); + Assert.That(prefix3.RemoveLeadingSegments("~/foo/bar"), Is.EqualTo("~/bar")); + } + + [Test] + public void PrependLeadingSegmentsInsertsBeforeNormalVirtualPath() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.PrependLeadingSegments("~/bar"), Is.EqualTo("~/foo/bar")); + } + + [Test] + public void PrependLeadingSegmentsPreservesNatureOfIncomingPath() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.PrependLeadingSegments("~/bar"), Is.EqualTo("~/foo/bar")); + Assert.That(prefix.PrependLeadingSegments("/bar"), Is.EqualTo("/foo/bar")); + Assert.That(prefix.PrependLeadingSegments("bar"), Is.EqualTo("foo/bar")); + } + + [Test] + public void PrependLeadingSegmentsHandlesShortUrlConditionsAppropriately() { + var prefix = new UrlPrefix("foo"); + Assert.That(prefix.PrependLeadingSegments("~/"), Is.EqualTo("~/foo/")); + Assert.That(prefix.PrependLeadingSegments("/"), Is.EqualTo("/foo/")); + Assert.That(prefix.PrependLeadingSegments("~"), Is.EqualTo("~/foo/")); + Assert.That(prefix.PrependLeadingSegments(""), Is.EqualTo("foo/")); + } + + } +} diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 43e1a0240..00e849f60 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -169,6 +169,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubHttpContext.cs b/src/Orchard.Tests/Stubs/StubHttpContext.cs index d8495e5a1..df66a262a 100644 --- a/src/Orchard.Tests/Stubs/StubHttpContext.cs +++ b/src/Orchard.Tests/Stubs/StubHttpContext.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -25,6 +26,10 @@ namespace Orchard.Tests.Stubs { public override HttpRequestBase Request { get { return new StubHttpRequest(this); } } + public override HttpResponseBase Response { + get { return new StubHttpResponse(this); } + } + public override IDictionary Items { get { return _items; } @@ -56,7 +61,18 @@ namespace Orchard.Tests.Stubs { ?? new NameValueCollection { { "HTTP_HOST", _httpContext._hostHeader } }; } } - } + + class StubHttpResponse : HttpResponseBase { + private readonly StubHttpContext _httpContext; + + public StubHttpResponse(StubHttpContext httpContext) { + _httpContext = httpContext; + } + public override string ApplyAppPathModifier(string virtualPath) { + return "~/" + virtualPath.TrimStart('/'); + } + } + } } \ No newline at end of file diff --git a/src/Orchard/Mvc/Routes/ShellRoute.cs b/src/Orchard/Mvc/Routes/ShellRoute.cs index 6ead5e404..3a0585bb4 100644 --- a/src/Orchard/Mvc/Routes/ShellRoute.cs +++ b/src/Orchard/Mvc/Routes/ShellRoute.cs @@ -16,12 +16,15 @@ namespace Orchard.Mvc.Routes { private readonly ShellSettings _shellSettings; private readonly IContainer _container; private readonly IRunningShellTable _runningShellTable; + private UrlPrefix _urlPrefix; public ShellRoute(RouteBase route, ShellSettings shellSettings, ILifetimeScope shellLifetimeScope, IRunningShellTable runningShellTable) { _route = route; _shellSettings = shellSettings; _runningShellTable = runningShellTable; _container = new LifetimeScopeContainer(shellLifetimeScope); + if (!string.IsNullOrEmpty(_shellSettings.RequestUrlPrefix)) + _urlPrefix = new UrlPrefix(_shellSettings.RequestUrlPrefix); var routeWithArea = route as IRouteWithArea; if (routeWithArea != null) { @@ -45,8 +48,11 @@ namespace Orchard.Mvc.Routes { if (settings == null || settings.Name != _shellSettings.Name) return null; - // route didn't match anyway - var routeData = _route.GetRouteData(httpContext); + var effectiveHttpContext = httpContext; + if (_urlPrefix != null) + effectiveHttpContext = new UrlPrefixAdjustedHttpContext(httpContext, _urlPrefix); + + var routeData = _route.GetRouteData(effectiveHttpContext); if (routeData == null) return null; @@ -59,12 +65,24 @@ namespace Orchard.Mvc.Routes { public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { - // todo - ignore conditionally + // locate appropriate shell settings for request + var settings = _runningShellTable.Match(requestContext.HttpContext); - var virtualPath = _route.GetVirtualPath(requestContext, values); + // only proceed if there was a match, and it was for this client + if (settings == null || settings.Name != _shellSettings.Name) + return null; + + var effectiveRequestContext = requestContext; + if (_urlPrefix != null) + effectiveRequestContext = new RequestContext(new UrlPrefixAdjustedHttpContext(requestContext.HttpContext, _urlPrefix), requestContext.RouteData); + + var virtualPath = _route.GetVirtualPath(effectiveRequestContext, values); if (virtualPath == null) return null; + if (_urlPrefix != null) + virtualPath.VirtualPath = _urlPrefix.PrependLeadingSegments(virtualPath.VirtualPath); + return virtualPath; } diff --git a/src/Orchard/Mvc/Routes/UrlPrefix.cs b/src/Orchard/Mvc/Routes/UrlPrefix.cs new file mode 100644 index 000000000..0faa4bd12 --- /dev/null +++ b/src/Orchard/Mvc/Routes/UrlPrefix.cs @@ -0,0 +1,56 @@ +using System; + +namespace Orchard.Mvc.Routes { + /// + /// Small worker class to perform path prefix adjustments + /// + public class UrlPrefix { + private readonly string _prefix; + + public UrlPrefix(string prefix) { + _prefix = prefix.TrimStart('~').Trim('/'); + } + + public string RemoveLeadingSegments(string path) { + var beginIndex = 0; + if (path.Length > beginIndex && path[beginIndex] == '~') + ++beginIndex; + if (path.Length > beginIndex && path[beginIndex] == '/') + ++beginIndex; + + var endIndex = beginIndex + _prefix.Length; + if (path.Length == endIndex) { + // no-op + } + else if (path.Length > endIndex && path[endIndex] == '/') { + // don't include slash after segment in result + ++endIndex; + } + else { + // too short to compare - return unmodified + return path; + } + + if (string.Compare(path, beginIndex, _prefix, 0, _prefix.Length, StringComparison.OrdinalIgnoreCase) == 0) { + return path.Substring(0, beginIndex) + path.Substring(endIndex); + } + + return path; + } + + public string PrependLeadingSegments(string path) { + if (path == "~") { + // special case for peculiar situation + return "~/" + _prefix + "/"; + } + + var index = 0; + if (path.Length > index && path[index] == '~') + ++index; + if (path.Length > index && path[index] == '/') + ++index; + + return path.Substring(0, index) + _prefix + '/' + path.Substring(index); + } + } +} diff --git a/src/Orchard/Mvc/Routes/UrlPrefixAdjustedHttpContext.cs b/src/Orchard/Mvc/Routes/UrlPrefixAdjustedHttpContext.cs new file mode 100644 index 000000000..41ba52951 --- /dev/null +++ b/src/Orchard/Mvc/Routes/UrlPrefixAdjustedHttpContext.cs @@ -0,0 +1,36 @@ +using System; +using System.Web; +using Orchard.Mvc.Wrappers; + +namespace Orchard.Mvc.Routes { + public class UrlPrefixAdjustedHttpContext : HttpContextBaseWrapper { + private readonly UrlPrefix _prefix; + + public UrlPrefixAdjustedHttpContext(HttpContextBase httpContextBase, UrlPrefix prefix) + : base(httpContextBase) { + _prefix = prefix; + } + + public override HttpRequestBase Request { + get { + return new AdjustedRequest(_httpContextBase.Request, _prefix); + } + } + + class AdjustedRequest : HttpRequestBaseWrapper { + private readonly UrlPrefix _prefix; + + public AdjustedRequest(HttpRequestBase httpRequestBase, UrlPrefix prefix) + : base(httpRequestBase) { + _prefix = prefix; + } + + public override string AppRelativeCurrentExecutionFilePath { + get { + return _prefix.RemoveLeadingSegments(_httpRequestBase.AppRelativeCurrentExecutionFilePath); + } + } + } + + } +} diff --git a/src/Orchard/Mvc/Wrappers/HttpContextBaseWrapper.cs b/src/Orchard/Mvc/Wrappers/HttpContextBaseWrapper.cs new file mode 100644 index 000000000..6261f2217 --- /dev/null +++ b/src/Orchard/Mvc/Wrappers/HttpContextBaseWrapper.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Security.Principal; +using System.Web; +using System.Web.Caching; +using System.Web.Profile; + +namespace Orchard.Mvc.Wrappers { + public abstract class HttpContextBaseWrapper : HttpContextBase { + protected readonly HttpContextBase _httpContextBase; + + protected HttpContextBaseWrapper(HttpContextBase httpContextBase) { + _httpContextBase = httpContextBase; + } + + public override void AddError(Exception errorInfo) { + _httpContextBase.AddError(errorInfo); + } + + public override void ClearError() { + _httpContextBase.ClearError(); + } + + public override object GetGlobalResourceObject(string classKey, string resourceKey) { + return _httpContextBase.GetGlobalResourceObject(classKey, resourceKey); + } + + public override object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture) { + return _httpContextBase.GetGlobalResourceObject(classKey, resourceKey, culture); + } + + public override object GetLocalResourceObject(string virtualPath, string resourceKey) { + return _httpContextBase.GetLocalResourceObject(virtualPath, resourceKey); + } + + public override object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture) { + return _httpContextBase.GetLocalResourceObject(virtualPath, resourceKey, culture); + } + + public override object GetSection(string sectionName) { + return _httpContextBase.GetSection(sectionName); + } + + public override object GetService(Type serviceType) { + return ((IServiceProvider)_httpContextBase).GetService(serviceType); + } + + public override void RewritePath(string path) { + _httpContextBase.RewritePath(path); + } + + public override void RewritePath(string path, bool rebaseClientPath) { + _httpContextBase.RewritePath(path, rebaseClientPath); + } + + public override void RewritePath(string filePath, string pathInfo, string queryString) { + _httpContextBase.RewritePath(filePath, pathInfo, queryString); + } + + public override void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath) { + _httpContextBase.RewritePath(filePath, pathInfo, queryString, setClientFilePath); + } + + public override Exception[] AllErrors { + get { + return _httpContextBase.AllErrors; + } + } + + public override HttpApplicationStateBase Application { + get { + return _httpContextBase.Application; + } + } + + public override HttpApplication ApplicationInstance { + get { + return _httpContextBase.ApplicationInstance; + } + set { + _httpContextBase.ApplicationInstance = value; + } + } + + public override Cache Cache { + get { + return _httpContextBase.Cache; + } + } + + public override IHttpHandler CurrentHandler { + get { + return _httpContextBase.CurrentHandler; + } + } + + public override RequestNotification CurrentNotification { + get { + return _httpContextBase.CurrentNotification; + } + } + + public override Exception Error { + get { + return _httpContextBase.Error; + } + } + + public override IHttpHandler Handler { + get { + return _httpContextBase.Handler; + } + set { + _httpContextBase.Handler = value; + } + } + + public override bool IsCustomErrorEnabled { + get { + return _httpContextBase.IsCustomErrorEnabled; + } + } + + public override bool IsDebuggingEnabled { + get { + return _httpContextBase.IsDebuggingEnabled; + } + } + + public override bool IsPostNotification { + get { + return _httpContextBase.IsDebuggingEnabled; + } + } + + public override IDictionary Items { + get { + return _httpContextBase.Items; + } + } + + public override IHttpHandler PreviousHandler { + get { + return _httpContextBase.PreviousHandler; + } + } + + public override ProfileBase Profile { + get { + return _httpContextBase.Profile; + } + } + + public override HttpRequestBase Request { + get { + return _httpContextBase.Request; + } + } + + public override HttpResponseBase Response { + get { + return _httpContextBase.Response; + } + } + + public override HttpServerUtilityBase Server { + get { + return _httpContextBase.Server; + } + } + + public override HttpSessionStateBase Session { + get { + return _httpContextBase.Session; + } + } + + public override bool SkipAuthorization { + get { + return _httpContextBase.SkipAuthorization; + } + set { + _httpContextBase.SkipAuthorization = value; + } + } + + public override DateTime Timestamp { + get { + return _httpContextBase.Timestamp; + } + } + + public override TraceContext Trace { + get { + return _httpContextBase.Trace; + } + } + + public override IPrincipal User { + get { + return _httpContextBase.User; + } + set { + _httpContextBase.User = value; + } + } + + + } +} \ No newline at end of file diff --git a/src/Orchard/Mvc/Wrappers/HttpRequestBaseWrapper.cs b/src/Orchard/Mvc/Wrappers/HttpRequestBaseWrapper.cs new file mode 100644 index 000000000..82bea62ba --- /dev/null +++ b/src/Orchard/Mvc/Wrappers/HttpRequestBaseWrapper.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Security.Principal; +using System.Text; +using System.Web; + +namespace Orchard.Mvc.Wrappers { + public abstract class HttpRequestBaseWrapper : HttpRequestBase { + protected readonly HttpRequestBase _httpRequestBase; + + protected HttpRequestBaseWrapper(HttpRequestBase httpRequestBase) { + _httpRequestBase = httpRequestBase; + } + + public override byte[] BinaryRead(int count) { + return _httpRequestBase.BinaryRead(count); + } + + public override int[] MapImageCoordinates(string imageFieldName) { + return _httpRequestBase.MapImageCoordinates(imageFieldName); + } + + public override string MapPath(string virtualPath) { + return _httpRequestBase.MapPath(virtualPath); + } + + public override string MapPath(string virtualPath, string baseVirtualDir, bool allowCrossAppMapping) { + return _httpRequestBase.MapPath(virtualPath, baseVirtualDir, allowCrossAppMapping); + } + + public override void SaveAs(string filename, bool includeHeaders) { + _httpRequestBase.SaveAs(filename, includeHeaders); + } + + public override void ValidateInput() { + _httpRequestBase.ValidateInput(); + } + + public override string[] AcceptTypes { + get { + return _httpRequestBase.AcceptTypes; + } + } + + public override string AnonymousID { + get { + return _httpRequestBase.AnonymousID; + } + } + + public override string ApplicationPath { + get { + return _httpRequestBase.ApplicationPath; + } + } + + public override string AppRelativeCurrentExecutionFilePath { + get { + return _httpRequestBase.AppRelativeCurrentExecutionFilePath; + } + } + + public override HttpBrowserCapabilitiesBase Browser { + get { + return _httpRequestBase.Browser; + } + } + + public override HttpClientCertificate ClientCertificate { + get { + return _httpRequestBase.ClientCertificate; + } + } + + public override Encoding ContentEncoding { + get { + return _httpRequestBase.ContentEncoding; + } + set { + _httpRequestBase.ContentEncoding = value; + } + } + + public override int ContentLength { + get { + return _httpRequestBase.ContentLength; + } + } + + public override string ContentType { + get { + return _httpRequestBase.ContentType; + } + set { + _httpRequestBase.ContentType = value; + } + } + + public override HttpCookieCollection Cookies { + get { + return _httpRequestBase.Cookies; + } + } + + public override string CurrentExecutionFilePath { + get { + return _httpRequestBase.CurrentExecutionFilePath; + } + } + + public override string FilePath { + get { + return _httpRequestBase.FilePath; + } + } + + public override HttpFileCollectionBase Files { + get { + return _httpRequestBase.Files; + } + } + + public override Stream Filter { + get { + return _httpRequestBase.Filter; + } + set { + _httpRequestBase.Filter = value; + } + } + + public override NameValueCollection Form { + get { + return _httpRequestBase.Form; + } + } + + public override NameValueCollection Headers { + get { + return _httpRequestBase.Headers; + } + } + + public override string HttpMethod { + get { + return _httpRequestBase.HttpMethod; + } + } + + public override Stream InputStream { + get { + return _httpRequestBase.InputStream; + } + } + + public override bool IsAuthenticated { + get { + return _httpRequestBase.IsAuthenticated; + } + } + + public override bool IsLocal { + get { + return _httpRequestBase.IsLocal; + } + } + + public override bool IsSecureConnection { + get { + return _httpRequestBase.IsSecureConnection; + } + } + + public override string this[string key] { + get { + return _httpRequestBase[key]; + } + } + + public override WindowsIdentity LogonUserIdentity { + get { + return _httpRequestBase.LogonUserIdentity; + } + } + + public override NameValueCollection Params { + get { + return _httpRequestBase.Params; + } + } + + public override string Path { + get { + return _httpRequestBase.Path; + } + } + + public override string PathInfo { + get { + return _httpRequestBase.PathInfo; + } + } + + public override string PhysicalApplicationPath { + get { + return _httpRequestBase.PhysicalApplicationPath; + } + } + + public override string PhysicalPath { + get { + return _httpRequestBase.PhysicalPath; + } + } + + public override NameValueCollection QueryString { + get { + return _httpRequestBase.QueryString; + } + } + + public override string RawUrl { + get { + return _httpRequestBase.RawUrl; + } + } + + public override string RequestType { + get { + return _httpRequestBase.RequestType; + } + set { + _httpRequestBase.RequestType = value; + } + } + + public override NameValueCollection ServerVariables { + get { + return _httpRequestBase.ServerVariables; + } + } + + public override int TotalBytes { + get { + return _httpRequestBase.TotalBytes; + } + } + + public override Uri Url { + get { + return _httpRequestBase.Url; + } + } + + public override Uri UrlReferrer { + get { + return _httpRequestBase.UrlReferrer; + } + } + + public override string UserAgent { + get { + return _httpRequestBase.UserAgent; + } + } + + public override string UserHostAddress { + get { + return _httpRequestBase.UserHostAddress; + } + } + + public override string UserHostName { + get { + return _httpRequestBase.UserHostName; + } + } + + public override string[] UserLanguages { + get { + return _httpRequestBase.UserLanguages; + } + } + + } +} \ No newline at end of file diff --git a/src/Orchard/Mvc/Wrappers/HttpResponseBaseWrapper.cs b/src/Orchard/Mvc/Wrappers/HttpResponseBaseWrapper.cs new file mode 100644 index 000000000..5728503ce --- /dev/null +++ b/src/Orchard/Mvc/Wrappers/HttpResponseBaseWrapper.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using System.Web; +using System.Web.Caching; + +namespace Orchard.Mvc.Wrappers { + public abstract class HttpResponseBaseWrapper : HttpResponseBase { + private readonly HttpResponseBase _httpResponseBase; + + protected HttpResponseBaseWrapper(HttpResponseBase httpResponse) { + _httpResponseBase = httpResponse; + } + + public override void AddCacheDependency(params CacheDependency[] dependencies) { + _httpResponseBase.AddCacheDependency(dependencies); + } + + public override void AddCacheItemDependencies(ArrayList cacheKeys) { + _httpResponseBase.AddCacheItemDependencies(cacheKeys); + } + + public override void AddCacheItemDependencies(string[] cacheKeys) { + _httpResponseBase.AddCacheItemDependencies(cacheKeys); + } + + public override void AddCacheItemDependency(string cacheKey) { + _httpResponseBase.AddCacheItemDependency(cacheKey); + } + + public override void AddFileDependencies(string[] filenames) { + _httpResponseBase.AddFileDependencies(filenames); + } + + public override void AddFileDependencies(ArrayList filenames) { + _httpResponseBase.AddFileDependencies(filenames); + } + + public override void AddFileDependency(string filename) { + _httpResponseBase.AddFileDependency(filename); + } + + public override void AddHeader(string name, string value) { + _httpResponseBase.AddHeader(name, value); + } + + public override void AppendCookie(HttpCookie cookie) { + _httpResponseBase.AppendCookie(cookie); + } + + public override void AppendHeader(string name, string value) { + _httpResponseBase.AppendHeader(name, value); + } + + public override void AppendToLog(string param) { + _httpResponseBase.AppendToLog(param); + } + + public override string ApplyAppPathModifier(string virtualPath) { + return _httpResponseBase.ApplyAppPathModifier(virtualPath); + } + + public override void BinaryWrite(byte[] buffer) { + _httpResponseBase.BinaryWrite(buffer); + } + + public override void Clear() { + _httpResponseBase.Clear(); + } + + public override void ClearContent() { + _httpResponseBase.ClearContent(); + } + + public override void ClearHeaders() { + _httpResponseBase.ClearHeaders(); + } + + public override void Close() { + _httpResponseBase.Close(); + } + + public override void DisableKernelCache() { + _httpResponseBase.DisableKernelCache(); + } + + public override void End() { + _httpResponseBase.End(); + } + + public override void Flush() { + _httpResponseBase.Flush(); + } + + public override void Pics(string value) { + _httpResponseBase.Pics(value); + } + + public override void Redirect(string url) { + _httpResponseBase.Redirect(url); + } + + public override void Redirect(string url, bool endResponse) { + _httpResponseBase.Redirect(url, endResponse); + } + + public override void RemoveOutputCacheItem(string path) { + _httpResponseBase.RemoveOutputCacheItem(path); + } + + public override void SetCookie(HttpCookie cookie) { + _httpResponseBase.SetCookie(cookie); + } + + public override void TransmitFile(string filename) { + _httpResponseBase.TransmitFile(filename); + } + + public override void TransmitFile(string filename, long offset, long length) { + _httpResponseBase.TransmitFile(filename, offset, length); + } + + public override void Write(char ch) { + _httpResponseBase.Write(ch); + } + + public override void Write(object obj) { + _httpResponseBase.Write(obj); + } + + public override void Write(string s) { + _httpResponseBase.Write(s); + } + + public override void Write(char[] buffer, int index, int count) { + _httpResponseBase.Write(buffer, index, count); + } + + public override void WriteFile(string filename) { + _httpResponseBase.WriteFile(filename); + } + + public override void WriteFile(string filename, bool readIntoMemory) { + _httpResponseBase.WriteFile(filename, readIntoMemory); + } + + public override void WriteFile(IntPtr fileHandle, long offset, long size) { + _httpResponseBase.WriteFile(fileHandle, offset, size); + } + + public override void WriteFile(string filename, long offset, long size) { + _httpResponseBase.WriteFile(filename, offset, size); + } + + public override void WriteSubstitution(HttpResponseSubstitutionCallback callback) { + _httpResponseBase.WriteSubstitution(callback); + } + + // Properties + public override bool Buffer { + get { + return _httpResponseBase.Buffer; + } + set { + _httpResponseBase.Buffer = value; + } + } + + public override bool BufferOutput { + get { + return _httpResponseBase.BufferOutput; + } + set { + _httpResponseBase.BufferOutput = value; + } + } + + public override HttpCachePolicyBase Cache { + get { + return _httpResponseBase.Cache; + } + } + + public override string CacheControl { + get { + return _httpResponseBase.CacheControl; + } + set { + _httpResponseBase.CacheControl = value; + } + } + + public override string Charset { + get { + return _httpResponseBase.Charset; + } + set { + _httpResponseBase.Charset = value; + } + } + + public override Encoding ContentEncoding { + get { + return _httpResponseBase.ContentEncoding; + } + set { + _httpResponseBase.ContentEncoding = value; + } + } + + public override string ContentType { + get { + return _httpResponseBase.ContentType; + } + set { + _httpResponseBase.ContentType = value; + } + } + + public override HttpCookieCollection Cookies { + get { + return _httpResponseBase.Cookies; + } + } + + public override int Expires { + get { + return _httpResponseBase.Expires; + } + set { + _httpResponseBase.Expires = value; + } + } + + public override DateTime ExpiresAbsolute { + get { + return _httpResponseBase.ExpiresAbsolute; + } + set { + _httpResponseBase.ExpiresAbsolute = value; + } + } + + public override Stream Filter { + get { + return _httpResponseBase.Filter; + } + set { + _httpResponseBase.Filter = value; + } + } + + public override Encoding HeaderEncoding { + get { + return _httpResponseBase.HeaderEncoding; + } + set { + _httpResponseBase.HeaderEncoding = value; + } + } + + public override NameValueCollection Headers { + get { + return _httpResponseBase.Headers; + } + } + + public override bool IsClientConnected { + get { + return _httpResponseBase.IsClientConnected; + } + } + + public override bool IsRequestBeingRedirected { + get { + return _httpResponseBase.IsRequestBeingRedirected; + } + } + + public override TextWriter Output { + get { + return _httpResponseBase.Output; + } + } + + public override Stream OutputStream { + get { + return _httpResponseBase.OutputStream; + } + } + + public override string RedirectLocation { + get { + return _httpResponseBase.RedirectLocation; + } + set { + _httpResponseBase.RedirectLocation = value; + } + } + + public override string Status { + get { + return _httpResponseBase.Status; + } + set { + _httpResponseBase.Status = value; + } + } + + public override int StatusCode { + get { + return _httpResponseBase.StatusCode; + } + set { + _httpResponseBase.StatusCode = value; + } + } + + public override string StatusDescription { + get { + return _httpResponseBase.StatusDescription; + } + set { + _httpResponseBase.StatusDescription = value; + } + } + + public override int SubStatusCode { + get { + return _httpResponseBase.SubStatusCode; + } + set { + _httpResponseBase.SubStatusCode = value; + } + } + + public override bool SuppressContent { + get { + return _httpResponseBase.SuppressContent; + } + set { + _httpResponseBase.SuppressContent = value; + } + } + + public override bool TrySkipIisCustomErrors { + get { + return _httpResponseBase.TrySkipIisCustomErrors; + } + set { + _httpResponseBase.TrySkipIisCustomErrors = value; + } + } + + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 44589f27c..7be14bbab 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -196,11 +196,16 @@ + + ASPXCodeBehind + + + From 0b3dbcb161e2e9db3d6c9c5f11370ad647efacec Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 28 Apr 2010 11:47:29 -0700 Subject: [PATCH 2/8] Adding some feature enable/disable UI --HG-- branch : dev --- src/Orchard.Specs/Modules.feature | 1 + src/Orchard.Specs/Modules.feature.cs | 2 + .../Content/Admin/images/disabled.gif | Bin 0 -> 603 bytes .../Content/Admin/images/enabled.gif | Bin 0 -> 1024 bytes .../Controllers/AdminController.cs | 82 +++++++++++++++- .../Orchard.Modules/Models/ModuleFeature.cs | 8 ++ .../Orchard.Modules/Orchard.Modules.csproj | 6 +- .../Modules/Orchard.Modules/Routes.cs | 36 ++++++- .../Routing/FeatureNameConstraint.cs | 28 ++++++ .../Orchard.Modules/Services/ModuleService.cs | 48 +++++++-- .../ViewModels/FeatureListViewModel.cs | 9 -- .../ViewModels/FeaturesViewModel.cs | 19 ++++ .../Orchard.Modules/Views/Admin/Features.ascx | 92 +++++++++++++----- src/Orchard/Modules/IModuleFeature.cs | 8 ++ src/Orchard/Modules/IModuleService.cs | 3 +- src/Orchard/Orchard.Framework.csproj | 1 + 16 files changed, 294 insertions(+), 49 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Content/Admin/images/disabled.gif create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Content/Admin/images/enabled.gif create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Models/ModuleFeature.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Routing/FeatureNameConstraint.cs delete mode 100644 src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeatureListViewModel.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeaturesViewModel.cs create mode 100644 src/Orchard/Modules/IModuleFeature.cs diff --git a/src/Orchard.Specs/Modules.feature b/src/Orchard.Specs/Modules.feature index 718784b3a..39cdf7377 100644 --- a/src/Orchard.Specs/Modules.feature +++ b/src/Orchard.Specs/Modules.feature @@ -20,4 +20,5 @@ Scenario: Features of installed modules are listed Given I have installed Orchard When I go to "admin/modules/features" Then I should see "

Available Features

" + And I should see "

Common

" And the status should be 200 OK \ No newline at end of file diff --git a/src/Orchard.Specs/Modules.feature.cs b/src/Orchard.Specs/Modules.feature.cs index 67ca158e7..9a6d22936 100644 --- a/src/Orchard.Specs/Modules.feature.cs +++ b/src/Orchard.Specs/Modules.feature.cs @@ -104,6 +104,8 @@ this.ScenarioSetup(scenarioInfo); #line 22 testRunner.Then("I should see \"

Available Features

\""); #line 23 + testRunner.And("I should see \"

Common

\""); +#line 24 testRunner.And("the status should be 200 OK"); #line hidden testRunner.CollectScenarioErrors(); diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Content/Admin/images/disabled.gif b/src/Orchard.Web/Modules/Orchard.Modules/Content/Admin/images/disabled.gif new file mode 100644 index 0000000000000000000000000000000000000000..42c8bde22365a15c7481853c2ca8b74bdeccd86b GIT binary patch literal 603 zcmV-h0;K&%Nk%w1VGsZi0Oo!G&`&=9|Nrb?RQv1e=ft`Cos0I*&+d|s-g$J~rk3x? z#qhPY^rD{We|Ye=w(rWu`ns<2cVqRYp}tl*?u32uoR;ZhTlu-K`m3Gtt*Yu~T;pqD z`jdbAshs3^ap`+-?}mQyotEiaPwKF&=2%Sq+tj*JG|XmI_2%K+rHK_vq%`cyqs8K=tS1-fL>aWK8#we)x=b?URneXH4U6WAmq@ z@MKo~+}O-gJmywR?#aXSj)Bc!QT42$#85Q(>ge`_Z01%_=2}wVWnAuNQq5>s;(K-D zczO4ee&T<9)Ny6pYiRk?%=hT!+g3~NYF^f0RO_>^`lFHIsh-DcRQSWbz*#@yd3oWh zpxkb0__nC%dUe4~Gxn>Z?u>!$$;0@DaQw%*^`oESaAyDi{{R30A^8LW004dfEC2ui z01yBW000NLfPR95goT9z4r54V2x~Nkeg<(&Z)Q3{owSQ{bi@JcHQ2(@BY>)C;GQP+)#8n)N_NnX1VIi?@!m>pMCK0wyd22fB*fx z_U_W^%d^vtdfLylsXLe%w7}`vm&c_iG79&_H=HW2JeczMwH&1tr^~qxP9Eyf$_Ir3v=k*6Xe>%-tStT5mY*bX(50 zu-ZLoF$b(?oN3>FbL-phucw{u*mhxS!H&qXlbN%w^gev^uz6puT8nbY;gp#dyZvT6 z&N$t+`R3BRqsf&gvroT0kaN^~;(`9z*C%G4@S1hJebem~_N~_SJ2H!pr){~rYTL!F z`G*tpjwI$EO6)vRRj_2=xyu)ds zamk^`#n-0+BbQ+m;0*!ApDc_F4D%UuK&n7_f`Q{MgFUB=$A$z)m)5kBnTHkzu{Y0u zaN*?>O$P=o%LNZKZX~rbvaqmlC=@VoDoZfuelaLeXzXU{ZP}Cgp?$Joqq%{iM}R}~ zY?m&H3@Jy=wgVO#&n8%2YVV(~!qBwB@QL$-cE?GdPTaVdBHX~(UKiu2JfW@0)g_35 z;j<~v63#$1&X`9le580<7+zdxIvUhz6~+?QxM2AS0YUYUoP`V;ho$(L9CZv5)t#0Y z^6PnIHmi5fY}99(8~CV|o70R>C*@Y~hi+j7(TbLeLoQ7c9n+t1RE0b~$0jDwEY8Eh GU=0BKCVeOX literal 0 HcmV?d00001 diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs index 8b7704dad..47d8fc3a0 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Web.Mvc; using Orchard.Localization; using Orchard.Modules.ViewModels; +using Orchard.Mvc.AntiForgery; using Orchard.Mvc.Results; +using Orchard.UI.Notify; namespace Orchard.Modules.Controllers { public class AdminController : Controller { @@ -39,12 +44,85 @@ namespace Orchard.Modules.Controllers { }); } - public ActionResult Features() { + public ActionResult Features(FeaturesOptions options) { if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to manage features"))) return new HttpUnauthorizedResult(); var features = _moduleService.GetAvailableFeatures(); - return View(new FeatureListViewModel {Features = features}); + return View(new FeaturesViewModel {Features = features, Options = options}); + } + + [HttpPost, ActionName("Features")] + [FormValueRequired("submit.BulkEdit")] + public ActionResult FeaturesPOST(FeaturesOptions options, IList selection) { + if (selection != null && selection.Count > 0) + { + switch (options.BulkAction) + { + case FeaturesBulkAction.None: + break; + case FeaturesBulkAction.Enable: + if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to enable features"))) + return new HttpUnauthorizedResult(); + _moduleService.EnableFeatures(selection); + //todo: (heskew) need better messages + //todo: (heskew) hmmm...need a helper to comma-separate all but last, which would get the " and " treatment...all localized, of course + Services.Notifier.Information(T("{0} were enabled", string.Join(", ", selection.ToArray()))); + break; + case FeaturesBulkAction.Disable: + if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to disable features"))) + return new HttpUnauthorizedResult(); + _moduleService.DisableFeatures(selection); + //todo: (heskew) need better messages + Services.Notifier.Information(T("{0} were disabled", string.Join(", ", selection.ToArray()))); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + return RedirectToAction("Features"); + } + + [ValidateAntiForgeryTokenOrchard] + public ActionResult Enable(string featureName) { + if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to manage features"))) + return new HttpUnauthorizedResult(); + + if (string.IsNullOrEmpty(featureName)) + return new NotFoundResult(); + + _moduleService.EnableFeatures(new [] {featureName}); + Services.Notifier.Information(T("{0} was enabled", featureName)); + + return RedirectToAction("Features"); + } + + [ValidateAntiForgeryTokenOrchard] + public ActionResult Disable(string featureName) { + if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to manage features"))) + return new HttpUnauthorizedResult(); + + if (string.IsNullOrEmpty(featureName)) + return new NotFoundResult(); + + _moduleService.DisableFeatures(new[] { featureName }); + Services.Notifier.Information(T("{0} was disabled", featureName)); + + return RedirectToAction("Features"); + } + + private class FormValueRequiredAttribute : ActionMethodSelectorAttribute { + private readonly string _submitButtonName; + + public FormValueRequiredAttribute(string submitButtonName) { + _submitButtonName = submitButtonName; + } + + public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { + var value = controllerContext.HttpContext.Request.Form[_submitButtonName]; + return !string.IsNullOrEmpty(value); + } } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Models/ModuleFeature.cs b/src/Orchard.Web/Modules/Orchard.Modules/Models/ModuleFeature.cs new file mode 100644 index 000000000..5d947048e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Models/ModuleFeature.cs @@ -0,0 +1,8 @@ +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Modules.Models { + public class ModuleFeature : IModuleFeature { + public FeatureDescriptor Descriptor { get; set; } + public bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj index 4b092dbcc..a1206df0f 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj +++ b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj @@ -63,7 +63,9 @@ - + + + @@ -85,6 +87,8 @@ + + diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Routes.cs b/src/Orchard.Web/Modules/Orchard.Modules/Routes.cs index 51b69717c..aaccb5679 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Routes.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Routes.cs @@ -7,9 +7,11 @@ using Orchard.Mvc.Routes; namespace Orchard.Modules { public class Routes : IRouteProvider { private readonly IModuleNameConstraint _moduleNameConstraint; + private readonly IFeatureNameConstraint _featureNameConstraint; - public Routes(IModuleNameConstraint moduleNameConstraint) { + public Routes(IModuleNameConstraint moduleNameConstraint, IFeatureNameConstraint featureNameConstraint) { _moduleNameConstraint = moduleNameConstraint; + _featureNameConstraint = featureNameConstraint; } public IEnumerable GetRoutes() { @@ -29,6 +31,38 @@ namespace Orchard.Modules { {"area", "Orchard.Modules"} }, new MvcRouteHandler()) + }, + new RouteDescriptor { + Route = new Route( + "Admin/Modules/Enable/{featureName}", + new RouteValueDictionary { + {"area", "Orchard.Modules"}, + {"controller", "Admin"}, + {"action", "Enable"} + }, + new RouteValueDictionary { + {"featureName", _featureNameConstraint} + }, + new RouteValueDictionary { + {"area", "Orchard.Modules"} + }, + new MvcRouteHandler()) + }, + new RouteDescriptor { + Route = new Route( + "Admin/Modules/Disable/{featureName}", + new RouteValueDictionary { + {"area", "Orchard.Modules"}, + {"controller", "Admin"}, + {"action", "Disable"} + }, + new RouteValueDictionary { + {"featureName", _featureNameConstraint} + }, + new RouteValueDictionary { + {"area", "Orchard.Modules"} + }, + new MvcRouteHandler()) } }; } diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Routing/FeatureNameConstraint.cs b/src/Orchard.Web/Modules/Orchard.Modules/Routing/FeatureNameConstraint.cs new file mode 100644 index 000000000..bee40d3c1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Routing/FeatureNameConstraint.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Web; +using System.Web.Routing; + +namespace Orchard.Modules.Routing { + public interface IFeatureNameConstraint : IRouteConstraint, ISingletonDependency { + } + + public class FeatureNameConstraint : IFeatureNameConstraint { + private readonly IModuleService _moduleService; + + public FeatureNameConstraint(IModuleService moduleService) { + _moduleService = moduleService; + } + + public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { + if (routeDirection == RouteDirection.UrlGeneration) + return true; + + object value; + if (values.TryGetValue(parameterName, out value)) + return _moduleService.GetModuleByFeatureName(Convert.ToString(value)) != null; + + return false; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs index 528827c44..e75b70d45 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs @@ -4,19 +4,23 @@ using System.Linq; using System.Web; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; +using Orchard.Environment.Topology; +using Orchard.Environment.Topology.Models; using Orchard.Modules.Models; namespace Orchard.Modules.Services { public class ModuleService : IModuleService { private const string ModuleExtensionType = "module"; private readonly IExtensionManager _extensionManager; + private readonly IShellDescriptorManager _shellDescriptorManager; - public ModuleService(IExtensionManager extensionManager) { + public ModuleService(IExtensionManager extensionManager, IShellDescriptorManager shellDescriptorManager) { _extensionManager = extensionManager; + _shellDescriptorManager = shellDescriptorManager; } public IModule GetModuleByName(string moduleName) { - return _extensionManager.AvailableExtensions().Where(e => string.Equals(e.Name, moduleName, StringComparison.OrdinalIgnoreCase) && string.Equals(e.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)).Select( + return _extensionManager.AvailableExtensions().Where(e => string.Equals(e.Name, moduleName, StringComparison.OrdinalIgnoreCase) && string.Equals(e.ExtensionType, ModuleExtensionType, StringComparison.OrdinalIgnoreCase)).Select( descriptor => AssembleModuleFromDescriptor(descriptor)).FirstOrDefault(); } @@ -35,24 +39,41 @@ namespace Orchard.Modules.Services { _extensionManager.UninstallExtension(ModuleExtensionType, moduleName); } - public IEnumerable GetAvailableFeatures() { + public IModule GetModuleByFeatureName(string featureName) { return GetInstalledModules() - .Where(m => m.Features != null) - .SelectMany(m => _extensionManager.LoadFeatures(m.Features)); + .Where( + m => + m.Features.FirstOrDefault(f => string.Equals(f.Name, featureName, StringComparison.OrdinalIgnoreCase)) != + null).FirstOrDefault(); + } + + public IEnumerable GetAvailableFeatures() { + var enabledFeatures = _shellDescriptorManager.GetShellDescriptor().EnabledFeatures; + return GetInstalledModules() + .SelectMany(m => _extensionManager.LoadFeatures(m.Features)) + .Select(f => AssembleModuleFromDescriptor(f, enabledFeatures.FirstOrDefault(sf => string.Equals(sf.Name, f.Descriptor.Name, StringComparison.OrdinalIgnoreCase)) != null)); } public IEnumerable GetAvailableFeaturesByModule(string moduleName) { - var module = GetModuleByName(moduleName); - if (module == null || module.Features == null) - return null; - - return _extensionManager.LoadFeatures(module.Features); + throw new NotImplementedException(); } public void EnableFeatures(IEnumerable featureNames) { + var shellDescriptor = _shellDescriptorManager.GetShellDescriptor(); + + var enabledFeatures = shellDescriptor.EnabledFeatures + .Union(featureNames.Select(s => new ShellFeature {Name = s})); + + _shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures, shellDescriptor.Parameters); } public void DisableFeatures(IEnumerable featureNames) { + var shellDescriptor = _shellDescriptorManager.GetShellDescriptor(); + + var enabledFeatures = shellDescriptor.EnabledFeatures.ToList(); + enabledFeatures.RemoveAll(f => featureNames.Contains(f.Name)); + + _shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures, shellDescriptor.Parameters); } private static IModule AssembleModuleFromDescriptor(ExtensionDescriptor extensionDescriptor) { @@ -67,5 +88,12 @@ namespace Orchard.Modules.Services { Features = extensionDescriptor.Features }; } + + private static IModuleFeature AssembleModuleFromDescriptor(Feature feature, bool isEnabled) { + return new ModuleFeature { + Descriptor = feature.Descriptor, + IsEnabled = isEnabled + }; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeatureListViewModel.cs b/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeatureListViewModel.cs deleted file mode 100644 index 31815667e..000000000 --- a/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeatureListViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; -using Orchard.Environment.Extensions.Models; -using Orchard.Mvc.ViewModels; - -namespace Orchard.Modules.ViewModels { - public class FeatureListViewModel : BaseViewModel { - public IEnumerable Features { get; set; } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeaturesViewModel.cs b/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeaturesViewModel.cs new file mode 100644 index 000000000..210ce308c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/ViewModels/FeaturesViewModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Modules.ViewModels { + public class FeaturesViewModel : BaseViewModel { + public IEnumerable Features { get; set; } + public FeaturesOptions Options { get; set; } + } + + public class FeaturesOptions { + public FeaturesBulkAction BulkAction { get; set; } + } + + public enum FeaturesBulkAction { + None, + Enable, + Disable + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx index 4482efca5..9fb6ee803 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx @@ -1,33 +1,75 @@ -<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> <%@ Import Namespace="Orchard.Mvc.Html"%> <%@ Import Namespace="Orchard.Modules.ViewModels"%>

<%=Html.TitleForPage(T("Manage Features").ToString()) %>

-

<%=T("Available Features") %>

-<% if (Model.Features.Count() > 0) { %> -
    <% - foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Name).GroupBy(f => f.Descriptor.Category)) { %> -
  • -

    <%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("General")) %>

    -
      <% - foreach (var feature in featureGroup.Select(f => f.Descriptor)) {%> +
      <%=Html.ActionLink(T("∞").ToString(), "Features", new { }, new { @class = "button primaryAction" })%>
      +<% if (Model.Features.Count() > 0) { + +using (Html.BeginFormAntiForgeryPost()) { %> + <%=Html.ValidationSummary()%> +
      + + + " /> +
      +
      +
        <% + foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Name).GroupBy(f => f.Descriptor.Category)) { %>
      • -

        <%=Html.Encode(feature.Name) %>

        -

        <%=T("From: {0}", Html.Encode(feature.Extension.DisplayName)) %>

        <% - if (!string.IsNullOrEmpty(feature.Description)) { %> -

        <%=Html.Encode(feature.Description) %>

        <% - } - if (feature.Dependencies.Count() > 0) {%> -
        <%=_Encoded("Depends on:")%>
        +

        <%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("General")) %>

          <% - foreach (var dependency in feature.Dependencies) { %> -
        • <%=Html.Encode(dependency) %>
        • <% + foreach (var feature in featureGroup) {%> +
        • +
          +
          + +

          <%=Html.Encode(feature.Descriptor.Name) %>

          +
            +
          • <% + //enabled or not + if (feature.IsEnabled) { %> + " alt="<%=_Encoded("Enabled") %>" title="<%=_Encoded("This feature is currently enabled") %>" /><%=_Encoded("Enabled") %><% + } + else { %> + " alt="<%=_Encoded("Disabled") %>" title="<%=_Encoded("This feature is currently disabled") %>" /><%=_Encoded("Disabled")%><% + } %> +
          • <% + //dependencies + if (feature.Descriptor.Dependencies.Count() > 0) { %> +
          •  |  + <%=_Encoded("Depends on: ") %><% + foreach (var dependency in feature.Descriptor.Dependencies) { + %><% if (dependency != feature.Descriptor.Dependencies.First()) { %><%=_Encoded(", ") %><% } + %><%=Html.Encode(dependency) %><% + } %> +
          • <% + } + //dependencies == nothing <- temporary just to get some stuff in the UI + else { %> +
          •  |  + <%=T("Depends on: nothing") %> +
          • <% + } %> +
          +
          + +
          +
        • <% } %> -
        <% - }%> +
      <% } %> -
    -
  • <% - } %> -
<% - } %> \ No newline at end of file + <% + } %> + <% +} %> \ No newline at end of file diff --git a/src/Orchard/Modules/IModuleFeature.cs b/src/Orchard/Modules/IModuleFeature.cs new file mode 100644 index 000000000..cc59efa9b --- /dev/null +++ b/src/Orchard/Modules/IModuleFeature.cs @@ -0,0 +1,8 @@ +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Modules { + public interface IModuleFeature { + FeatureDescriptor Descriptor { get; set; } + bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard/Modules/IModuleService.cs b/src/Orchard/Modules/IModuleService.cs index a7c67b3d2..d2792315e 100644 --- a/src/Orchard/Modules/IModuleService.cs +++ b/src/Orchard/Modules/IModuleService.cs @@ -8,7 +8,8 @@ namespace Orchard.Modules { IEnumerable GetInstalledModules(); void InstallModule(HttpPostedFileBase file); void UninstallModule(string moduleName); - IEnumerable GetAvailableFeatures(); + IModule GetModuleByFeatureName(string featureName); + IEnumerable GetAvailableFeatures(); void EnableFeatures(IEnumerable featureNames); void DisableFeatures(IEnumerable featureNames); } diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 44589f27c..f8b0a0de0 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -191,6 +191,7 @@ + From cadfd78c55a26e1c16402906c4fd5ab744516835 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 28 Apr 2010 12:44:16 -0700 Subject: [PATCH 3/8] Orchard host recreates shell context based on significant events At the moment the events related to shell settings and shell descriptor changes will cause the host to rebuild their compositions. --HG-- branch : dev --- .hgignore | 1 + .../Bindings/OrchardSiteFactory.cs | 13 ----------- .../DefaultTenantManagerTests.cs | 10 ++++---- .../Topology/ShellDescriptorManager.cs | 2 +- .../Controllers/SetupController.cs | 3 --- .../Configuration/ShellSettingsManager.cs | 7 +++++- src/Orchard/Environment/DefaultOrchardHost.cs | 10 +++++++- .../DefaultOrchardHostEventSink.cs | 23 +++++++++++++++++++ src/Orchard/Environment/IOrchardHost.cs | 3 +-- src/Orchard/Events/DefaultOrchardEventBus.cs | 9 ++++---- src/Orchard/Orchard.Framework.csproj | 1 + 11 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 src/Orchard/Environment/DefaultOrchardHostEventSink.cs diff --git a/.hgignore b/.hgignore index 704e42375..0e4e5c05c 100644 --- a/.hgignore +++ b/.hgignore @@ -12,3 +12,4 @@ glob:src/Orchard.Web/Modules/Orchard.DevTools/Module.txt glob:src/Orchard.Web/Modules/Orchard.Sandbox/Module.txt glob:src/Orchard.Web/Media/* glob:desktop.ini +glob:src/Orchard.Azure.suo diff --git a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs index 605692f79..0558fb854 100644 --- a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs +++ b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs @@ -47,23 +47,11 @@ namespace Orchard.Specs.Bindings { descriptor.EnabledFeatures.Concat(new[] { new ShellFeature { Name = name } }), descriptor.Parameters); } - - Trace.WriteLine("This call to Host.Reinitialize should not be needed, eventually"); - MvcApplication.Host.Reinitialize_Obsolete(); }); } - [When(@"I cycle the app domain")] - public void WhenICycleTheAppDomain() { - var webApp = Binding(); - webApp.Host.Execute(() => { - Trace.WriteLine("This call to Host.Reinitialize should not be needed, eventually"); - MvcApplication.Host.Reinitialize_Obsolete(); - }); - } - [Given(@"I have tenant ""(.*)\"" on ""(.*)\"" as ""(.*)\""")] public void GivenIHaveTenantOnSiteAsName(string shellName, string hostName, string siteName) { var webApp = Binding(); @@ -76,7 +64,6 @@ namespace Orchard.Specs.Bindings { using (var environment = MvcApplication.CreateStandaloneEnvironment("Default")) { environment.Resolve().SaveSettings(shellSettings); } - MvcApplication.Host.Reinitialize_Obsolete(); }); webApp.WhenIGoToPathOnHost("Setup", hostName); diff --git a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs index 6bae8039a..d47d66904 100644 --- a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs +++ b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs @@ -1,7 +1,9 @@ using System.IO; using System.Linq; +using Moq; using NUnit.Framework; using Orchard.Environment.Configuration; +using Orchard.Events; namespace Orchard.Tests.Environment.Configuration { [TestFixture] @@ -26,7 +28,7 @@ namespace Orchard.Tests.Environment.Configuration { _appData.CreateFile("Sites\\Default\\Settings.txt", "Name: Default\r\nDataProvider: SQLite\r\nDataConnectionString: something else"); - IShellSettingsManager loader = new ShellSettingsManager(_appData); + IShellSettingsManager loader = new ShellSettingsManager(_appData, new Mock().Object); var settings = loader.LoadSettings().Single(); Assert.That(settings, Is.Not.Null); Assert.That(settings.Name, Is.EqualTo("Default")); @@ -41,7 +43,7 @@ namespace Orchard.Tests.Environment.Configuration { _appData.CreateFile("Sites\\Default\\Settings.txt", "Name: Default\r\nDataProvider: SQLite\r\nDataConnectionString: something else"); _appData.CreateFile("Sites\\Another\\Settings.txt", "Name: Another\r\nDataProvider: SQLite2\r\nDataConnectionString: something else2"); - IShellSettingsManager loader = new ShellSettingsManager(_appData); + IShellSettingsManager loader = new ShellSettingsManager(_appData, new Mock().Object); var settings = loader.LoadSettings(); Assert.That(settings.Count(), Is.EqualTo(2)); @@ -59,8 +61,8 @@ namespace Orchard.Tests.Environment.Configuration { [Test] public void NewSettingsCanBeStored() { _appData.CreateFile("Sites\\Default\\Settings.txt", "Name: Default\r\nDataProvider: SQLite\r\nDataConnectionString: something else"); - - IShellSettingsManager loader = new ShellSettingsManager(_appData); + + IShellSettingsManager loader = new ShellSettingsManager(_appData, new Mock().Object); var foo = new ShellSettings {Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux"}; Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); diff --git a/src/Orchard.Web/Core/Settings/Topology/ShellDescriptorManager.cs b/src/Orchard.Web/Core/Settings/Topology/ShellDescriptorManager.cs index bd38f87e4..98bcddd67 100644 --- a/src/Orchard.Web/Core/Settings/Topology/ShellDescriptorManager.cs +++ b/src/Orchard.Web/Core/Settings/Topology/ShellDescriptorManager.cs @@ -86,7 +86,7 @@ namespace Orchard.Core.Settings.Topology { } _eventBus.Notify( - typeof(IShellDescriptorManager).FullName + ".UpdateShellDescriptor", + "ShellDescriptor_Changed", null); } } diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs b/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs index 2ded69bb3..eb8e1abe4 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs @@ -173,9 +173,6 @@ namespace Orchard.Setup.Controllers { _shellSettingsManager.SaveSettings(shellSettings); - // MultiTenancy: This will not be needed when host listens to event bus - _orchardHost.Reinitialize_Obsolete(); - // redirect to the welcome page. return Redirect("~/"); } diff --git a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs index d1de2a657..0267bc955 100644 --- a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs +++ b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Yaml.Serialization; +using Orchard.Events; using Orchard.Localization; namespace Orchard.Environment.Configuration { @@ -13,10 +14,12 @@ namespace Orchard.Environment.Configuration { public class ShellSettingsManager : IShellSettingsManager { private readonly IAppDataFolder _appDataFolder; + private readonly IEventBus _eventBus; Localizer T { get; set; } - public ShellSettingsManager(IAppDataFolder appDataFolder) { + public ShellSettingsManager(IAppDataFolder appDataFolder, IEventBus eventBus) { _appDataFolder = appDataFolder; + _eventBus = eventBus; T = NullLocalizer.Instance; } @@ -32,6 +35,8 @@ namespace Orchard.Environment.Configuration { var filePath = Path.Combine(Path.Combine("Sites", settings.Name), "Settings.txt"); _appDataFolder.CreateFile(filePath, ComposeSettings(settings)); + + _eventBus.Notify("ShellSettings_Saved", null); } IEnumerable LoadSettings() { diff --git a/src/Orchard/Environment/DefaultOrchardHost.cs b/src/Orchard/Environment/DefaultOrchardHost.cs index 3f6e182d3..35a1f394b 100644 --- a/src/Orchard/Environment/DefaultOrchardHost.cs +++ b/src/Orchard/Environment/DefaultOrchardHost.cs @@ -1,7 +1,7 @@ +using System; using System.Linq; using System.Web.Mvc; using Autofac; -using Autofac.Integration.Web; using System.Collections.Generic; using Orchard.Environment.Configuration; using Orchard.Environment.Extensions; @@ -131,5 +131,13 @@ namespace Orchard.Environment { } } + public void Process(string messageName, IDictionary eventData) { + if (messageName == "ShellSettings_Saved") { + _current = null; + } + else if (messageName == "ShellDescriptor_Changed") { + _current = null; + } + } } } diff --git a/src/Orchard/Environment/DefaultOrchardHostEventSink.cs b/src/Orchard/Environment/DefaultOrchardHostEventSink.cs new file mode 100644 index 000000000..9d0542e21 --- /dev/null +++ b/src/Orchard/Environment/DefaultOrchardHostEventSink.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Orchard.Events; + +namespace Orchard.Environment { + /// + /// This handler forwards calls to the IOrchardHost when it is an instance of DefaultOrchardHost. + /// The reason for this is to avoid adding IEventBusHandler, because DefaultOrchardHost is a component + /// that should not be detected and registererd automatically as an IDependency. + /// + public class DefaultOrchardHostEventSink : IEventBusHandler { + private readonly DefaultOrchardHost _host; + + public DefaultOrchardHostEventSink(IOrchardHost host) { + _host = host as DefaultOrchardHost; + } + + public void Process(string messageName, IDictionary eventData) { + if (_host != null) { + _host.Process(messageName, eventData); + } + } + } +} diff --git a/src/Orchard/Environment/IOrchardHost.cs b/src/Orchard/Environment/IOrchardHost.cs index 314560677..754ca84a0 100644 --- a/src/Orchard/Environment/IOrchardHost.cs +++ b/src/Orchard/Environment/IOrchardHost.cs @@ -27,6 +27,5 @@ namespace Orchard.Environment { /// Services may be resolved from within this instance to configure and initialize it's storage. /// IStandaloneEnvironment CreateStandaloneEnvironment(ShellSettings shellSettings); - } - + } } diff --git a/src/Orchard/Events/DefaultOrchardEventBus.cs b/src/Orchard/Events/DefaultOrchardEventBus.cs index d1ae5652d..820b1fa55 100644 --- a/src/Orchard/Events/DefaultOrchardEventBus.cs +++ b/src/Orchard/Events/DefaultOrchardEventBus.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Orchard.Logging; namespace Orchard.Events { public class DefaultOrchardEventBus : IEventBus { - private readonly IEnumerable _handlers; + private readonly Func> _handlers; - public DefaultOrchardEventBus(IEnumerable handlers) { + public DefaultOrchardEventBus(Func> handlers) { _handlers = handlers; Logger = NullLogger.Instance; } @@ -15,7 +16,7 @@ namespace Orchard.Events { #region Implementation of IEventBus public void Notify(string messageName, IDictionary eventData) { - _handlers.Invoke(handler => handler.Process(messageName, eventData), Logger); + _handlers().Invoke(handler => handler.Process(messageName, eventData), Logger); } #endregion diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 7be14bbab..00b4497e4 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -163,6 +163,7 @@ + From f892a18588e542aa650d481db135f53cb218a215 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 28 Apr 2010 12:46:29 -0700 Subject: [PATCH 4/8] Removed one other place non-test code was calling Reinitialize_Obsolete --HG-- branch : dev --- .../Orchard.MultiTenancy/Services/TenantService.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs index f4fd6ecbd..d838927e1 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs @@ -12,20 +12,12 @@ namespace Orchard.MultiTenancy.Services { _orchardHost = orchardHost; } - #region Implementation of ITenantService - public IEnumerable GetTenants() { return _shellSettingsManager.LoadSettings(); } public void CreateTenant(ShellSettings settings) { _shellSettingsManager.SaveSettings(settings); - - - // MultiTenancy: This will not be needed when host listens to event bus - _orchardHost.Reinitialize_Obsolete(); } - - #endregion } } \ No newline at end of file From ee29c5214120b4a86338c3b39aa531cc037fce8f Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 28 Apr 2010 12:53:44 -0700 Subject: [PATCH 5/8] Updating name of event in unit test --HG-- branch : dev --- .../Settings/Topology/ShellDescriptorManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Tests.Modules/Settings/Topology/ShellDescriptorManagerTests.cs b/src/Orchard.Tests.Modules/Settings/Topology/ShellDescriptorManagerTests.cs index ae058f009..17175465e 100644 --- a/src/Orchard.Tests.Modules/Settings/Topology/ShellDescriptorManagerTests.cs +++ b/src/Orchard.Tests.Modules/Settings/Topology/ShellDescriptorManagerTests.cs @@ -137,7 +137,7 @@ namespace Orchard.Tests.Modules.Settings.Topology { Enumerable.Empty(), Enumerable.Empty()); - Assert.That(eventBus.LastMessageName, Is.EqualTo(typeof(IShellDescriptorManager).FullName + ".UpdateShellDescriptor")); + Assert.That(eventBus.LastMessageName, Is.EqualTo("ShellDescriptor_Changed")); } } } From eea9bfb2f0540b621fe4e916e0225d0c11c7d1b2 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Wed, 28 Apr 2010 14:33:08 -0700 Subject: [PATCH 6/8] - Adding OrchardSwitches attribute to the tenant add command handler. - Removing UrlPrefix from the tenant creation admin UI (will be readded once implemented). --HG-- branch : dev --- .../Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs | 1 + .../Modules/Orchard.MultiTenancy/Views/Admin/Add.ascx | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs index c5eed01f3..5cb662507 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs @@ -39,6 +39,7 @@ namespace Orchard.MultiTenancy.Commands { [CommandHelp("tenant add /Host: /UrlPrefix:" + ": create new tenant named on the site")] [CommandName("tenant add")] + [OrchardSwitches("Host,UrlPrefix")] public void Create(string tenantName) { Context.Output.WriteLine(T("Creating tenant")); _tenantService.CreateTenant( diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Add.ascx b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Add.ascx index 048ce3936..eb6aca4dc 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Add.ascx +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Add.ascx @@ -11,8 +11,6 @@

- -
" /> From 34587c62a4c43832f3e3a5380d37502c5cd48f1e Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 28 Apr 2010 14:34:01 -0700 Subject: [PATCH 7/8] More work on the module/feature UI* - reshuffled the related admin menu - cleaned up a little of each module and feature list page * still all based on the page list semantics --HG-- branch : dev --- .../Modules/Orchard.Blogs/Models/Blog.cs | 2 ++ src/Orchard.Web/Modules/Orchard.Blogs/Module.txt | 7 ++++++- .../Modules/Orchard.Modules/AdminMenu.cs | 10 +++++----- .../Orchard.Modules/Views/Admin/Features.ascx | 15 ++++----------- .../Orchard.Modules/Views/Admin/Index.ascx | 5 ++++- src/Orchard.Web/Themes/TheAdmin/Styles/site.css | 4 ++++ 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs index 75362ec5f..ec1d606ac 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs @@ -1,8 +1,10 @@ using System.Web.Mvc; using Orchard.Core.Common.Models; using Orchard.ContentManagement; +using Orchard.Environment.Extensions; namespace Orchard.Blogs.Models { + [OrchardFeature("Blog")] public class Blog : ContentPart { [HiddenInput(DisplayValue = false)] public int Id { get { return ContentItem.Id; } } diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt index d82dcda7f..938f6a297 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt @@ -1,2 +1,7 @@ name: Blogs -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +features: + Blog: + Description: A simple web log + Dependencies: Common, XmlRpc + Category: Blog \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs index d12009d21..fc8439236 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs @@ -5,12 +5,12 @@ namespace Orchard.Modules { public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Modules", "10", + builder.Add("Features", "10", menu => menu - .Add("Manage Modules", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Modules" }) - .Permission(Permissions.ManageModules)) - .Add("Manage Features", "2.0", item => item.Action("Features", "Admin", new { area = "Orchard.Modules" }) - .Permission(Permissions.ManageFeatures))); + .Add("Manage Features", "1.0", item => item.Action("Features", "Admin", new { area = "Orchard.Modules" }) + .Permission(Permissions.ManageFeatures)) + .Add("Manage Modules", "2.0", item => item.Action("Index", "Admin", new { area = "Orchard.Modules" }) + .Permission(Permissions.ManageModules))); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx index 9fb6ee803..b604c24c6 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx @@ -2,7 +2,7 @@ <%@ Import Namespace="Orchard.Mvc.Html"%> <%@ Import Namespace="Orchard.Modules.ViewModels"%>

<%=Html.TitleForPage(T("Manage Features").ToString()) %>

-
<%=Html.ActionLink(T("∞").ToString(), "Features", new { }, new { @class = "button primaryAction" })%>
+ <% if (Model.Features.Count() > 0) { using (Html.BeginFormAntiForgeryPost()) { %> @@ -19,11 +19,11 @@ using (Html.BeginFormAntiForgeryPost()) { %>
    <% foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Name).GroupBy(f => f.Descriptor.Category)) { %> -
  • + >

    <%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("General")) %>

      <% foreach (var feature in featureGroup) {%> -
    • + >
      @@ -40,19 +40,12 @@ using (Html.BeginFormAntiForgeryPost()) { %>
    • <% //dependencies if (feature.Descriptor.Dependencies.Count() > 0) { %> -
    •  |  - <%=_Encoded("Depends on: ") %><% +
    •  | <%=_Encoded("Depends on: ") %><% foreach (var dependency in feature.Descriptor.Dependencies) { %><% if (dependency != feature.Descriptor.Dependencies.First()) { %><%=_Encoded(", ") %><% } %><%=Html.Encode(dependency) %><% } %>
    • <% - } - //dependencies == nothing <- temporary just to get some stuff in the UI - else { %> -
    •  |  - <%=T("Depends on: nothing") %> -
    • <% } %>
    diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx index ad5a45bb2..ca0588562 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx @@ -2,8 +2,10 @@ <%@ Import Namespace="Orchard.Mvc.Html"%> <%@ Import Namespace="Orchard.Modules.ViewModels"%>

    <%=Html.TitleForPage(T("Manage Modules").ToString()) %>

    +
    <%=Html.ActionLink(T("Install a module").ToString(), "Features", new { }, new { @class = "button primaryAction" })%>

    <%=T("Installed Modules") %>

    <% if (Model.Modules.Count() > 0) { %> +
      <% foreach (var module in Model.Modules.OrderBy(m => m.DisplayName)) { %>
    • @@ -21,5 +23,6 @@ } %>
    • <% } %> -
    <% +
+
<% } %> \ No newline at end of file diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css index cf3ecef3f..768ef4a9f 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css +++ b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css @@ -607,6 +607,9 @@ button.ibutton { .contentItems li.first { background:#fff url(images/backgroundGradient.gif) repeat-x top left; } +.contentItems li li.last { + border-bottom:0; +} #main .contentItems li h3 { border-bottom:0; margin-top:0; @@ -753,6 +756,7 @@ table.items, textarea, input.text, input.text-box, margin-top:1em; } .summary { + overflow:auto; padding:1.2em .4em; } .actions { From 5964e6ef554d0995d32b0707a234748a3a628ecc Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 28 Apr 2010 14:55:17 -0700 Subject: [PATCH 8/8] Minor update to the Orchard.Blogs Module.txt to get its feature detailed correctly --HG-- branch : dev --- src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs | 2 -- src/Orchard.Web/Modules/Orchard.Blogs/Module.txt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs index ec1d606ac..75362ec5f 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs @@ -1,10 +1,8 @@ using System.Web.Mvc; using Orchard.Core.Common.Models; using Orchard.ContentManagement; -using Orchard.Environment.Extensions; namespace Orchard.Blogs.Models { - [OrchardFeature("Blog")] public class Blog : ContentPart { [HiddenInput(DisplayValue = false)] public int Id { get { return ContentItem.Id; } } diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt index 938f6a297..67d16c464 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt @@ -1,7 +1,7 @@ name: Blogs antiforgery: enabled features: - Blog: + Orchard.Blogs: Description: A simple web log Dependencies: Common, XmlRpc Category: Blog \ No newline at end of file