Merge branch 'dev' into feature/recipesteps

This commit is contained in:
Sipke Schoorstra
2015-07-15 14:53:07 +01:00
25 changed files with 774 additions and 192 deletions

View File

@@ -87,6 +87,13 @@
<Property Name="Disabled" Value="false"/> <Property Name="Disabled" Value="false"/>
</Properties> </Properties>
</Component> </Component>
<Component Type="Orchard.Environment.Descriptor.ShellDescriptorCache">
<Properties>
<!-- Set Value="true" to disable shell descriptors cache (cache.dat). Recommended when using multiple instances. -->
<Property Name="Disabled" Value="false"/>
</Properties>
</Component>
<Component Type="Orchard.Services.ClientAddressAccessor"> <Component Type="Orchard.Services.ClientAddressAccessor">
<Properties> <Properties>

View File

@@ -85,7 +85,7 @@
<levelMin value="ERROR" /> <levelMin value="ERROR" />
</filter> </filter>
<layout type="log4net.Layout.PatternLayout"> <layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %logger - %P{Tenant} - %message%newline%P{Url}%newline" /> <conversionPattern value="%date [%thread] %logger - %P{Tenant} - %message [%P{Url}]%newline" />
</layout> </layout>
</appender> </appender>

View File

@@ -1,5 +1,4 @@
(function ($) { (function ($) {
var LayoutDesignerHost = function (element) { var LayoutDesignerHost = function (element) {
var self = this; var self = this;
this.element = element; this.element = element;
@@ -125,5 +124,16 @@
$(function () { $(function () {
var host = new LayoutDesignerHost($(".layout-designer")); var host = new LayoutDesignerHost($(".layout-designer"));
$(".layout-designer").each(function (e) {
var designer = $(this);
var dialog = designer.find(".layout-editor-help-dialog");
designer.find(".layout-editor-help-link").click(function (e) {
dialog.dialog({
modal: true,
width: 840
});
e.preventDefault();
});
});
}); });
})(jQuery); })(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,20 @@
@import "Variables.less"; @import "Variables.less";
.layout-editor-toolbar {
display: flex;
justify-content: space-between;
position: relative;
top: 10px;
.layout-editor-toolbar-group {
display: flex;
> li + li {
margin-left: 12px;
}
}
}
.layout-editor { .layout-editor {
display: flex; display: flex;
margin-top: 1em; margin-top: 1em;
@@ -32,3 +47,53 @@
background: #e8e8e8; background: #e8e8e8;
} }
} }
.layout-editor-help-dialog {
display: none;
.help-row {
&:before, &:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
> .help-column-full, > .help-column-half {
margin: 0.5em 0;
}
> .help-column-half {
box-sizing: border-box;
float: left;
width: 50%;
&:nth-child(2n) {
padding-right: 10px;
clear: left;
}
}
+ .help-row {
margin-top: 1em;
}
}
code {
border-radius: 4px;
background-color: #f3f4f5;
padding: 2px 4px;
font-family: monospace;
}
p {
margin-bottom: 0.5em;
line-height: 1.6em;
}
table > tbody > tr > td:first-child {
padding-right: 10px;
}
}

View File

@@ -9,13 +9,14 @@
// Ideally the MediaLibrary is refactored such that each media item type provides its own set of styles, enabling extensibility of the set of media item types. // Ideally the MediaLibrary is refactored such that each media item type provides its own set of styles, enabling extensibility of the set of media item types.
// In turn we could get rid of this cross-module reference. // In turn we could get rid of this cross-module reference.
Style.Include("~/Modules/Orchard.MediaLibrary/Styles/media-library-picker-admin.css"); Style.Include("~/Modules/Orchard.MediaLibrary/Styles/media-library-picker-admin.css");
Script.Require("jQueryCookie"); Script.Require("jQueryCookie");
Script.Require("jQueryUI_Draggable"); Script.Require("jQueryUI_Draggable");
Script.Require("jQueryUI_Droppable"); Script.Require("jQueryUI_Droppable");
Script.Require("jQueryUI_Sortable"); Script.Require("jQueryUI_Sortable");
Script.Require("jQueryUI_Resizable"); Script.Require("jQueryUI_Resizable");
Script.Require("jQueryUI_Position"); Script.Require("jQueryUI_Position");
Script.Require("jQueryUI_Dialog");
Script.Require("TinyMce"); Script.Require("TinyMce");
Script.Require("Layouts.LayoutEditor"); Script.Require("Layouts.LayoutEditor");
Script.Include("jquery.deserialize.js"); Script.Include("jquery.deserialize.js");
@@ -35,11 +36,11 @@
angular angular
.module("LayoutEditor") .module("LayoutEditor")
.constant("environment", { .constant("environment", {
templateUrl: function(templateName) { templateUrl: function (templateName) {
return "@Url.Action("Get", "Template", new { area = "Orchard.Layouts" })" + "/" + templateName; return "@Url.Action("Get", "Template", new { area = "Orchard.Layouts" })" + "/" + templateName;
} }
}); });
(function() { (function () {
var editorConfig = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.ConfigurationData))")); var editorConfig = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.ConfigurationData))"));
var editorCanvasData = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.Data))")); var editorCanvasData = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.Data))"));
@@ -52,44 +53,170 @@
var contentType = Model.Content != null ? Model.Content.ContentItem.ContentType : default(string); var contentType = Model.Content != null ? Model.Content.ContentItem.ContentType : default(string);
} }
<div class="layout-designer" <fieldset>
data-modelstate-valid="@ViewData.ModelState.IsValid.ToString().ToLower()" <div class="layout-designer"
data-display-type="Design" data-modelstate-valid="@ViewData.ModelState.IsValid.ToString().ToLower()"
data-edit-url="@Url.Action("Edit", "Element", new { session = Model.SessionKey, contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })" data-display-type="Design"
data-add-url="@Url.Action("Add", "Element", new {session = Model.SessionKey, contentId = contentId, contentType = contentType, area = "Orchard.Layouts"})" data-edit-url="@Url.Action("Edit", "Element", new { session = Model.SessionKey, contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })"
data-add-direct-url="@Url.Action("CreateDirect", "Element", new { contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })" data-add-url="@Url.Action("Add", "Element", new {session = Model.SessionKey, contentId = contentId, contentType = contentType, area = "Orchard.Layouts"})"
data-apply-template-url="@Url.Action("ApplyTemplate", "Layout", new { contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })" data-add-direct-url="@Url.Action("CreateDirect", "Element", new { contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })"
data-confirm-delete-prompt="@T("Are you sure you want to delete this element?")" data-apply-template-url="@Url.Action("ApplyTemplate", "Layout", new { contentId = contentId, contentType = contentType, area = "Orchard.Layouts" })"
data-editor-dialog-title-format="@T("$1 Properties")" data-confirm-delete-prompt="@T("Are you sure you want to delete this element?")"
data-editor-dialog-name="Layout" data-editor-dialog-title-format="@T("$1 Properties")"
data-anti-forgery-token="@Html.AntiForgeryTokenValueOrchard()" data-editor-dialog-name="Layout"
data-session-key="@Model.SessionKey"> data-anti-forgery-token="@Html.AntiForgeryTokenValueOrchard()"
data-session-key="@Model.SessionKey">
@Html.HiddenFor(m => m.SessionKey) @Html.HiddenFor(m => m.SessionKey)
@Html.HiddenFor(m => m.Data, new { @class = "layout-data-field" }) @Html.HiddenFor(m => m.Data, new { @class = "layout-data-field" })
<fieldset> <div class="layout-editor-toolbar">
<label>@T("Layout")</label> <ol class="layout-editor-toolbar-group">
<div class="group canvas-toolbar"> <li>
<div class="pull-right"> <label>@T("Layout")</label>
<ol class="group"> </li>
@if (Model.Templates.Any()) { </ol>
var options = Model.Templates.Select(x => new SelectListItem { Text = Html.ItemDisplayText(x).ToString(), Value = x.Id.ToString(CultureInfo.InvariantCulture), Selected = x.Id == Model.TemplateId }); <ol class="layout-editor-toolbar-group">
<li> <li>
<div class="template-picker"> <a class="layout-editor-help-link" href="#"><i class="fa fa-info-circle"></i> Clipboard, keyboard shortcuts, etc.</a>
<label> </li>
@T("Use existing layout:") @if (Model.Templates.Any()) {
@Html.DropDownListFor(x => x.TemplateId, options, T("(None)").Text) var options = Model.Templates.Select(x => new SelectListItem { Text = Html.ItemDisplayText(x).ToString(), Value = x.Id.ToString(CultureInfo.InvariantCulture), Selected = x.Id == Model.TemplateId });
</label> <li>
</div> <div class="template-picker">
</li> <label>
} @T("Use existing layout:")
</ol> @Html.DropDownListFor(x => x.TemplateId, options, T("(None)").Text)
</div> </label>
</div>
</li>
}
</ol>
</div> </div>
<div class="layout-editor-holder"> <div class="layout-editor-holder">
<orc-layout-editor model="window.layoutEditor" ng-app="LayoutEditor" /> <orc-layout-editor model="window.layoutEditor" ng-app="LayoutEditor" />
</div> </div>
</fieldset> @Display.DialogTemplate(Name: "Layout")
<div class="trash"></div> <div class="layout-editor-help-dialog" title="Layout editor help">
@Display.DialogTemplate(Name: "Layout") <div class="help-row">
</div> <h3>Clipboard</h3>
<div class="help-column-full">
<p>Elements (including containers) can be cut, copied and pasted using the standard clipboard shortcuts (<code>Ctrl+X</code> / <code>Ctrl+C</code> / <code>Ctrl-V</code> on Windows, <code>⌘+X</code> / <code>⌘+C</code> / <code>⌘+V</code> on Mac OS).</p>
<p>On browsers that support native clipboard events, clipboard operations can be performed across different layout editor instances, in different tabs or browser windows. Text content can also be pasted into other applications.</p>
<p>On other browsers, clipboard operations work only within the same layout editor instance.</p>
</div>
</div>
<div class="help-row">
<h3>Keyboard shortcuts</h3>
<div class="help-column-half">
<h4>Resizing columns</h4>
<table>
<tbody>
<tr>
<td><code>Alt+Left</code></td>
<td>Moves the left edge of the focused column left</td>
</tr>
<tr>
<td><code>Alt+Right</code></td>
<td>Moves the left edge of the focused column right</td>
</tr>
<tr>
<td><code>Shift+Left</code></td>
<td>Moves the right edge of the focused column left</td>
</tr>
<tr>
<td><code>Shift+Right</code></td>
<td>Moves the right edge of the focused column right</td>
</tr>
</tbody>
</table>
<p>The <code>Alt</code> and <code>Shift</code> keys can also be combined to move both edges simultaneously.</p>
</div>
<div class="help-column-half">
<h4>Focus</h4>
<table>
<tbody>
<tr>
<td><code>Up</code></td>
<td>Moves focus to the previous element (above)</td>
</tr>
<tr>
<td><code>Down</code></td>
<td>Moves focus to the next element (below)</td>
</tr>
<tr>
<td><code>Left</code></td>
<td>Moves focus to the previous column (to the left)</td>
</tr>
<tr>
<td><code>Right</code></td>
<td>Moves focus to the next column (to the right)</td>
</tr>
<tr>
<td><code>Alt+Up</code></td>
<td>Moves focus to the parent element</td>
</tr>
<tr>
<td><code>Alt+Down</code></td>
<td>Moves focus to the first child element</td>
</tr>
</tbody>
</table>
</div>
<div class="help-column-half">
<h4>Editing</h4>
<table>
<tbody>
<tr>
<td><code>Enter</code></td>
<td>Opens the content editor of the focused element</td>
</tr>
<tr>
<td><code>Space</code></td>
<td>Opens the properties popup of the focused element</td>
</tr>
<tr>
<td><code>Esc</code></td>
<td>Closes the properties popup of the focused element</td>
</tr>
<tr>
<td><code>Del</code></td>
<td>Deletes the focused element</td>
</tr>
</tbody>
</table>
</div>
<div class="help-column-half">
<h4>Moving</h4>
<table>
<tbody>
<tr>
<td><code>Ctrl+Up</code></td>
<td>Moves (reorders) the focused element up</td>
</tr>
<tr>
<td><code>Ctrl+Down</code></td>
<td>Moves (reorders) the focused element down</td>
</tr>
<tr>
<td><code>Ctrl+Left</code></td>
<td>Moves (reorders) the focused column left</td>
</tr>
<tr>
<td><code>Ctrl+Right</code></td>
<td>Moves (reorders) the focused column right</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="help-row">
<h3>Drag and drop</h3>
<section class="help-column-full">
<p>Drag any existing element to reorder within its parent.</p>
<p>Drag a new element from the toolbox and drop it within a compatible container.</p>
<p>Drag the left and right edges of a focused column to resize the column. By default any adjacent column will be attached and resized accordingly; holding down the <code>Alt</code> key while resizing unattaches from the adjacent column and instead modifies the offset between.</p>
</section>
</div>
</div>
</div>
</fieldset>

