From 417af34b4e791c29b75a231fe012dc3749062966 Mon Sep 17 00:00:00 2001 From: Andrea Piovanelli <83577153+AndreaPiovanelli@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:12:09 +0100 Subject: [PATCH 1/4] Added checks to properly display CustomForm content items at front end (#8751) * Display CustomForm_Wrapper shape only if shape type is Detail. Publish button has to be visible only if content is draftable AND is set to show the publish button # Conflicts: # src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs # src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml # src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml * Added EditorBuilderWrapper. --- .../Drivers/CustomFormPartDriver.cs | 60 +++++++++++-------- .../Orchard.CustomForms.csproj | 4 +- .../Services/EditorBuilderWrapper.cs | 22 +++++++ .../Services/IEditorBuilderWrapper.cs | 8 +++ .../Views/Item/Create.cshtml | 34 ++++++++++- .../Views/Parts.CustomForm.Wrapper.cshtml | 46 ++++++++++---- 6 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs create mode 100644 src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs index 28d8a6ea3b8..4fe5d8899be 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs @@ -11,20 +11,26 @@ using Orchard.Core.Contents.Settings; using Orchard.Security; using Orchard.Localization; +using Orchard.CustomForms.Services; namespace Orchard.CustomForms.Drivers { public class CustomFormPartDriver : ContentPartDriver { 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 @@ public CustomFormPartDriver( 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)) - return null; - } else { - contentItem = _orchardServices.ContentManager.New(part.ContentType); + if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem)) + return null; + } - if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem)) + if (contentItem == null || contentItem.ContentType != part.ContentType) return null; - } - if (contentItem == null || contentItem.ContentType != part.ContentType) - return null; + if (!contentItem.Has()) { + return null; + } - if (!contentItem.Has()) { - return null; + return ContentShape("Parts_CustomForm_Wrapper", () => { + return shapeHelper.Parts_CustomForm_Wrapper() + .Editor(_editorBuilderWrapper.BuildEditor(contentItem)) + .ContentPart(part); + }); } - - 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) { diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj b/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj index 3f38e029540..43c8c516db9 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj @@ -146,6 +146,8 @@ + + @@ -231,4 +233,4 @@ - + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs new file mode 100644 index 00000000000..5ec318a0403 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs new file mode 100644 index 00000000000..8b9978b41fe --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs @@ -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); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml index e35d358db1e..2d569380273 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml @@ -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(); + 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().Draftable; + } } @Display(New.Parts_Title().Title(displayText)) @@ -29,8 +47,22 @@ @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == false || Model.ContentItem.CustomFormPart.SaveContentItem == true) { } - @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) { + @if (showPublishButton) { } + + +
+ @if (Model.Actions != null) { +
+ @Display(Model.Actions) +
+ } + @if (Model.Sidebar != null) { +
+ @Display(Model.Sidebar) +
+ } +
} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml index fe0d2109a37..ee78a66f968 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml @@ -1,4 +1,10 @@ -@{ +@using Orchard.ContentManagement.MetaData; +@using Orchard.ContentManagement.MetaData.Models; +@using Orchard.Core.Contents.Settings; + +@{ + IContentDefinitionManager _contentDefinitionManager = WorkContext.Resolve(); + 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().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 }); -
- - @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) { - - } -
-} +@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 }); + +
+ + + @if (showPublishButton) { + + } +
+} \ No newline at end of file From f167c5fac9bd96528f26a8514849311789277666 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Thu, 15 Feb 2024 19:40:18 +0100 Subject: [PATCH 2/4] Simplifying the merged migrations in Common and Projections that had conflicts between 1.10.x and dev --- src/Orchard.Web/Core/Common/Migrations.cs | 44 ++++--------------- .../Modules/Orchard.Projections/Migrations.cs | 25 +++-------- 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index f331ecd80fd..2c2d9e1dc24 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -167,33 +167,20 @@ public int UpdateFrom5() { 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; - } + // This step's logic is executed together with the original logic from UpdateFrom7 and UpdateFrom8 and in + // UpdateFrom8 to make sure that an upgrade is possible from version 6 of both 1.10.x and dev, which both + // included these steps, but in a different order. + public int UpdateFrom6() => 7; - public int UpdateFrom7() { - // This is the original step of the dev branch. - AddIndexForCommonPartRecordContainerId(); + // See UpdateFrom6 for explanation. + public int UpdateFrom7() => 8; - // 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. + // See UpdateFrom6 for explanation. + public int UpdateFrom8() { AddIndexForIdentityPartRecordIdentifier(); - return 8; - } - - public int UpdateFrom8() { - // This is the original step of the dev branch. AddIndexesForCommonPartOwner(); - // When upgrading from version 8 of 1.10.x, this index isn't created yet, so we need to run this step - // "again" AddIndexForCommonPartRecordContainerId(); return 9; @@ -208,8 +195,6 @@ private void AddIndexForIdentityPartRecordIdentifier() { 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. @@ -220,8 +205,6 @@ private void AddIndexForCommonPartRecordContainerId() { // 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. @@ -258,10 +241,6 @@ private void AddIndexesForCommonPartOwner() { 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) { @@ -286,12 +265,5 @@ private bool IndexExists(string tableName, string indexName) { 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}"); - } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs index 9c413a32590..32475f4d21f 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -366,27 +366,21 @@ public int UpdateFrom4() { 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; - } + // This step's logic is now executed in UpdateFrom6 as MigratePropertyRecordToRewriteOutputCondition to make + // sure that it's executed even when upgrading from version 6 of 1.10.x, which (as opposed to dev) executed the + // changes that make up AddLayoutRecordGuid in UpdateFrom6. + public int UpdateFrom5() => 6; + // See UpdateFrom5 for explanation. public int UpdateFrom6() { - // This is the original step of the dev branch. AddLayoutRecordGuid(); - // When upgrading from version 6 of 1.10.x, this column isn't created yet, so we need to run this step - // "again". MigratePropertyRecordToRewriteOutputCondition(); 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; @@ -399,8 +393,6 @@ private void MigratePropertyRecordToRewriteOutputCondition() { // 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. @@ -414,8 +406,6 @@ private void AddLayoutRecordGuid() { foreach (var layout in layoutRecords) { layout.GUIdentifier = Guid.NewGuid().ToString(); } - - ColumnAdded("LayoutRecord", "GUIdentifier"); } private bool ColumnExists(string tableName, string columnName) { @@ -437,8 +427,5 @@ private bool ColumnExists(string tableName, string columnName) { return _existingColumnNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{columnName}"); } - - private void ColumnAdded(string tableName, string columnName) => - _existingColumnNames.Add($"{SchemaBuilder.TableDbName(tableName)}.{columnName}"); } } From 81b9a6050f451c92135021c95c187b6cf1670c6f Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 6 Mar 2024 16:07:15 +0100 Subject: [PATCH 3/4] Projections: Consolidating Migration Update steps into Create --- .../Modules/Orchard.Projections/Migrations.cs | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs index 32475f4d21f..a14f315e0f7 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -48,6 +48,7 @@ public int Create() { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value", c => c.WithLength(4000)) + .Column("LatestValue", c => c.WithLength(4000)) .Column("FieldIndexPartRecord_Id") ); @@ -56,6 +57,7 @@ public int Create() { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("FieldIndexPartRecord_Id") ); @@ -64,6 +66,7 @@ public int Create() { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("FieldIndexPartRecord_Id") ); @@ -72,9 +75,28 @@ public int Create() { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("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 +111,7 @@ public int Create() { SchemaBuilder.CreateTable("QueryPartRecord", table => table .ContentPartRecord() + .Column("VersionScope", c => c.WithLength(15)) ); SchemaBuilder.CreateTable("FilterGroupRecord", @@ -127,6 +150,7 @@ public int Create() { .Column("Description", c => c.WithLength(255)) .Column("State", c => c.Unlimited()) .Column("DisplayType", c => c.WithLength(64)) + .Column("GUIdentifier", column => column.WithLength(68)) .Column("Display") .Column("QueryPartRecord_id") .Column("GroupProperty_id") @@ -162,6 +186,7 @@ public int Create() { .Column("HideEmpty") .Column("RewriteOutput") + .Column("RewriteOutputCondition", c => c.Unlimited()) .Column("RewriteText", c => c.Unlimited()) .Column("StripHtmlTags") .Column("TrimLength") @@ -260,19 +285,6 @@ public int Create() { 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("Items") @@ -291,7 +303,7 @@ public int Create() { .WithIdentity() ); - return 4; + return 7; } public int UpdateFrom1() { @@ -314,7 +326,7 @@ public int UpdateFrom1() { ContentDefinitionManager.AlterTypeDefinition("ProjectionPage", cfg => cfg.Listable()); - return 3; + return 2; } public int UpdateFrom2() { @@ -336,29 +348,34 @@ public int UpdateFrom3() { public int UpdateFrom4() { SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table - .AddColumn("LatestValue", c => c.WithLength(4000))); + .AddColumn("LatestValue", c => c.WithLength(4000))); SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("LatestValue")); SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("LatestValue")); SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("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("VersionScope", c => c.WithLength(15))); From fcf960f7e37ecebeb40ddb64c747aaa29272a109 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Thu, 7 Mar 2024 16:50:58 +0100 Subject: [PATCH 4/4] Reworking the merge conflict resolution between 1.10.x and dev for the Core.Common and Projections migrations --- src/Orchard.Web/Core/Common/Migrations.cs | 150 +++++++----------- .../Modules/Orchard.Projections/Migrations.cs | 80 ++++------ 2 files changed, 88 insertions(+), 142 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index 2c2d9e1dc24..65565f04531 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -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 _identityPartRepository; - private readonly ISessionFactoryHolder _sessionFactoryHolder; - private readonly ShellSettings _shellSettings; - private HashSet _existingIndexNames = new HashSet(); - - - public Migrations( - IRepository identityPartRepository, - ISessionFactoryHolder sessionFactoryHolder, - ShellSettings shellSettings) { + /// + /// 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. + /// + public bool IsUpgradingFromOrchard_1_10_x_Version_7 { get; set; } + public bool IsUpgradingFromOrchard_1_10_x_Version_8 { get; set; } + + public Migrations(IRepository identityPartRepository) { _identityPartRepository = identityPartRepository; - _sessionFactoryHolder = sessionFactoryHolder; - _shellSettings = shellSettings; } @@ -167,22 +164,60 @@ public int UpdateFrom5() { return 6; } - // This step's logic is executed together with the original logic from UpdateFrom7 and UpdateFrom8 and in - // UpdateFrom8 to make sure that an upgrade is possible from version 6 of both 1.10.x and dev, which both - // included these steps, but in a different order. - public int UpdateFrom6() => 7; - - // See UpdateFrom6 for explanation. - public int UpdateFrom7() => 8; - - // See UpdateFrom6 for explanation. - public int UpdateFrom8() { + public int UpdateFrom6() { AddIndexForIdentityPartRecordIdentifier(); - AddIndexesForCommonPartOwner(); + return 7; + } + public int UpdateFrom7() { AddIndexForCommonPartRecordContainerId(); + return 8; + } + + public int UpdateFrom8() { + 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. + + // 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; } @@ -190,8 +225,6 @@ public int UpdateFrom8() { 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))); @@ -201,69 +234,8 @@ private void AddIndexForIdentityPartRecordIdentifier() { 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")); } - - // 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)); - }); - } - - 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(); - - 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}"); - } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs index a14f315e0f7..cbf33106f9a 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -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.Title.Models; using Orchard.Data; using Orchard.Data.Migration; -using Orchard.Environment.Configuration; using Orchard.Localization; using Orchard.Projections.Models; @@ -17,22 +15,23 @@ public class Migrations : DataMigrationImpl { private readonly IRepository _memberBindingRepository; private readonly IRepository _layoutRepository; private readonly IRepository _propertyRecordRepository; - private readonly ISessionFactoryHolder _sessionFactoryHolder; - private readonly ShellSettings _shellSettings; - private HashSet _existingColumnNames = new HashSet(); + /// + /// 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. + /// + public bool IsUpgradingFromOrchard_1_10_x_Version_6 { get; set; } public Migrations( IRepository memberBindingRepository, IRepository layoutRepository, - IRepository propertyRecordRepository, - ISessionFactoryHolder sessionFactoryHolder, - ShellSettings shellSettings) { + IRepository propertyRecordRepository) { _memberBindingRepository = memberBindingRepository; _layoutRepository = layoutRepository; _propertyRecordRepository = propertyRecordRepository; - _sessionFactoryHolder = sessionFactoryHolder; - _shellSettings = shellSettings; T = NullLocalizer.Instance; } @@ -383,24 +382,32 @@ public int UpdateFrom4() { return 5; } - // This step's logic is now executed in UpdateFrom6 as MigratePropertyRecordToRewriteOutputCondition to make - // sure that it's executed even when upgrading from version 6 of 1.10.x, which (as opposed to dev) executed the - // changes that make up AddLayoutRecordGuid in UpdateFrom6. - public int UpdateFrom5() => 6; + public int UpdateFrom5() { + MigratePropertyRecordToRewriteOutputCondition(); - // See UpdateFrom5 for explanation. - public int UpdateFrom6() { - AddLayoutRecordGuid(); + return 6; + } - MigratePropertyRecordToRewriteOutputCondition(); + public int UpdateFrom6() { + 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("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(); + } + } return 7; } // 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("RewriteOutputCondition", c => c.Unlimited()) ); @@ -411,38 +418,5 @@ private void MigratePropertyRecordToRewriteOutputCondition() { if (property.RewriteOutput) property.RewriteOutputCondition = "true"; #pragma warning restore CS0618 // Type or member is obsolete } - - // 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("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(); - } - } - - 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(); - - 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}"); - } } }