diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/Filter/FilterContext.cs b/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/Filter/FilterContext.cs index b1ce0b5d5..6e472c981 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/Filter/FilterContext.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/Filter/FilterContext.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Orchard.ContentManagement; +using Orchard.Projections.Models; namespace Orchard.Projections.Descriptors.Filter { public class FilterContext { @@ -10,5 +11,10 @@ namespace Orchard.Projections.Descriptors.Filter { public IDictionary Tokens { get; set; } public dynamic State { get; set; } public IHqlQuery Query { get; set; } + + public QueryPartRecord QueryPartRecord { get; set; } + public string GetFilterColumnName() { + return QueryPartRecord != null && QueryPartRecord.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/SortCriterion/SortCriteriaContext.cs b/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/SortCriterion/SortCriteriaContext.cs index ced8cd412..406a57899 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/SortCriterion/SortCriteriaContext.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Descriptors/SortCriterion/SortCriteriaContext.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Orchard.ContentManagement; +using Orchard.Projections.Models; namespace Orchard.Projections.Descriptors.SortCriterion { public class SortCriterionContext { @@ -10,5 +11,10 @@ namespace Orchard.Projections.Descriptors.SortCriterion { public IDictionary Tokens { get; set; } public dynamic State { get; set; } public IHqlQuery Query { get; set; } + + public QueryPartRecord QueryPartRecord { get; set; } + public string GetSortColumnName() { + return QueryPartRecord != null && QueryPartRecord.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Drivers/QueryPartDriver.cs b/src/Orchard.Web/Modules/Orchard.Projections/Drivers/QueryPartDriver.cs index f5ca4f4aa..64327db64 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Drivers/QueryPartDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Drivers/QueryPartDriver.cs @@ -7,9 +7,10 @@ using Orchard.ContentManagement.Handlers; using Orchard.Forms.Services; using Orchard.Projections.Models; using Orchard.Projections.Services; +using Orchard.Projections.ViewModels; namespace Orchard.Projections.Drivers { - + public class QueryPartDriver : ContentPartDriver { private readonly IProjectionManager _projectionManager; private readonly IFormManager _formManager; @@ -18,13 +19,25 @@ namespace Orchard.Projections.Drivers { _projectionManager = projectionManager; _formManager = formManager; } - - protected override DriverResult Editor(QueryPart part, IUpdateModel updater, dynamic shapeHelper) { - if(updater == null) { - return null; + protected override string Prefix { + get { + return "Query_Part"; } - - return null; + } + protected override DriverResult Editor(QueryPart part, dynamic shapeHelper) { + return Editor(part, null, shapeHelper); + } + protected override DriverResult Editor(QueryPart part, IUpdateModel updater, dynamic shapeHelper) { + var model = new QueryViewModel { VersionScope = part.VersionScope }; + if (updater != null) { + if (updater.TryUpdateModel(model, Prefix, null, null)) { + part.VersionScope = model.VersionScope; + } + } + return ContentShape("Parts_QueryPart_Edit", + () => { + return shapeHelper.EditorTemplate(TemplateName: "Parts/QueryPart_Edit", Model: model, Prefix: Prefix); + }); } protected override void Exporting(QueryPart part, ExportContentContext context) { @@ -114,16 +127,16 @@ namespace Orchard.Projections.Drivers { foreach (var item in queryElement.Element("FilterGroups").Elements("FilterGroup").Select(filterGroup => new FilterGroupRecord { Filters = filterGroup.Elements("Filter").Select(filter => { - + var category = filter.Attribute("Category").Value; var type = filter.Attribute("Type").Value; var state = filter.Attribute("State").Value; - + var descriptor = _projectionManager.GetFilter(category, type); if (descriptor != null) { state = _formManager.Import(descriptor.Form, state, context); } - + return new FilterRecord { Category = category, Description = filter.Attribute("Description").Value, @@ -186,7 +199,7 @@ namespace Orchard.Projections.Drivers { } private XElement GetPropertyXml(PropertyRecord property) { - if(property == null) { + if (property == null) { return null; } @@ -226,7 +239,7 @@ namespace Orchard.Projections.Drivers { } private PropertyRecord GetProperty(XElement property) { - if(property == null) { + if (property == null) { return null; } diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/BooleanFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/BooleanFieldTypeEditor.cs index 793eb08b7..7ad4b2eff 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/BooleanFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/BooleanFieldTypeEditor.cs @@ -25,7 +25,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return BooleanFilterForm.GetFilterPredicate(formState, "Value"); + return BooleanFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DateTimeFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DateTimeFieldTypeEditor.cs index 8df4aaf50..fbbb5d736 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DateTimeFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DateTimeFieldTypeEditor.cs @@ -29,7 +29,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return DateTimeFilterForm.GetFilterPredicate(formState, "Value", _clock.UtcNow, true); + return DateTimeFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value", _clock.UtcNow, true); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DecimalFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DecimalFieldTypeEditor.cs index 1871b829a..d4e9a3f79 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DecimalFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/DecimalFieldTypeEditor.cs @@ -27,7 +27,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return NumericFilterForm.GetFilterPredicate(formState, "Value"); + return NumericFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/FloatFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/FloatFieldTypeEditor.cs index a1bd7f5a3..17b5d8883 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/FloatFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/FloatFieldTypeEditor.cs @@ -28,7 +28,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return NumericFilterForm.GetFilterPredicate(formState, "Value"); + return NumericFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/IntegerFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/IntegerFieldTypeEditor.cs index 78a6ed57d..02137ea21 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/IntegerFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/IntegerFieldTypeEditor.cs @@ -34,7 +34,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return NumericFilterForm.GetFilterPredicate(formState, "Value"); + return NumericFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/StringFieldTypeEditor.cs b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/StringFieldTypeEditor.cs index e87e1635c..647acf414 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/StringFieldTypeEditor.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FieldTypeEditors/StringFieldTypeEditor.cs @@ -25,7 +25,7 @@ namespace Orchard.Projections.FieldTypeEditors { } public Action GetFilterPredicate(dynamic formState) { - return StringFilterForm.GetFilterPredicate(formState, "Value"); + return StringFilterForm.GetFilterPredicate(formState, formState.VersionScope == QueryVersionScopeOptions.Latest ? "LatestValue" : "Value"); } public LocalizedString DisplayFilter(string fieldName, string storageName, dynamic formState) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Handlers/FieldIndexPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Projections/Handlers/FieldIndexPartHandler.cs index 12b3df9f2..faa2ca07c 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Handlers/FieldIndexPartHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Handlers/FieldIndexPartHandler.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; @@ -16,11 +17,13 @@ namespace Orchard.Projections.Handlers { private readonly IFieldIndexService _fieldIndexService; private readonly IFieldStorageProvider _fieldStorageProvider; private readonly IEnumerable _contentFieldDrivers; + private readonly IDraftFieldIndexService _draftFieldIndexService; public FieldIndexPartHandler( IContentDefinitionManager contentDefinitionManager, IRepository repository, IFieldIndexService fieldIndexService, + IDraftFieldIndexService draftFieldIndexService, IFieldStorageProvider fieldStorageProvider, IEnumerable contentFieldDrivers) { Filters.Add(StorageFilter.For(repository)); @@ -28,7 +31,8 @@ namespace Orchard.Projections.Handlers { _fieldIndexService = fieldIndexService; _fieldStorageProvider = fieldStorageProvider; _contentFieldDrivers = contentFieldDrivers; - + _draftFieldIndexService = draftFieldIndexService; + OnUpdated(Updated); OnPublishing(Publishing); } @@ -43,29 +47,55 @@ namespace Orchard.Projections.Handlers { context.Builder.Weld(); } } + private void Updated(UpdateContentContext context, FieldIndexPart fieldIndexPart) { + if (context.UpdatingItemVersionRecord.Latest) { // updates projection draft indexes only if it is the latest version + DescribeValuesToindex(fieldIndexPart, (indexServiceContext) => { + _draftFieldIndexService.Set(fieldIndexPart, + indexServiceContext.LocalPart.PartDefinition.Name, + indexServiceContext.LocalField.Name, + indexServiceContext.StorageName, indexServiceContext.FieldValue, indexServiceContext.StorageType); + }); + } + } + public void Publishing(PublishContentContext context, FieldIndexPart fieldIndexPart) { + DescribeValuesToindex(fieldIndexPart, (indexServiceContext) => { + _fieldIndexService.Set(fieldIndexPart, + indexServiceContext.LocalPart.PartDefinition.Name, + indexServiceContext.LocalField.Name, + indexServiceContext.StorageName, indexServiceContext.FieldValue, indexServiceContext.StorageType); + + }); + } + /// + /// + /// + /// + /// + private void DescribeValuesToindex(FieldIndexPart fieldIndexPart, Action indexService) { foreach (var part in fieldIndexPart.ContentItem.Parts) { - foreach(var field in part.PartDefinition.Fields) { - + foreach (var field in part.PartDefinition.Fields) { + // get all drivers for the current field type // the driver will describe what values of the field should be indexed var drivers = _contentFieldDrivers.Where(x => x.GetFieldInfo().Any(fi => fi.FieldTypeName == field.FieldDefinition.Name)).ToList(); - + ContentPart localPart = part; ContentPartFieldDefinition localField = field; - var membersContext = new DescribeMembersContext( + var membersContext = new DescribeMembersContext( (storageName, storageType, displayName, description) => { var fieldStorage = _fieldStorageProvider.BindStorage(localPart, localField); // fieldStorage.Get(storageName) var getter = typeof(IFieldStorage).GetMethod("Get").MakeGenericMethod(storageType); - var fieldValue = getter.Invoke(fieldStorage, new[] {storageName}); - - _fieldIndexService.Set(fieldIndexPart, - localPart.PartDefinition.Name, - localField.Name, - storageName, fieldValue, storageType); + var fieldValue = getter.Invoke(fieldStorage, new[] { storageName }); + indexService(new IndexServiceContext { + LocalPart = localPart, + LocalField = localField, + StorageName = storageName, + FieldValue = fieldValue, + StorageType = storageType }); }); foreach (var driver in drivers) { @@ -74,5 +104,13 @@ namespace Orchard.Projections.Handlers { } } } + private class IndexServiceContext { + public ContentPart LocalPart { get; set; } + public ContentPartFieldDefinition LocalField { get; set; } + public string StorageName { get; set; } + public object FieldValue { get; set; } + public Type StorageType { get; set; } + + } } } \ 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 149e87667..aad20e82f 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.Data; +using System.Data; using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; using Orchard.Core.Contents.Extensions; @@ -282,5 +281,35 @@ namespace Orchard.Projections { return 4; } + public int UpdateFrom4() { + SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table + .AddColumn("LatestValue", c => c.WithLength(4000))); + + SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table + .AddColumn("LatestValue")); + + SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table + .AddColumn("LatestValue")); + + SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table + .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("QueryPartRecord", table => table + .AddColumn("VersionScope", c => c.WithLength(15))); + return 5; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Models/FieldIndexRecord.cs b/src/Orchard.Web/Modules/Orchard.Projections/Models/FieldIndexRecord.cs index 7b2b03325..c81fea701 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Models/FieldIndexRecord.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Models/FieldIndexRecord.cs @@ -8,18 +8,22 @@ namespace Orchard.Projections.Models { public class StringFieldIndexRecord : FieldIndexRecord { public virtual string Value { get; set; } + public virtual string LatestValue { get; set; } } public class IntegerFieldIndexRecord : FieldIndexRecord { public virtual long? Value { get; set; } + public virtual long? LatestValue { get; set; } } public class DoubleFieldIndexRecord : FieldIndexRecord { public virtual double? Value { get; set; } + public virtual double? LatestValue { get; set; } } public class DecimalFieldIndexRecord : FieldIndexRecord { public virtual decimal? Value { get; set; } + public virtual decimal? LatestValue { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPart.cs b/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPart.cs index f085c59e1..b4873a642 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPart.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPart.cs @@ -10,6 +10,10 @@ namespace Orchard.Projections.Models { set { this.As().Title = value; } } + public QueryVersionScopeOptions VersionScope { + get { return this.Retrieve(x => x.VersionScope); } + set { this.Store(x => x.VersionScope, value); } + } public IList SortCriteria { get { return Record.SortCriteria; } } diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPartRecord.cs b/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPartRecord.cs index a295f18af..9fd91f083 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPartRecord.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Models/QueryPartRecord.cs @@ -1,16 +1,20 @@ using System.Collections.Generic; using System.Xml.Serialization; +using Orchard.ContentManagement; using Orchard.ContentManagement.Records; using Orchard.Data.Conventions; namespace Orchard.Projections.Models { public class QueryPartRecord : ContentPartRecord { public QueryPartRecord() { + VersionScope = QueryVersionScopeOptions.Published; FilterGroups = new List(); SortCriteria = new List(); Layouts = new List(); } + public virtual QueryVersionScopeOptions VersionScope { get; set; } + [CascadeAllDeleteOrphan, Aggregate] [XmlArray("FilterGroupRecords")] public virtual IList FilterGroups { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Orchard.Projections.csproj b/src/Orchard.Web/Modules/Orchard.Projections/Orchard.Projections.csproj index 5103ce9a2..0e0403546 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Orchard.Projections.csproj +++ b/src/Orchard.Web/Modules/Orchard.Projections/Orchard.Projections.csproj @@ -179,6 +179,9 @@ + + + @@ -282,6 +285,7 @@ + @@ -325,6 +329,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Placement.info b/src/Orchard.Web/Modules/Orchard.Projections/Placement.info index 96b4df8d9..42174c424 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Placement.info +++ b/src/Orchard.Web/Modules/Orchard.Projections/Placement.info @@ -5,7 +5,7 @@ - + diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Providers/Filters/ContentFieldsFilter.cs b/src/Orchard.Web/Modules/Orchard.Projections/Providers/Filters/ContentFieldsFilter.cs index d9703e30b..98cd0910e 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Providers/Filters/ContentFieldsFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Providers/Filters/ContentFieldsFilter.cs @@ -75,7 +75,9 @@ namespace Orchard.Projections.Providers.Filters { var relationship = fieldTypeEditor.GetFilterRelationship(propertyName.ToSafeName()); // generate the predicate based on the editor which has been used - Action predicate = fieldTypeEditor.GetFilterPredicate(context.State); + dynamic fullState = context.State; + fullState.VersionScope = context.QueryPartRecord.VersionScope; + Action predicate = fieldTypeEditor.GetFilterPredicate(fullState); // combines the predicate with a filter on the specific property name of the storage, as implemented in FieldIndexService Action andPredicate = x => x.And(y => y.Eq("PropertyName", propertyName), predicate); diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Providers/SortCriteria/ContentFieldsSortCriteria.cs b/src/Orchard.Web/Modules/Orchard.Projections/Providers/SortCriteria/ContentFieldsSortCriteria.cs index 20eb1ac7e..05ef6cd0d 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Providers/SortCriteria/ContentFieldsSortCriteria.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Providers/SortCriteria/ContentFieldsSortCriteria.cs @@ -32,14 +32,14 @@ namespace Orchard.Projections.Providers.SortCriteria { public Localizer T { get; set; } public void Describe(DescribeSortCriterionContext describe) { - foreach(var part in _contentDefinitionManager.ListPartDefinitions()) { - if(!part.Fields.Any()) { + foreach (var part in _contentDefinitionManager.ListPartDefinitions()) { + if (!part.Fields.Any()) { continue; } var descriptor = describe.For(part.Name + "ContentFields", T("{0} Content Fields", part.Name.CamelFriendly()), T("Content Fields for {0}", part.Name.CamelFriendly())); - foreach(var field in part.Fields) { + foreach (var field in part.Fields) { var localField = field; var localPart = part; var drivers = _contentFieldDrivers.Where(x => x.GetFieldInfo().Any(fi => fi.FieldTypeName == localField.FieldDefinition.Name)).ToList(); @@ -57,8 +57,8 @@ namespace Orchard.Projections.Providers.SortCriteria { display: context => DisplaySortCriterion(context, localPart, localField), form: SortCriterionFormProvider.FormName); }); - - foreach(var driver in drivers) { + + foreach (var driver in drivers) { driver.Describe(membersContext); } } @@ -79,11 +79,11 @@ namespace Orchard.Projections.Providers.SortCriteria { // apply where clause context.Query = context.Query.Where(relationship, predicate); - + // apply sort - context.Query = ascending - ? context.Query.OrderBy(relationship, x => x.Asc("Value")) - : context.Query.OrderBy(relationship, x => x.Desc("Value")); + context.Query = ascending + ? context.Query.OrderBy(relationship, x => x.Asc(context.GetSortColumnName())) + : context.Query.OrderBy(relationship, x => x.Desc(context.GetSortColumnName())); } public LocalizedString DisplaySortCriterion(SortCriterionContext context, ContentPartDefinition part, ContentPartFieldDefinition fieldDefinition) { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/QueryVersionScopeOptions.cs b/src/Orchard.Web/Modules/Orchard.Projections/QueryVersionScopeOptions.cs new file mode 100644 index 000000000..62a830624 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Projections/QueryVersionScopeOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.Projections { + public enum QueryVersionScopeOptions { + Published, + Latest + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Services/DraftFieldIndexService.cs b/src/Orchard.Web/Modules/Orchard.Projections/Services/DraftFieldIndexService.cs new file mode 100644 index 000000000..fd246407f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Projections/Services/DraftFieldIndexService.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using Orchard.Projections.Models; + +namespace Orchard.Projections.Services { + public class DraftFieldIndexService : IDraftFieldIndexService { + + public void Set(FieldIndexPart part, string partName, string fieldName, string valueName, object value, Type valueType) { + var propertyName = String.Join(".", partName, fieldName, valueName ?? ""); + + var typeCode = Type.GetTypeCode(valueType); + + if(valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>)) { + typeCode = Type.GetTypeCode(Nullable.GetUnderlyingType(valueType)); + } + + switch (typeCode) { + case TypeCode.Char: + case TypeCode.String: + var stringRecord = part.Record.StringFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (stringRecord == null) { + stringRecord = new StringFieldIndexRecord { PropertyName = propertyName }; + part.Record.StringFieldIndexRecords.Add(stringRecord); + } + + // take the first 4000 chars as it is the limit for the field + stringRecord.LatestValue = value == null ? null : value.ToString().Substring(0, Math.Min(value.ToString().Length, 4000)); + + + break; + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + var integerRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (integerRecord == null) { + integerRecord = new IntegerFieldIndexRecord { PropertyName = propertyName }; + part.Record.IntegerFieldIndexRecords.Add(integerRecord); + } + + integerRecord.LatestValue = value == null ? default(long?) : Convert.ToInt64(value); + break; + case TypeCode.DateTime: + var dateTimeRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (dateTimeRecord == null) { + dateTimeRecord = new IntegerFieldIndexRecord { PropertyName = propertyName }; + part.Record.IntegerFieldIndexRecords.Add(dateTimeRecord); + } + + dateTimeRecord.LatestValue = value == null ? default(long?) : ((DateTime)value).Ticks; + break; + case TypeCode.Boolean: + var booleanRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (booleanRecord == null) { + booleanRecord = new IntegerFieldIndexRecord { PropertyName = propertyName }; + part.Record.IntegerFieldIndexRecords.Add(booleanRecord); + } + + booleanRecord.LatestValue = value == null ? default(long?) : Convert.ToInt64((bool)value); + break; + case TypeCode.Decimal: + var decimalRecord = part.Record.DecimalFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (decimalRecord == null) { + decimalRecord = new DecimalFieldIndexRecord { PropertyName = propertyName }; + part.Record.DecimalFieldIndexRecords.Add(decimalRecord); + } + + decimalRecord.LatestValue = value == null ? default(decimal?) : Convert.ToDecimal((decimal)value); + break; + case TypeCode.Single: + case TypeCode.Double: + var doubleRecord = part.Record.DoubleFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + if (doubleRecord == null) { + doubleRecord = new DoubleFieldIndexRecord { PropertyName = propertyName }; + part.Record.DoubleFieldIndexRecords.Add(doubleRecord); + } + + doubleRecord.LatestValue = value == null ? default(double?) : Convert.ToDouble(value); + break; + } + } + + public T Get(FieldIndexPart part, string partName, string fieldName, string valueName) { + var propertyName = String.Join(".", partName, fieldName, valueName ?? ""); + + var typeCode = Type.GetTypeCode(typeof(T)); + + switch (typeCode) { + case TypeCode.Char: + case TypeCode.String: + var stringRecord = part.Record.StringFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return stringRecord != null ? (T)Convert.ChangeType(stringRecord.LatestValue, typeof(T)) : default(T); + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + var integerRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return integerRecord != null ? (T)Convert.ChangeType(integerRecord.LatestValue, typeof(T)) : default(T); + case TypeCode.Decimal: + var decimalRecord = part.Record.DecimalFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return decimalRecord != null ? (T)Convert.ChangeType(decimalRecord.LatestValue, typeof(T)) : default(T); + case TypeCode.Single: + case TypeCode.Double: + var doubleRecord = part.Record.DoubleFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return doubleRecord != null ? (T)Convert.ChangeType(doubleRecord.LatestValue, typeof(T)) : default(T); + case TypeCode.DateTime: + var dateTimeRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return dateTimeRecord != null ? (T)Convert.ChangeType(new DateTime(Convert.ToInt64(dateTimeRecord.LatestValue)), typeof(T)) : default(T); + case TypeCode.Boolean: + var booleanRecord = part.Record.IntegerFieldIndexRecords.FirstOrDefault(r => r.PropertyName == propertyName); + return booleanRecord != null ? (T)Convert.ChangeType(booleanRecord.LatestValue, typeof(T)) : default(T); + default: + return default(T); + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Services/IDraftFieldIndexService.cs b/src/Orchard.Web/Modules/Orchard.Projections/Services/IDraftFieldIndexService.cs new file mode 100644 index 000000000..7d76c1bd3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Projections/Services/IDraftFieldIndexService.cs @@ -0,0 +1,9 @@ +using System; +using Orchard.Projections.Models; + +namespace Orchard.Projections.Services { + public interface IDraftFieldIndexService : IDependency { + void Set(FieldIndexPart part, string partName, string fieldName, string valueName, object value, Type valueType); + T Get(FieldIndexPart part, string partName, string fieldName, string valueName); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Services/ProjectionManager.cs b/src/Orchard.Web/Modules/Orchard.Projections/Services/ProjectionManager.cs index 69e343132..d3852fc75 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Services/ProjectionManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Services/ProjectionManager.cs @@ -168,7 +168,8 @@ namespace Orchard.Projections.Services { foreach (var sortCriterion in queryRecord.SortCriteria.OrderBy(s => s.Position)) { var sortCriterionContext = new SortCriterionContext { Query = groupQuery, - State = FormParametersHelper.ToDynamic(sortCriterion.State) + State = FormParametersHelper.ToDynamic(sortCriterion.State), + QueryPartRecord = queryRecord }; string category = sortCriterion.Category; @@ -200,16 +201,24 @@ namespace Orchard.Projections.Services { } // pre-executing all groups + var versionScope = queryRecord.VersionScope; foreach (var group in queryRecord.FilterGroups) { - var contentQuery = _contentManager.HqlQuery().ForVersion(VersionOptions.Published); + IHqlQuery contentQuery; + if (versionScope == QueryVersionScopeOptions.Latest) { + contentQuery = _contentManager.HqlQuery().ForVersion(VersionOptions.Latest); + } + else { + contentQuery = _contentManager.HqlQuery().ForVersion(VersionOptions.Published); + } // iterate over each filter to apply the alterations to the query object foreach (var filter in group.Filters) { var tokenizedState = _tokenizer.Replace(filter.State, tokens); var filterContext = new FilterContext { Query = contentQuery, - State = FormParametersHelper.ToDynamic(tokenizedState) + State = FormParametersHelper.ToDynamic(tokenizedState), + QueryPartRecord = queryRecord }; string category = filter.Category; @@ -235,7 +244,8 @@ namespace Orchard.Projections.Services { foreach (var sortCriterion in sortCriteria.OrderBy(s => s.Position)) { var sortCriterionContext = new SortCriterionContext { Query = contentQuery, - State = FormParametersHelper.ToDynamic(sortCriterion.State) + State = FormParametersHelper.ToDynamic(sortCriterion.State), + QueryPartRecord= queryRecord }; string category = sortCriterion.Category; diff --git a/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/FilterEditViewModel.cs b/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/FilterEditViewModel.cs index 8a7f51ec0..d5ea5843e 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/FilterEditViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/FilterEditViewModel.cs @@ -7,5 +7,6 @@ namespace Orchard.Projections.ViewModels { public string Description { get; set; } public FilterDescriptor Filter { get; set; } public dynamic Form { get; set; } + public QueryVersionScopeOptions VersionScope { get; set; } } } diff --git a/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/QueryViewModel.cs b/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/QueryViewModel.cs new file mode 100644 index 000000000..f1f6a1e9b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Projections/ViewModels/QueryViewModel.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.Projections.ViewModels { + public class QueryViewModel { + public QueryVersionScopeOptions VersionScope { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Views/EditorTemplates/Parts/QueryPart_Edit.cshtml b/src/Orchard.Web/Modules/Orchard.Projections/Views/EditorTemplates/Parts/QueryPart_Edit.cshtml new file mode 100644 index 000000000..48e09c2b2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Projections/Views/EditorTemplates/Parts/QueryPart_Edit.cshtml @@ -0,0 +1,8 @@ +@model Orchard.Projections.ViewModels.QueryViewModel +@using Orchard.Projections; + +
+ @Html.LabelFor(m => m.VersionScope, T("Content's version")) + @Html.DropDownListFor(m=>m.VersionScope, new SelectList(Enum.GetValues(typeof(QueryVersionScopeOptions)), Model.VersionScope)) + @T("The content's version to query.") +
diff --git a/src/Orchard.Web/Modules/Upgrade/AdminMenu.cs b/src/Orchard.Web/Modules/Upgrade/AdminMenu.cs index 2b713c2d4..71651bb36 100644 --- a/src/Orchard.Web/Modules/Upgrade/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Upgrade/AdminMenu.cs @@ -13,7 +13,8 @@ namespace Upgrade { public void GetNavigation(NavigationBuilder builder) { builder .AddImageSet("upgrade") - .Add(T("Upgrade to 1.8"), "0", menu => menu.Action("Index", "Route", new { area = "Upgrade" }) + .Add(T("Upgrade to 1.10.3"), "0", menu => menu.Action("Index", "Route", new { area = "Upgrade" }) + .Add(T("Projections (1.10.3)"), "1.0", item => item.Action("Index", "Projections", new { area = "Upgrade" }).LocalNav().Permission(StandardPermissions.SiteOwner)) .Add(T("Infoset (1.8)"), "1.0", item => item.Action("Index", "Infoset", new { area = "Upgrade" }).LocalNav().Permission(StandardPermissions.SiteOwner)) .Add(T("Messaging (1.8)"), "1.1", item => item.Action("Index", "Messaging", new { area = "Upgrade" }).LocalNav().Permission(StandardPermissions.SiteOwner)) .Add(T("Media (1.7)"), "2", item => item.Action("Index", "Media", new { area = "Upgrade" }).LocalNav().Permission(StandardPermissions.SiteOwner)) diff --git a/src/Orchard.Web/Modules/Upgrade/Controllers/ProjectionsController.cs b/src/Orchard.Web/Modules/Upgrade/Controllers/ProjectionsController.cs new file mode 100644 index 000000000..46a131c4f --- /dev/null +++ b/src/Orchard.Web/Modules/Upgrade/Controllers/ProjectionsController.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Authentication; +using System.Web.Mvc; +using Orchard; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData; +using Orchard.Data; +using Orchard.Environment.Features; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.Projections.Handlers; +using Orchard.Security; +using Orchard.UI.Admin; +using Orchard.UI.Notify; +using Upgrade.ViewModels; + +namespace Upgrade.Controllers { + [Admin] + public class ProjectionsController : Controller { + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IOrchardServices _orchardServices; + private readonly IFeatureManager _featureManager; + private readonly Lazy> _handlers; + private readonly ITransactionManager _transactionManager; + + private const int BATCH = 50; + + public ProjectionsController( + IContentDefinitionManager contentDefinitionManager, + IOrchardServices orchardServices, + ITransactionManager transactionManager, + IFeatureManager featureManager, + Lazy> handlers) { + _contentDefinitionManager = contentDefinitionManager; + _orchardServices = orchardServices; + _transactionManager = transactionManager; + _featureManager = featureManager; + _handlers = handlers; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public ActionResult Index() { + var viewModel = new MigrateViewModel { ContentTypes = new List() }; + foreach (var contentType in _contentDefinitionManager.ListTypeDefinitions().OrderBy(c => c.Name)) { + // only display parts with fields + if (contentType.Parts.Any(x => x.PartDefinition.Fields.Any())) { + viewModel.ContentTypes.Add(new ContentTypeEntry { ContentTypeName = contentType.Name }); + } + } + + if (!viewModel.ContentTypes.Any()) { + _orchardServices.Notifier.Warning(T("There are no content types with custom fields")); + } + + if (!_featureManager.GetEnabledFeatures().Any(x => x.Id == "Orchard.Fields")) { + _orchardServices.Notifier.Warning(T("You need to enable Orchard.Fields in order to migrate current fields.")); + } + + return View(viewModel); + } + + //[HttpPost, ActionName("Index")] + //public ActionResult IndexPOST() { + // if (!_orchardServices.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to update fields' indexes."))) + // return new HttpUnauthorizedResult(); + + // // Get all ContentTypes with fields and Updates the LatestValue if necessary + // var contentTypesWithFields = _contentDefinitionManager.ListTypeDefinitions().OrderBy(c => c.Name).Where(w => w.Parts.Any(x => x.PartDefinition.Fields.Any())); + // foreach (var contentTypeWithFields in contentTypesWithFields) { + // var contents = _orchardServices.ContentManager.HqlQuery().ForType(contentTypeWithFields.Name).ForVersion(VersionOptions.Latest).List(); + // foreach (var content in contents) { + // _handlers.Value.Where(x => x.GetType() == typeof(FieldIndexPartHandler)).Invoke(handler => handler.Updated(new UpdateContentContext(content)), Logger); + // _transactionManager.RequireNew(); + // } + // } + // _orchardServices.Notifier.Information(T("Fields latest values were indexed successfully")); + + // return RedirectToAction("Index"); + //} + + [HttpPost] + public JsonResult MigrateLatestValue(int id) { + if (!_orchardServices.Authorizer.Authorize(StandardPermissions.SiteOwner)) + throw new AuthenticationException(""); + var contentTypeNamesWithFields = _contentDefinitionManager.ListTypeDefinitions() + .Where(w => w.Parts.Any(x => x.PartDefinition.Fields.Any())) + .Select(x => x.Name) + .ToArray(); + + var contents = _orchardServices.ContentManager.HqlQuery().ForVersion(VersionOptions.Latest) + .Where(ci => ci.ContentItem(), cix => cix.Gt("Id", id)) + .OrderBy(alias => alias.ContentItem(), order => order.Asc("Id")) + .Slice(0, BATCH).ToList(); + var lastContentItemId = id; + + foreach (var content in contents) { + if (contentTypeNamesWithFields.Contains(content.ContentType)) { + _handlers.Value.Where(x => x.GetType() == typeof(FieldIndexPartHandler)).Invoke(handler => handler.Updated(new UpdateContentContext(content)), Logger); + } + lastContentItemId = content.Id; + } + + return new JsonResult { Data = lastContentItemId }; + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Upgrade/Upgrade.csproj b/src/Orchard.Web/Modules/Upgrade/Upgrade.csproj index 6383dfb19..a18501baf 100644 --- a/src/Orchard.Web/Modules/Upgrade/Upgrade.csproj +++ b/src/Orchard.Web/Modules/Upgrade/Upgrade.csproj @@ -122,6 +122,7 @@ + @@ -148,6 +149,10 @@ {73a7688a-5bd3-4f7e-adfa-ce36c5a10e3b} Orchard.MediaLibrary + + {5531e894-d259-45a3-aa61-26dbe720c1ce} + Orchard.Projections + {194d3ccc-1153-474d-8176-fde8d7d0d0bd} Orchard.Widgets @@ -184,6 +189,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Orchard.Web/Modules/Upgrade/Views/Projections/Index.cshtml b/src/Orchard.Web/Modules/Upgrade/Views/Projections/Index.cshtml new file mode 100644 index 000000000..08dba3299 --- /dev/null +++ b/src/Orchard.Web/Modules/Upgrade/Views/Projections/Index.cshtml @@ -0,0 +1,68 @@ +@model Upgrade.ViewModels.MigrateViewModel +@{ Layout.Title = T("Updates projections index tables with fields' latest values.").ToString(); } +@if (Model.ContentTypes.Count() > 0) { + + using (Html.BeginFormAntiForgeryPost()) { + Html.ValidationSummary(); +

@T("The update will process fields data and will update their latest value into projections index tables.")

+
+ @T("These types will be processed:") +
    + @foreach (var contentTypeEntry in Model.ContentTypes) { +
  1. + @contentTypeEntry.ContentTypeName +
  2. + } +
+
+
+ +
+ } +} +else { +

@T("There are no content types with custom fields, nothing to upgrade.")

+} +@using (Script.Foot()) { + +} \ No newline at end of file