View File

@@ -58,7 +58,9 @@ namespace Orchard.Localization.Drivers {
protected override DriverResult Editor(LocalizationPart part, IUpdateModel updater, dynamic shapeHelper) { protected override DriverResult Editor(LocalizationPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditLocalizationViewModel(); 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); _localizationService.SetContentCulture(part, model.SelectedCulture);
} }

View File

@@ -71,11 +71,12 @@ $(function () {
var listWidth = $('#media-library-main-list').width(); var listWidth = $('#media-library-main-list').width();
var listHeight = $('#media-library-main-list').height(); 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 draftText = $("#media-library").data("draft-text");
var itemsPerRow = Math.floor(listWidth / itemSize); var itemsPerRow = Math.floor(listWidth / itemWidth);
var itemsPerColumn = Math.ceil(listHeight / itemSize); var itemsPerColumn = Math.ceil(listHeight / itemHeight);
var pageCount = itemsPerRow * itemsPerColumn; var pageCount = itemsPerRow * itemsPerColumn;

View File

@@ -8,18 +8,30 @@ using Orchard.MultiTenancy.Services;
namespace Orchard.MultiTenancy.Commands { namespace Orchard.MultiTenancy.Commands {
public class TenantCommand : DefaultOrchardCommandHandler { public class TenantCommand : DefaultOrchardCommandHandler {
private readonly ITenantService _tenantService; private readonly ITenantService _tenantService;
private readonly string[] _validDataProviderNames = new[] { "SqlCe", "SqlServer", "MySql", "PostgreSql" };
public TenantCommand(ITenantService tenantService) { public TenantCommand(ITenantService tenantService) {
_tenantService = tenantService; _tenantService = tenantService;
} }
[OrchardSwitch] [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] [OrchardSwitch]
public string UrlPrefix { get; set; } 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")] [CommandName("tenant list")]
public void List() { public void List() {
Context.Output.WriteLine(T("List of tenants")); Context.Output.WriteLine(T("List of tenants"));
@@ -28,61 +40,157 @@ namespace Orchard.MultiTenancy.Commands {
var tenants = _tenantService.GetTenants(); var tenants = _tenantService.GetTenants();
foreach (var tenant in tenants) { foreach (var tenant in tenants) {
Context.Output.WriteLine(T("Name: ") + tenant.Name); 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("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("---------------------------")); Context.Output.WriteLine(T("---------------------------"));
} }
} }
[CommandHelp("tenant add <tenantName> /Host:<hostname> /UrlPrefix:<url prefix>\r\n\t" + [CommandHelp("tenant info <tenantName>\r\n\t" + "Display the current settings for a tenant.")]
"Create new tenant named <tenantName> on the site")] [CommandName("tenant info")]
[CommandName("tenant add")] public void Info(string tenantName) {
[OrchardSwitches("Host,UrlPrefix")] var tenant = _tenantService.GetTenants().FirstOrDefault(x => x.Name == tenantName);
public void Create(string tenantName) { if (tenant == null) {
Context.Output.WriteLine(T("Creating tenant")); 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 <tenantName> /DataProvider:<provider> /DataConnectionString:<connectionString> /DataTablePrefix:<prefix> /UrlHost:<hostname> /UrlPrefix:<prefix> /Themes:<themes> /Modules:<modules>\r\n\t" + "Create a new tenant named <tenantName> on the site.\r\n" + "The <themes> and <modules> 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.")); Context.Output.WriteLine(T("Invalid tenant name. Must contain characters only and no spaces."));
return; return;
} }
if (_tenantService.GetTenants().Any(tenant => string.Equals(tenant.Name, tenantName, StringComparison.OrdinalIgnoreCase))) { 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)); 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; return;
} }
_tenantService.CreateTenant( _tenantService.CreateTenant(
new ShellSettings { new ShellSettings {
Name = tenantName, Name = tenantName,
RequestUrlHost = Host, State = TenantState.Uninitialized,
RequestUrlPrefix = UrlPrefix, DataProvider = DataProvider,
State = TenantState.Uninitialized DataConnectionString = DataConnectionString,
}); DataTablePrefix = DataTablePrefix,
RequestUrlHost = UrlHost,
RequestUrlPrefix = UrlPrefix,
Themes = Themes.Split(';'),
Modules = Modules.Split(';')
});
} }
[CommandHelp("tenant info <tenantName>\r\n\t" + "Display settings for a tenant")] [CommandHelp("tenant update <tenantName> /DataProvider:<SqlCe|SqlServer|MySql|PostgreSql> /DataConnectionString:<connectionString> /DataTablePrefix:<prefix> /UrlHost:<hostname> /UrlPrefix:<prefix> /Themes:<themes> /Modules:<modules>\r\n\t" + "Update the settings of the existing tenant <tenantName>.\r\n" + "The <themes> and <modules> parameters should be semicolon-separated lists of module names.")]
[CommandName("tenant info")] [CommandName("tenant update")]
public void Info(string tenantName) { [OrchardSwitches("DataProvider,DataConnectionString,DataTablePrefix,UrlHost,UrlPrefix,Themes,Modules")]
ShellSettings tenant = _tenantService.GetTenants().Where(x => x.Name == tenantName).FirstOrDefault(); 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) { 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:")); if (DataProvider != null && !_validDataProviderNames.Contains(DataProvider)) {
Context.Output.WriteLine(T("---------------------------")); Context.Output.WriteLine(T("Invalid value '{0}' for parameter DataProvider. Expect one of the following: {1}", DataProvider, String.Join(", ", _validDataProviderNames)));
Context.Output.WriteLine(T("Name: ") + tenant.Name); return;
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("---------------------------"));
} }
_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 <tenantName>\r\n\t" + "Disable the tenant <tenantName>.")]
[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 <tenantName>\r\n\t" + "Enable the tenant <tenantName>.")]
[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 <tenantName> /DropDatabaseTables:<true|false>\r\n\t" + "Reset the tenant <tenantName> 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);
} }
} }
} }

