From 30cb7dc75d2ed62e4148b07deebdd171744a38cc Mon Sep 17 00:00:00 2001 From: Dave Reed Date: Tue, 12 Oct 2010 16:52:36 -0700 Subject: [PATCH] Themes admin allows you to enable/disable themes separately from setting them to the default. DataMigrations for themes are done there too. --HG-- branch : dev --- .../Themes/Services/ThemeServiceTests.cs | 32 ++++++++- .../DefaultDisplayManagerTests.cs | 1 + .../Modules/Orchard.Setup/SetupMode.cs | 9 +++ .../Controllers/AdminController.cs | 67 +++++++++++++++++-- .../Modules/Orchard.Themes/Models/Theme.cs | 1 + .../Orchard.Themes/Services/ThemeService.cs | 62 +++++++++++------ .../ViewModels/ThemesIndexViewModel.cs | 1 + .../Orchard.Themes/Views/Admin/Index.cshtml | 12 +++- src/Orchard/Themes/ITheme.cs | 1 + src/Orchard/Themes/IThemeService.cs | 3 + 10 files changed, 159 insertions(+), 30 deletions(-) diff --git a/src/Orchard.Tests.Modules/Themes/Services/ThemeServiceTests.cs b/src/Orchard.Tests.Modules/Themes/Services/ThemeServiceTests.cs index b7fe2f28b..ce7905744 100644 --- a/src/Orchard.Tests.Modules/Themes/Services/ThemeServiceTests.cs +++ b/src/Orchard.Tests.Modules/Themes/Services/ThemeServiceTests.cs @@ -68,7 +68,7 @@ namespace Orchard.Tests.Modules.Themes.Services { var context = new DynamicProxyContext(); var builder = new ContainerBuilder(); builder.RegisterModule(new SettingsModule()); - builder.RegisterType().As(); // test + builder.RegisterType().As(); builder.RegisterType().EnableDynamicProxy(context).As(); builder.RegisterType().As(); builder.RegisterType().As(); @@ -86,7 +86,7 @@ namespace Orchard.Tests.Modules.Themes.Services { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); @@ -136,6 +136,34 @@ namespace Orchard.Tests.Modules.Themes.Services { Assert.That(siteTheme.ThemeName, Is.EqualTo("ThemeOne")); } + [Test] + public void CanEnableAndDisableThemes() { + _themeService.EnableTheme("ThemeOne"); + Assert.IsTrue(_themeService.GetThemeByName("ThemeOne").Enabled); + Assert.IsTrue(_container.Resolve().GetShellDescriptor().Features.Any(sf => sf.Name == "ThemeOne")); + _themeService.DisableTheme("ThemeOne"); + Assert.IsFalse(_themeService.GetThemeByName("ThemeOne").Enabled); + Assert.IsFalse(_container.Resolve().GetShellDescriptor().Features.Any(sf => sf.Name == "ThemeOne")); + } + + [Test] + public void ActivatingThemeEnablesIt() { + _themeService.SetSiteTheme("ThemeOne"); + Assert.IsTrue(_themeService.GetThemeByName("ThemeOne").Enabled); + Assert.IsTrue(_container.Resolve().GetShellDescriptor().Features.Any(sf => sf.Name == "ThemeOne")); + } + + [Test] + public void ActivatingThemeDoesNotDisableOldTheme() { + _themeService.SetSiteTheme("ThemeOne"); + _themeService.SetSiteTheme("ThemeTwo"); + Assert.IsTrue(_themeService.GetThemeByName("ThemeOne").Enabled); + Assert.IsTrue(_themeService.GetThemeByName("ThemeTwo").Enabled); + Assert.IsTrue(_container.Resolve().GetShellDescriptor().Features.Any(sf => sf.Name == "ThemeOne")); + Assert.IsTrue(_container.Resolve().GetShellDescriptor().Features.Any(sf => sf.Name == "ThemeTwo")); + } + + #region Stubs public class TestSessionLocator : ISessionLocator { diff --git a/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs b/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs index b91b10e08..5b68f19e2 100644 --- a/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs +++ b/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs @@ -46,6 +46,7 @@ namespace Orchard.Tests.DisplayManagement { } public class Theme : ITheme { + public bool Enabled { get; set; } public string ThemeName { get; set; } public string DisplayName { get; set; } public string Description { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 41b2be61b..ccca1cdc3 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Web; using System.Web.Routing; using Autofac; +using JetBrains.Annotations; using Orchard.Commands; using Orchard.Commands.Builtin; using Orchard.ContentManagement; @@ -91,6 +92,7 @@ namespace Orchard.Setup { } + [UsedImplicitly] class SafeModeText : IText { public LocalizedString Get(string textHint, params object[] args) { if (args == null || args.Length == 0) { @@ -100,9 +102,11 @@ namespace Orchard.Setup { } } + [UsedImplicitly] class SafeModeThemeService : IThemeService { class SafeModeTheme : ITheme { public ContentItem ContentItem { get; set; } + public bool Enabled { get; set; } public string ThemeName { get; set; } public string DisplayName { get; set; } public string Description { get; set; } @@ -115,6 +119,7 @@ namespace Orchard.Setup { } private readonly SafeModeTheme _theme = new SafeModeTheme { + Enabled = true, ThemeName = "SafeMode", DisplayName = "SafeMode", }; @@ -126,8 +131,11 @@ namespace Orchard.Setup { public IEnumerable GetInstalledThemes() { return new[] { _theme }; } public void InstallTheme(HttpPostedFileBase file) { } public void UninstallTheme(string themeName) { } + public void EnableTheme(string themeName) { } + public void DisableTheme(string themeName) { } } + [UsedImplicitly] class SafeModeSiteWorkContextProvider : IWorkContextStateProvider { public T Get(string name) { if (name == "CurrentSite") @@ -136,6 +144,7 @@ namespace Orchard.Setup { } } + [UsedImplicitly] class SafeModeSiteService : ISiteService { public ISite GetSiteSettings() { var siteType = new ContentTypeDefinitionBuilder().Named("Site").Build(); diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs index 77febbd52..f098fbb9e 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs @@ -2,8 +2,11 @@ using System.Reflection; using System.Web; using System.Web.Mvc; +using Orchard.Data.Migration; using Orchard.DisplayManagement; using Orchard.Localization; +using Orchard.Mvc.Results; +using Orchard.Reports.Services; using Orchard.Security; using Orchard.Themes.Preview; using Orchard.Themes.ViewModels; @@ -14,8 +17,12 @@ namespace Orchard.Themes.Controllers { public class AdminController : Controller { private readonly IThemeService _themeService; private readonly IPreviewTheme _previewTheme; + private readonly IDataMigrationManager _dataMigrationManager; + private readonly IReportsCoordinator _reportsCoordinator; public AdminController( + IDataMigrationManager dataMigraitonManager, + IReportsCoordinator reportsCoordinator, IOrchardServices services, IThemeService themeService, IPreviewTheme previewTheme, @@ -23,6 +30,8 @@ namespace Orchard.Themes.Controllers { INotifier notifier, IShapeHelperFactory shapeHelperFactory) { Services = services; + _dataMigrationManager = dataMigraitonManager; + _reportsCoordinator = reportsCoordinator; _themeService = themeService; _previewTheme = previewTheme; T = NullLocalizer.Instance; @@ -36,7 +45,8 @@ namespace Orchard.Themes.Controllers { try { var themes = _themeService.GetInstalledThemes(); var currentTheme = _themeService.GetSiteTheme(); - var model = new ThemesIndexViewModel { CurrentTheme = currentTheme, Themes = themes }; + var featuresThatNeedUpdate = _dataMigrationManager.GetFeaturesThatNeedUpdate(); + var model = new ThemesIndexViewModel { CurrentTheme = currentTheme, Themes = themes, FeaturesThatNeedUpdate = featuresThatNeedUpdate }; return View(model); } catch (Exception exception) { @@ -66,12 +76,11 @@ namespace Orchard.Themes.Controllers { return new HttpUnauthorizedResult(); _previewTheme.SetPreviewTheme(null); _themeService.SetSiteTheme(themeName); - return RedirectToAction("Index"); } catch (Exception exception) { Services.Notifier.Error(T("Previewing theme failed: " + exception.Message)); - return RedirectToAction("Index"); } + return RedirectToAction("Index"); } [HttpPost, ActionName("Preview"), FormValueRequired("submit.Cancel")] @@ -80,12 +89,37 @@ namespace Orchard.Themes.Controllers { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) return new HttpUnauthorizedResult(); _previewTheme.SetPreviewTheme(null); - return RedirectToAction("Index"); } catch (Exception exception) { Services.Notifier.Error(T("Previewing theme failed: " + exception.Message)); - return RedirectToAction("Index"); } + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult Enable(string themeName) { + try { + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't enable the theme"))) + return new HttpUnauthorizedResult(); + _themeService.EnableTheme(themeName); + } + catch (Exception exception) { + Services.Notifier.Error(T("Enabling theme failed: " + exception.Message)); + } + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult Disable(string themeName) { + try { + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't disable the current theme"))) + return new HttpUnauthorizedResult(); + _themeService.DisableTheme(themeName); + } + catch (Exception exception) { + Services.Notifier.Error(T("Disabling theme failed: " + exception.Message)); + } + return RedirectToAction("Index"); } [HttpPost] @@ -94,12 +128,11 @@ namespace Orchard.Themes.Controllers { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't set the current theme"))) return new HttpUnauthorizedResult(); _themeService.SetSiteTheme(themeName); - return RedirectToAction("Index"); } catch (Exception exception) { Services.Notifier.Error(T("Activating theme failed: " + exception.Message)); - return RedirectToAction("Index"); } + return RedirectToAction("Index"); } public ActionResult Install() { @@ -137,6 +170,26 @@ namespace Orchard.Themes.Controllers { } } + [HttpPost] + public ActionResult Update(string themeName) { + if (!Services.Authorizer.Authorize(Permissions.ManageThemes, T("Couldn't update theme"))) + return new HttpUnauthorizedResult(); + + if (string.IsNullOrEmpty(themeName)) + return new NotFoundResult(); + + try { + _reportsCoordinator.Register("Data Migration", "Upgrade " + themeName, "Orchard installation"); + _dataMigrationManager.Update(themeName); + Services.Notifier.Information(T("The theme {0} was updated succesfuly", themeName)); + } + catch (Exception ex) { + Services.Notifier.Error(T("An error occured while updating the theme {0}: {1}", themeName, ex.Message)); + } + + return RedirectToAction("Index"); + } + class FormValueRequiredAttribute : ActionMethodSelectorAttribute { private readonly string _submitButtonName; diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Models/Theme.cs b/src/Orchard.Web/Modules/Orchard.Themes/Models/Theme.cs index 70f24ec98..92877e672 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Models/Theme.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Models/Theme.cs @@ -1,5 +1,6 @@ namespace Orchard.Themes.Models { public class Theme : ITheme { + public bool Enabled { get; set; } public string ThemeName { get; set; } public string DisplayName { get; set; } public string Description { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs index 829fbf860..d1b8760e7 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Web; using System.Web.Routing; using JetBrains.Annotations; +using Orchard.Environment.Descriptor; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Localization; @@ -19,13 +20,16 @@ namespace Orchard.Themes.Services { private readonly IExtensionManager _extensionManager; private readonly IEnumerable _themeSelectors; private readonly IModuleService _moduleService; - private IWorkContextAccessor _workContextAccessor; + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IShellDescriptorManager _shellDescriptorManager; public ThemeService( + IShellDescriptorManager shellDescriptorManager, IExtensionManager extensionManager, IEnumerable themeSelectors, IModuleService moduleService, IWorkContextAccessor workContextAccessor) { + _shellDescriptorManager = shellDescriptorManager; _extensionManager = extensionManager; _themeSelectors = themeSelectors; _moduleService = moduleService; @@ -49,26 +53,17 @@ namespace Orchard.Themes.Services { } public void SetSiteTheme(string themeName) { - if (string.IsNullOrWhiteSpace(themeName)) - return; + if (DoEnableTheme(themeName)) { + CurrentSite.As().Record.CurrentThemeName = themeName; + } + } - //todo: (heskew) need messages given in addition to all of these early returns so something meaningful can be presented to the user - var themeToSet = GetThemeByName(themeName); - if (themeToSet == null) - return; + public void EnableTheme(string themeName) { + DoEnableTheme(themeName); + } - // ensure all base themes down the line are present and accounted for - //todo: (heskew) dito on the need of a meaningful message - if (!AllBaseThemesAreInstalled(themeToSet.BaseTheme)) - return; - - // disable all theme features - DisableThemeFeatures(CurrentSite.As().CurrentThemeName); - - // enable all theme features - EnableThemeFeatures(themeToSet.ThemeName); - - CurrentSite.As().Record.CurrentThemeName = themeToSet.ThemeName; + public void DisableTheme(string themeName) { + DisableThemeFeatures(themeName); } private bool AllBaseThemesAreInstalled(string baseThemeName) { @@ -93,12 +88,15 @@ namespace Orchard.Themes.Services { while (themeName != null) { if (themes.Contains(themeName)) throw new InvalidOperationException(T("The theme \"{0}\" is already in the stack of themes that need features disabled.", themeName).Text); + var theme = GetThemeByName(themeName); + if (theme == null) + break; themes.Enqueue(themeName); - var theme = GetThemeByName(themeName); themeName = !string.IsNullOrWhiteSpace(theme.BaseTheme) ? theme.BaseTheme : null; + } while (themes.Count > 0) @@ -122,6 +120,25 @@ namespace Orchard.Themes.Services { _moduleService.EnableFeatures(new[] {themes.Pop()}); } + private bool DoEnableTheme(string themeName) { + if (string.IsNullOrWhiteSpace(themeName)) + return false; + + //todo: (heskew) need messages given in addition to all of these early returns so something meaningful can be presented to the user + var themeToEnable = GetThemeByName(themeName); + if (themeToEnable == null) + return false; + + // ensure all base themes down the line are present and accounted for + //todo: (heskew) dito on the need of a meaningful message + if (!AllBaseThemesAreInstalled(themeToEnable.BaseTheme)) + return false; + + // enable all theme features + EnableThemeFeatures(themeToEnable.ThemeName); + return true; + } + public ITheme GetRequestTheme(RequestContext requestContext) { var requestTheme = _themeSelectors .Select(x => x.GetTheme(requestContext)) @@ -188,6 +205,10 @@ namespace Orchard.Themes.Services { return localized; } + private bool IsThemeEnabled(ExtensionDescriptor descriptor) { + return _shellDescriptorManager.GetShellDescriptor().Features.Any(sf => sf.Name == descriptor.Name); + } + private ITheme CreateTheme(ExtensionDescriptor descriptor) { var localizer = LocalizationUtilities.Resolve(_workContextAccessor.GetContext(), String.Concat(descriptor.Location, "/", descriptor.Name, "/Theme.txt")); @@ -202,6 +223,7 @@ namespace Orchard.Themes.Services { Tags = TryLocalize("Tags", descriptor.Tags, localizer) ?? "", Zones = descriptor.Zones ?? "", BaseTheme = descriptor.BaseTheme ?? "", + Enabled = IsThemeEnabled(descriptor) }; } } diff --git a/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs index 3927530ba..c31cc5a98 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs @@ -4,5 +4,6 @@ namespace Orchard.Themes.ViewModels { public class ThemesIndexViewModel { public ITheme CurrentTheme { get; set; } public IEnumerable Themes { get; set; } + public IEnumerable FeaturesThatNeedUpdate { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml index c4092d0f2..ce05c4e67 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml @@ -29,9 +29,13 @@

@theme.DisplayName

@Html.Image(Href(Html.ThemePath(theme, "/Theme.png")), Html.Encode(theme.DisplayName), null) + @using (Html.BeginFormAntiForgeryPost(Url.Action(theme.Enabled ? "Disable" : "Enable"), FormMethod.Post, new { @class = "inline" })) { + @Html.Hidden("themeName", theme.ThemeName) + + } @using (Html.BeginFormAntiForgeryPost(Url.Action("Activate"), FormMethod.Post, new { @class = "inline" })) { @Html.Hidden("themeName", theme.ThemeName) - + } @using (Html.BeginFormAntiForgeryPost(Url.Action("Preview"), FormMethod.Post, new { @class = "inline" })) { @Html.Hidden("themeName", theme.ThemeName) @@ -43,6 +47,12 @@ @theme.Description
@theme.HomePage

+ @if(Model.FeaturesThatNeedUpdate.Contains(theme.ThemeName)){ + using (Html.BeginFormAntiForgeryPost(Url.Action("Update"), FormMethod.Post, new { @class = "inline link" })) { + @Html.Hidden("themeName", theme.ThemeName) +
+ } + } @using (Html.BeginFormAntiForgeryPost(Url.Action("Uninstall"), FormMethod.Post, new { @class = "inline link" })) { @Html.Hidden("themeName", theme.ThemeName) diff --git a/src/Orchard/Themes/ITheme.cs b/src/Orchard/Themes/ITheme.cs index 178a651e6..e7e53ce86 100644 --- a/src/Orchard/Themes/ITheme.cs +++ b/src/Orchard/Themes/ITheme.cs @@ -3,6 +3,7 @@ /// Interface provided by the "themes" model. /// public interface ITheme { + bool Enabled { get; set; } string ThemeName { get; set; } string DisplayName { get; set; } string Description { get; set; } diff --git a/src/Orchard/Themes/IThemeService.cs b/src/Orchard/Themes/IThemeService.cs index d9968c56a..d492f64e0 100644 --- a/src/Orchard/Themes/IThemeService.cs +++ b/src/Orchard/Themes/IThemeService.cs @@ -10,6 +10,9 @@ namespace Orchard.Themes { void SetSiteTheme(string themeName); ITheme GetRequestTheme(RequestContext requestContext); + void EnableTheme(string themeName); + void DisableTheme(string themeName); + IEnumerable GetInstalledThemes(); void InstallTheme(HttpPostedFileBase file); void UninstallTheme(string themeName);