diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs index d3597c7f5..6bf7988ee 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs @@ -5,6 +5,7 @@ using System.Web; using System.Web.Mvc; using Orchard.Data.Migration; using Orchard.DisplayManagement; +using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; using Orchard.Environment.Features; using Orchard.Localization; @@ -18,11 +19,11 @@ using Orchard.UI.Notify; namespace Orchard.Themes.Controllers { [ValidateInput(false)] public class AdminController : Controller { - private readonly IThemeManager _themeManager; - private readonly IFeatureManager _featureManager; private readonly ISiteThemeService _siteThemeService; private readonly IPreviewTheme _previewTheme; private readonly IExtensionManager _extensionManager; + private readonly ShellDescriptor _shellDescriptor; + private readonly IThemeService _themeService; private readonly IDataMigrationManager _dataMigrationManager; private readonly IReportsCoordinator _reportsCoordinator; @@ -36,27 +37,38 @@ namespace Orchard.Themes.Controllers { IPreviewTheme previewTheme, IAuthorizer authorizer, INotifier notifier, - IExtensionManager extensionManager) { + IExtensionManager extensionManager, + ShellDescriptor shellDescriptor, + IThemeService themeService) { Services = services; _dataMigrationManager = dataMigraitonManager; _reportsCoordinator = reportsCoordinator; - _themeManager = themeManager; - _featureManager = featureManager; _siteThemeService = siteThemeService; _previewTheme = previewTheme; _extensionManager = extensionManager; + _shellDescriptor = shellDescriptor; + _themeService = themeService; T = NullLocalizer.Instance; } - public IOrchardServices Services{ get; set; } + public IOrchardServices Services { get; set; } public Localizer T { get; set; } public ActionResult Index() { try { - var themes = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Theme"); var currentTheme = _siteThemeService.GetSiteTheme(); var featuresThatNeedUpdate = _dataMigrationManager.GetFeaturesThatNeedUpdate(); - var model = new ThemesIndexViewModel { CurrentTheme = currentTheme, Themes = themes, FeaturesThatNeedUpdate = featuresThatNeedUpdate }; + + var themes = _extensionManager.AvailableExtensions() + .Where(d => d.ExtensionType == "Theme") + .Select(d => new ThemeEntry { + Descriptor = d, + NeedsUpdate = featuresThatNeedUpdate.Contains(d.Name), + Enabled = _shellDescriptor.Features.Any(sf => sf.Name == d.Name) + }) + .ToArray(); + + var model = new ThemesIndexViewModel { CurrentTheme = currentTheme, Themes = themes }; return View(model); } catch (Exception exception) { @@ -112,8 +124,7 @@ namespace Orchard.Themes.Controllers { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't enable the theme"))) return new HttpUnauthorizedResult(); - // feature id always == extension id, in this case - _featureManager.EnableFeature(themeName); + _themeService.EnableThemeFeatures(themeName); } catch (Exception exception) { Services.Notifier.Error(T("Enabling theme failed: " + exception.Message)); @@ -127,8 +138,7 @@ namespace Orchard.Themes.Controllers { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't disable the current theme"))) return new HttpUnauthorizedResult(); - // feature id always == extension id, in this case - _featureManager.DisableFeature(themeName); + _themeService.DisableThemeFeatures(themeName); } catch (Exception exception) { Services.Notifier.Error(T("Disabling theme failed: " + exception.Message)); @@ -141,6 +151,8 @@ namespace Orchard.Themes.Controllers { try { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't set the current theme"))) return new HttpUnauthorizedResult(); + + _themeService.EnableThemeFeatures(themeName); _siteThemeService.SetSiteTheme(themeName); } catch (Exception exception) { diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj b/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj index 72e4baa20..6f349ce98 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj +++ b/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj @@ -86,7 +86,7 @@ - + diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs new file mode 100644 index 000000000..1441c7058 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Routing; +using JetBrains.Annotations; +using Orchard.Environment.Descriptor; +using Orchard.Environment.Descriptor.Models; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; +using Orchard.Environment.Features; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.ContentManagement; +using Orchard.Themes.Models; + +namespace Orchard.Themes.Services { + public interface IThemeService : IDependency { + void DisableThemeFeatures(string themeName); + void EnableThemeFeatures(string themeName); + } + + [UsedImplicitly] + public class ThemeService : IThemeService { + private readonly IExtensionManager _extensionManager; + private readonly IFeatureManager _featureManager; + private readonly IEnumerable _themeSelectors; + + private readonly IWorkContextAccessor _workContextAccessor; + private readonly ShellDescriptor _shellDescriptor; + private readonly IOrchardServices _orchardServices; + private readonly IShellDescriptorManager _shellDescriptorManager; + + public ThemeService( + IShellDescriptorManager shellDescriptorManager, + IExtensionManager extensionManager, + IFeatureManager featureManager, + IEnumerable themeSelectors, + + IWorkContextAccessor workContextAccessor, + ShellDescriptor shellDescriptor, + IOrchardServices orchardServices) { + _shellDescriptorManager = shellDescriptorManager; + _extensionManager = extensionManager; + _featureManager = featureManager; + _themeSelectors = themeSelectors; + + _workContextAccessor = workContextAccessor; + _shellDescriptor = shellDescriptor; + _orchardServices = orchardServices; + Logger = NullLogger.Instance; + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + + + private bool AllBaseThemesAreInstalled(string baseThemeName) { + var themesSeen = new List(); + while (!string.IsNullOrWhiteSpace(baseThemeName)) { + //todo: (heskew) need a better way to protect from recursive references + if (themesSeen.Contains(baseThemeName)) + throw new InvalidOperationException(T("The theme \"{0}\" was already seen - looks like we're going around in circles.", baseThemeName).Text); + themesSeen.Add(baseThemeName); + + var baseTheme = _extensionManager.GetExtension(baseThemeName); + if (baseTheme == null) + return false; + baseThemeName = baseTheme.BaseTheme; + } + + return true; + } + + public void DisableThemeFeatures(string themeName) { + var themes = new Queue(); + 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 = _extensionManager.GetExtension(themeName); + if (theme == null) + break; + themes.Enqueue(themeName); + + themeName = !string.IsNullOrWhiteSpace(theme.BaseTheme) + ? theme.BaseTheme + : null; + } + + while (themes.Count > 0) + _featureManager.DisableFeatures(new[] { themes.Dequeue() }); + } + + public void EnableThemeFeatures(string themeName) { + var themes = new Stack(); + while(themeName != null) { + if (themes.Contains(themeName)) + throw new InvalidOperationException(T("The theme \"{0}\" is already in the stack of themes that need features enabled.", themeName).Text); + themes.Push(themeName); + + var theme = _extensionManager.GetExtension(themeName); + themeName = !string.IsNullOrWhiteSpace(theme.BaseTheme) + ? theme.BaseTheme + : null; + } + + while (themes.Count > 0) + _featureManager.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 = _extensionManager.GetExtension(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.Name); + return true; + } + + public ExtensionDescriptor GetRequestTheme(RequestContext requestContext) { + var requestTheme = _themeSelectors + .Select(x => x.GetTheme(requestContext)) + .Where(x => x != null) + .OrderByDescending(x => x.Priority); + + if (requestTheme.Count() < 1) + return null; + + foreach (var theme in requestTheme) { + var t = _extensionManager.GetExtension(theme.ThemeName); + if (t != null) + return t; + } + + return _extensionManager.GetExtension("SafeMode"); + } + + /// + /// Loads only installed themes + /// + public IEnumerable GetInstalledThemes() { + return GetThemes(_extensionManager.AvailableExtensions()); + } + + private IEnumerable GetThemes(IEnumerable extensions) { + var themes = new List(); + foreach (var descriptor in extensions) { + + if (!string.Equals(descriptor.ExtensionType, "Theme", StringComparison.OrdinalIgnoreCase)) { + continue; + } + + ExtensionDescriptor theme = descriptor; + + if (!theme.Tags.Contains("hidden")) { + themes.Add(theme); + } + } + return themes; + } + + private static string TryLocalize(string key, string original, Localizer localizer) { + var localized = localizer(key).Text; + + if ( key == localized ) { + // no specific localization available + return original; + } + + return localized; + } + + private bool IsThemeEnabled(ExtensionDescriptor descriptor) { + return (descriptor.Name == "TheAdmin" || descriptor.Name == "SafeMode") || + _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")); + + // return new Theme { + // //Author = TryLocalize("Author", descriptor.Author, localizer) ?? "", + // //Description = TryLocalize("Description", descriptor.Description, localizer) ?? "", + // DisplayName = TryLocalize("Name", descriptor.DisplayName, localizer) ?? "", + // //HomePage = TryLocalize("Website", descriptor.WebSite, localizer) ?? "", + // ThemeName = descriptor.Name, + // //Version = descriptor.Version ?? "", + // 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 6c872c896..10aba3a0f 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/ViewModels/ThemesIndexViewModel.cs @@ -2,9 +2,20 @@ using Orchard.Environment.Extensions.Models; namespace Orchard.Themes.ViewModels { - public class ThemesIndexViewModel { + public class ThemesIndexViewModel { public ExtensionDescriptor CurrentTheme { get; set; } - public IEnumerable Themes { get; set; } - public IEnumerable FeaturesThatNeedUpdate { get; set; } + public IEnumerable Themes { get; set; } + } + + public class ThemeEntry { + public ExtensionDescriptor Descriptor { get; set; } + public bool Enabled { get; set; } + public bool NeedsUpdate { get; set; } + + public string ThemeName { get { return Descriptor.Name; } } + public string DisplayName { get { return Descriptor.DisplayName; } } + public string ThemePath(string path) { + return Descriptor.Location + "/" + Descriptor.Name + path; + } } } \ 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 ce05c4e67..241155080 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.cshtml @@ -15,7 +15,7 @@

@T("Version:") @Model.CurrentTheme.Version
@Model.CurrentTheme.Description
- @Model.CurrentTheme.HomePage + @Model.CurrentTheme.WebSite

@Html.ActionLink(T("Install a new Theme").ToString(), "Install", null, new { @class = "button primaryAction" }) @@ -24,11 +24,11 @@

@T("Available Themes")

    @foreach (var theme in Model.Themes) { - if (Model.CurrentTheme == null || theme.ThemeName != Model.CurrentTheme.ThemeName) { + if (Model.CurrentTheme == null || theme.ThemeName != Model.CurrentTheme.Name) {
  • @theme.DisplayName

    - @Html.Image(Href(Html.ThemePath(theme, "/Theme.png")), Html.Encode(theme.DisplayName), null) + @Html.Image(Href(theme.ThemePath("/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) @@ -41,13 +41,13 @@ @Html.Hidden("themeName", theme.ThemeName) } -
    @T("By") @theme.Author
    +
    @T("By") @theme.Descriptor.Author

    - @T("Version:") @theme.Version
    - @theme.Description
    - @theme.HomePage + @T("Version:") @theme.Descriptor.Version
    + @theme.Descriptor.Description
    + @theme.Descriptor.WebSite

    - @if(Model.FeaturesThatNeedUpdate.Contains(theme.ThemeName)){ + @if(theme.NeedsUpdate){ using (Html.BeginFormAntiForgeryPost(Url.Action("Update"), FormMethod.Post, new { @class = "inline link" })) { @Html.Hidden("themeName", theme.ThemeName)
    diff --git a/src/Orchard.Web/Themes/Primus/Theme.png b/src/Orchard.Web/Themes/Primus/Theme.png new file mode 100644 index 000000000..e1371de56 Binary files /dev/null and b/src/Orchard.Web/Themes/Primus/Theme.png differ diff --git a/src/Orchard.Web/Themes/Primus/Theme.txt b/src/Orchard.Web/Themes/Primus/Theme.txt new file mode 100644 index 000000000..ec969317d --- /dev/null +++ b/src/Orchard.Web/Themes/Primus/Theme.txt @@ -0,0 +1,6 @@ +Name: Primus +Author: Lou +Description: desc +Version: 0.1 +Website: http://whereslou.com +Zones: Main, Sidebar diff --git a/src/Orchard.Web/Themes/Secundus/Theme.png b/src/Orchard.Web/Themes/Secundus/Theme.png new file mode 100644 index 000000000..e1371de56 Binary files /dev/null and b/src/Orchard.Web/Themes/Secundus/Theme.png differ diff --git a/src/Orchard.Web/Themes/Secundus/Theme.txt b/src/Orchard.Web/Themes/Secundus/Theme.txt new file mode 100644 index 000000000..5e4860dda --- /dev/null +++ b/src/Orchard.Web/Themes/Secundus/Theme.txt @@ -0,0 +1,6 @@ +Name: Secundus +Author: Lou +Description: desc +Version: 0.1 +Website: http://whereslou.com +Zones: Main, Sidebar diff --git a/src/Orchard.Web/Themes/Themes.csproj b/src/Orchard.Web/Themes/Themes.csproj index 7b6afc1bf..6e4a12edf 100644 --- a/src/Orchard.Web/Themes/Themes.csproj +++ b/src/Orchard.Web/Themes/Themes.csproj @@ -49,6 +49,10 @@ + + + + @@ -93,7 +97,6 @@ - - + diff --git a/src/Orchard/Environment/Features/FeatureManager.cs b/src/Orchard/Environment/Features/FeatureManager.cs index d574b2481..b4f347f71 100644 --- a/src/Orchard/Environment/Features/FeatureManager.cs +++ b/src/Orchard/Environment/Features/FeatureManager.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using Orchard.Environment.Descriptor; +using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; @@ -8,16 +11,22 @@ namespace Orchard.Environment.Features { IEnumerable GetAvailableFeatures(); IEnumerable GetEnabledFeatures(); - void EnableFeature(string name); - void DisableFeature(string name); + void EnableFeatures(IEnumerable featureNames); + void DisableFeatures(IEnumerable featureNames); } - public class FeatureManager : IFeatureManager { private readonly IExtensionManager _extensionManager; + private readonly ShellDescriptor _shellDescriptor; + private readonly IShellDescriptorManager _shellDescriptorManager; - public FeatureManager(IExtensionManager extensionManager) { + public FeatureManager( + IExtensionManager extensionManager, + ShellDescriptor shellDescriptor, + IShellDescriptorManager shellDescriptorManager) { _extensionManager = extensionManager; + _shellDescriptor = shellDescriptor; + _shellDescriptorManager = shellDescriptorManager; } public IEnumerable GetAvailableFeatures() { @@ -28,12 +37,30 @@ namespace Orchard.Environment.Features { throw new NotImplementedException(); } - public void EnableFeature(string name) { - throw new NotImplementedException(); + public void EnableFeatures(IEnumerable featureNames) { + var currentShellDescriptor = _shellDescriptorManager.GetShellDescriptor(); + + var updatedFeatures = currentShellDescriptor.Features + .Union(featureNames + .Where(name => !currentShellDescriptor.Features.Any(sf => sf.Name == name)) + .Select(name => new ShellFeature {Name = name})); + + _shellDescriptorManager.UpdateShellDescriptor( + currentShellDescriptor.SerialNumber, + updatedFeatures, + currentShellDescriptor.Parameters); } - public void DisableFeature(string name) { - throw new NotImplementedException(); + public void DisableFeatures(IEnumerable featureNames) { + var currentShellDescriptor = _shellDescriptorManager.GetShellDescriptor(); + + var updatedFeatures = currentShellDescriptor.Features + .Where(sf => !featureNames.Contains(sf.Name)); + + _shellDescriptorManager.UpdateShellDescriptor( + currentShellDescriptor.SerialNumber, + updatedFeatures, + currentShellDescriptor.Parameters); } diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 6db017c77..294fc8e21 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -186,6 +186,7 @@ + diff --git a/src/Orchard/Themes/IThemeManager.cs b/src/Orchard/Themes/IThemeManager.cs index 1fc41ed14..40f37ccf4 100644 --- a/src/Orchard/Themes/IThemeManager.cs +++ b/src/Orchard/Themes/IThemeManager.cs @@ -1,42 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Web.Routing; -using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; namespace Orchard.Themes { public interface IThemeManager : IDependency { - [Obsolete] ExtensionDescriptor GetRequestTheme(RequestContext requestContext); } - - public class ThemeManager : IThemeManager { - private readonly IEnumerable _themeSelectors; - private readonly IExtensionManager _extensionManager; - - public ThemeManager(IEnumerable themeSelectors, - IExtensionManager extensionManager) { - _themeSelectors = themeSelectors; - _extensionManager = extensionManager; - } - - public ExtensionDescriptor GetRequestTheme(RequestContext requestContext) { - var requestTheme = _themeSelectors - .Select(x => x.GetTheme(requestContext)) - .Where(x => x != null) - .OrderByDescending(x => x.Priority); - - if (requestTheme.Count() < 1) - return null; - - foreach (var theme in requestTheme) { - var t = _extensionManager.GetExtension(theme.ThemeName); - if (t != null) - return t; - } - - return _extensionManager.GetExtension("SafeMode"); - } - } } diff --git a/src/Orchard/Themes/ThemeManager.cs b/src/Orchard/Themes/ThemeManager.cs new file mode 100644 index 000000000..21dd709a8 --- /dev/null +++ b/src/Orchard/Themes/ThemeManager.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Routing; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Themes { + public class ThemeManager : IThemeManager { + private readonly IEnumerable _themeSelectors; + private readonly IExtensionManager _extensionManager; + + public ThemeManager(IEnumerable themeSelectors, + IExtensionManager extensionManager) { + _themeSelectors = themeSelectors; + _extensionManager = extensionManager; + } + + public ExtensionDescriptor GetRequestTheme(RequestContext requestContext) { + var requestTheme = _themeSelectors + .Select(x => x.GetTheme(requestContext)) + .Where(x => x != null) + .OrderByDescending(x => x.Priority); + + if (requestTheme.Count() < 1) + return null; + + foreach (var theme in requestTheme) { + var t = _extensionManager.GetExtension(theme.ThemeName); + if (t != null) + return t; + } + + return _extensionManager.GetExtension("SafeMode"); + } + } +} \ No newline at end of file