+
diff --git a/src/Orchard.Web/Modules/Orchard.Localization/Drivers/LocalizationPartDriver.cs b/src/Orchard.Web/Modules/Orchard.Localization/Drivers/LocalizationPartDriver.cs
index 486e5014e..b4a06897c 100644
--- a/src/Orchard.Web/Modules/Orchard.Localization/Drivers/LocalizationPartDriver.cs
+++ b/src/Orchard.Web/Modules/Orchard.Localization/Drivers/LocalizationPartDriver.cs
@@ -58,7 +58,9 @@ namespace Orchard.Localization.Drivers {
protected override DriverResult Editor(LocalizationPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditLocalizationViewModel();
- if (updater != null && updater.TryUpdateModel(model, TemplatePrefix, null, null)) {
+
+ // Content culture has to be set only if it's not set already.
+ if (updater != null && updater.TryUpdateModel(model, TemplatePrefix, null, null) && GetCulture(part) == null) {
_localizationService.SetContentCulture(part, model.SelectedCulture);
}
diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Scripts/media-library.js b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Scripts/media-library.js
index be7e8209b..049d75e38 100644
--- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Scripts/media-library.js
+++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Scripts/media-library.js
@@ -71,11 +71,12 @@ $(function () {
var listWidth = $('#media-library-main-list').width();
var listHeight = $('#media-library-main-list').height();
- var itemSize = $('.thumbnail').first().width();
+ var itemWidth = $('.thumbnail').first().width();
+ var itemHeight = $('.thumbnail').first().height();
var draftText = $("#media-library").data("draft-text");
- var itemsPerRow = Math.floor(listWidth / itemSize);
- var itemsPerColumn = Math.ceil(listHeight / itemSize);
+ var itemsPerRow = Math.floor(listWidth / itemWidth);
+ var itemsPerColumn = Math.ceil(listHeight / itemHeight);
var pageCount = itemsPerRow * itemsPerColumn;
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs
index 2122f2096..890236e7d 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Commands/TenantCommand.cs
@@ -8,18 +8,30 @@ using Orchard.MultiTenancy.Services;
namespace Orchard.MultiTenancy.Commands {
public class TenantCommand : DefaultOrchardCommandHandler {
private readonly ITenantService _tenantService;
+ private readonly string[] _validDataProviderNames = new[] { "SqlCe", "SqlServer", "MySql", "PostgreSql" };
public TenantCommand(ITenantService tenantService) {
_tenantService = tenantService;
}
[OrchardSwitch]
- public string Host { get; set; }
-
+ public string DataProvider { get; set; }
+ [OrchardSwitch]
+ public string DataConnectionString { get; set; }
+ [OrchardSwitch]
+ public string DataTablePrefix { get; set; }
+ [OrchardSwitch]
+ public string UrlHost { get; set; }
[OrchardSwitch]
public string UrlPrefix { get; set; }
+ [OrchardSwitch]
+ public string Themes { get; set; }
+ [OrchardSwitch]
+ public string Modules { get; set; }
+ [OrchardSwitch]
+ public bool DropDatabaseTables { get; set; }
- [CommandHelp("tenant list\r\n\t" + "Display current tenants of a site")]
+ [CommandHelp("tenant list\r\n\t" + "Display current tenants of the site.")]
[CommandName("tenant list")]
public void List() {
Context.Output.WriteLine(T("List of tenants"));
@@ -28,61 +40,157 @@ namespace Orchard.MultiTenancy.Commands {
var tenants = _tenantService.GetTenants();
foreach (var tenant in tenants) {
Context.Output.WriteLine(T("Name: ") + tenant.Name);
- Context.Output.WriteLine(T("Provider: ") + tenant.DataProvider);
- Context.Output.WriteLine(T("ConnectionString: ") + tenant.DataConnectionString);
- Context.Output.WriteLine(T("Data Table Prefix: ") + tenant.DataTablePrefix);
- Context.Output.WriteLine(T("Request Url Host: ") + tenant.RequestUrlHost);
- Context.Output.WriteLine(T("Request Url Prefix: ") + tenant.RequestUrlPrefix);
Context.Output.WriteLine(T("State: ") + tenant.State.ToString());
+ Context.Output.WriteLine(T("Data provider: ") + tenant.DataProvider);
+ Context.Output.WriteLine(T("Connection string: ") + tenant.DataConnectionString);
+ Context.Output.WriteLine(T("Data table prefix: ") + tenant.DataTablePrefix);
+ Context.Output.WriteLine(T("Request URL host: ") + tenant.RequestUrlHost);
+ Context.Output.WriteLine(T("Request URL prefix: ") + tenant.RequestUrlPrefix);
+ Context.Output.WriteLine(T("Encryption algorithm: ") + tenant.EncryptionAlgorithm);
+ Context.Output.WriteLine(T("Encryption key: ") + tenant.EncryptionKey);
+ Context.Output.WriteLine(T("Hash algorithm: ") + tenant.HashAlgorithm);
+ Context.Output.WriteLine(T("Hash key: ") + tenant.HashKey);
+ Context.Output.WriteLine(T("Themes: ") + String.Join(";", tenant.Themes));
+ Context.Output.WriteLine(T("Modules: ") + String.Join(";", tenant.Modules));
Context.Output.WriteLine(T("---------------------------"));
}
}
- [CommandHelp("tenant add /Host: /UrlPrefix:\r\n\t" +
- "Create new tenant named on the site")]
- [CommandName("tenant add")]
- [OrchardSwitches("Host,UrlPrefix")]
- public void Create(string tenantName) {
- Context.Output.WriteLine(T("Creating tenant"));
+ [CommandHelp("tenant info \r\n\t" + "Display the current settings for a tenant.")]
+ [CommandName("tenant info")]
+ public void Info(string tenantName) {
+ var tenant = _tenantService.GetTenants().FirstOrDefault(x => x.Name == tenantName);
+ if (tenant == null) {
+ Context.Output.WriteLine(T("Could not read tenant '{0}'. No tenant with that name exists.", tenantName));
+ return;
+ }
- if (string.IsNullOrWhiteSpace(tenantName) || !Regex.IsMatch(tenantName, @"^\w+$")) {
+ Context.Output.WriteLine(T("Tenant settings:"));
+ Context.Output.WriteLine(T("---------------------------"));
+ Context.Output.WriteLine(T("Name: ") + tenant.Name);
+ Context.Output.WriteLine(T("State: ") + tenant.State.ToString());
+ Context.Output.WriteLine(T("Data provider: ") + tenant.DataProvider);
+ Context.Output.WriteLine(T("Connection string: ") + tenant.DataConnectionString);
+ Context.Output.WriteLine(T("Data table prefix: ") + tenant.DataTablePrefix);
+ Context.Output.WriteLine(T("Request URL host: ") + tenant.RequestUrlHost);
+ Context.Output.WriteLine(T("Request URL prefix: ") + tenant.RequestUrlPrefix);
+ Context.Output.WriteLine(T("Encryption algorithm: ") + tenant.EncryptionAlgorithm);
+ Context.Output.WriteLine(T("Encryption key: ") + tenant.EncryptionKey);
+ Context.Output.WriteLine(T("Hash algorithm: ") + tenant.HashAlgorithm);
+ Context.Output.WriteLine(T("Hash key: ") + tenant.HashKey);
+ Context.Output.WriteLine(T("Themes: ") + String.Join(";", tenant.Themes));
+ Context.Output.WriteLine(T("Modules: ") + String.Join(";", tenant.Modules));
+ Context.Output.WriteLine(T("---------------------------"));
+ }
+
+ [CommandHelp("tenant add /DataProvider: /DataConnectionString: /DataTablePrefix: /UrlHost: /UrlPrefix: /Themes: /Modules:\r\n\t" + "Create a new tenant named on the site.\r\n" + "The and parameters should be semicolon-separated lists of module names.")]
+ [CommandName("tenant add")]
+ [OrchardSwitches("DataProvider,DataConnectionString,DataTablePrefix,UrlHost,UrlPrefix,Themes,Modules")]
+ public void Create(string tenantName) {
+ Context.Output.WriteLine(T("Creating tenant '{0}'...", tenantName));
+
+ if (String.IsNullOrWhiteSpace(tenantName) || !Regex.IsMatch(tenantName, @"^\w+$")) {
Context.Output.WriteLine(T("Invalid tenant name. Must contain characters only and no spaces."));
return;
}
- if (_tenantService.GetTenants().Any(tenant => string.Equals(tenant.Name, tenantName, StringComparison.OrdinalIgnoreCase))) {
- Context.Output.WriteLine(T("Could not create tenant \"{0}\". A tenant with the same name already exists.", tenantName));
+ if (_tenantService.GetTenants().Any(tenant => String.Equals(tenant.Name, tenantName, StringComparison.OrdinalIgnoreCase))) {
+ Context.Output.WriteLine(T("Could not create tenant '{0}'. A tenant with the same name already exists.", tenantName));
+ return;
+ }
+
+ if (DataProvider != null && !_validDataProviderNames.Contains(DataProvider)) {
+ Context.Output.WriteLine(T("Invalid value '{0}' for parameter DataProvider. Expect one of the following: {1}", DataProvider, String.Join(", ", _validDataProviderNames)));
return;
}
_tenantService.CreateTenant(
- new ShellSettings {
- Name = tenantName,
- RequestUrlHost = Host,
- RequestUrlPrefix = UrlPrefix,
- State = TenantState.Uninitialized
- });
+ new ShellSettings {
+ Name = tenantName,
+ State = TenantState.Uninitialized,
+ DataProvider = DataProvider,
+ DataConnectionString = DataConnectionString,
+ DataTablePrefix = DataTablePrefix,
+ RequestUrlHost = UrlHost,
+ RequestUrlPrefix = UrlPrefix,
+ Themes = Themes.Split(';'),
+ Modules = Modules.Split(';')
+ });
}
- [CommandHelp("tenant info \r\n\t" + "Display settings for a tenant")]
- [CommandName("tenant info")]
- public void Info(string tenantName) {
- ShellSettings tenant = _tenantService.GetTenants().Where(x => x.Name == tenantName).FirstOrDefault();
+ [CommandHelp("tenant update /DataProvider: /DataConnectionString: /DataTablePrefix: /UrlHost: /UrlPrefix: /Themes: /Modules:\r\n\t" + "Update the settings of the existing tenant .\r\n" + "The and parameters should be semicolon-separated lists of module names.")]
+ [CommandName("tenant update")]
+ [OrchardSwitches("DataProvider,DataConnectionString,DataTablePrefix,UrlHost,UrlPrefix,Themes,Modules")]
+ public void Edit(string tenantName) {
+ Context.Output.WriteLine(T("Updating tenant '{0}'...", tenantName));
+ var tenant = _tenantService.GetTenants().FirstOrDefault(t => String.Equals(t.Name, tenantName, StringComparison.OrdinalIgnoreCase));
if (tenant == null) {
- Context.Output.Write(T("Tenant: ") + tenantName + T(" was not found"));
+ Context.Output.WriteLine(T("Could not update tenant '{0}'. No tenant with that name exists.", tenantName));
+ return;
}
- else {
- Context.Output.WriteLine(T("Tenant Settings:"));
- Context.Output.WriteLine(T("---------------------------"));
- Context.Output.WriteLine(T("Name: ") + tenant.Name);
- Context.Output.WriteLine(T("Provider: ") + tenant.DataProvider);
- Context.Output.WriteLine(T("ConnectionString: ") + tenant.DataConnectionString);
- Context.Output.WriteLine(T("Data Table Prefix: ") + tenant.DataTablePrefix);
- Context.Output.WriteLine(T("Request Url Host: ") + tenant.RequestUrlHost);
- Context.Output.WriteLine(T("Request Url Prefix: ") + tenant.RequestUrlPrefix);
- Context.Output.WriteLine(T("State: ") + tenant.State.ToString());
- Context.Output.WriteLine(T("---------------------------"));
+
+ if (DataProvider != null && !_validDataProviderNames.Contains(DataProvider)) {
+ Context.Output.WriteLine(T("Invalid value '{0}' for parameter DataProvider. Expect one of the following: {1}", DataProvider, String.Join(", ", _validDataProviderNames)));
+ return;
}
+
+ _tenantService.UpdateTenant(
+ new ShellSettings {
+ Name = tenant.Name,
+ State = tenant.State,
+ DataProvider = DataProvider ?? tenant.DataProvider,
+ DataConnectionString = DataConnectionString ?? tenant.DataConnectionString,
+ DataTablePrefix = DataTablePrefix ?? tenant.DataTablePrefix,
+ RequestUrlHost = UrlHost ?? tenant.RequestUrlHost,
+ RequestUrlPrefix = UrlPrefix ?? tenant.RequestUrlPrefix,
+ Themes = Themes != null ? Themes.Split(';') : tenant.Themes,
+ Modules = Modules != null ? Modules.Split(';') : tenant.Modules
+ });
+ }
+
+ [CommandHelp("tenant disable \r\n\t" + "Disable the tenant .")]
+ [CommandName("tenant disable")]
+ public void Disable(string tenantName) {
+ Context.Output.WriteLine(T("Disabling tenant '{0}'...", tenantName));
+
+ var tenant = _tenantService.GetTenants().FirstOrDefault(t => String.Equals(t.Name, tenantName, StringComparison.OrdinalIgnoreCase));
+ if (tenant == null) {
+ Context.Output.WriteLine(T("Could not disable tenant '{0}'. No tenant with that name exists.", tenantName));
+ return;
+ }
+
+ tenant.State = TenantState.Disabled;
+ _tenantService.UpdateTenant(tenant);
+ }
+
+ [CommandHelp("tenant enable \r\n\t" + "Enable the tenant .")]
+ [CommandName("tenant enable")]
+ public void Enable(string tenantName) {
+ Context.Output.WriteLine(T("Enabling tenant '{0}'...", tenantName));
+
+ var tenant = _tenantService.GetTenants().FirstOrDefault(t => String.Equals(t.Name, tenantName, StringComparison.OrdinalIgnoreCase));
+ if (tenant == null) {
+ Context.Output.WriteLine(T("Could not enable tenant '{0}'. No tenant with that name exists.", tenantName));
+ return;
+ }
+
+ tenant.State = TenantState.Running;
+ _tenantService.UpdateTenant(tenant);
+ }
+
+ [CommandHelp("tenant reset /DropDatabaseTables:\r\n\t" + "Reset the tenant to its uninitialized, optionally dropping its tables from the database.")]
+ [CommandName("tenant reset")]
+ [OrchardSwitches("DropDatabaseTables")]
+ public void Reset(string tenantName) {
+ Context.Output.WriteLine(T("Resetting tenant '{0}'...", tenantName));
+
+ var tenant = _tenantService.GetTenants().FirstOrDefault(t => String.Equals(t.Name, tenantName, StringComparison.OrdinalIgnoreCase));
+ if (tenant == null) {
+ Context.Output.WriteLine(T("Could not reset tenant '{0}'. No tenant with that name exists.", tenantName));
+ return;
+ }
+
+ _tenantService.ResetTenant(tenant, DropDatabaseTables);
}
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs
index 38d6091b7..fb172f67c 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs
@@ -19,7 +19,7 @@ namespace Orchard.MultiTenancy.Controllers {
public AdminController(ITenantService tenantService, IOrchardServices orchardServices, ShellSettings shellSettings) {
_tenantService = tenantService;
_thisShellSettings = shellSettings;
-
+
Services = orchardServices;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
@@ -30,32 +30,34 @@ namespace Orchard.MultiTenancy.Controllers {
public ILogger Logger { get; set; }
public ActionResult Index() {
- return View(new TenantsIndexViewModel { TenantSettings = _tenantService.GetTenants() });
+ return View(new TenantsIndexViewModel {
+ TenantSettings = _tenantService.GetTenants()
+ });
}
public ActionResult Add() {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Cannot create tenant")))
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to create tenants.")))
return new HttpUnauthorizedResult();
- if ( !EnsureDefaultTenant() )
+ if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult();
- var model = new TenantAddViewModel();
+ var viewModel = new TenantAddViewModel();
- // fetches all available themes and modules
- model.Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry { ThemeId = x.Id, ThemeName = x.Name }).ToList();
- model.Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry { ModuleId = x.Id, ModuleName = x.Name }).ToList();
+ // Fetches all available themes and modules.
+ viewModel.Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry { ThemeId = x.Id, ThemeName = x.Name }).ToList();
+ viewModel.Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry { ModuleId = x.Id, ModuleName = x.Name }).ToList();
- return View(model);
+ return View(viewModel);
}
[HttpPost, ActionName("Add")]
- public ActionResult AddPOST(TenantAddViewModel viewModel) {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't create tenant"))) {
+ public ActionResult AddPost(TenantAddViewModel viewModel) {
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to create tenants."))) {
return new HttpUnauthorizedResult();
}
- if (!EnsureDefaultTenant()) {
+ if (!IsExecutingInDefaultTenant()) {
return new HttpUnauthorizedResult();
}
@@ -63,7 +65,7 @@ namespace Orchard.MultiTenancy.Controllers {
ModelState.AddModelError("Name", T("A tenant with the same name already exists.", viewModel.Name).Text);
}
- // ensure tenants name are valid
+ // Ensure tenants name are valid.
if (!String.IsNullOrEmpty(viewModel.Name) && !Regex.IsMatch(viewModel.Name, @"^\w+$")) {
ModelState.AddModelError("Name", T("Invalid tenant name. Must contain characters only and no spaces.").Text);
}
@@ -88,56 +90,58 @@ namespace Orchard.MultiTenancy.Controllers {
return RedirectToAction("Index");
}
- catch (ArgumentException exception) {
- Services.Notifier.Error(T("Creating Tenant failed: {0}", exception.Message));
+ catch (ArgumentException ex) {
+ Logger.Error(ex, "Error while creating tenant.");
+ Services.Notifier.Error(T("Tenant creation failed with error: {0}", ex.Message));
return View(viewModel);
}
}
public ActionResult Edit(string name) {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Cannot edit tenant")))
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to edit tenants.")))
return new HttpUnauthorizedResult();
- if ( !EnsureDefaultTenant() )
+ if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
-
if (tenant == null)
return HttpNotFound();
return View(new TenantEditViewModel {
- Name = tenant.Name,
- RequestUrlHost = tenant.RequestUrlHost,
- RequestUrlPrefix = tenant.RequestUrlPrefix,
- DataProvider = tenant.DataProvider,
- DatabaseConnectionString = tenant.DataConnectionString,
- DatabaseTablePrefix = tenant.DataTablePrefix,
- State = tenant.State,
- Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry {
- ThemeId = x.Id,
- ThemeName = x.Name,
- Checked = tenant.Themes.Contains(x.Id)
- }).ToList(),
- Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry {
- ModuleId = x.Id,
- ModuleName = x.Name,
- Checked = tenant.Modules.Contains(x.Id)
- }).ToList()
- });
+ Name = tenant.Name,
+ RequestUrlHost = tenant.RequestUrlHost,
+ RequestUrlPrefix = tenant.RequestUrlPrefix,
+ DataProvider = tenant.DataProvider,
+ DatabaseConnectionString = tenant.DataConnectionString,
+ DatabaseTablePrefix = tenant.DataTablePrefix,
+ State = tenant.State,
+ Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry {
+ ThemeId = x.Id,
+ ThemeName = x.Name,
+ Checked = tenant.Themes.Contains(x.Id)
+ }).ToList(),
+ Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry {
+ ModuleId = x.Id,
+ ModuleName = x.Name,
+ Checked = tenant.Modules.Contains(x.Id)
+ }).ToList()
+ });
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(TenantEditViewModel viewModel) {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't edit tenant")))
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to edit tenants.")))
return new HttpUnauthorizedResult();
- if ( !EnsureDefaultTenant() )
+ if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == viewModel.Name);
if (tenant == null)
return HttpNotFound();
+ else if (tenant.Name == _thisShellSettings.Name)
+ return new HttpUnauthorizedResult();
if (!ModelState.IsValid) {
return View(viewModel);
@@ -163,18 +167,19 @@ namespace Orchard.MultiTenancy.Controllers {
return RedirectToAction("Index");
}
- catch (Exception exception) {
- Services.Notifier.Error(T("Failed to edit tenant: {0} ", exception.Message));
+ catch (Exception ex) {
+ Logger.Error(ex, "Error while editing tenant.");
+ Services.Notifier.Error(T("Failed to edit tenant: {0} ", ex.Message));
return View(viewModel);
}
}
[HttpPost]
public ActionResult Disable(string name) {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't disable tenant")))
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to disable tenants.")))
return new HttpUnauthorizedResult();
- if ( !EnsureDefaultTenant() )
+ if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
@@ -189,10 +194,10 @@ namespace Orchard.MultiTenancy.Controllers {
[HttpPost]
public ActionResult Enable(string name) {
- if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't enable tenant")))
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to enable tenants.")))
return new HttpUnauthorizedResult();
- if ( !EnsureDefaultTenant() )
+ if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
@@ -205,7 +210,55 @@ namespace Orchard.MultiTenancy.Controllers {
return RedirectToAction("Index");
}
- private bool EnsureDefaultTenant() {
+ public ActionResult Reset(string name) {
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to reset tenants.")))
+ return new HttpUnauthorizedResult();
+
+ if (!IsExecutingInDefaultTenant())
+ return new HttpUnauthorizedResult();
+
+ var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
+ if (tenant == null)
+ return HttpNotFound();
+
+ return View(new TenantResetViewModel() {
+ Name = name,
+ DatabaseTableNames = _tenantService.GetTenantDatabaseTableNames(tenant)
+ });
+ }
+
+ [HttpPost, ActionName("Reset")]
+ public ActionResult ResetPost(TenantResetViewModel viewModel) {
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to reset tenants.")))
+ return new HttpUnauthorizedResult();
+
+ if (!IsExecutingInDefaultTenant())
+ return new HttpUnauthorizedResult();
+
+ var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == viewModel.Name);
+ if (tenant == null)
+ return HttpNotFound();
+ else if (tenant.Name == _thisShellSettings.Name)
+ return new HttpUnauthorizedResult();
+
+ if (!ModelState.IsValid) {
+ viewModel.DatabaseTableNames = _tenantService.GetTenantDatabaseTableNames(tenant);
+ return View(viewModel);
+ }
+
+ try {
+ _tenantService.ResetTenant(tenant, viewModel.DropDatabaseTables);
+ return RedirectToAction("Index");
+ }
+ catch (Exception ex) {
+ Logger.Error(ex, "Error while resetting tenant.");
+ Services.Notifier.Error(T("Failed to reset tenant: {0} ", ex.Message));
+ viewModel.DatabaseTableNames = _tenantService.GetTenantDatabaseTableNames(tenant);
+ return View(viewModel);
+ }
+ }
+
+ private bool IsExecutingInDefaultTenant() {
return _thisShellSettings.Name == ShellSettings.DefaultName;
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj
index 46bf0f356..3fc0ac7b3 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj
@@ -25,6 +25,7 @@
+ true
@@ -48,6 +49,12 @@
false
+
+ ..\..\..\..\lib\autofac\Autofac.dll
+
+
+ ..\..\..\..\lib\nhibernate\NHibernate.dll
+ 3.5
@@ -73,9 +80,11 @@
+
+
@@ -123,6 +132,9 @@
+
+
+ 10.0$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantResetEventHandler.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantResetEventHandler.cs
new file mode 100644
index 000000000..aba7b77a9
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantResetEventHandler.cs
@@ -0,0 +1,10 @@
+using Orchard.Events;
+
+namespace Orchard.MultiTenancy.Services {
+ ///
+ /// An event handler interface that allows implementers to execute code when a tenant is being reset.
+ ///
+ public interface ITenantResetEventHandler : IEventHandler {
+ void Resetting();
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantService.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantService.cs
index 4d0c3366b..d38dcf0b8 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantService.cs
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/ITenantService.cs
@@ -5,23 +5,35 @@ using Orchard.Environment.Extensions.Models;
namespace Orchard.MultiTenancy.Services {
public interface ITenantService : IDependency {
///
- /// Retrieves all tenants' shell settings.
+ /// Retrieves ShellSettings objects for all tenants.
///
- /// All tenants' shell settings.
IEnumerable GetTenants();
///
/// Creates a new tenant.
///
- /// Shell settings of the tenant.
+ /// A ShellSettings object specifying the settings for the new tenant.
void CreateTenant(ShellSettings settings);
///
- /// Updates the shell settings of a tenant.
+ /// Updates the settings of a tenant.
///
- /// Shell settings of the tenant.
+ /// The new ShellSettings object for the tenant.
void UpdateTenant(ShellSettings settings);
+ ///
+ /// Resets a tenant to its uninitialized state.
+ ///
+ /// A ShellSettings object for the tenant to reset.
+ /// A boolean indicated whether tenant database tables should be dropped also.
+ void ResetTenant(ShellSettings settings, bool dropDatabaseTables);
+
+ ///
+ /// Returns a list of all known database tables in a tenant.
+ ///
+ /// A ShellSettings object for the tenant.
+ IEnumerable GetTenantDatabaseTableNames(ShellSettings settings);
+
///
/// Returns a list of all installed themes.
///
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs
index e907b7104..791b161a1 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs
@@ -1,21 +1,36 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Models;
using Orchard.Environment.Extensions;
+using Orchard.Environment.ShellBuilders;
+using Orchard.Data.Migration.Interpreters;
+using Orchard.Data.Migration.Schema;
+using Orchard.Data;
+using Orchard.Logging;
namespace Orchard.MultiTenancy.Services {
public class TenantService : ITenantService {
private readonly IShellSettingsManager _shellSettingsManager;
private readonly IExtensionManager _extensionManager;
+ private readonly IShellContextFactory _shellContextFactory;
+ private readonly IShellContainerFactory _shellContainerFactory;
public TenantService(
IShellSettingsManager shellSettingsManager,
- IExtensionManager extensionManager) {
+ IExtensionManager extensionManager,
+ IShellContextFactory shellContextFactory,
+ IShellContainerFactory shellContainerFactory) {
_shellSettingsManager = shellSettingsManager;
_extensionManager = extensionManager;
+ _shellContextFactory = shellContextFactory;
+ _shellContainerFactory = shellContainerFactory;
+ Logger = NullLogger.Instance;
}
+ public ILogger Logger { get; set; }
+
public IEnumerable GetTenants() {
return _shellSettingsManager.LoadSettings();
}
@@ -28,16 +43,32 @@ namespace Orchard.MultiTenancy.Services {
_shellSettingsManager.SaveSettings(settings);
}
- ///
- /// Loads only installed themes
- ///
+ public void ResetTenant(ShellSettings settings, bool dropDatabaseTables) {
+ if (settings.State != TenantState.Disabled)
+ throw new InvalidOperationException(String.Format("Tenant state is '{0}'; must be '{1}' to perform reset action.", settings.State, TenantState.Disabled));
+
+ ExecuteOnTenantScope(settings, environment => {
+ ExecuteResetEventHandlers(environment);
+ if (dropDatabaseTables)
+ DropTenantDatabaseTables(environment);
+ });
+
+ settings.State = TenantState.Uninitialized;
+ _shellSettingsManager.SaveSettings(settings);
+ }
+
+ public IEnumerable GetTenantDatabaseTableNames(ShellSettings settings) {
+ IEnumerable result = null;
+ ExecuteOnTenantScope(settings, environment => {
+ result = GetTenantDatabaseTableNames(environment);
+ });
+ return result;
+ }
+
public IEnumerable GetInstalledThemes() {
return GetThemes(_extensionManager.AvailableExtensions());
}
- ///
- /// Loads only installed modules
- ///
public IEnumerable GetInstalledModules() {
return _extensionManager.AvailableExtensions().Where(descriptor => DefaultExtensionTypes.IsModule(descriptor.ExtensionType));
}
@@ -58,5 +89,45 @@ namespace Orchard.MultiTenancy.Services {
}
return themes;
}
+
+ private void ExecuteOnTenantScope(ShellSettings settings, Action action) {
+ var shellContext = _shellContextFactory.CreateShellContext(settings);
+ using (var container = _shellContainerFactory.CreateContainer(shellContext.Settings, shellContext.Blueprint)) {
+ using (var environment = container.CreateWorkContextScope()) {
+ action(environment);
+ }
+ }
+ }
+
+ private IEnumerable GetTenantDatabaseTableNames(IWorkContextScope environment) {
+ var sessionFactoryHolder = environment.Resolve();
+ var schemaBuilder = new SchemaBuilder(environment.Resolve());
+ var configuration = sessionFactoryHolder.GetConfiguration();
+
+ var result =
+ from mapping in configuration.ClassMappings
+ select mapping.Table.Name;
+
+ return result.ToArray();
+ }
+
+ private void DropTenantDatabaseTables(IWorkContextScope environment) {
+ var sessionFactoryHolder = environment.Resolve();
+ var schemaBuilder = new SchemaBuilder(environment.Resolve());
+ 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);
+ }
+ }
+ }
+
+ private void ExecuteResetEventHandlers(IWorkContextScope environment) {
+ var handler = environment.Resolve();
+ handler.Resetting();
+ }
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/ViewModels/TenantResetViewModel.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/ViewModels/TenantResetViewModel.cs
new file mode 100644
index 000000000..6969fa091
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/ViewModels/TenantResetViewModel.cs
@@ -0,0 +1,17 @@
+using System.ComponentModel.DataAnnotations;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Orchard.MultiTenancy.ViewModels {
+ public class TenantResetViewModel {
+ public TenantResetViewModel() {
+ DatabaseTableNames = Enumerable.Empty();
+ }
+
+ [Required]
+ public string Name { get; set; }
+ public bool DropDatabaseTables { get; set; }
+ public IEnumerable DatabaseTableNames { get; set; }
+ }
+}
+
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForDisabled.cshtml b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForDisabled.cshtml
index 8b8d91eeb..4e3235c45 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForDisabled.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForDisabled.cshtml
@@ -1,7 +1,8 @@
@model Orchard.Environment.Configuration.ShellSettings
@using Orchard.MultiTenancy.Extensions;
-@using(Html.BeginFormAntiForgeryPost(Url.Action("enable", new {area = "Orchard.MultiTenancy"}), FormMethod.Post, new {@class = "inline link"})) {
+@using(Html.BeginFormAntiForgeryPost(Url.Action("Enable", new {area = "Orchard.MultiTenancy"}), FormMethod.Post, new {@class = "inline link"})) {
@Html.HiddenFor(ss => ss.Name)
-}
+} @T(" | ")
+@Html.ActionLink(T("Reset").ToString(), "Reset", new { name = Model.Name, area = "Orchard.MultiTenancy" })
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForRunning.cshtml b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForRunning.cshtml
index 4ca7ef259..02d1ece66 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForRunning.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/DisplayTemplates/ActionsForRunning.cshtml
@@ -1,7 +1,7 @@
@model Orchard.Environment.Configuration.ShellSettings
@using Orchard.MultiTenancy.Extensions;
-@using(Html.BeginFormAntiForgeryPost(Url.Action("disable", new {area = "Orchard.MultiTenancy"}), FormMethod.Post, new {@class = "inline link"})) {
+@using(Html.BeginFormAntiForgeryPost(Url.Action("Disable", new {area = "Orchard.MultiTenancy"}), FormMethod.Post, new {@class = "inline link"})) {
@Html.HiddenFor(ss => ss.Name)
-}
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Index.cshtml
index 25c564182..a7e6faa07 100644
--- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Index.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Views/Admin/Index.cshtml
@@ -7,30 +7,31 @@
Layout.Title = T("List of Site's Tenants").ToString();
}
-
@Html.ActionLink(T("Add a Tenant").ToString(), "Add", new {area = "Orchard.MultiTenancy"}, new { @class = "button primaryAction" })
+
@Html.ActionLink(T("Add a Tenant").ToString(), "Add", new { area = "Orchard.MultiTenancy" }, new { @class = "button primaryAction" })
- @foreach (var tenant in Model.TenantSettings) {
-
-
-
-
@tenant.Name @if (!string.IsNullOrEmpty(tenant.RequestUrlHost)) {
- var tenantClone = new ShellSettings(tenant);
- foreach (var t in tenant.RequestUrlHost.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
- tenantClone.RequestUrlHost = t;
- var url = Url.Tenant(tenantClone);
- - @Html.Link(url, url)
- }
- }
-
-
-
- @if (!string.Equals(tenant.Name, "default", StringComparison.OrdinalIgnoreCase)) { //todo: (heskew) base this off the view model so logic on what can be removed and have its state changed stays in the controller
+ @foreach (var tenant in Model.TenantSettings) {
+
+
+
+
+ @tenant.Name @if (!string.IsNullOrEmpty(tenant.RequestUrlHost)) {
+ var tenantClone = new ShellSettings(tenant);
+ foreach (var t in tenant.RequestUrlHost.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
+ tenantClone.RequestUrlHost = t;
+ var url = Url.Tenant(tenantClone);
+ - @Html.Link(url, url)
+ }
+ }
+
+
+
+ @if (!String.Equals(tenant.Name, "default", StringComparison.OrdinalIgnoreCase)) { //todo: (heskew) base this off the view model so logic on what can be removed and have its state changed stays in the controller
var t = tenant;
- @Html.DisplayFor(m => t, string.Format("ActionsFor{0}", tenant.State.ToString()), "") @T(" | ")
- }
- @Html.ActionLink(T("Edit").ToString(), "Edit", new {name = tenant.Name, area = "Orchard.MultiTenancy"})
+ @Html.DisplayFor(m => t, String.Format("ActionsFor{0}", tenant.State.ToString()), "") @T(" | ")
+ }
+ @Html.ActionLink(T("Edit").ToString(), "Edit", new { name = tenant.Name, area = "Orchard.MultiTenancy" })
+