View File

@@ -19,7 +19,7 @@ namespace Orchard.MultiTenancy.Controllers {
public AdminController(ITenantService tenantService, IOrchardServices orchardServices, ShellSettings shellSettings) { public AdminController(ITenantService tenantService, IOrchardServices orchardServices, ShellSettings shellSettings) {
_tenantService = tenantService; _tenantService = tenantService;
_thisShellSettings = shellSettings; _thisShellSettings = shellSettings;
Services = orchardServices; Services = orchardServices;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
@@ -30,32 +30,34 @@ namespace Orchard.MultiTenancy.Controllers {
public ILogger Logger { get; set; } public ILogger Logger { get; set; }
public ActionResult Index() { public ActionResult Index() {
return View(new TenantsIndexViewModel { TenantSettings = _tenantService.GetTenants() }); return View(new TenantsIndexViewModel {
TenantSettings = _tenantService.GetTenants()
});
} }
public ActionResult Add() { 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(); return new HttpUnauthorizedResult();
if ( !EnsureDefaultTenant() ) if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var model = new TenantAddViewModel(); var viewModel = new TenantAddViewModel();
// fetches all available themes and modules // Fetches all available themes and modules.
model.Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry { ThemeId = x.Id, ThemeName = x.Name }).ToList(); viewModel.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(); viewModel.Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry { ModuleId = x.Id, ModuleName = x.Name }).ToList();
return View(model); return View(viewModel);
} }
[HttpPost, ActionName("Add")] [HttpPost, ActionName("Add")]
public ActionResult AddPOST(TenantAddViewModel viewModel) { public ActionResult AddPost(TenantAddViewModel viewModel) {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't create tenant"))) { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("You don't have permission to create tenants."))) {
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
} }
if (!EnsureDefaultTenant()) { if (!IsExecutingInDefaultTenant()) {
return new HttpUnauthorizedResult(); 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); 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+$")) { 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); 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"); return RedirectToAction("Index");
} }
catch (ArgumentException exception) { catch (ArgumentException ex) {
Services.Notifier.Error(T("Creating Tenant failed: {0}", exception.Message)); Logger.Error(ex, "Error while creating tenant.");
Services.Notifier.Error(T("Tenant creation failed with error: {0}", ex.Message));
return View(viewModel); return View(viewModel);
} }
} }
public ActionResult Edit(string name) { 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(); return new HttpUnauthorizedResult();
if ( !EnsureDefaultTenant() ) if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name); var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
if (tenant == null) if (tenant == null)
return HttpNotFound(); return HttpNotFound();
return View(new TenantEditViewModel { return View(new TenantEditViewModel {
Name = tenant.Name, Name = tenant.Name,
RequestUrlHost = tenant.RequestUrlHost, RequestUrlHost = tenant.RequestUrlHost,
RequestUrlPrefix = tenant.RequestUrlPrefix, RequestUrlPrefix = tenant.RequestUrlPrefix,
DataProvider = tenant.DataProvider, DataProvider = tenant.DataProvider,
DatabaseConnectionString = tenant.DataConnectionString, DatabaseConnectionString = tenant.DataConnectionString,
DatabaseTablePrefix = tenant.DataTablePrefix, DatabaseTablePrefix = tenant.DataTablePrefix,
State = tenant.State, State = tenant.State,
Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry { Themes = _tenantService.GetInstalledThemes().Select(x => new ThemeEntry {
ThemeId = x.Id, ThemeId = x.Id,
ThemeName = x.Name, ThemeName = x.Name,
Checked = tenant.Themes.Contains(x.Id) Checked = tenant.Themes.Contains(x.Id)
}).ToList(), }).ToList(),
Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry { Modules = _tenantService.GetInstalledModules().Select(x => new ModuleEntry {
ModuleId = x.Id, ModuleId = x.Id,
ModuleName = x.Name, ModuleName = x.Name,
Checked = tenant.Modules.Contains(x.Id) Checked = tenant.Modules.Contains(x.Id)
}).ToList() }).ToList()
}); });
} }
[HttpPost, ActionName("Edit")] [HttpPost, ActionName("Edit")]
public ActionResult EditPost(TenantEditViewModel viewModel) { 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(); return new HttpUnauthorizedResult();
if ( !EnsureDefaultTenant() ) if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == viewModel.Name); var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == viewModel.Name);
if (tenant == null) if (tenant == null)
return HttpNotFound(); return HttpNotFound();
else if (tenant.Name == _thisShellSettings.Name)
return new HttpUnauthorizedResult();
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
return View(viewModel); return View(viewModel);
@@ -163,18 +167,19 @@ namespace Orchard.MultiTenancy.Controllers {
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
catch (Exception exception) { catch (Exception ex) {
Services.Notifier.Error(T("Failed to edit tenant: {0} ", exception.Message)); Logger.Error(ex, "Error while editing tenant.");
Services.Notifier.Error(T("Failed to edit tenant: {0} ", ex.Message));
return View(viewModel); return View(viewModel);
} }
} }
[HttpPost] [HttpPost]
public ActionResult Disable(string name) { 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(); return new HttpUnauthorizedResult();
if ( !EnsureDefaultTenant() ) if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name); var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
@@ -189,10 +194,10 @@ namespace Orchard.MultiTenancy.Controllers {
[HttpPost] [HttpPost]
public ActionResult Enable(string name) { 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(); return new HttpUnauthorizedResult();
if ( !EnsureDefaultTenant() ) if (!IsExecutingInDefaultTenant())
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name); var tenant = _tenantService.GetTenants().FirstOrDefault(ss => ss.Name == name);
@@ -205,7 +210,55 @@ namespace Orchard.MultiTenancy.Controllers {
return RedirectToAction("Index"); 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; return _thisShellSettings.Name == ShellSettings.DefaultName;
} }
} }

