mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Merging ResetSiteAction with UploadRecipeAction and implementing site setup during import.
This commit is contained in:
@@ -12,8 +12,3 @@ Features:
|
||||
Description: Imports and exports content item data.
|
||||
Category: Content
|
||||
Dependencies: Orchard.jQuery, Orchard.Recipes
|
||||
Orchard.ImportExport.ResetSite
|
||||
Name: Reset Site Action
|
||||
Description: Adds a Reset Site action to the Import screen.
|
||||
Category: Hosting
|
||||
Dependencies: Orchard.ImportExport, Orchard.MultiTenancy
|
||||
@@ -48,7 +48,15 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\lib\autofac\Autofac.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="NHibernate, Version=4.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
@@ -72,8 +80,10 @@
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Providers\ExportActions\BuildRecipeAction.cs" />
|
||||
<Compile Include="Providers\ImportActions\ResetSiteAction.cs" />
|
||||
<Compile Include="ViewModels\ResetSiteViewModel.cs" />
|
||||
<Compile Include="Services\ISetupService.cs" />
|
||||
<Compile Include="Services\SetupContext.cs" />
|
||||
<Compile Include="Services\SetupService.cs" />
|
||||
<Compile Include="ViewModels\UploadRecipeViewModel.cs" />
|
||||
<Compile Include="Providers\ImportActions\UploadRecipeAction.cs" />
|
||||
<Compile Include="Recipes\Builders\CustomStepsStep.cs" />
|
||||
<Compile Include="Services\ImportAction.cs" />
|
||||
@@ -149,9 +159,6 @@
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\ExportActions\BuildRecipe.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\ImportActions\ResetSite.cshtml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Web.Mvc;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.ImportExport.Services;
|
||||
using Orchard.ImportExport.ViewModels;
|
||||
using Orchard.MultiTenancy.Services;
|
||||
|
||||
namespace Orchard.ImportExport.Providers.ImportActions {
|
||||
[OrchardFeature("Orchard.ImportExport.ResetSite")]
|
||||
public class ResetSiteAction : ImportAction {
|
||||
private readonly ITenantService _tenantService;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
|
||||
public ResetSiteAction(ITenantService tenantService, ShellSettings shellSettings) {
|
||||
_tenantService = tenantService;
|
||||
_shellSettings = shellSettings;
|
||||
}
|
||||
|
||||
public override string Name { get { return "ResetSite"; } }
|
||||
|
||||
public override int Priority {
|
||||
get { return 500; }
|
||||
}
|
||||
|
||||
public override dynamic BuildEditor(dynamic shapeFactory) {
|
||||
return UpdateEditor(shapeFactory, null);
|
||||
}
|
||||
|
||||
public bool ResetSite { get; set; }
|
||||
public bool DropTables { get; set; }
|
||||
|
||||
public override dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater) {
|
||||
var viewModel = new ResetSiteViewModel();
|
||||
if (updater != null) {
|
||||
ResetSite = viewModel.ResetSite;
|
||||
DropTables = viewModel.DropTables;
|
||||
}
|
||||
|
||||
return shapeFactory.EditorTemplate(TemplateName: "ImportActions/ResetSite", Model: viewModel, Prefix: Prefix);
|
||||
}
|
||||
|
||||
public override void Execute(ImportActionContext context) {
|
||||
_shellSettings.State = TenantState.Disabled;
|
||||
_tenantService.ResetTenant(_shellSettings, DropTables);
|
||||
_shellSettings.DataProvider = null;
|
||||
_tenantService.UpdateTenant(_shellSettings);
|
||||
context.ActionResult = new RedirectResult("~/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,93 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Features;
|
||||
using Orchard.ImportExport.Services;
|
||||
using Orchard.ImportExport.ViewModels;
|
||||
|
||||
namespace Orchard.ImportExport.Providers.ImportActions {
|
||||
public class UploadRecipeAction : ImportAction {
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
private readonly IImportExportService _importExportService;
|
||||
private readonly ISetupService _setupService;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
private readonly IFeatureManager _featureManager;
|
||||
|
||||
public UploadRecipeAction(
|
||||
IOrchardServices orchardServices,
|
||||
IImportExportService importExportService,
|
||||
ISetupService setupService,
|
||||
ShellSettings shellSettings,
|
||||
IFeatureManager featureManager) {
|
||||
|
||||
public UploadRecipeAction(IOrchardServices orchardServices, IImportExportService importExportService) {
|
||||
_orchardServices = orchardServices;
|
||||
_importExportService = importExportService;
|
||||
_setupService = setupService;
|
||||
_shellSettings = shellSettings;
|
||||
_featureManager = featureManager;
|
||||
}
|
||||
|
||||
public override string Name { get { return "UploadRecipe"; } }
|
||||
|
||||
public HttpPostedFileBase File { get; set; }
|
||||
public bool ResetSite { get; set; }
|
||||
|
||||
public override dynamic BuildEditor(dynamic shapeFactory) {
|
||||
return UpdateEditor(shapeFactory, null);
|
||||
}
|
||||
|
||||
public override dynamic UpdateEditor(dynamic shapeFactory, IUpdateModel updater) {
|
||||
var viewModel = new UploadRecipeViewModel();
|
||||
|
||||
if (updater != null) {
|
||||
var request = _orchardServices.WorkContext.HttpContext.Request;
|
||||
File = request.Files["RecipeFile"];
|
||||
|
||||
if (updater.TryUpdateModel(viewModel, Prefix, null, null)) {
|
||||
var request = _orchardServices.WorkContext.HttpContext.Request;
|
||||
|
||||
File = request.Files["RecipeFile"];
|
||||
ResetSite = viewModel.ResetSite;
|
||||
|
||||
if (File == null)
|
||||
updater.AddModelError("RecipeFile", T("No recipe file selected."));
|
||||
}
|
||||
}
|
||||
|
||||
return shapeFactory.EditorTemplate(TemplateName: "ImportActions/UploadRecipe", Prefix: Prefix);
|
||||
return shapeFactory.EditorTemplate(TemplateName: "ImportActions/UploadRecipe", Model: viewModel, Prefix: Prefix);
|
||||
}
|
||||
|
||||
public override void Execute(ImportActionContext context) {
|
||||
if (File == null || File.ContentLength == 0)
|
||||
return;
|
||||
|
||||
var executionId = _importExportService.Import(new StreamReader(File.InputStream).ReadToEnd());
|
||||
var executionId = ResetSite ? Setup() : ExecuteRecipe();
|
||||
context.ActionResult = new RedirectToRouteResult(new RouteValueDictionary(new { action = "ImportResult", controller = "Admin", area = "Orchard.ImportExport", executionId = executionId }));
|
||||
}
|
||||
|
||||
private string Setup() {
|
||||
var setupContext = new SetupContext {
|
||||
DropExistingTables = true,
|
||||
RecipeText = ReadRecipeFile(),
|
||||
AdminPassword = "password",
|
||||
AdminUsername = _orchardServices.WorkContext.CurrentSite.SuperUser,
|
||||
DatabaseConnectionString = _shellSettings.DataConnectionString,
|
||||
DatabaseProvider = _shellSettings.DataProvider,
|
||||
DatabaseTablePrefix = _shellSettings.DataTablePrefix,
|
||||
SiteName = _orchardServices.WorkContext.CurrentSite.SiteName,
|
||||
EnabledFeatures = _featureManager.GetEnabledFeatures().Select(x => x.Id).ToArray()
|
||||
};
|
||||
return _setupService.Setup(setupContext);
|
||||
}
|
||||
|
||||
private string ExecuteRecipe() {
|
||||
return _importExportService.Import(ReadRecipeFile());
|
||||
}
|
||||
|
||||
private string ReadRecipeFile() {
|
||||
return new StreamReader(File.InputStream).ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Orchard.ImportExport.Services {
|
||||
public interface ISetupService : IDependency {
|
||||
string Setup(SetupContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.ImportExport.Services {
|
||||
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<string> EnabledFeatures { get; set; }
|
||||
public string RecipeText { get; set; }
|
||||
public bool DropExistingTables { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
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.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<string>()).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()) {
|
||||
|
||||
// Check if the database is already created (in case an exception occured in the second phase).
|
||||
var schemaBuilder = new SchemaBuilder(environment.Resolve<IDataMigrationInterpreter>());
|
||||
var installationPresent = true;
|
||||
try {
|
||||
var tablePrefix = String.IsNullOrEmpty(shellSettings.DataTablePrefix) ? "" : shellSettings.DataTablePrefix + "_";
|
||||
schemaBuilder.ExecuteSql("SELECT * FROM " + tablePrefix + "Settings_ShellDescriptorRecord");
|
||||
}
|
||||
catch {
|
||||
installationPresent = false;
|
||||
}
|
||||
|
||||
if (installationPresent) {
|
||||
if (context.DropExistingTables) {
|
||||
DropTenantDatabaseTables(environment);
|
||||
}
|
||||
}
|
||||
|
||||
// Make a workaround to avoid the Transaction issue for PostgreSQL.
|
||||
environment.Resolve<ITransactionManager>().RequireNew();
|
||||
|
||||
schemaBuilder.CreateTable("Orchard_Framework_DataMigrationRecord", table => table
|
||||
.Column<int>("Id", column => column.PrimaryKey().Identity())
|
||||
.Column<string>("DataMigrationClass")
|
||||
.Column<int>("Version"));
|
||||
|
||||
var dataMigrationManager = environment.Resolve<IDataMigrationManager>();
|
||||
dataMigrationManager.Update("Settings");
|
||||
|
||||
foreach (var feature in context.EnabledFeatures) {
|
||||
dataMigrationManager.Update(feature);
|
||||
}
|
||||
|
||||
var descriptorManager = environment.Resolve<IShellDescriptorManager>();
|
||||
descriptorManager.UpdateShellDescriptor(0, shellDescriptor.Features, shellDescriptor.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
// In effect "pump messages" see PostMessage circa 1980.
|
||||
while ( _processingEngine.AreTasksPending() )
|
||||
_processingEngine.ExecuteNextTask();
|
||||
|
||||
// Creating a standalone environment.
|
||||
// In theory this environment can be used to resolve any normal components by interface, and those
|
||||
// components will exist entirely in isolation - no crossover between the safemode container currently in effect.
|
||||
|
||||
// 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<ITransactionManager>().Cancel();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
_shellSettingsManager.SaveSettings(shellSettings);
|
||||
|
||||
return executionId;
|
||||
}
|
||||
|
||||
private string CreateTenantData(SetupContext context, IWorkContextScope environment) {
|
||||
// Create superuser.
|
||||
var membershipService = environment.Resolve<IMembershipService>();
|
||||
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<IAuthenticationService>();
|
||||
authenticationService.SetAuthenticatedUserForRequest(user);
|
||||
|
||||
// Set site name and settings.
|
||||
var siteService = environment.Resolve<ISiteService>();
|
||||
var siteSettings = siteService.GetSiteSettings().As<SiteSettingsPart>();
|
||||
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<ICultureManager>();
|
||||
cultureManager.AddCulture("en-US");
|
||||
|
||||
// Execute recipe
|
||||
var recipeParser = environment.Resolve<IRecipeParser>();
|
||||
var recipe = recipeParser.ParseRecipe(context.RecipeText);
|
||||
var recipeManager = environment.Resolve<IRecipeManager>();
|
||||
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;
|
||||
}
|
||||
|
||||
private void DropTenantDatabaseTables(IWorkContextScope environment) {
|
||||
var sessionFactoryHolder = environment.Resolve<ISessionFactoryHolder>();
|
||||
var schemaBuilder = new SchemaBuilder(environment.Resolve<IDataMigrationInterpreter>());
|
||||
var configuration = sessionFactoryHolder.GetConfiguration();
|
||||
foreach (var mapping in configuration.ClassMappings) {
|
||||
try {
|
||||
schemaBuilder.DropTable(mapping.Table.Name);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Warning(ex, "Failed to drop table '{0}'.", mapping.Table.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
namespace Orchard.ImportExport.ViewModels {
|
||||
public class ResetSiteViewModel {
|
||||
public class UploadRecipeViewModel {
|
||||
public bool ResetSite { get; set; }
|
||||
public bool DropTables { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@model Orchard.ImportExport.ViewModels.ResetSiteViewModel
|
||||
@{
|
||||
Script.Require("ShapesBase");
|
||||
}
|
||||
<fieldset>
|
||||
<div>
|
||||
@Html.CheckBoxFor(m => m.ResetSite)
|
||||
@Html.LabelFor(m => m.ResetSite, T("Reset Site").ToString(), new {@class = "forcheckbox"})
|
||||
@Html.Hint(T("Check this option to resets your site to its uninitialized state."))
|
||||
</div>
|
||||
<div data-controllerid="@Html.FieldIdFor(m => m.ResetSite)">
|
||||
@Html.CheckBoxFor(m => m.DropTables)
|
||||
@Html.LabelFor(m => m.DropTables, T("Drop Tables").ToString(), new { @class = "forcheckbox" })
|
||||
@Html.Hint(T("Check this option to also drop all of the database tables of the current tenant."))
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -1,8 +1,18 @@
|
||||
@model dynamic
|
||||
@model Orchard.ImportExport.ViewModels.UploadRecipeViewModel
|
||||
<p>
|
||||
@T("Choose a recipe file to import. Please consider {0} or backing up your data first.", @Html.Link(T("exporting").Text, Url.Action("Export", "Admin", new { area = "Orchard.ImportExport" })))
|
||||
</p>
|
||||
<fieldset>
|
||||
<label for="RecipeFile">@T("Recipe File:")</label>
|
||||
<input type="file" id="RecipeFile" size="64" name="RecipeFile" value="@T("Browse...")" />
|
||||
<input type="file" id="RecipeFile" size="64" name="RecipeFile" value="@T("Browse...")"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div>
|
||||
@Html.CheckBoxFor(m => m.ResetSite)
|
||||
@Html.LabelFor(m => m.ResetSite, T("Reset Site").ToString(), new { @class = "forcheckbox" })
|
||||
@Html.Hint(T("Check this option to reset your site before executing the uploaded recipe."))
|
||||
</div>
|
||||
<div data-controllerid="@Html.FieldIdFor(m => m.ResetSite)">
|
||||
<div class="message message-Warning">@T("This will delete your database tables. Please consider creating a backup first.")</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
Reference in New Issue
Block a user