diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index bc1ebc906..c115db794 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -294,6 +294,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs index b5fcdeeb6..5beb1c196 100644 --- a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs +++ b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs @@ -13,7 +13,7 @@ namespace Orchard.Tests.Stubs { public StubWorkContextAccessor(ILifetimeScope lifetimeScope) { _lifetimeScope = lifetimeScope; - _workContext = new WorkContextImpl(_lifetimeScope); + _workContext = new WorkContextImpl(lifetimeScope); } public class WorkContextImpl : WorkContext { @@ -149,7 +149,9 @@ namespace Orchard.Tests.Stubs { } public IWorkContextScope CreateWorkContextScope() { - throw new NotSupportedException(); + var workLifetime = _lifetimeScope.BeginLifetimeScope("work"); + var workContext = new WorkContextImpl(workLifetime); + return new StubWorkContextScope(workContext, workLifetime); } } } diff --git a/src/Orchard.Tests/Stubs/StubWorkContextScope.cs b/src/Orchard.Tests/Stubs/StubWorkContextScope.cs new file mode 100644 index 000000000..f8fb42041 --- /dev/null +++ b/src/Orchard.Tests/Stubs/StubWorkContextScope.cs @@ -0,0 +1,26 @@ +using Autofac; + +namespace Orchard.Tests.Stubs { + public class StubWorkContextScope : IWorkContextScope { + private readonly ILifetimeScope _lifetimeScope; + + public StubWorkContextScope(WorkContext workContext, ILifetimeScope lifetimeScope) { + _lifetimeScope = lifetimeScope; + WorkContext = workContext; + } + + public WorkContext WorkContext { get; } + + public void Dispose() { + _lifetimeScope.Dispose(); + } + + public TService Resolve() { + return WorkContext.Resolve(); + } + + public bool TryResolve(out TService service) { + return WorkContext.TryResolve(out service); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Models/SetupContext.cs b/src/Orchard.Web/Modules/Orchard.ImportExport/Models/SetupContext.cs deleted file mode 100644 index 0727bd888..000000000 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Models/SetupContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Orchard.ImportExport.Models { - public class SetupContext { - public string SiteName { get; set; } - public string AdminUsername { get; set; } - public string AdminPassword { get; set; } - public string DatabaseProvider { get; set; } - public string DatabaseConnectionString { get; set; } - public string DatabaseTablePrefix { get; set; } - public IEnumerable EnabledFeatures { get; set; } - public XDocument RecipeDocument { get; set; } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Module.txt b/src/Orchard.Web/Modules/Orchard.ImportExport/Module.txt index 3b6960348..9c918c300 100644 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.ImportExport/Module.txt @@ -11,4 +11,4 @@ Features: Name: Import Export Description: Imports and exports content item data. Category: Content - Dependencies: Orchard.jQuery, Orchard.Recipes \ No newline at end of file + Dependencies: Orchard.jQuery, Orchard.Recipes, Orchard.Setup.Services \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Orchard.ImportExport.csproj b/src/Orchard.Web/Modules/Orchard.ImportExport/Orchard.ImportExport.csproj index 74c14c623..df4c6767f 100644 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Orchard.ImportExport.csproj +++ b/src/Orchard.Web/Modules/Orchard.ImportExport/Orchard.ImportExport.csproj @@ -90,9 +90,6 @@ - - - @@ -139,6 +136,10 @@ {fc1d74e8-7a4d-48f4-83de-95c6173780c4} Orchard.Recipes + + {8c7fcbc2-e6e1-405e-bfb5-d8d9e67a09c4} + Orchard.Setup + diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Providers/ImportActions/ExecuteRecipeAction.cs b/src/Orchard.Web/Modules/Orchard.ImportExport/Providers/ImportActions/ExecuteRecipeAction.cs index 9b52d8678..da7f43edf 100644 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Providers/ImportActions/ExecuteRecipeAction.cs +++ b/src/Orchard.Web/Modules/Orchard.ImportExport/Providers/ImportActions/ExecuteRecipeAction.cs @@ -12,6 +12,7 @@ using Orchard.ImportExport.ViewModels; using Orchard.Mvc; using Orchard.Recipes.Models; using Orchard.Recipes.Services; +using Orchard.Setup.Services; using Orchard.Tasks; using Orchard.UI.Notify; @@ -181,7 +182,7 @@ namespace Orchard.ImportExport.Providers.ImportActions { private string Setup(XDocument recipeDocument) { // Prepare Setup. var setupContext = new SetupContext { - RecipeDocument = recipeDocument, + Recipe = _recipeParser.ParseRecipe(recipeDocument), AdminPassword = SuperUserPassword, AdminUsername = _orchardServices.WorkContext.CurrentSite.SuperUser, DatabaseConnectionString = _shellSettings.DataConnectionString, diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Services/ISetupService.cs b/src/Orchard.Web/Modules/Orchard.ImportExport/Services/ISetupService.cs deleted file mode 100644 index 176bf128c..000000000 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Services/ISetupService.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Orchard.ImportExport.Models; - -namespace Orchard.ImportExport.Services { - public interface ISetupService : IDependency { - string Setup(SetupContext context); - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.ImportExport/Services/SetupService.cs deleted file mode 100644 index c125a21c9..000000000 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Services/SetupService.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Linq; -using System.Security.Cryptography; -using System.Web; -using Orchard.ContentManagement; -using Orchard.Core.Settings.Models; -using Orchard.Data; -using Orchard.Data.Migration; -using Orchard.Data.Migration.Interpreters; -using Orchard.Data.Migration.Schema; -using Orchard.Environment; -using Orchard.Environment.Configuration; -using Orchard.Environment.Descriptor; -using Orchard.Environment.Descriptor.Models; -using Orchard.Environment.ShellBuilders; -using Orchard.Environment.State; -using Orchard.ImportExport.Models; -using Orchard.Localization.Services; -using Orchard.Logging; -using Orchard.Recipes.Services; -using Orchard.Security; -using Orchard.Settings; -using Orchard.Utility.Extensions; - -namespace Orchard.ImportExport.Services -{ - public class SetupService : Component, ISetupService { - private readonly ShellSettings _shellSettings; - private readonly IOrchardHost _orchardHost; - private readonly IShellSettingsManager _shellSettingsManager; - private readonly IShellContainerFactory _shellContainerFactory; - private readonly ICompositionStrategy _compositionStrategy; - private readonly IProcessingEngine _processingEngine; - - public SetupService( - ShellSettings shellSettings, - IOrchardHost orchardHost, - IShellSettingsManager shellSettingsManager, - IShellContainerFactory shellContainerFactory, - ICompositionStrategy compositionStrategy, - IProcessingEngine processingEngine) { - - _shellSettings = shellSettings; - _orchardHost = orchardHost; - _shellSettingsManager = shellSettingsManager; - _shellContainerFactory = shellContainerFactory; - _compositionStrategy = compositionStrategy; - _processingEngine = processingEngine; - } - - public string Setup(SetupContext context) { - string executionId; - - Logger.Information("Running setup for tenant '{0}'.", _shellSettings.Name); - - // The vanilla Orchard distibution has the following features enabled. - string[] hardcoded = { - // Framework - "Orchard.Framework", - // Core - "Common", "Containers", "Contents", "Dashboard", "Feeds", "Navigation","Scheduling", "Settings", "Shapes", "Title", - // Modules - "Orchard.Pages", "Orchard.ContentPicker", "Orchard.Themes", "Orchard.Users", "Orchard.Roles", "Orchard.Modules", - "PackagingServices", "Orchard.Packaging", "Gallery", "Orchard.Recipes" - }; - - context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty()).Distinct().ToList(); - - var shellSettings = new ShellSettings(_shellSettings); - - if (String.IsNullOrEmpty(shellSettings.DataProvider)) { - shellSettings.DataProvider = context.DatabaseProvider; - shellSettings.DataConnectionString = context.DatabaseConnectionString; - shellSettings.DataTablePrefix = context.DatabaseTablePrefix; - } - - shellSettings.EncryptionAlgorithm = "AES"; - shellSettings.EncryptionKey = SymmetricAlgorithm.Create(shellSettings.EncryptionAlgorithm).Key.ToHexString(); - shellSettings.HashAlgorithm = "HMACSHA256"; - shellSettings.HashKey = HMAC.Create(shellSettings.HashAlgorithm).Key.ToHexString(); - - var shellDescriptor = new ShellDescriptor { - Features = context.EnabledFeatures.Select(name => new ShellFeature { Name = name }) - }; - - var shellBlueprint = _compositionStrategy.Compose(shellSettings, shellDescriptor); - - // Initialize database explicitly, and store shell descriptor. - using (var bootstrapLifetimeScope = _shellContainerFactory.CreateContainer(shellSettings, shellBlueprint)) { - using (var environment = bootstrapLifetimeScope.CreateWorkContextScope()) { - // Workaround to avoid a Transaction issue with PostgreSQL. - environment.Resolve().RequireNew(); - - var schemaBuilder = new SchemaBuilder(environment.Resolve()); - - schemaBuilder.CreateTable("Orchard_Framework_DataMigrationRecord", table => table - .Column("Id", column => column.PrimaryKey().Identity()) - .Column("DataMigrationClass") - .Column("Version")); - - schemaBuilder.AlterTable("Orchard_Framework_DataMigrationRecord", - table => table.AddUniqueConstraint("UC_DMR_DataMigrationClass_Version", "DataMigrationClass", "Version")); - - var dataMigrationManager = environment.Resolve(); - dataMigrationManager.Update("Settings"); - - foreach (var feature in context.EnabledFeatures) { - dataMigrationManager.Update(feature); - } - - var descriptorManager = environment.Resolve(); - descriptorManager.UpdateShellDescriptor(0, shellDescriptor.Features, shellDescriptor.Parameters); - } - } - - // In effect "pump messages" see PostMessage circa 1980. - while ( _processingEngine.AreTasksPending() ) - _processingEngine.ExecuteNextTask(); - - // Create a standalone environment. - // Must mark state as Running - otherwise standalone environment is created "for setup". - shellSettings.State = TenantState.Running; - using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings)) { - try { - executionId = CreateTenantData(context, environment); - } - catch { - environment.Resolve().Cancel(); - throw; - } - } - - _shellSettingsManager.SaveSettings(shellSettings); - - return executionId; - } - - private string CreateTenantData(SetupContext context, IWorkContextScope environment) { - // Create superuser. - var membershipService = environment.Resolve(); - var user = membershipService.CreateUser( - new CreateUserParams( - context.AdminUsername, - context.AdminPassword, - email: String.Empty, - passwordQuestion: String.Empty, - passwordAnswer: String.Empty, - isApproved: true)); - - // Set superuser as current user for request (it will be set as the owner of all content items). - var authenticationService = environment.Resolve(); - authenticationService.SetAuthenticatedUserForRequest(user); - - // Set site name and settings. - var siteService = environment.Resolve(); - var siteSettings = siteService.GetSiteSettings().As(); - siteSettings.SiteSalt = Guid.NewGuid().ToString("N"); - siteSettings.SiteName = context.SiteName; - siteSettings.SuperUser = context.AdminUsername; - siteSettings.SiteCulture = "en-US"; - - // Add default culture. - var cultureManager = environment.Resolve(); - cultureManager.AddCulture("en-US"); - - // Execute recipe - var recipeParser = environment.Resolve(); - var recipe = recipeParser.ParseRecipe(context.RecipeDocument); - var recipeManager = environment.Resolve(); - var executionId = recipeManager.Execute(recipe); - - // Null check: temporary fix for running setup in command line. - if (HttpContext.Current != null) { - authenticationService.SignIn(user, true); - } - - return executionId; - } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeHarvester.cs b/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeHarvester.cs index d177be9ef..d1ca05060 100644 --- a/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeHarvester.cs +++ b/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeHarvester.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.WebSite; using Orchard.Localization; using Orchard.Logging; @@ -29,27 +30,35 @@ namespace Orchard.Recipes.Services { public Localizer T { get; set; } public ILogger Logger { get; set; } - public IEnumerable HarvestRecipes(string extensionId) { - var recipes = new List(); + public IEnumerable HarvestRecipes() { + return _extensionManager.AvailableExtensions().SelectMany(HarvestRecipes); + } + public IEnumerable HarvestRecipes(string extensionId) { var extension = _extensionManager.GetExtension(extensionId); if (extension != null) { - var recipeLocation = Path.Combine(extension.Location, extensionId, "Recipes"); - var recipeFiles = _webSiteFolder.ListFiles(recipeLocation, true); - - recipeFiles.Where(r => r.EndsWith(".recipe.xml", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(r => { - try { - recipes.Add(_recipeParser.ParseRecipe(_webSiteFolder.ReadFile(r))); - } - catch (Exception ex) { - Logger.Error(ex, "Error while parsing recipe file '{0}'.", r); - } - }); - } - else { - Logger.Error("Could not discover recipes because module '{0}' was not found.", extensionId); + return HarvestRecipes(extension); } + Logger.Error("Could not discover recipes because module '{0}' was not found.", extensionId); + return Enumerable.Empty(); + } + + private IEnumerable HarvestRecipes(ExtensionDescriptor extension) { + var recipes = new List(); + + var recipeLocation = Path.Combine(extension.Location, extension.Id, "Recipes"); + var recipeFiles = _webSiteFolder.ListFiles(recipeLocation, true); + + recipeFiles.Where(r => r.EndsWith(".recipe.xml", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(r => { + try { + recipes.Add(_recipeParser.ParseRecipe(_webSiteFolder.ReadFile(r))); + } + catch (Exception ex) { + Logger.Error(ex, "Error while parsing recipe file '{0}'.", r); + } + }); + return recipes; } } diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Commands/SetupCommand.cs b/src/Orchard.Web/Modules/Orchard.Setup/Commands/SetupCommand.cs index e56c2cd61..306c626fe 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Commands/SetupCommand.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Commands/SetupCommand.cs @@ -2,14 +2,17 @@ using System.Collections.Generic; using System.Linq; using Orchard.Commands; +using Orchard.Recipes.Services; using Orchard.Setup.Services; namespace Orchard.Setup.Commands { public class SetupCommand : DefaultOrchardCommandHandler { private readonly ISetupService _setupService; + private readonly IRecipeHarvester _recipeHarvester; - public SetupCommand(ISetupService setupService) { + public SetupCommand(ISetupService setupService, IRecipeHarvester recipeHarvester) { _setupService = setupService; + _recipeHarvester = recipeHarvester; } [OrchardSwitch] @@ -51,6 +54,7 @@ namespace Orchard.Setup.Commands { .Where(s => !String.IsNullOrEmpty(s)); } Recipe = String.IsNullOrEmpty(Recipe) ? "Default" : Recipe; + var recipe = _setupService.Recipes().GetRecipeByName(Recipe); var setupContext = new SetupContext { SiteName = SiteName, @@ -60,7 +64,7 @@ namespace Orchard.Setup.Commands { DatabaseConnectionString = DatabaseConnectionString, DatabaseTablePrefix = DatabaseTablePrefix, EnabledFeatures = enabledFeatures, - Recipe = Recipe, + Recipe = recipe, }; var executionId = _setupService.Setup(setupContext); diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs b/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs index 401e7ee99..8b3698666 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Controllers/SetupController.cs @@ -7,6 +7,7 @@ using Orchard.Logging; using Orchard.Setup.Services; using Orchard.Setup.ViewModels; using Orchard.Localization; +using Orchard.Recipes.Services; using Orchard.Themes; using Orchard.UI.Notify; @@ -137,6 +138,7 @@ namespace Orchard.Setup.Controllers { throw new ApplicationException("Unknown database type: " + model.DatabaseProvider); } + var recipe = _setupService.Recipes().GetRecipeByName(model.Recipe); var setupContext = new SetupContext { SiteName = model.SiteName, AdminUsername = model.AdminUsername, @@ -145,7 +147,7 @@ namespace Orchard.Setup.Controllers { DatabaseConnectionString = model.DatabaseConnectionString, DatabaseTablePrefix = model.DatabaseTablePrefix, EnabledFeatures = null, // Default list - Recipe = model.Recipe + Recipe = recipe }; var executionId = _setupService.Setup(setupContext); diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Module.txt b/src/Orchard.Web/Modules/Orchard.Setup/Module.txt index 9895478e3..18f17cbfc 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Setup/Module.txt @@ -6,3 +6,9 @@ OrchardVersion: 1.9 Description: The setup module is creating the application's setup experience. FeatureDescription: Standard site setup. This feature is disabled automatically once setup is over. Category: Core +Dependencies: Orchard.Setup.Services +Features: + Orchard.Setup.Services + Name: Setup Services + Description: Provides programmatic APIs to the setup service. + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupContext.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupContext.cs index 06c32c914..0c87402bc 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupContext.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Orchard.Recipes.Models; namespace Orchard.Setup.Services { public class SetupContext { @@ -9,6 +10,6 @@ namespace Orchard.Setup.Services { public string DatabaseConnectionString { get; set; } public string DatabaseTablePrefix { get; set; } public IEnumerable EnabledFeatures { get; set; } - public string Recipe { get; set; } + public Recipe Recipe { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index e70987b09..48a0f419f 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -26,6 +26,7 @@ using Orchard.Settings; using Orchard.Utility.Extensions; namespace Orchard.Setup.Services { + [OrchardFeature("Orchard.Setup.Services")] public class SetupService : Component, ISetupService { private readonly ShellSettings _shellSettings; private readonly IOrchardHost _orchardHost; @@ -33,7 +34,6 @@ namespace Orchard.Setup.Services { private readonly IShellContainerFactory _shellContainerFactory; private readonly ICompositionStrategy _compositionStrategy; private readonly IProcessingEngine _processingEngine; - private readonly IExtensionManager _extensionManager; private readonly IRecipeHarvester _recipeHarvester; private IEnumerable _recipes; @@ -44,7 +44,6 @@ namespace Orchard.Setup.Services { IShellContainerFactory shellContainerFactory, ICompositionStrategy compositionStrategy, IProcessingEngine processingEngine, - IExtensionManager extensionManager, IRecipeHarvester recipeHarvester) { _shellSettings = shellSettings; @@ -53,7 +52,6 @@ namespace Orchard.Setup.Services { _shellContainerFactory = shellContainerFactory; _compositionStrategy = compositionStrategy; _processingEngine = processingEngine; - _extensionManager = extensionManager; _recipeHarvester = recipeHarvester; } @@ -64,14 +62,9 @@ namespace Orchard.Setup.Services { public IEnumerable Recipes() { if (_recipes == null) { var recipes = new List(); - - foreach (var extension in _extensionManager.AvailableExtensions()) { - recipes.AddRange(_recipeHarvester.HarvestRecipes(extension.Id).Where(recipe => recipe.IsSetupRecipe)); - } - + recipes.AddRange(_recipeHarvester.HarvestRecipes().Where(recipe => recipe.IsSetupRecipe)); _recipes = recipes; } - return _recipes; } @@ -212,11 +205,7 @@ namespace Orchard.Setup.Services { cultureManager.AddCulture("en-US"); var recipeManager = environment.Resolve(); - var recipe = Recipes().FirstOrDefault(r => r.Name.Equals(context.Recipe, StringComparison.OrdinalIgnoreCase)); - - if (recipe == null) - throw new OrchardException(T("The recipe '{0}' could not be found.", context.Recipe)); - + var recipe = context.Recipe; var executionId = recipeManager.Execute(recipe); // Once the recipe has finished executing, we need to update the shell state to "Running", so add a recipe step that does exactly that. diff --git a/src/Orchard/Recipes/Services/IRecipeHarvester.cs b/src/Orchard/Recipes/Services/IRecipeHarvester.cs index c566bed59..3cea30e98 100644 --- a/src/Orchard/Recipes/Services/IRecipeHarvester.cs +++ b/src/Orchard/Recipes/Services/IRecipeHarvester.cs @@ -1,8 +1,24 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Orchard.Recipes.Models; namespace Orchard.Recipes.Services { public interface IRecipeHarvester : IDependency { + /// + /// Returns a collection of all recipes. + /// + IEnumerable HarvestRecipes(); + + /// + /// Returns a collection of all recipes found in the specified extension. + /// IEnumerable HarvestRecipes(string extensionId); } + + public static class RecipeHarvesterExtensions { + public static Recipe GetRecipeByName(this IEnumerable recipes, string recipeName) { + return recipes.FirstOrDefault(r => r.Name.Equals(recipeName, StringComparison.OrdinalIgnoreCase)); + } + } }