View File

@@ -25,6 +25,7 @@
<IISExpressAnonymousAuthentication /> <IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication /> <IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode /> <IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@@ -48,6 +49,12 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Autofac">
<HintPath>..\..\..\..\lib\autofac\Autofac.dll</HintPath>
</Reference>
<Reference Include="NHibernate">
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations"> <Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework> <RequiredTargetFramework>3.5</RequiredTargetFramework>
@@ -73,9 +80,11 @@
<Compile Include="Controllers\AdminController.cs" /> <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Extensions\UrlHelperExtensions.cs" /> <Compile Include="Extensions\UrlHelperExtensions.cs" />
<Compile Include="Routes.cs" /> <Compile Include="Routes.cs" />
<Compile Include="Services\ITenantResetEventHandler.cs" />
<Compile Include="Services\ITenantService.cs" /> <Compile Include="Services\ITenantService.cs" />
<Compile Include="Services\TenantService.cs" /> <Compile Include="Services\TenantService.cs" />
<Compile Include="ViewModels\ModuleEntry.cs" /> <Compile Include="ViewModels\ModuleEntry.cs" />
<Compile Include="ViewModels\TenantResetViewModel.cs" />
<Compile Include="ViewModels\TenantEditViewModel.cs" /> <Compile Include="ViewModels\TenantEditViewModel.cs" />
<Compile Include="ViewModels\TenantAddViewModel.cs" /> <Compile Include="ViewModels\TenantAddViewModel.cs" />
<Compile Include="ViewModels\TenantsIndexViewModel.cs" /> <Compile Include="ViewModels\TenantsIndexViewModel.cs" />
@@ -123,6 +132,9 @@
</SubType> </SubType>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Views\Admin\Reset.cshtml" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -0,0 +1,10 @@
using Orchard.Events;
namespace Orchard.MultiTenancy.Services {
/// <summary>
/// An event handler interface that allows implementers to execute code when a tenant is being reset.
/// </summary>
public interface ITenantResetEventHandler : IEventHandler {
void Resetting();
}
}

