mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Merge branch 'dev' into issue/8686-dev
This commit is contained in:
@@ -1,30 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Contents.Extensions;
|
||||
using Orchard.Data;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Environment.Configuration;
|
||||
|
||||
namespace Orchard.Core.Common {
|
||||
public class Migrations : DataMigrationImpl {
|
||||
private readonly IRepository<IdentityPartRecord> _identityPartRepository;
|
||||
private readonly ISessionFactoryHolder _sessionFactoryHolder;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
|
||||
private HashSet<string> _existingIndexNames = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// When upgrading from "1.10.x" branch code committed after 1.10.3 to "dev" branch code or 1.11, merge
|
||||
/// conflicts between "1.10.x" and "dev" caused by running the same migration steps in a different order need to
|
||||
/// be resolved by instructing this migration to decide which steps need to be executed. If you're upgrading
|
||||
/// under these conditions and your pre-upgrade migration version is 7 or 8, use HostComponents.config to
|
||||
/// override one of these properties to true.
|
||||
/// </summary>
|
||||
public bool IsUpgradingFromOrchard_1_10_x_Version_7 { get; set; }
|
||||
public bool IsUpgradingFromOrchard_1_10_x_Version_8 { get; set; }
|
||||
|
||||
|
||||
public Migrations(
|
||||
IRepository<IdentityPartRecord> identityPartRepository,
|
||||
ISessionFactoryHolder sessionFactoryHolder,
|
||||
ShellSettings shellSettings) {
|
||||
public Migrations(IRepository<IdentityPartRecord> identityPartRepository) {
|
||||
_identityPartRepository = identityPartRepository;
|
||||
_sessionFactoryHolder = sessionFactoryHolder;
|
||||
_shellSettings = shellSettings;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,34 +164,59 @@ namespace Orchard.Core.Common {
|
||||
return 6;
|
||||
}
|
||||
|
||||
// When upgrading from version 6 of 1.10.x (up until version 9), we'll just execute the same steps, but in a
|
||||
// different order.
|
||||
public int UpdateFrom6() {
|
||||
// This is the original step of the dev branch.
|
||||
AddIndexForIdentityPartRecordIdentifier();
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
public int UpdateFrom7() {
|
||||
// This is the original step of the dev branch.
|
||||
AddIndexForCommonPartRecordContainerId();
|
||||
|
||||
// When upgrading from version 7 of 1.10.x, this index isn't created yet, so we need to run this step
|
||||
// "again". On the other hand, AddIndexesForCommonPartOwner in UpdateFrom8 won't do anything, because those
|
||||
// indexes were added in the 1.10.x version of UpdateFrom6.
|
||||
AddIndexForIdentityPartRecordIdentifier();
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int UpdateFrom8() {
|
||||
// This is the original step of the dev branch.
|
||||
AddIndexesForCommonPartOwner();
|
||||
if (IsUpgradingFromOrchard_1_10_x_Version_7) {
|
||||
AddIndexForIdentityPartRecordIdentifier();
|
||||
}
|
||||
else if (IsUpgradingFromOrchard_1_10_x_Version_8) {
|
||||
AddIndexForCommonPartRecordContainerId();
|
||||
}
|
||||
else {
|
||||
// This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev.
|
||||
|
||||
// When upgrading from version 8 of 1.10.x, this index isn't created yet, so we need to run this step
|
||||
// "again"
|
||||
AddIndexForCommonPartRecordContainerId();
|
||||
// Studying SQL Server query execution plans we noticed that when the system tries to find content items for
|
||||
// requests such as "The items of type TTT owned by me, ordered from the most recent" the existing indexes
|
||||
// are not used. SQL Server does an index scan on the Primary key for CommonPartRecord. This may lead to
|
||||
// annoying deadlocks when there are two concurrent transactions that are doing both this kind of query as
|
||||
// well as an update (or insert) in the CommonPartRecord. Tests show that this can be easily fixed by adding
|
||||
// a non-clustered index with these keys: OwnerId, {one of PublishedUTC, ModifiedUTC, CreatedUTC}. That
|
||||
// means we need three indexes (one for each DateTime) to support ordering on either of them.
|
||||
|
||||
// The queries we analyzed look like (in pseudo sql)
|
||||
// SELECT TOP (N) *
|
||||
// FROM
|
||||
// ContentItemVersionRecord this_
|
||||
// inner join ContentItemRecord contentite1_ on this_.ContentItemRecord_id=contentite1_.Id
|
||||
// inner join CommonPartRecord commonpart2_ on contentite1_.Id=commonpart2.Id
|
||||
// left outer join ContentTypeRecord contenttyp6_ on contentite1_.ContentType_id=contenttyp6_.Id
|
||||
// WHERE
|
||||
// contentite1.ContentType_id = {TTT}
|
||||
// and commonpart2_.OwnerId = {userid}
|
||||
// and this_.Published = 1
|
||||
// ORDER BY
|
||||
// commonpart2_PublishedUtc desc
|
||||
var createdUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByCreation";
|
||||
var modifiedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByModification";
|
||||
var publishedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByPublication";
|
||||
|
||||
SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => {
|
||||
table.CreateIndex(createdUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.CreatedUtc));
|
||||
table.CreateIndex(modifiedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.ModifiedUtc));
|
||||
table.CreateIndex(publishedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc));
|
||||
});
|
||||
}
|
||||
|
||||
return 9;
|
||||
}
|
||||
@@ -203,95 +225,17 @@ namespace Orchard.Core.Common {
|
||||
private void AddIndexForIdentityPartRecordIdentifier() {
|
||||
var indexName = $"IDX_{nameof(IdentityPartRecord)}_{nameof(IdentityPartRecord.Identifier)}";
|
||||
|
||||
if (IndexExists(nameof(IdentityPartRecord), indexName)) return;
|
||||
|
||||
SchemaBuilder.AlterTable(nameof(IdentityPartRecord), table => table.CreateIndex(
|
||||
indexName,
|
||||
nameof(IdentityPartRecord.Identifier)));
|
||||
|
||||
IndexCreated(nameof(IdentityPartRecord), indexName);
|
||||
}
|
||||
|
||||
// This change was originally UpdateFrom8 on 1.10.x and UpdateFrom7 on dev.
|
||||
private void AddIndexForCommonPartRecordContainerId() {
|
||||
var indexName = $"IDX_{nameof(CommonPartRecord)}_Container_id";
|
||||
|
||||
if (IndexExists(nameof(CommonPartRecord), indexName)) return;
|
||||
|
||||
// Container_Id is used in several queries like a foreign key.
|
||||
SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => table.CreateIndex(indexName, "Container_id"));
|
||||
|
||||
IndexCreated(nameof(CommonPartRecord), indexName);
|
||||
}
|
||||
|
||||
// This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev.
|
||||
private void AddIndexesForCommonPartOwner() {
|
||||
// Studying SQL Server query execution plans we noticed that when the system tries to find content items for
|
||||
// requests such as "The items of type TTT owned by me, ordered from the most recent" the existing indexes
|
||||
// are not used. SQL Server does an index scan on the Primary key for CommonPartRecord. This may lead to
|
||||
// annoying deadlocks when there are two concurrent transactions that are doing both this kind of query as
|
||||
// well as an update (or insert) in the CommonPartRecord. Tests show that this can be easily fixed by adding
|
||||
// a non-clustered index with these keys: OwnerId, {one of PublishedUTC, ModifiedUTC, CreatedUTC}. That
|
||||
// means we need three indexes (one for each DateTime) to support ordering on either of them.
|
||||
|
||||
// The queries we analyzed look like (in pseudo sql)
|
||||
// SELECT TOP (N) *
|
||||
// FROM
|
||||
// ContentItemVersionRecord this_
|
||||
// inner join ContentItemRecord contentite1_ on this_.ContentItemRecord_id=contentite1_.Id
|
||||
// inner join CommonPartRecord commonpart2_ on contentite1_.Id=commonpart2.Id
|
||||
// left outer join ContentTypeRecord contenttyp6_ on contentite1_.ContentType_id=contenttyp6_.Id
|
||||
// WHERE
|
||||
// contentite1.ContentType_id = {TTT}
|
||||
// and commonpart2_.OwnerId = {userid}
|
||||
// and this_.Published = 1
|
||||
// ORDER BY
|
||||
// commonpart2_PublishedUtc desc
|
||||
var createdUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByCreation";
|
||||
var modifiedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByModification";
|
||||
var publishedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByPublication";
|
||||
|
||||
if (IndexExists(nameof(CommonPartRecord), createdUtcIndexName)) return;
|
||||
|
||||
SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => {
|
||||
table.CreateIndex(createdUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.CreatedUtc));
|
||||
table.CreateIndex(modifiedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.ModifiedUtc));
|
||||
table.CreateIndex(publishedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc));
|
||||
});
|
||||
|
||||
IndexCreated(nameof(CommonPartRecord), createdUtcIndexName);
|
||||
IndexCreated(nameof(CommonPartRecord), modifiedUtcIndexName);
|
||||
IndexCreated(nameof(CommonPartRecord), publishedUtcIndexName);
|
||||
}
|
||||
|
||||
private bool IndexExists(string tableName, string indexName) {
|
||||
var tenantTablesPrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix)
|
||||
? string.Empty : $"{_shellSettings.DataTablePrefix}_";
|
||||
|
||||
if (!_existingIndexNames.Any()) {
|
||||
// Database-agnostic way of checking the existence of an index.
|
||||
using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) {
|
||||
var connection = session.Connection ?? throw new InvalidOperationException(
|
||||
"The database connection object should derive from DbConnection to check if an index exists.");
|
||||
|
||||
var indexes = connection.GetSchema("Indexes").Rows.Cast<DataRow>();
|
||||
|
||||
if (!string.IsNullOrEmpty(tenantTablesPrefix)) {
|
||||
indexes = indexes.Where(row => row["TABLE_NAME"].ToString().StartsWith(tenantTablesPrefix));
|
||||
}
|
||||
|
||||
_existingIndexNames = indexes.Select(row => $"{row["TABLE_NAME"]}.{row["INDEX_NAME"]}").ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
return _existingIndexNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{tenantTablesPrefix}{indexName}");
|
||||
}
|
||||
|
||||
private void IndexCreated(string tableName, string indexName) {
|
||||
var tenantTablesPrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix)
|
||||
? string.Empty : $"{_shellSettings.DataTablePrefix}_";
|
||||
|
||||
_existingIndexNames.Add($"{SchemaBuilder.TableDbName(tableName)}.{tenantTablesPrefix}{indexName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,26 @@ using System;
|
||||
using Orchard.Core.Contents.Settings;
|
||||
using Orchard.Security;
|
||||
using Orchard.Localization;
|
||||
using Orchard.CustomForms.Services;
|
||||
|
||||
namespace Orchard.CustomForms.Drivers {
|
||||
public class CustomFormPartDriver : ContentPartDriver<CustomFormPart> {
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
private readonly IAuthorizationService _authService;
|
||||
private readonly IEditorBuilderWrapper _editorBuilderWrapper;
|
||||
|
||||
public CustomFormPartDriver(
|
||||
IContentDefinitionManager contentDefinitionManager,
|
||||
IOrchardServices orchardServices,
|
||||
IAuthorizationService authService) {
|
||||
IAuthorizationService authService,
|
||||
IEditorBuilderWrapper editorBuilderWrapper) {
|
||||
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
_orchardServices = orchardServices;
|
||||
_authService = authService;
|
||||
_editorBuilderWrapper = editorBuilderWrapper;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
@@ -32,39 +38,43 @@ namespace Orchard.CustomForms.Drivers {
|
||||
|
||||
protected override DriverResult Display(CustomFormPart part, string displayType, dynamic shapeHelper) {
|
||||
// this method is used by the widget to render the form when it is displayed
|
||||
// Display CustomForm_Wrapper shape only if shape type is Detail.
|
||||
if (displayType.Equals("Detail")) {
|
||||
int contentId = 0;
|
||||
var queryString = _orchardServices.WorkContext.HttpContext.Request.QueryString;
|
||||
|
||||
int contentId = 0;
|
||||
var queryString = _orchardServices.WorkContext.HttpContext.Request.QueryString;
|
||||
if (queryString.AllKeys.Contains("contentId")) {
|
||||
int.TryParse(queryString["contentId"], out contentId);
|
||||
}
|
||||
|
||||
if (queryString.AllKeys.Contains("contentId")) {
|
||||
int.TryParse(queryString["contentId"], out contentId);
|
||||
}
|
||||
ContentItem contentItem;
|
||||
if (contentId > 0) {
|
||||
contentItem = _orchardServices.ContentManager.Get(contentId);
|
||||
|
||||
ContentItem contentItem;
|
||||
if (contentId > 0) {
|
||||
contentItem = _orchardServices.ContentManager.Get(contentId);
|
||||
if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.EditContent, contentItem))
|
||||
return null;
|
||||
} else {
|
||||
contentItem = _orchardServices.ContentManager.New(part.ContentType);
|
||||
|
||||
if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.EditContent, contentItem))
|
||||
if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem))
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentItem == null || contentItem.ContentType != part.ContentType)
|
||||
return null;
|
||||
} else {
|
||||
contentItem = _orchardServices.ContentManager.New(part.ContentType);
|
||||
|
||||
if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem))
|
||||
if (!contentItem.Has<ICommonPart>()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ContentShape("Parts_CustomForm_Wrapper", () => {
|
||||
return shapeHelper.Parts_CustomForm_Wrapper()
|
||||
.Editor(_editorBuilderWrapper.BuildEditor(contentItem))
|
||||
.ContentPart(part);
|
||||
});
|
||||
}
|
||||
|
||||
if (contentItem == null || contentItem.ContentType != part.ContentType)
|
||||
return null;
|
||||
|
||||
if (!contentItem.Has<ICommonPart>()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ContentShape("Parts_CustomForm_Wrapper", () => {
|
||||
return shapeHelper.Parts_CustomForm_Wrapper()
|
||||
.Editor(_orchardServices.ContentManager.BuildEditor(contentItem))
|
||||
.ContentPart(part);
|
||||
});
|
||||
// Returning null avoids rendering the edit shape for current custom form.
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(CustomFormPart part, dynamic shapeHelper) {
|
||||
|
||||
@@ -146,6 +146,8 @@
|
||||
<Compile Include="Rules\CustomFormEvents.cs" />
|
||||
<Compile Include="Rules\IRulesManager.cs" />
|
||||
<Compile Include="Security\AuthorizationEventHandler.cs" />
|
||||
<Compile Include="Services\EditorBuilderWrapper.cs" />
|
||||
<Compile Include="Services\IEditorBuilderWrapper.cs" />
|
||||
<Compile Include="ViewModels\CustomFormIndexViewModel.cs" />
|
||||
<Compile Include="ViewModels\CustomFormPartEditViewModel.cs" />
|
||||
</ItemGroup>
|
||||
@@ -231,4 +233,4 @@
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.CustomForms.Services {
|
||||
public class EditorBuilderWrapper : IEditorBuilderWrapper {
|
||||
|
||||
private readonly IContentManager _contentManager;
|
||||
|
||||
public EditorBuilderWrapper(
|
||||
IContentManager contentManager) {
|
||||
|
||||
_contentManager = contentManager;
|
||||
}
|
||||
|
||||
public dynamic BuildEditor(IContent content) {
|
||||
return _contentManager.BuildEditor(content);
|
||||
}
|
||||
|
||||
public dynamic UpdateEditor(IContent content, IUpdateModel updateModel) {
|
||||
return _contentManager.UpdateEditor(content, updateModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.CustomForms.Services {
|
||||
public interface IEditorBuilderWrapper : IDependency {
|
||||
dynamic BuildEditor(IContent content);
|
||||
dynamic UpdateEditor(IContent content, IUpdateModel updateModel);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
@using Orchard.ContentManagement
|
||||
@using Orchard.Utility.Extensions
|
||||
@using Orchard.ContentManagement.Aspects;
|
||||
@using Orchard.ContentManagement.MetaData;
|
||||
@using Orchard.ContentManagement.MetaData.Models;
|
||||
@using Orchard.Core.Contents.Settings;
|
||||
|
||||
@{
|
||||
IContentDefinitionManager _contentDefinitionManager = WorkContext.Resolve<IContentDefinitionManager>();
|
||||
|
||||
ContentItem customForm = Model.ContentItem;
|
||||
string returnUrl = Model.ReturnUrl;
|
||||
var metadata = customForm.ContentManager.GetItemMetadata(customForm);
|
||||
@@ -14,6 +20,18 @@
|
||||
|
||||
var submitButtonText = String.IsNullOrEmpty(Model.ContentItem.CustomFormPart.SubmitButtonText) ? T("Submit").Text : Model.ContentItem.CustomFormPart.SubmitButtonText;
|
||||
var publishButtonText = String.IsNullOrEmpty(Model.ContentItem.CustomFormPart.PublishButtonText) ? T("Publish").Text : Model.ContentItem.CustomFormPart.PublishButtonText;
|
||||
|
||||
var showPublishButton = Model.ContentItem.CustomFormPart.SavePublishContentItem;
|
||||
// Read type definition to check if content is draftable
|
||||
var typeDefinition = _contentDefinitionManager
|
||||
.ListTypeDefinitions()
|
||||
.Where(x => String.Equals(x.Name, Model.ContentItem.CustomFormPart.ContentType, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
if (typeDefinition != null) {
|
||||
// Publish button has to be visible only if content is draftable AND is set to show the publish button
|
||||
showPublishButton = showPublishButton &&
|
||||
typeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable;
|
||||
}
|
||||
}
|
||||
|
||||
@Display(New.Parts_Title().Title(displayText))
|
||||
@@ -29,8 +47,22 @@
|
||||
@if (Model.ContentItem.CustomFormPart.SavePublishContentItem == false || Model.ContentItem.CustomFormPart.SaveContentItem == true) {
|
||||
<button type="submit" name="submit.Save" value="submit.Save">@submitButtonText</button>
|
||||
}
|
||||
@if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) {
|
||||
@if (showPublishButton) {
|
||||
<button type="submit" name="submit.Publish" value="submit.Publish">@publishButtonText</button>
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
|
||||
<div class="edit-item-secondary group">
|
||||
@if (Model.Actions != null) {
|
||||
<div class="edit-item-actions">
|
||||
@Display(Model.Actions)
|
||||
</div>
|
||||
}
|
||||
@if (Model.Sidebar != null) {
|
||||
<div class="edit-item-sidebar group">
|
||||
@Display(Model.Sidebar)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
@{
|
||||
@using Orchard.ContentManagement.MetaData;
|
||||
@using Orchard.ContentManagement.MetaData.Models;
|
||||
@using Orchard.Core.Contents.Settings;
|
||||
|
||||
@{
|
||||
IContentDefinitionManager _contentDefinitionManager = WorkContext.Resolve<IContentDefinitionManager>();
|
||||
|
||||
dynamic editor = Model.Editor;
|
||||
|
||||
if (TempData.ContainsKey("CustomFormWidget.InvalidCustomFormState")) {
|
||||
@@ -7,18 +13,34 @@
|
||||
|
||||
// remove default Save/Publish buttons
|
||||
editor.Zones["Sidebar"].Items.Clear();
|
||||
|
||||
var showPublishButton = Model.ContentItem.CustomFormPart.SavePublishContentItem;
|
||||
|
||||
// Read type definition to check if content is draftable
|
||||
var typeDefinition = _contentDefinitionManager
|
||||
.ListTypeDefinitions()
|
||||
.Where(x => String.Equals(x.Name, Model.ContentItem.CustomFormPart.ContentType, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
if (typeDefinition != null) {
|
||||
// Publish button has to be visible only if content is draftable AND is set to show the publish button
|
||||
showPublishButton = showPublishButton &&
|
||||
typeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable;
|
||||
}
|
||||
}
|
||||
|
||||
@using (Html.BeginFormAntiForgeryPost(Url.Action("Create", "Item", new { area = "Orchard.CustomForms", id = Model.ContentItem.Id }))) {
|
||||
@Html.ValidationSummary()
|
||||
@Html.ValidationSummary()
|
||||
// Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type
|
||||
@Display(editor)
|
||||
@Html.Hidden("returnUrl", Request.RawUrl, new { id = string.Empty });
|
||||
@Html.Hidden("contentId", !string.IsNullOrWhiteSpace(Request.QueryString["contentId"]) ? Request.QueryString["contentId"] : "0", new { id = string.Empty });
|
||||
<fieldset class="submit-button">
|
||||
<button type="submit" name="submit.Save" value="submit.Save">@Model.ContentPart.SubmitButtonText</button>
|
||||
@if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) {
|
||||
<button type="submit" name="submit.Publish" value="submit.Publish">@Model.ContentPart.PublishButtonText</button>
|
||||
}
|
||||
</fieldset>
|
||||
}
|
||||
@Display(editor)
|
||||
|
||||
@Html.Hidden("returnUrl", Request.RawUrl, new { id = string.Empty });
|
||||
@Html.Hidden("contentId", !string.IsNullOrWhiteSpace(Request.QueryString["contentId"]) ? Request.QueryString["contentId"] : "0", new { id = string.Empty });
|
||||
|
||||
<fieldset class="submit-button">
|
||||
|
||||
<button type="submit" name="submit.Save" value="submit.Save">@Model.ContentPart.SubmitButtonText</button>
|
||||
@if (showPublishButton) {
|
||||
<button type="submit" name="submit.Publish" value="submit.Publish">@Model.ContentPart.PublishButtonText</button>
|
||||
}
|
||||
</fieldset>
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
@@ -8,7 +7,6 @@ using Orchard.Core.Contents.Extensions;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Projections.Models;
|
||||
|
||||
@@ -17,22 +15,23 @@ namespace Orchard.Projections {
|
||||
private readonly IRepository<MemberBindingRecord> _memberBindingRepository;
|
||||
private readonly IRepository<LayoutRecord> _layoutRepository;
|
||||
private readonly IRepository<PropertyRecord> _propertyRecordRepository;
|
||||
private readonly ISessionFactoryHolder _sessionFactoryHolder;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
|
||||
private HashSet<string> _existingColumnNames = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// When upgrading from "1.10.x" branch code committed after 1.10.3 to "dev" branch code or 1.11, merge
|
||||
/// conflicts between "1.10.x" and "dev" caused by running the same migration steps in a different order need to
|
||||
/// be resolved by instructing this migration to decide which steps need to be executed. If you're upgrading
|
||||
/// under these conditions and your pre-upgrade migration version is 6, use HostComponents.config to override
|
||||
/// this property to true.
|
||||
/// </summary>
|
||||
public bool IsUpgradingFromOrchard_1_10_x_Version_6 { get; set; }
|
||||
|
||||
public Migrations(
|
||||
IRepository<MemberBindingRecord> memberBindingRepository,
|
||||
IRepository<LayoutRecord> layoutRepository,
|
||||
IRepository<PropertyRecord> propertyRecordRepository,
|
||||
ISessionFactoryHolder sessionFactoryHolder,
|
||||
ShellSettings shellSettings) {
|
||||
IRepository<PropertyRecord> propertyRecordRepository) {
|
||||
_memberBindingRepository = memberBindingRepository;
|
||||
_layoutRepository = layoutRepository;
|
||||
_propertyRecordRepository = propertyRecordRepository;
|
||||
_sessionFactoryHolder = sessionFactoryHolder;
|
||||
_shellSettings = shellSettings;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
@@ -48,6 +47,7 @@ namespace Orchard.Projections {
|
||||
.Column<int>("Id", c => c.PrimaryKey().Identity())
|
||||
.Column<string>("PropertyName")
|
||||
.Column<string>("Value", c => c.WithLength(4000))
|
||||
.Column<string>("LatestValue", c => c.WithLength(4000))
|
||||
.Column<int>("FieldIndexPartRecord_Id")
|
||||
);
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace Orchard.Projections {
|
||||
.Column<int>("Id", c => c.PrimaryKey().Identity())
|
||||
.Column<string>("PropertyName")
|
||||
.Column<long>("Value")
|
||||
.Column<long>("LatestValue")
|
||||
.Column<int>("FieldIndexPartRecord_Id")
|
||||
);
|
||||
|
||||
@@ -64,6 +65,7 @@ namespace Orchard.Projections {
|
||||
.Column<int>("Id", c => c.PrimaryKey().Identity())
|
||||
.Column<string>("PropertyName")
|
||||
.Column<double>("Value")
|
||||
.Column<double>("LatestValue")
|
||||
.Column<int>("FieldIndexPartRecord_Id")
|
||||
);
|
||||
|
||||
@@ -72,9 +74,28 @@ namespace Orchard.Projections {
|
||||
.Column<int>("Id", c => c.PrimaryKey().Identity())
|
||||
.Column<string>("PropertyName")
|
||||
.Column<decimal>("Value")
|
||||
.Column<decimal>("LatestValue")
|
||||
.Column<int>("FieldIndexPartRecord_Id")
|
||||
);
|
||||
|
||||
//Adds indexes for better performances in queries
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
|
||||
SchemaBuilder.CreateTable("FieldIndexPartRecord", table => table.ContentPartRecord());
|
||||
|
||||
// Query
|
||||
@@ -89,6 +110,7 @@ namespace Orchard.Projections {
|
||||
SchemaBuilder.CreateTable("QueryPartRecord",
|
||||
table => table
|
||||
.ContentPartRecord()
|
||||
.Column<string>("VersionScope", c => c.WithLength(15))
|
||||
);
|
||||
|
||||
SchemaBuilder.CreateTable("FilterGroupRecord",
|
||||
@@ -127,6 +149,7 @@ namespace Orchard.Projections {
|
||||
.Column<string>("Description", c => c.WithLength(255))
|
||||
.Column<string>("State", c => c.Unlimited())
|
||||
.Column<string>("DisplayType", c => c.WithLength(64))
|
||||
.Column<string>("GUIdentifier", column => column.WithLength(68))
|
||||
.Column<int>("Display")
|
||||
.Column<int>("QueryPartRecord_id")
|
||||
.Column<int>("GroupProperty_id")
|
||||
@@ -162,6 +185,7 @@ namespace Orchard.Projections {
|
||||
.Column<bool>("HideEmpty")
|
||||
|
||||
.Column<bool>("RewriteOutput")
|
||||
.Column<string>("RewriteOutputCondition", c => c.Unlimited())
|
||||
.Column<string>("RewriteText", c => c.Unlimited())
|
||||
.Column<bool>("StripHtmlTags")
|
||||
.Column<bool>("TrimLength")
|
||||
@@ -260,19 +284,6 @@ namespace Orchard.Projections {
|
||||
Description = T("The text from the Body part").Text
|
||||
});
|
||||
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table
|
||||
.CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id")
|
||||
);
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table
|
||||
.CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id")
|
||||
);
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table
|
||||
.CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id")
|
||||
);
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table
|
||||
.CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id")
|
||||
);
|
||||
|
||||
SchemaBuilder.CreateTable("NavigationQueryPartRecord",
|
||||
table => table.ContentPartRecord()
|
||||
.Column<int>("Items")
|
||||
@@ -291,7 +302,7 @@ namespace Orchard.Projections {
|
||||
.WithIdentity()
|
||||
);
|
||||
|
||||
return 4;
|
||||
return 7;
|
||||
}
|
||||
|
||||
public int UpdateFrom1() {
|
||||
@@ -314,7 +325,7 @@ namespace Orchard.Projections {
|
||||
|
||||
ContentDefinitionManager.AlterTypeDefinition("ProjectionPage", cfg => cfg.Listable());
|
||||
|
||||
return 3;
|
||||
return 2;
|
||||
}
|
||||
|
||||
public int UpdateFrom2() {
|
||||
@@ -336,29 +347,34 @@ namespace Orchard.Projections {
|
||||
|
||||
public int UpdateFrom4() {
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table
|
||||
.AddColumn<string>("LatestValue", c => c.WithLength(4000)));
|
||||
.AddColumn<string>("LatestValue", c => c.WithLength(4000)));
|
||||
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table
|
||||
.AddColumn<long>("LatestValue"));
|
||||
.AddColumn<long>("LatestValue"));
|
||||
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table
|
||||
.AddColumn<double>("LatestValue"));
|
||||
.AddColumn<double>("LatestValue"));
|
||||
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table
|
||||
.AddColumn<decimal>("LatestValue"));
|
||||
.AddColumn<decimal>("LatestValue"));
|
||||
|
||||
//Adds indexes for better performances in queries
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => {
|
||||
table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName");
|
||||
table.CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id");
|
||||
});
|
||||
|
||||
SchemaBuilder.AlterTable("QueryPartRecord", table => table
|
||||
.AddColumn<string>("VersionScope", c => c.WithLength(15)));
|
||||
@@ -366,30 +382,32 @@ namespace Orchard.Projections {
|
||||
return 5;
|
||||
}
|
||||
|
||||
// When upgrading from version 5 of 1.10.x (up until version 7), we'll just execute the same steps, but in a
|
||||
// different order.
|
||||
public int UpdateFrom5() {
|
||||
// This is the original step of the dev branch.
|
||||
MigratePropertyRecordToRewriteOutputCondition();
|
||||
|
||||
return 6;
|
||||
}
|
||||
|
||||
public int UpdateFrom6() {
|
||||
// This is the original step of the dev branch.
|
||||
AddLayoutRecordGuid();
|
||||
if (IsUpgradingFromOrchard_1_10_x_Version_6) {
|
||||
MigratePropertyRecordToRewriteOutputCondition();
|
||||
}
|
||||
else {
|
||||
// This change was originally UpdateFrom5 on 1.10.x and UpdateFrom6 on dev.
|
||||
SchemaBuilder.AlterTable("LayoutRecord", table =>
|
||||
table.AddColumn<string>("GUIdentifier", column => column.WithLength(68)));
|
||||
|
||||
// When upgrading from version 6 of 1.10.x, this column isn't created yet, so we need to run this step
|
||||
// "again".
|
||||
MigratePropertyRecordToRewriteOutputCondition();
|
||||
var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList();
|
||||
foreach (var layout in layoutRecords) {
|
||||
layout.GUIdentifier = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
// This change was originally UpdateFrom5 on dev (but didn't exist on 1.10.x).
|
||||
// This change was originally in UpdateFrom5 on dev, but didn't exist on 1.10.x.
|
||||
private void MigratePropertyRecordToRewriteOutputCondition() {
|
||||
if (ColumnExists("PropertyRecord", "RewriteOutputCondition")) return;
|
||||
|
||||
SchemaBuilder.AlterTable("PropertyRecord", table => table
|
||||
.AddColumn<string>("RewriteOutputCondition", c => c.Unlimited())
|
||||
);
|
||||
@@ -399,46 +417,6 @@ namespace Orchard.Projections {
|
||||
// Reading this obsolete property to migrate its data to a new one.
|
||||
if (property.RewriteOutput) property.RewriteOutputCondition = "true";
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
ColumnAdded("PropertyRecord", "RewriteOutputCondition");
|
||||
}
|
||||
|
||||
// This change was originally UpdateFrom5 on 1.10.x and UpdateFrom6 on dev.
|
||||
private void AddLayoutRecordGuid() {
|
||||
if (ColumnExists("LayoutRecord", "GUIdentifier")) return;
|
||||
|
||||
SchemaBuilder.AlterTable("LayoutRecord", table =>
|
||||
table.AddColumn<string>("GUIdentifier", column => column.WithLength(68)));
|
||||
|
||||
var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList();
|
||||
foreach (var layout in layoutRecords) {
|
||||
layout.GUIdentifier = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
ColumnAdded("LayoutRecord", "GUIdentifier");
|
||||
}
|
||||
|
||||
private bool ColumnExists(string tableName, string columnName) {
|
||||
if (!_existingColumnNames.Any()) {
|
||||
// Database-agnostic way of checking the existence of a column.
|
||||
using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) {
|
||||
var connection = session.Connection ?? throw new InvalidOperationException(
|
||||
"The database connection object should derive from DbConnection to check if a column exists.");
|
||||
|
||||
var columns = connection.GetSchema("Columns").Rows.Cast<DataRow>();
|
||||
|
||||
if (!string.IsNullOrEmpty(_shellSettings.DataTablePrefix)) {
|
||||
columns = columns.Where(row => row["TABLE_NAME"].ToString().StartsWith($"{_shellSettings.DataTablePrefix}_"));
|
||||
}
|
||||
|
||||
_existingColumnNames = columns.Select(row => $"{row["TABLE_NAME"]}.{row["COLUMN_NAME"]}").ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
return _existingColumnNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{columnName}");
|
||||
}
|
||||
|
||||
private void ColumnAdded(string tableName, string columnName) =>
|
||||
_existingColumnNames.Add($"{SchemaBuilder.TableDbName(tableName)}.{columnName}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user