View File

@@ -5,23 +5,35 @@ using Orchard.Environment.Extensions.Models;
namespace Orchard.MultiTenancy.Services { namespace Orchard.MultiTenancy.Services {
public interface ITenantService : IDependency { public interface ITenantService : IDependency {
/// <summary> /// <summary>
/// Retrieves all tenants' shell settings. /// Retrieves ShellSettings objects for all tenants.
/// </summary> /// </summary>
/// <returns>All tenants' shell settings.</returns>
IEnumerable<ShellSettings> GetTenants(); IEnumerable<ShellSettings> GetTenants();
/// <summary> /// <summary>
/// Creates a new tenant. /// Creates a new tenant.
/// </summary> /// </summary>
/// <param name="settings">Shell settings of the tenant.</param> /// <param name="settings">A ShellSettings object specifying the settings for the new tenant.</param>
void CreateTenant(ShellSettings settings); void CreateTenant(ShellSettings settings);
/// <summary> /// <summary>
/// Updates the shell settings of a tenant. /// Updates the settings of a tenant.
/// </summary> /// </summary>
/// <param name="settings">Shell settings of the tenant.</param> /// <param name="settings">The new ShellSettings object for the tenant.</param>
void UpdateTenant(ShellSettings settings); void UpdateTenant(ShellSettings settings);
/// <summary>
/// Resets a tenant to its uninitialized state.
/// </summary>
/// <param name="tenantName">A ShellSettings object for the tenant to reset.</param>
/// <param name="dropDatabaseTables">A boolean indicated whether tenant database tables should be dropped also.</param>
void ResetTenant(ShellSettings settings, bool dropDatabaseTables);
/// <summary>
/// Returns a list of all known database tables in a tenant.
/// </summary>
/// <returns>A ShellSettings object for the tenant.</returns>
IEnumerable<string> GetTenantDatabaseTableNames(ShellSettings settings);
/// <summary> /// <summary>
/// Returns a list of all installed themes. /// Returns a list of all installed themes.
/// </summary> /// </summary>

View File

@@ -1,21 +1,36 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Orchard.Environment.Configuration; using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.Environment.Extensions; 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 { namespace Orchard.MultiTenancy.Services {
public class TenantService : ITenantService { public class TenantService : ITenantService {
private readonly IShellSettingsManager _shellSettingsManager; private readonly IShellSettingsManager _shellSettingsManager;
private readonly IExtensionManager _extensionManager; private readonly IExtensionManager _extensionManager;
private readonly IShellContextFactory _shellContextFactory;
private readonly IShellContainerFactory _shellContainerFactory;
public TenantService( public TenantService(
IShellSettingsManager shellSettingsManager, IShellSettingsManager shellSettingsManager,
IExtensionManager extensionManager) { IExtensionManager extensionManager,
IShellContextFactory shellContextFactory,
IShellContainerFactory shellContainerFactory) {
_shellSettingsManager = shellSettingsManager; _shellSettingsManager = shellSettingsManager;
_extensionManager = extensionManager; _extensionManager = extensionManager;
_shellContextFactory = shellContextFactory;
_shellContainerFactory = shellContainerFactory;
Logger = NullLogger.Instance;
} }
public ILogger Logger { get; set; }
public IEnumerable<ShellSettings> GetTenants() { public IEnumerable<ShellSettings> GetTenants() {
return _shellSettingsManager.LoadSettings(); return _shellSettingsManager.LoadSettings();
} }
@@ -28,16 +43,32 @@ namespace Orchard.MultiTenancy.Services {
_shellSettingsManager.SaveSettings(settings); _shellSettingsManager.SaveSettings(settings);
} }
/// <summary> public void ResetTenant(ShellSettings settings, bool dropDatabaseTables) {
/// Loads only installed themes if (settings.State != TenantState.Disabled)
/// </summary> 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<string> GetTenantDatabaseTableNames(ShellSettings settings) {
IEnumerable<string> result = null;
ExecuteOnTenantScope(settings, environment => {
result = GetTenantDatabaseTableNames(environment);
});
return result;
}
public IEnumerable<ExtensionDescriptor> GetInstalledThemes() { public IEnumerable<ExtensionDescriptor> GetInstalledThemes() {
return GetThemes(_extensionManager.AvailableExtensions()); return GetThemes(_extensionManager.AvailableExtensions());
} }
/// <summary>
/// Loads only installed modules
/// </summary>
public IEnumerable<ExtensionDescriptor> GetInstalledModules() { public IEnumerable<ExtensionDescriptor> GetInstalledModules() {
return _extensionManager.AvailableExtensions().Where(descriptor => DefaultExtensionTypes.IsModule(descriptor.ExtensionType)); return _extensionManager.AvailableExtensions().Where(descriptor => DefaultExtensionTypes.IsModule(descriptor.ExtensionType));
} }
@@ -58,5 +89,45 @@ namespace Orchard.MultiTenancy.Services {
} }
return themes; return themes;
} }
private void ExecuteOnTenantScope(ShellSettings settings, Action<IWorkContextScope> action) {
var shellContext = _shellContextFactory.CreateShellContext(settings);
using (var container = _shellContainerFactory.CreateContainer(shellContext.Settings, shellContext.Blueprint)) {
using (var environment = container.CreateWorkContextScope()) {
action(environment);
}
}
}
private IEnumerable<string> GetTenantDatabaseTableNames(IWorkContextScope environment) {
var sessionFactoryHolder = environment.Resolve<ISessionFactoryHolder>();
var schemaBuilder = new SchemaBuilder(environment.Resolve<IDataMigrationInterpreter>());
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<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);
}
}
}
private void ExecuteResetEventHandlers(IWorkContextScope environment) {
var handler = environment.Resolve<ITenantResetEventHandler>();
handler.Resetting();
}
} }
} }

View File

@@ -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<string>();
}
[Required]
public string Name { get; set; }
public bool DropDatabaseTables { get; set; }
public IEnumerable<string> DatabaseTableNames { get; set; }
}
}

View File

@@ -1,7 +1,8 @@
@model Orchard.Environment.Configuration.ShellSettings @model Orchard.Environment.Configuration.ShellSettings
@using Orchard.MultiTenancy.Extensions; @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) @Html.HiddenFor(ss => ss.Name)
<button type="submit">@T("Resume")</button> <button type="submit">@T("Resume")</button>
} } @T(" | ")
@Html.ActionLink(T("Reset").ToString(), "Reset", new { name = Model.Name, area = "Orchard.MultiTenancy" })

View File

@@ -1,7 +1,7 @@
@model Orchard.Environment.Configuration.ShellSettings @model Orchard.Environment.Configuration.ShellSettings
@using Orchard.MultiTenancy.Extensions; @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) @Html.HiddenFor(ss => ss.Name)
<button type="submit">@T("Suspend")</button> <button type="submit">@T("Suspend")</button>
} }

View File

@@ -7,30 +7,31 @@
Layout.Title = T("List of Site's Tenants").ToString(); Layout.Title = T("List of Site's Tenants").ToString();
} }
<div class="manage">@Html.ActionLink(T("Add a Tenant").ToString(), "Add", new {area = "Orchard.MultiTenancy"}, new { @class = "button primaryAction" })</div> <div class="manage">@Html.ActionLink(T("Add a Tenant").ToString(), "Add", new { area = "Orchard.MultiTenancy" }, new { @class = "button primaryAction" })</div>
<ul class="contentItems tenants"> <ul class="contentItems tenants">
@foreach (var tenant in Model.TenantSettings) { @foreach (var tenant in Model.TenantSettings) {
<li class="tenant @tenant.State"> <li class="tenant @tenant.State">
<div class="summary"> <div class="summary">
<div class="properties"> <div class="properties">
<h3>@tenant.Name @if (!string.IsNullOrEmpty(tenant.RequestUrlHost)) { <h3>
var tenantClone = new ShellSettings(tenant); @tenant.Name @if (!string.IsNullOrEmpty(tenant.RequestUrlHost)) {
foreach (var t in tenant.RequestUrlHost.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { var tenantClone = new ShellSettings(tenant);
tenantClone.RequestUrlHost = t; foreach (var t in tenant.RequestUrlHost.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
var url = Url.Tenant(tenantClone); tenantClone.RequestUrlHost = t;
<span class="tenantHost"> - @Html.Link(url, url)</span> var url = Url.Tenant(tenantClone);
} <span class="tenantHost"> - @Html.Link(url, url)</span>
} }
</h3> }
</div> </h3>
<div class="related"> </div>
@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 <div class="related">
@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; var t = tenant;
@Html.DisplayFor(m => t, string.Format("ActionsFor{0}", tenant.State.ToString()), "") @T(" | ") @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.ActionLink(T("Edit").ToString(), "Edit", new { name = tenant.Name, area = "Orchard.MultiTenancy" })
</div>
</div> </div>
</div> </li>
</li>
} }
</ul> </ul>

View File

@@ -0,0 +1,30 @@
@model Orchard.MultiTenancy.ViewModels.TenantResetViewModel
@{
Layout.Title = T("Reset Tenant").ToString();
Script.Require("jQuery");
Script.Include(Url.Content("~/Themes/TheAdmin/Scripts/admin.js")).AtFoot();
}
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<fieldset>
<p>@T("This will reset the tenant <strong>{0}</strong> to its uninitialized state, allowing you to set it up again.", Model.Name)</p>
</fieldset>
<fieldset>
@Html.CheckBoxFor(m => Model.DropDatabaseTables)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.DropDatabaseTables)">@("Also delete tenant database tables:")</label>
<ul style="margin-left: 4em; margin-top: 1em; -webkit-column-width: 24em; -moz-column-width: 24em; column-width: 24em;">
@foreach (var tableName in Model.DatabaseTableNames) {
<li><span class="hint">@tableName</span></li>
}
</ul>
</fieldset>
<fieldset>
<button class="primaryAction" type="submit">@T("Reset")</button>
</fieldset>
}

View File

@@ -3,12 +3,6 @@
namespace Orchard.Recipes { namespace Orchard.Recipes {
public class Migrations : DataMigrationImpl { public class Migrations : DataMigrationImpl {
public int Create() { public int Create() {
//SchemaBuilder.CreateTable("RecipeResultRecord", table => table
// .Column<int>("Id", c => c.PrimaryKey().Identity())
// .Column<string>("ExecutionId", c => c.WithLength(128).Unique().NotNull())
// .Column<bool>("IsCompleted", c => c.NotNull())
//);
SchemaBuilder.CreateTable("RecipeStepResultRecord", table => table SchemaBuilder.CreateTable("RecipeStepResultRecord", table => table
.Column<int>("Id", c => c.PrimaryKey().Identity()) .Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<string>("ExecutionId", c => c.WithLength(128).NotNull()) .Column<string>("ExecutionId", c => c.WithLength(128).NotNull())
@@ -18,14 +12,11 @@ namespace Orchard.Recipes {
.Column<string>("ErrorMessage", c => c.Unlimited().Nullable()) .Column<string>("ErrorMessage", c => c.Unlimited().Nullable())
); );
SchemaBuilder.AlterTable("RecipeStepResultRecord", table => table SchemaBuilder.AlterTable("RecipeStepResultRecord", table => {
.CreateIndex("IDX_RecipeStepResultRecord_ExecutionId", "ExecutionId") table.CreateIndex("IDX_RecipeStepResultRecord_ExecutionId", "ExecutionId");
); table.CreateIndex("IDX_RecipeStepResultRecord_ExecutionId_StepName", "ExecutionId", "StepName");
});
SchemaBuilder.AlterTable("RecipeStepResultRecord", table => table
.CreateIndex("IDX_RecipeStepResultRecord_ExecutionId_StepName", "ExecutionId", "StepName")
);
return 1; return 1;
} }
} }

View File

@@ -15,8 +15,8 @@
/* Component containers /* Component containers
----------------------------------*/ ----------------------------------*/
.ui-widget { .ui-widget {
font-family: Verdana,Arial,sans-serif; font-family: inherit;
font-size: 1.1em; font-size: inherit;
} }
.ui-widget .ui-widget { .ui-widget .ui-widget {
font-size: 1em; font-size: 1em;
@@ -25,7 +25,7 @@
.ui-widget select, .ui-widget select,
.ui-widget textarea, .ui-widget textarea,
.ui-widget button { .ui-widget button {
font-family: Verdana,Arial,sans-serif; font-family: inherit;
font-size: 1em; font-size: 1em;
} }
.ui-widget-content { .ui-widget-content {

File diff suppressed because one or more lines are too long

View File

@@ -153,7 +153,7 @@ namespace Orchard.ContentManagement {
/// <returns>The string representation of the value.</returns> /// <returns>The string representation of the value.</returns>
public static string ToString<T>(T value) { public static string ToString<T>(T value) {
var type = typeof(T); var type = typeof(T);
if (type == typeof(string)) { if (type == typeof(string) || type == typeof(char)) {
return Convert.ToString(value); return Convert.ToString(value);
} }
if ((!type.IsValueType || Nullable.GetUnderlyingType(type) != null) && if ((!type.IsValueType || Nullable.GetUnderlyingType(type) != null) &&
@@ -250,6 +250,9 @@ namespace Orchard.ContentManagement {
if (type == typeof(double)) return (T)(object)double.NegativeInfinity; if (type == typeof(double)) return (T)(object)double.NegativeInfinity;
throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name)); throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name));
} }
if (type == typeof(char) || type == typeof(char?)) {
return (T)(object)char.Parse(value);
}
if (type == typeof(int) || type == typeof(int?)) { if (type == typeof(int) || type == typeof(int?)) {
return (T)(object)int.Parse(value, CultureInfo.InvariantCulture); return (T)(object)int.Parse(value, CultureInfo.InvariantCulture);
} }
@@ -356,4 +359,4 @@ namespace Orchard.ContentManagement {
} }
} }
} }
} }

View File

@@ -20,7 +20,10 @@ namespace Orchard.Data.Migration.Schema {
dbType = DbType.Boolean; dbType = DbType.Boolean;
break; break;
default: default:
Enum.TryParse(Type.GetTypeCode(type).ToString(), true, out dbType); if(type == typeof(Guid))
dbType = DbType.Guid;
else
Enum.TryParse(Type.GetTypeCode(type).ToString(), true, out dbType);
break; break;
} }