mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-09-22 20:13:50 +08:00
Adding versioning support to content query api
--HG-- extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4044809
This commit is contained in:
@@ -29,6 +29,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
databaseFileName,
|
||||
typeof(GammaRecord),
|
||||
typeof(DeltaRecord),
|
||||
typeof(EpsilonRecord),
|
||||
typeof(ContentItemVersionRecord),
|
||||
typeof(ContentItemRecord),
|
||||
typeof(ContentTypeRecord));
|
||||
@@ -51,6 +52,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
builder.Register<BetaHandler>().As<IContentHandler>();
|
||||
builder.Register<GammaHandler>().As<IContentHandler>();
|
||||
builder.Register<DeltaHandler>().As<IContentHandler>();
|
||||
builder.Register<EpsilonHandler>().As<IContentHandler>();
|
||||
builder.Register<FlavoredHandler>().As<IContentHandler>();
|
||||
builder.Register<StyledHandler>().As<IContentHandler>();
|
||||
|
||||
@@ -61,6 +63,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
|
||||
_session.Delete("from GammaRecord");
|
||||
_session.Delete("from DeltaRecord");
|
||||
_session.Delete("from EpsilonRecord");
|
||||
_session.Delete("from ContentItemVersionRecord");
|
||||
_session.Delete("from ContentItemRecord");
|
||||
_session.Delete("from ContentTypeRecord");
|
||||
@@ -233,6 +236,206 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(subset.Skip(2).First().Id, Is.EqualTo(reverseById.Skip(4).First().Id));
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void QueryShouldJoinVersionedRecords() {
|
||||
AddSampleData();
|
||||
_manager.Create<Gamma>("gamma", init => {
|
||||
init.Record.Frap = "one";
|
||||
init.As<Epsilon>().Record.Quad = "1";
|
||||
});
|
||||
_manager.Create<Gamma>("gamma", init => {
|
||||
init.Record.Frap = "two";
|
||||
init.As<Epsilon>().Record.Quad = "2";
|
||||
});
|
||||
_manager.Create<Gamma>("gamma", init => {
|
||||
init.Record.Frap = "three";
|
||||
init.As<Epsilon>().Record.Quad = "3";
|
||||
});
|
||||
_manager.Create<Gamma>("gamma", init => {
|
||||
init.Record.Frap = "four";
|
||||
init.As<Epsilon>().Record.Quad = "4";
|
||||
});
|
||||
_session.Flush();
|
||||
_session.Clear();
|
||||
|
||||
var results = _manager.Query<Epsilon, EpsilonRecord>()
|
||||
.Where(x => x.Quad == "2" || x.Quad == "3")
|
||||
.OrderByDescending(x => x.Quad)
|
||||
.List();
|
||||
|
||||
Assert.That(results.Count(), Is.EqualTo(2));
|
||||
Assert.That(results.First().Record, Has.Property("Quad").EqualTo("3"));
|
||||
Assert.That(results.Last().Record, Has.Property("Quad").EqualTo("2"));
|
||||
}
|
||||
|
||||
|
||||
private void AddGammaVersions() {
|
||||
var gamma1 = _manager.Create<ContentItem>("gamma", init => {
|
||||
init.As<Gamma>().Record.Frap = "one";
|
||||
init.As<Epsilon>().Record.Quad = "v1";
|
||||
});
|
||||
_session.Flush();
|
||||
_session.Clear();
|
||||
|
||||
var gamma2 = _manager.Get(gamma1.Id, VersionOptions.DraftRequired);
|
||||
gamma2.As<Gamma>().Record.Frap = "two";
|
||||
gamma2.As<Epsilon>().Record.Quad = "v2";
|
||||
_session.Flush();
|
||||
_session.Clear();
|
||||
|
||||
var gamma3 = _manager.Create<ContentItem>("gamma", init => {
|
||||
init.As<Gamma>().Record.Frap = "three";
|
||||
init.As<Epsilon>().Record.Quad = "v3";
|
||||
});
|
||||
_session.Flush();
|
||||
_session.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueryShouldOnlyReturnPublishedByDefault() {
|
||||
AddGammaVersions();
|
||||
|
||||
var list1 = _manager.Query<Gamma>()
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v1")
|
||||
.List();
|
||||
|
||||
var list2 = _manager.Query<Gamma>()
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v2")
|
||||
.List();
|
||||
|
||||
var list3 = _manager.Query<Gamma>()
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v3")
|
||||
.List();
|
||||
|
||||
var listOne = _manager.Query<Gamma>()
|
||||
.Where<GammaRecord>(x => x.Frap == "one")
|
||||
.List();
|
||||
|
||||
var listTwo = _manager.Query<Gamma>()
|
||||
.Where<GammaRecord>(x => x.Frap == "two")
|
||||
.List();
|
||||
|
||||
var listThree = _manager.Query<Gamma>()
|
||||
.Where<GammaRecord>(x => x.Frap == "three")
|
||||
.List();
|
||||
|
||||
Assert.That(list1.Count(), Is.EqualTo(1));
|
||||
Assert.That(list2.Count(), Is.EqualTo(0));
|
||||
Assert.That(list3.Count(), Is.EqualTo(1));
|
||||
Assert.That(listOne.Count(), Is.EqualTo(0));
|
||||
Assert.That(listTwo.Count(), Is.EqualTo(1));
|
||||
Assert.That(listThree.Count(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueryForLatestShouldNotReturnEarlierVersions() {
|
||||
AddGammaVersions();
|
||||
|
||||
var list1 = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v1")
|
||||
.List();
|
||||
|
||||
var list2 = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v2")
|
||||
.List();
|
||||
|
||||
var list3 = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v3")
|
||||
.List();
|
||||
|
||||
var listOne = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<GammaRecord>(x => x.Frap == "one")
|
||||
.List();
|
||||
|
||||
var listTwo = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<GammaRecord>(x => x.Frap == "two")
|
||||
.List();
|
||||
|
||||
var listThree = _manager.Query<Gamma>(VersionOptions.Latest)
|
||||
.Where<GammaRecord>(x => x.Frap == "three")
|
||||
.List();
|
||||
|
||||
Assert.That(list1.Count(), Is.EqualTo(0));
|
||||
Assert.That(list2.Count(), Is.EqualTo(1));
|
||||
Assert.That(list3.Count(), Is.EqualTo(1));
|
||||
Assert.That(listOne.Count(), Is.EqualTo(0));
|
||||
Assert.That(listTwo.Count(), Is.EqualTo(1));
|
||||
Assert.That(listThree.Count(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueryForDraftShouldOnlyReturnLatestThatIsNotPublished() {
|
||||
AddGammaVersions();
|
||||
|
||||
var list1 = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v1")
|
||||
.List();
|
||||
|
||||
var list2 = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v2")
|
||||
.List();
|
||||
|
||||
var list3 = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v3")
|
||||
.List();
|
||||
|
||||
var listOne = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<GammaRecord>(x => x.Frap == "one")
|
||||
.List();
|
||||
|
||||
var listTwo = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<GammaRecord>(x => x.Frap == "two")
|
||||
.List();
|
||||
|
||||
var listThree = _manager.Query<Gamma>(VersionOptions.Draft)
|
||||
.Where<GammaRecord>(x => x.Frap == "three")
|
||||
.List();
|
||||
|
||||
Assert.That(list1.Count(), Is.EqualTo(0));
|
||||
Assert.That(list2.Count(), Is.EqualTo(1));
|
||||
Assert.That(list3.Count(), Is.EqualTo(0));
|
||||
Assert.That(listOne.Count(), Is.EqualTo(0));
|
||||
Assert.That(listTwo.Count(), Is.EqualTo(1));
|
||||
Assert.That(listThree.Count(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueryForAllShouldReturnMultipleQualifiedVersions() {
|
||||
AddGammaVersions();
|
||||
|
||||
var list1 = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v1")
|
||||
.List();
|
||||
|
||||
var list2 = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v2")
|
||||
.List();
|
||||
|
||||
var list3 = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<EpsilonRecord>(x => x.Quad == "v3")
|
||||
.List();
|
||||
|
||||
var listOne = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<GammaRecord>(x => x.Frap == "one")
|
||||
.List();
|
||||
|
||||
var listTwo = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<GammaRecord>(x => x.Frap == "two")
|
||||
.List();
|
||||
|
||||
var listThree = _manager.Query<Gamma>(VersionOptions.AllVersions)
|
||||
.Where<GammaRecord>(x => x.Frap == "three")
|
||||
.List();
|
||||
|
||||
Assert.That(list1.Count(), Is.EqualTo(1));
|
||||
Assert.That(list2.Count(), Is.EqualTo(1));
|
||||
Assert.That(list3.Count(), Is.EqualTo(1));
|
||||
Assert.That(listOne.Count(), Is.EqualTo(0));
|
||||
Assert.That(listTwo.Count(), Is.EqualTo(2));
|
||||
Assert.That(listThree.Count(), Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -386,7 +386,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(epsilon2.Record.Quad, Is.EqualTo("epsilon one"));
|
||||
epsilon2.Record.Quad = "epsilon two";
|
||||
Assert.That(epsilon1.Record.Quad, Is.EqualTo("epsilon one"));
|
||||
Assert.That(epsilon2.Record.Quad, Is.EqualTo("epsilon two"));
|
||||
Assert.That(epsilon2.Record.Quad, Is.EqualTo("epsilon two"));
|
||||
|
||||
|
||||
_session.Flush();
|
||||
@@ -409,6 +409,56 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(epsilon1.ContentItem.VersionRecord, Is.Not.SameAs(epsilon2.ContentItem.VersionRecord));
|
||||
Assert.That(epsilon1B.ContentItem.VersionRecord, Is.Not.SameAs(epsilon2B.ContentItem.VersionRecord));
|
||||
}
|
||||
|
||||
private void Flush() {
|
||||
Trace.WriteLine("flush");
|
||||
_session.Flush();
|
||||
|
||||
}
|
||||
private void FlushAndClear() {
|
||||
Trace.WriteLine("flush");
|
||||
_session.Flush();
|
||||
Trace.WriteLine("clear");
|
||||
_session.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllVersionsShouldReturnHistoryInOrder() {
|
||||
Trace.WriteLine("gamma1");
|
||||
var gamma1 = _manager.Create("gamma", VersionOptions.Published);
|
||||
Flush();
|
||||
|
||||
Trace.WriteLine("gamma2");
|
||||
var gamma2 = _manager.GetDraftRequired(gamma1.Id);
|
||||
Trace.WriteLine("publish");
|
||||
_manager.Publish(gamma2);
|
||||
Flush();
|
||||
|
||||
Trace.WriteLine("gamma3");
|
||||
var gamma3 = _manager.GetDraftRequired(gamma1.Id);
|
||||
Trace.WriteLine("publish");
|
||||
_manager.Publish(gamma3);
|
||||
Flush();
|
||||
|
||||
Trace.WriteLine("gamma4");
|
||||
var gamma4 = _manager.GetDraftRequired(gamma1.Id);
|
||||
Trace.WriteLine("publish");
|
||||
_manager.Publish(gamma2);
|
||||
FlushAndClear();
|
||||
|
||||
Assert.That(gamma1.Version, Is.EqualTo(1));
|
||||
Assert.That(gamma2.Version, Is.EqualTo(2));
|
||||
Assert.That(gamma3.Version, Is.EqualTo(3));
|
||||
Assert.That(gamma4.Version, Is.EqualTo(4));
|
||||
|
||||
var gammas = _manager.GetAllVersions(gamma1.Id).ToList();
|
||||
|
||||
Assert.That(gammas[0].Version, Is.EqualTo(1));
|
||||
Assert.That(gammas[1].Version, Is.EqualTo(2));
|
||||
Assert.That(gammas[2].Version, Is.EqualTo(3));
|
||||
Assert.That(gammas[3].Version, Is.EqualTo(4));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -166,13 +166,12 @@ namespace Orchard.Tests.Data {
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RepositoryCanCreateFetchUpdateAndDelete() {
|
||||
public void RepositoryCanCreateFetchAndDelete() {
|
||||
var foo1 = new Foo { Name = "yadda" };
|
||||
_fooRepos.Create(foo1);
|
||||
|
||||
var foo2 = _fooRepos.Get(foo1.Id);
|
||||
foo2.Name = "blah";
|
||||
_fooRepos.Update(foo2);
|
||||
foo2.Name = "blah";
|
||||
|
||||
Assert.That(foo1, Is.SameAs(foo2));
|
||||
|
||||
|
@@ -14,7 +14,7 @@ namespace Orchard.DevTools.Controllers {
|
||||
private readonly IRepository<ContentTypeRecord> _contentTypeRepository;
|
||||
private readonly IContentManager _contentManager;
|
||||
|
||||
public ContentController(
|
||||
public ContentController(
|
||||
IRepository<ContentTypeRecord> contentTypeRepository,
|
||||
IContentManager contentManager) {
|
||||
_contentTypeRepository = contentTypeRepository;
|
||||
@@ -28,9 +28,9 @@ namespace Orchard.DevTools.Controllers {
|
||||
});
|
||||
}
|
||||
|
||||
public ActionResult Details(int id) {
|
||||
public ActionResult Details(int id, int? version) {
|
||||
var model = new ContentDetailsViewModel {
|
||||
Item = _contentManager.Get(id)
|
||||
Item = version == null ? _contentManager.Get(id) : _contentManager.Get(id, VersionOptions.Number((int)version))
|
||||
};
|
||||
model.PartTypes = model.Item.ContentItem.Parts
|
||||
.Select(x => x.GetType())
|
||||
@@ -42,6 +42,8 @@ namespace Orchard.DevTools.Controllers {
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static IEnumerable<Type> AllTypes(Type type) {
|
||||
var scan = type;
|
||||
while (scan != null && scan != typeof(Object) && scan != typeof(ContentPart)) {
|
||||
|
@@ -4,9 +4,16 @@
|
||||
<h1><%=Html.TitleForPage(string.Format("{0} Content Type", Model.Item.ContentItem.ContentType), "Content")%></h1>
|
||||
<h3>Content Item</h3>
|
||||
<p>Id:
|
||||
<%=Model.Item.ContentItem.Id %></p>
|
||||
<p>ContentType:
|
||||
<%=Model.Item.ContentItem.ContentType%> <%=Html.ItemDisplayLink(Model.Item) %> <%=Html.ItemEditLink("edit", Model.Item) %></p>
|
||||
<%=Model.Item.ContentItem.Id %><br />
|
||||
Version:
|
||||
<%=Model.Item.ContentItem.Version %><br />
|
||||
ContentType:
|
||||
<%=Model.Item.ContentItem.ContentType%> <br />
|
||||
DisplayText:
|
||||
<%=Html.ItemDisplayText(Model.Item) %><br />
|
||||
Links:
|
||||
<%=Html.ItemDisplayLink("view", Model.Item) %> <%=Html.ItemEditLink("edit", Model.Item) %></p>
|
||||
|
||||
<h3>Content Item Parts</h3>
|
||||
<ul>
|
||||
<%foreach (var partType in Model.PartTypes.OrderBy(x => x.Name)) {%>
|
||||
|
@@ -2,4 +2,4 @@
|
||||
<%@ Import Namespace="Orchard.DevTools.Models" %>
|
||||
<div class="debug message">
|
||||
DevTools: showing
|
||||
<%= Html.ActionLink(Model.ContentItem.ContentType + " #" + Model.ContentItem.Id, "details", "content", new { area = "Orchard.DevTools", Model.ContentItem.Id }, new { })%></div>
|
||||
<%= Html.ActionLink(Model.ContentItem.ContentType + " #" + Model.ContentItem.Id + " v" + Model.ContentItem.Version, "details", "content", new { area = "Orchard.DevTools", Model.ContentItem.Id, Model.ContentItem.Version }, new { })%></div>
|
||||
|
@@ -3,5 +3,5 @@
|
||||
<% if (Model.ContentItem.Id > 0) { %>
|
||||
<div class="debug message">
|
||||
DevTools: editing
|
||||
<%= Html.ActionLink(Model.ContentItem.ContentType + " #" + Model.ContentItem.Id, "details", "content", new { area = "Orchard.DevTools", Model.ContentItem.Id }, new { })%></div>
|
||||
<%= Html.ActionLink(Model.ContentItem.ContentType + " #" + Model.ContentItem.Id + " v" + Model.ContentItem.Version, "details", "content", new { area = "Orchard.DevTools", Model.ContentItem.Id, Model.ContentItem.Version }, new { })%></div>
|
||||
<% } %>
|
||||
|
@@ -68,8 +68,9 @@ namespace Orchard.Sandbox.Controllers {
|
||||
return RedirectToAction("show", new { id });
|
||||
}
|
||||
|
||||
var latest = Services.ContentManager.GetLatest<SandboxPage>(id);
|
||||
return View(new PageEditViewModel {
|
||||
Page = Services.ContentManager.BuildEditorModel<SandboxPage>(id)
|
||||
Page = Services.ContentManager.BuildEditorModel(latest)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,15 +79,15 @@ namespace Orchard.Sandbox.Controllers {
|
||||
if (IsEditAllowed() == false) {
|
||||
return RedirectToAction("show", new { id });
|
||||
}
|
||||
|
||||
var latest = Services.ContentManager.GetDraftRequired<SandboxPage>(id);
|
||||
var model = new PageEditViewModel {
|
||||
Page = Services.ContentManager.UpdateEditorModel<SandboxPage>(id, this)
|
||||
Page = Services.ContentManager.UpdateEditorModel(latest, this)
|
||||
};
|
||||
if (!ModelState.IsValid) {
|
||||
Services.TransactionManager.Cancel();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
Services.ContentManager.Publish(latest.ContentItem);
|
||||
return RedirectToAction("show", new { id });
|
||||
}
|
||||
|
||||
|
@@ -60,19 +60,93 @@ namespace Orchard.ContentManagement {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContentExtensions {
|
||||
public static class ContentQueryExtensions {
|
||||
|
||||
/* Query related extension methods */
|
||||
|
||||
public static IContentQuery<TPart> Query<TPart>(this IContentManager manager)
|
||||
where TPart : ContentPart {
|
||||
return manager.Query().ForPart<TPart>();
|
||||
}
|
||||
public static IContentQuery<TPart, TRecord> Query<TPart, TRecord>(this IContentManager manager)
|
||||
where TPart : ContentPart<TRecord>
|
||||
where TRecord : ContentPartRecord {
|
||||
return manager.Query().ForPart<TPart>().Join<TRecord>();
|
||||
}
|
||||
|
||||
/* Query(VersionOptions options) */
|
||||
|
||||
public static IContentQuery<ContentItem> Query(this IContentManager manager, VersionOptions options) {
|
||||
return manager.Query().ForVersion(options);
|
||||
}
|
||||
|
||||
public static IContentQuery<TPart> Query<TPart>(this IContentManager manager, VersionOptions options) where TPart : ContentPart {
|
||||
return manager.Query().ForPart<TPart>().ForVersion(options);
|
||||
}
|
||||
|
||||
public static IContentQuery<TPart, TRecord> Query<TPart, TRecord>(this IContentManager manager, VersionOptions options)
|
||||
where TPart : ContentPart<TRecord>
|
||||
where TRecord : ContentPartRecord {
|
||||
return manager.Query().ForPart<TPart>().ForVersion(options).Join<TRecord>();
|
||||
}
|
||||
|
||||
/* Query(params string[] contentTypeNames) */
|
||||
|
||||
public static IContentQuery<ContentItem> Query(this IContentManager manager, params string[] contentTypeNames) {
|
||||
return manager.Query().ForType(contentTypeNames);
|
||||
}
|
||||
public static IContentQuery<TPart> Query<TPart>(this IContentManager manager, params string[] contentTypeNames) where TPart : ContentPart {
|
||||
return manager.Query().ForPart<TPart>().ForType(contentTypeNames);
|
||||
}
|
||||
public static IContentQuery<TPart, TRecord> Query<TPart, TRecord>(this IContentManager manager, params string[] contentTypeNames)
|
||||
where TPart : ContentPart<TRecord>
|
||||
where TRecord : ContentPartRecord {
|
||||
return manager.Query().ForPart<TPart>().ForType(contentTypeNames).Join<TRecord>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static IEnumerable<T> List<T>(this IContentManager manager, params string[] contentTypeNames) where T : ContentPart {
|
||||
return manager.Query<T>(contentTypeNames).List();
|
||||
}
|
||||
|
||||
public static IEnumerable<T> List<T>(this IContentQuery query) where T : IContent {
|
||||
return query.ForPart<T>().List();
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Slice<T>(this IContentQuery<T> query, int count) where T : IContent {
|
||||
return query.Slice(0, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ContentGetExtensions {
|
||||
|
||||
public static ContentItem GetLatest(this IContentManager manager, int id) {
|
||||
return manager.Get(id, VersionOptions.Latest);
|
||||
}
|
||||
public static ContentItem GetDraftRequired(this IContentManager manager, int id) {
|
||||
return manager.Get(id, VersionOptions.DraftRequired);
|
||||
}
|
||||
|
||||
public static T Get<T>(this IContentManager manager, int id) where T : class, IContent {
|
||||
var contentItem = manager.Get(id);
|
||||
return contentItem == null ? null : contentItem.Get<T>();
|
||||
}
|
||||
|
||||
public static T Get<T>(this IContentManager manager, int id, VersionOptions options) where T : class, IContent {
|
||||
var contentItem = manager.Get(id, options);
|
||||
return contentItem == null ? null : contentItem.Get<T>();
|
||||
}
|
||||
public static T GetLatest<T>(this IContentManager manager, int id) where T : class, IContent {
|
||||
return Get<T>(manager, id, VersionOptions.Latest);
|
||||
}
|
||||
public static T GetDraftRequired<T>(this IContentManager manager, int id) where T : class, IContent {
|
||||
return Get<T>(manager, id, VersionOptions.DraftRequired);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ContentExtensions {
|
||||
|
||||
|
||||
/* Display and editor convenience extension methods */
|
||||
@@ -90,41 +164,6 @@ namespace Orchard.ContentManagement {
|
||||
}
|
||||
|
||||
|
||||
/* Query related extension methods */
|
||||
|
||||
public static IContentQuery<TPart> Query<TPart>(this IContentManager manager)
|
||||
where TPart : ContentPart {
|
||||
return manager.Query().ForPart<TPart>();
|
||||
}
|
||||
public static IContentQuery<TPart, TRecord> Query<TPart, TRecord>(this IContentManager manager)
|
||||
where TPart : ContentPart<TRecord>
|
||||
where TRecord : ContentPartRecord {
|
||||
return manager.Query().ForPart<TPart>().Join<TRecord>();
|
||||
}
|
||||
|
||||
public static IContentQuery<ContentItem> Query(this IContentManager manager, params string[] contentTypeNames) {
|
||||
return manager.Query().ForType(contentTypeNames);
|
||||
}
|
||||
public static IContentQuery<TPart> Query<TPart>(this IContentManager manager, params string[] contentTypeNames) where TPart : ContentPart {
|
||||
return manager.Query().ForPart<TPart>().ForType(contentTypeNames);
|
||||
}
|
||||
public static IContentQuery<TPart,TRecord> Query<TPart,TRecord>(this IContentManager manager, params string[] contentTypeNames) where TPart : ContentPart<TRecord> where TRecord : ContentPartRecord {
|
||||
return manager.Query().ForPart<TPart>().ForType(contentTypeNames).Join<TRecord>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static IEnumerable<T> List<T>(this IContentManager manager, params string[] contentTypeNames) where T : ContentPart {
|
||||
return manager.Query<T>(contentTypeNames).List();
|
||||
}
|
||||
|
||||
public static IEnumerable<T> List<T>(this IContentQuery query) where T : IContent {
|
||||
return query.ForPart<T>().List();
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Slice<T>(this IContentQuery<T> query, int count) where T : IContent {
|
||||
return query.Slice(0, count);
|
||||
}
|
||||
|
||||
|
||||
/* Aggregate item/part type casting extension methods */
|
||||
|
@@ -84,6 +84,7 @@ namespace Orchard.ContentManagement {
|
||||
versionRecord = _contentItemVersionRepository.Get(options.VersionRecordId);
|
||||
}
|
||||
else {
|
||||
// FIX: rework this so it falls back to an in-memory scan when the results don't fit the criteria
|
||||
var record = _contentItemRepository.Get(id);
|
||||
if (options.IsPublished) {
|
||||
versionRecord = _contentItemVersionRepository.Get(x => x.ContentItemRecord == record && x.Published);
|
||||
@@ -143,13 +144,47 @@ namespace Orchard.ContentManagement {
|
||||
|
||||
// when draft is required and not currently available a new version is appended
|
||||
if (appendLatestVersion) {
|
||||
return AppendLatestVersion(context.ContentItem);
|
||||
return BuildNewVersion(context.ContentItem);
|
||||
}
|
||||
|
||||
return context.ContentItem;
|
||||
}
|
||||
|
||||
public virtual ContentItem AppendLatestVersion(ContentItem existingContentItem) {
|
||||
public virtual IEnumerable<ContentItem> GetAllVersions(int id) {
|
||||
return _contentItemVersionRepository
|
||||
.Fetch(x => x.ContentItemRecord.Id == id)
|
||||
.OrderBy(x => x.Number)
|
||||
.Select(x => Get(x.ContentItemRecord.Id, VersionOptions.VersionRecord(x.Id)));
|
||||
}
|
||||
|
||||
public virtual void Publish(ContentItem contentItem) {
|
||||
if (contentItem.VersionRecord.Published) {
|
||||
return;
|
||||
}
|
||||
var previous = contentItem.Record.Versions.SingleOrDefault(x => x.Published);
|
||||
if (previous != null) {
|
||||
previous.Published = false;
|
||||
//_contentItemVersionRepository.Update(previous);
|
||||
}
|
||||
contentItem.VersionRecord.Published = true;
|
||||
//_contentItemVersionRepository.Update(contentItem.VersionRecord);
|
||||
//TODO: fire content handler events
|
||||
}
|
||||
|
||||
public virtual void Remove(ContentItem contentItem) {
|
||||
var activeVersions = _contentItemVersionRepository.Fetch(x => x.ContentItemRecord == contentItem.Record && (x.Published || x.Latest));
|
||||
foreach (var version in activeVersions) {
|
||||
if (version.Published) {
|
||||
version.Published = false;
|
||||
}
|
||||
if (version.Latest) {
|
||||
version.Latest = false;
|
||||
}
|
||||
}
|
||||
//TODO: fire content handler events
|
||||
}
|
||||
|
||||
protected virtual ContentItem BuildNewVersion(ContentItem existingContentItem) {
|
||||
var contentItemRecord = existingContentItem.Record;
|
||||
|
||||
// locate the existing and the current latest versions, allocate building version
|
||||
@@ -160,17 +195,18 @@ namespace Orchard.ContentManagement {
|
||||
Published = false
|
||||
};
|
||||
|
||||
if (existingItemVersionRecord.Latest == false) {
|
||||
var latestVersion = _contentItemVersionRepository.Get(x => x.ContentItemRecord == contentItemRecord && x.Latest);
|
||||
|
||||
var latestVersion = contentItemRecord.Versions.SingleOrDefault(x => x.Latest);
|
||||
|
||||
if (latestVersion != null) {
|
||||
latestVersion.Latest = false;
|
||||
buildingItemVersionRecord.Number = latestVersion.Number + 1;
|
||||
}
|
||||
else {
|
||||
existingItemVersionRecord.Latest = false;
|
||||
buildingItemVersionRecord.Number = existingItemVersionRecord.Number + 1;
|
||||
buildingItemVersionRecord.Number = contentItemRecord.Versions.Max(x => x.Number) + 1;
|
||||
}
|
||||
|
||||
|
||||
contentItemRecord.Versions.Add(buildingItemVersionRecord);
|
||||
_contentItemVersionRepository.Create(buildingItemVersionRecord);
|
||||
|
||||
var buildingContentItem = New(existingContentItem.ContentType);
|
||||
|
@@ -8,12 +8,14 @@ using NHibernate.Impl;
|
||||
using NHibernate.Linq;
|
||||
using Orchard.ContentManagement.Records;
|
||||
using Orchard.Data;
|
||||
using Orchard.Utility;
|
||||
|
||||
namespace Orchard.ContentManagement {
|
||||
public class DefaultContentQuery : IContentQuery {
|
||||
private readonly ISessionLocator _sessionLocator;
|
||||
private ISession _session;
|
||||
private ICriteria _itemCriteria;
|
||||
private ICriteria _itemVersionCriteria;
|
||||
private VersionOptions _versionOptions;
|
||||
|
||||
public DefaultContentQuery(IContentManager contentManager, ISessionLocator sessionLocator) {
|
||||
_sessionLocator = sessionLocator;
|
||||
@@ -24,41 +26,53 @@ namespace Orchard.ContentManagement {
|
||||
|
||||
ISession BindSession() {
|
||||
if (_session == null)
|
||||
_session = _sessionLocator.For(typeof(ContentItemRecord));
|
||||
_session = _sessionLocator.For(typeof(ContentItemVersionRecord));
|
||||
return _session;
|
||||
}
|
||||
|
||||
ICriteria BindItemCriteria() {
|
||||
if (_itemCriteria == null) {
|
||||
_itemCriteria = BindSession().CreateCriteria<ContentItemRecord>();
|
||||
}
|
||||
return _itemCriteria;
|
||||
ICriteria BindCriteriaByPath(ICriteria criteria, string path) {
|
||||
return criteria.GetCriteriaByPath(path) ?? criteria.CreateCriteria(path);
|
||||
}
|
||||
|
||||
ICriteria BindCriteriaByPath(string path) {
|
||||
var itemCriteria = BindItemCriteria();
|
||||
|
||||
// special if the content item is ever used as where or order
|
||||
if (path == typeof(ContentItemRecord).Name)
|
||||
return itemCriteria;
|
||||
ICriteria BindTypeCriteria() {
|
||||
return BindCriteriaByPath(BindCriteriaByPath(BindItemVersionCriteria(), "ContentItemRecord"), "ContentType");
|
||||
}
|
||||
|
||||
return itemCriteria.GetCriteriaByPath(path) ?? itemCriteria.CreateCriteria(path);
|
||||
ICriteria BindItemCriteria() {
|
||||
return BindCriteriaByPath(BindItemVersionCriteria(), "ContentItemRecord");
|
||||
}
|
||||
|
||||
ICriteria BindItemVersionCriteria() {
|
||||
if (_itemVersionCriteria == null) {
|
||||
_itemVersionCriteria = BindSession().CreateCriteria<ContentItemVersionRecord>();
|
||||
}
|
||||
return _itemVersionCriteria;
|
||||
}
|
||||
|
||||
ICriteria BindPartCriteria<TRecord>() where TRecord : ContentPartRecord {
|
||||
if (typeof(TRecord).IsSubclassOf(typeof(ContentPartVersionRecord))) {
|
||||
return BindCriteriaByPath(BindItemVersionCriteria(), typeof(TRecord).Name);
|
||||
}
|
||||
return BindCriteriaByPath(BindItemCriteria(), typeof(TRecord).Name);
|
||||
}
|
||||
|
||||
|
||||
private void ForType(params string[] contentTypeNames) {
|
||||
BindCriteriaByPath("ContentType").Add(Restrictions.InG("Name", contentTypeNames));
|
||||
return;
|
||||
if (contentTypeNames != null && contentTypeNames.Length != 0)
|
||||
BindTypeCriteria().Add(Restrictions.InG("Name", contentTypeNames));
|
||||
}
|
||||
|
||||
public void ForVersion(VersionOptions options) {
|
||||
_versionOptions = options;
|
||||
}
|
||||
|
||||
private void Where<TRecord>() {
|
||||
private void Where<TRecord>() where TRecord : ContentPartRecord {
|
||||
// this simply demands an inner join
|
||||
BindCriteriaByPath(typeof(TRecord).Name);
|
||||
return;
|
||||
BindPartCriteria<TRecord>();
|
||||
}
|
||||
|
||||
private void Where<TRecord>(Expression<Func<TRecord, bool>> predicate) {
|
||||
private void Where<TRecord>(Expression<Func<TRecord, bool>> predicate) where TRecord : ContentPartRecord {
|
||||
|
||||
// build a linq to nhibernate expression
|
||||
var options = new QueryOptions();
|
||||
@@ -69,15 +83,13 @@ namespace Orchard.ContentManagement {
|
||||
var criteria = (CriteriaImpl)queryProvider.TranslateExpression(queryable.Expression);
|
||||
|
||||
// attach the criterion from the predicate to this query's criteria for the record
|
||||
var recordCriteria = BindCriteriaByPath(typeof(TRecord).Name);
|
||||
var recordCriteria = BindPartCriteria<TRecord>();
|
||||
foreach (var expressionEntry in criteria.IterateExpressionEntries()) {
|
||||
recordCriteria.Add(expressionEntry.Criterion);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void OrderBy<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) {
|
||||
private void OrderBy<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) where TRecord : ContentPartRecord {
|
||||
// build a linq to nhibernate expression
|
||||
var options = new QueryOptions();
|
||||
var queryProvider = new NHibernateQueryProvider(BindSession(), options);
|
||||
@@ -87,15 +99,13 @@ namespace Orchard.ContentManagement {
|
||||
var criteria = (CriteriaImpl)queryProvider.TranslateExpression(queryable.Expression);
|
||||
|
||||
// attaching orderings to the query's criteria
|
||||
var recordCriteria = BindCriteriaByPath(typeof(TRecord).Name);
|
||||
var recordCriteria = BindPartCriteria<TRecord>();
|
||||
foreach (var ordering in criteria.IterateOrderings()) {
|
||||
recordCriteria.AddOrder(ordering.Order);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void OrderByDescending<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) {
|
||||
private void OrderByDescending<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) where TRecord : ContentPartRecord {
|
||||
// build a linq to nhibernate expression
|
||||
var options = new QueryOptions();
|
||||
var queryProvider = new NHibernateQueryProvider(BindSession(), options);
|
||||
@@ -105,28 +115,48 @@ namespace Orchard.ContentManagement {
|
||||
var criteria = (CriteriaImpl)queryProvider.TranslateExpression(queryable.Expression);
|
||||
|
||||
// attaching orderings to the query's criteria
|
||||
var recordCriteria = BindCriteriaByPath(typeof(TRecord).Name);
|
||||
var recordCriteria = BindPartCriteria<TRecord>();
|
||||
foreach (var ordering in criteria.IterateOrderings()) {
|
||||
recordCriteria.AddOrder(ordering.Order);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> List() {
|
||||
return BindItemCriteria()
|
||||
.List<ContentItemRecord>()
|
||||
.Select(x => ContentManager.Get(x.Id));
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> Slice(int skip, int count) {
|
||||
var criteria = BindItemCriteria();
|
||||
if (skip != 0)
|
||||
var criteria = BindItemVersionCriteria();
|
||||
if (_versionOptions == null) {
|
||||
criteria.Add(Restrictions.Eq("Published", true));
|
||||
}
|
||||
else if (_versionOptions.IsPublished) {
|
||||
criteria.Add(Restrictions.Eq("Published", true));
|
||||
}
|
||||
else if (_versionOptions.IsLatest) {
|
||||
criteria.Add(Restrictions.Eq("Latest", true));
|
||||
}
|
||||
else if (_versionOptions.IsDraft) {
|
||||
criteria.Add(Restrictions.And(
|
||||
Restrictions.Eq("Latest", true),
|
||||
Restrictions.Eq("Published", false)));
|
||||
}
|
||||
else if (_versionOptions.IsAllVersions) {
|
||||
// no-op... all versions will be returned by default
|
||||
}
|
||||
else {
|
||||
throw new ApplicationException("Invalid VersionOptions for content query");
|
||||
}
|
||||
|
||||
// TODO: put 'removed false' filter in place
|
||||
|
||||
if (skip != 0) {
|
||||
criteria = criteria.SetFirstResult(skip);
|
||||
if (count != 0)
|
||||
}
|
||||
if (count != 0) {
|
||||
criteria = criteria.SetMaxResults(count);
|
||||
}
|
||||
return criteria
|
||||
.List<ContentItemRecord>()
|
||||
.Select(x => ContentManager.Get(x.Id));
|
||||
.List<ContentItemVersionRecord>()
|
||||
.Select(x => ContentManager.Get(x.Id, VersionOptions.VersionRecord(x.Id)))
|
||||
.ToReadOnlyCollection();
|
||||
}
|
||||
|
||||
IContentQuery<TPart> IContentQuery.ForPart<TPart>() {
|
||||
@@ -144,44 +174,50 @@ namespace Orchard.ContentManagement {
|
||||
get { return _query.ContentManager; }
|
||||
}
|
||||
|
||||
public IContentQuery<TPart> ForPart<TPart>() where TPart : IContent {
|
||||
IContentQuery<TPart> IContentQuery.ForPart<TPart>() {
|
||||
return new ContentQuery<TPart>(_query);
|
||||
}
|
||||
|
||||
public IContentQuery<T> ForType(params string[] contentTypes) {
|
||||
IContentQuery<T> IContentQuery<T>.ForType(params string[] contentTypes) {
|
||||
_query.ForType(contentTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerable<T> List() {
|
||||
return _query.List().AsPart<T>();
|
||||
IContentQuery<T> IContentQuery<T>.ForVersion(VersionOptions options) {
|
||||
_query.ForVersion(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerable<T> Slice(int skip, int count) {
|
||||
IEnumerable<T> IContentQuery<T>.List() {
|
||||
return _query.Slice(0, 0).AsPart<T>();
|
||||
}
|
||||
|
||||
IEnumerable<T> IContentQuery<T>.Slice(int skip, int count) {
|
||||
return _query.Slice(skip, count).AsPart<T>();
|
||||
}
|
||||
|
||||
public IContentQuery<T, TRecord> Join<TRecord>() where TRecord : ContentPartRecord {
|
||||
IContentQuery<T, TRecord> IContentQuery<T>.Join<TRecord>() {
|
||||
_query.Where<TRecord>();
|
||||
return new ContentQuery<T, TRecord>(_query);
|
||||
}
|
||||
|
||||
public IContentQuery<T, TRecord> Where<TRecord>(Expression<Func<TRecord, bool>> predicate) where TRecord : ContentPartRecord {
|
||||
IContentQuery<T, TRecord> IContentQuery<T>.Where<TRecord>(Expression<Func<TRecord, bool>> predicate) {
|
||||
_query.Where(predicate);
|
||||
return new ContentQuery<T, TRecord>(_query);
|
||||
}
|
||||
|
||||
public IContentQuery<T, TRecord> OrderBy<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) where TRecord : ContentPartRecord {
|
||||
IContentQuery<T, TRecord> IContentQuery<T>.OrderBy<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) {
|
||||
_query.OrderBy(keySelector);
|
||||
return new ContentQuery<T, TRecord>(_query);
|
||||
}
|
||||
|
||||
public IContentQuery<T, TRecord> OrderByDescending<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) where TRecord : ContentPartRecord {
|
||||
IContentQuery<T, TRecord> IContentQuery<T>.OrderByDescending<TRecord, TKey>(Expression<Func<TRecord, TKey>> keySelector) {
|
||||
_query.OrderByDescending(keySelector);
|
||||
return new ContentQuery<T, TRecord>(_query);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ContentQuery<T, TR> : ContentQuery<T>, IContentQuery<T, TR>
|
||||
where T : IContent
|
||||
where TR : ContentPartRecord {
|
||||
@@ -189,21 +225,26 @@ namespace Orchard.ContentManagement {
|
||||
: base(query) {
|
||||
}
|
||||
|
||||
public IContentQuery<T, TR> Where(Expression<Func<TR, bool>> predicate) {
|
||||
IContentQuery<T, TR> IContentQuery<T, TR>.ForVersion(VersionOptions options) {
|
||||
_query.ForVersion(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
IContentQuery<T, TR> IContentQuery<T, TR>.Where(Expression<Func<TR, bool>> predicate) {
|
||||
_query.Where(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IContentQuery<T, TR> OrderBy<TKey>(Expression<Func<TR, TKey>> keySelector) {
|
||||
IContentQuery<T, TR> IContentQuery<T, TR>.OrderBy<TKey>(Expression<Func<TR, TKey>> keySelector) {
|
||||
_query.OrderBy(keySelector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IContentQuery<T, TR> OrderByDescending<TKey>(Expression<Func<TR, TKey>> keySelector) {
|
||||
IContentQuery<T, TR> IContentQuery<T, TR>.OrderByDescending<TKey>(Expression<Func<TR, TKey>> keySelector) {
|
||||
_query.OrderByDescending(keySelector);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ using Orchard.ContentManagement.Records;
|
||||
using Orchard.Data;
|
||||
|
||||
namespace Orchard.ContentManagement.Handlers {
|
||||
public class StorageFilter<TRecord> : StorageFilterBase<ContentPart<TRecord>> where TRecord : ContentPartRecord,new() {
|
||||
public class StorageFilter<TRecord> : StorageFilterBase<ContentPart<TRecord>> where TRecord : ContentPartRecord, new() {
|
||||
private readonly IRepository<TRecord> _repository;
|
||||
|
||||
public StorageFilter(IRepository<TRecord> repository) {
|
||||
@@ -70,7 +70,7 @@ namespace Orchard.ContentManagement.Handlers {
|
||||
protected override void Versioning(VersionContentContext context, ContentPart<TRecord> existing, ContentPart<TRecord> building) {
|
||||
// move known ORM values over
|
||||
_repository.Copy(existing.Record, building.Record);
|
||||
|
||||
|
||||
// only the up-reference to the particular version differs at this point
|
||||
building.Record.ContentItemVersionRecord = context.BuildingItemVersionRecord;
|
||||
|
||||
|
@@ -12,8 +12,10 @@ namespace Orchard.ContentManagement {
|
||||
|
||||
ContentItem Get(int id);
|
||||
ContentItem Get(int id, VersionOptions options);
|
||||
IEnumerable<ContentItem> GetAllVersions(int id);
|
||||
|
||||
ContentItem AppendLatestVersion(ContentItem sourceVersion);
|
||||
void Publish(ContentItem contentItem);
|
||||
void Remove(ContentItem contentItem);
|
||||
|
||||
IContentQuery<ContentItem> Query();
|
||||
|
||||
@@ -29,6 +31,7 @@ namespace Orchard.ContentManagement {
|
||||
public static VersionOptions Published { get { return new VersionOptions { IsPublished = true }; } }
|
||||
public static VersionOptions Draft { get { return new VersionOptions { IsDraft = true }; } }
|
||||
public static VersionOptions DraftRequired { get { return new VersionOptions { IsDraft = true, IsDraftRequired = true }; } }
|
||||
public static VersionOptions AllVersions { get { return new VersionOptions { IsAllVersions = true }; } }
|
||||
public static VersionOptions Number(int version) { return new VersionOptions { VersionNumber = version }; }
|
||||
public static VersionOptions VersionRecord(int id) { return new VersionOptions { VersionRecordId = id }; }
|
||||
|
||||
@@ -36,6 +39,7 @@ namespace Orchard.ContentManagement {
|
||||
public bool IsPublished { get; private set; }
|
||||
public bool IsDraft { get; private set; }
|
||||
public bool IsDraftRequired { get; private set; }
|
||||
public bool IsAllVersions { get; private set; }
|
||||
public int VersionNumber { get; private set; }
|
||||
public int VersionRecordId { get; private set; }
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@ namespace Orchard.ContentManagement {
|
||||
|
||||
public interface IContentQuery<TPart> : IContentQuery where TPart : IContent {
|
||||
IContentQuery<TPart> ForType(params string[] contentTypes);
|
||||
IContentQuery<TPart> ForVersion(VersionOptions options);
|
||||
|
||||
IEnumerable<TPart> List();
|
||||
IEnumerable<TPart> Slice(int skip, int count);
|
||||
|
||||
@@ -23,9 +25,12 @@ namespace Orchard.ContentManagement {
|
||||
}
|
||||
|
||||
public interface IContentQuery<TPart, TRecord> : IContentQuery<TPart> where TPart : IContent where TRecord : ContentPartRecord {
|
||||
new IContentQuery<TPart, TRecord> ForVersion(VersionOptions options);
|
||||
|
||||
IContentQuery<TPart, TRecord> Where(Expression<Func<TRecord, bool>> predicate);
|
||||
IContentQuery<TPart, TRecord> OrderBy<TKey>(Expression<Func<TRecord, TKey>> keySelector);
|
||||
IContentQuery<TPart, TRecord> OrderByDescending<TKey>(Expression<Func<TRecord, TKey>> keySelector);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
193
src/Orchard/ContentManagement/Records/ContentItemAlteration.cs
Normal file
193
src/Orchard/ContentManagement/Records/ContentItemAlteration.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
class ContentItemAlteration : IAutoMappingAlteration {
|
||||
private readonly IEnumerable<Type> _recordTypes;
|
||||
|
||||
[UsedImplicitly]
|
||||
public ContentItemAlteration() {
|
||||
_recordTypes = Enumerable.Empty<Type>();
|
||||
}
|
||||
public ContentItemAlteration(IEnumerable<Type> recordTypes) {
|
||||
_recordTypes = recordTypes;
|
||||
}
|
||||
|
||||
public void Alter(AutoPersistenceModel model) {
|
||||
|
||||
model.Override<ContentItemRecord>(mapping => {
|
||||
foreach (var recordType in _recordTypes.Where(Utility.IsPartRecord)) {
|
||||
var type = typeof(Alteration<,>).MakeGenericType(typeof(ContentItemRecord), recordType);
|
||||
var alteration = (IAlteration<ContentItemRecord>)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
}
|
||||
});
|
||||
|
||||
model.Override<ContentItemVersionRecord>(mapping => {
|
||||
foreach (var recordType in _recordTypes.Where(Utility.IsPartVersionRecord)) {
|
||||
var type = typeof(Alteration<,>).MakeGenericType(typeof(ContentItemVersionRecord), recordType);
|
||||
var alteration = (IAlteration<ContentItemVersionRecord>)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface IAlteration<TItemRecord> {
|
||||
void Override(AutoMapping<TItemRecord> mapping);
|
||||
}
|
||||
|
||||
class Alteration<TItemRecord, TPartRecord> : IAlteration<TItemRecord> {
|
||||
public void Override(AutoMapping<TItemRecord> mapping) {
|
||||
|
||||
// public TPartRecord TPartRecord {get;set;}
|
||||
var name = typeof(TPartRecord).Name;
|
||||
var syntheticMethod = new DynamicMethod(name, typeof(TPartRecord), null, typeof(TItemRecord));
|
||||
var syntheticProperty = new SyntheticPropertyInfo(syntheticMethod);
|
||||
|
||||
// record => record.TPartRecord
|
||||
var parameter = Expression.Parameter(typeof(TItemRecord), "record");
|
||||
var syntheticExpression = (Expression<Func<TItemRecord, TPartRecord>>)Expression.Lambda(
|
||||
typeof(Func<TItemRecord, TPartRecord>),
|
||||
Expression.Property(parameter, syntheticProperty),
|
||||
parameter);
|
||||
|
||||
mapping.References(syntheticExpression)
|
||||
.Access.NoOp()
|
||||
.Column("Id")
|
||||
.Unique()
|
||||
.Not.Insert()
|
||||
.Not.Update()
|
||||
.Cascade.All();
|
||||
}
|
||||
}
|
||||
class PartAlteration<TPartRecord> : IAlteration<ContentItemRecord> where TPartRecord : ContentPartRecord {
|
||||
public void Override(AutoMapping<ContentItemRecord> mapping) {
|
||||
|
||||
// public TPartRecord TPartRecord {get;set;}
|
||||
var name = typeof(TPartRecord).Name;
|
||||
var syntheticMethod = new DynamicMethod(name, typeof(TPartRecord), null, typeof(ContentItemRecord));
|
||||
var syntheticProperty = new SyntheticPropertyInfo(syntheticMethod);
|
||||
|
||||
// record => record.TPartRecord
|
||||
var parameter = Expression.Parameter(typeof(ContentItemRecord), "record");
|
||||
var syntheticExpression = (Expression<Func<ContentItemRecord, TPartRecord>>)Expression.Lambda(
|
||||
typeof(Func<ContentItemRecord, TPartRecord>),
|
||||
Expression.Property(parameter, syntheticProperty),
|
||||
parameter);
|
||||
|
||||
mapping.References(syntheticExpression)
|
||||
.Access.NoOp()
|
||||
.Column("Id")
|
||||
.Unique()
|
||||
.Not.Insert()
|
||||
.Not.Update()
|
||||
.Cascade.All();
|
||||
}
|
||||
}
|
||||
|
||||
class PartVersionAlteration<TPartRecord> : IAlteration<ContentItemVersionRecord> where TPartRecord : ContentPartVersionRecord {
|
||||
public void Override(AutoMapping<ContentItemVersionRecord> mapping) {
|
||||
|
||||
// public TPartRecord TPartRecord {get;set;}
|
||||
var name = typeof(TPartRecord).Name;
|
||||
var syntheticMethod = new DynamicMethod(name, typeof(TPartRecord), null, typeof(ContentItemVersionRecord));
|
||||
var syntheticProperty = new SyntheticPropertyInfo(syntheticMethod);
|
||||
|
||||
// record => record.TPartRecord
|
||||
var parameter = Expression.Parameter(typeof(ContentItemVersionRecord), "record");
|
||||
var syntheticExpression = (Expression<Func<ContentItemVersionRecord, TPartRecord>>)Expression.Lambda(
|
||||
typeof(Func<ContentItemVersionRecord, TPartRecord>),
|
||||
Expression.Property(parameter, syntheticProperty),
|
||||
parameter);
|
||||
|
||||
mapping.References(syntheticExpression)
|
||||
.Access.NoOp()
|
||||
.Column("Id")
|
||||
.Unique()
|
||||
.Not.Insert()
|
||||
.Not.Update()
|
||||
.Cascade.All();
|
||||
}
|
||||
}
|
||||
private class SyntheticPropertyInfo : PropertyInfo {
|
||||
private readonly DynamicMethod _getMethod;
|
||||
|
||||
public SyntheticPropertyInfo(DynamicMethod dynamicMethod) {
|
||||
_getMethod = dynamicMethod;
|
||||
}
|
||||
|
||||
public override object[] GetCustomAttributes(bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool IsDefined(Type attributeType, bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodInfo[] GetAccessors(bool nonPublic) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodInfo GetGetMethod(bool nonPublic) {
|
||||
return _getMethod;
|
||||
}
|
||||
|
||||
public override MethodInfo GetSetMethod(bool nonPublic) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override ParameterInfo[] GetIndexParameters() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string Name {
|
||||
get { return _getMethod.Name; }
|
||||
}
|
||||
|
||||
public override Type DeclaringType {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override Type ReflectedType {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override Type PropertyType {
|
||||
get { return _getMethod.ReturnType; }
|
||||
}
|
||||
|
||||
public override PropertyAttributes Attributes {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override bool CanRead {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanWrite {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
class ContentItemRecordAlteration : IAutoMappingAlteration {
|
||||
private readonly IEnumerable<Type> _recordTypes;
|
||||
|
||||
public ContentItemRecordAlteration() {
|
||||
_recordTypes = Enumerable.Empty<Type>();
|
||||
}
|
||||
public ContentItemRecordAlteration(IEnumerable<Type> recordTypes) {
|
||||
_recordTypes = recordTypes;
|
||||
}
|
||||
|
||||
public void Alter(AutoPersistenceModel model) {
|
||||
model.Override<ContentItemRecord>(mapping => {
|
||||
foreach (var recordType in _recordTypes.Where(x => x.IsSubclassOf(typeof(ContentPartRecord)))) {
|
||||
var type = typeof(Alteration<>).MakeGenericType(recordType);
|
||||
var alteration = (IAlteration)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface IAlteration {
|
||||
void Override(AutoMapping<ContentItemRecord> mapping);
|
||||
}
|
||||
|
||||
class Alteration<TPartRecord> : IAlteration where TPartRecord : ContentPartRecord {
|
||||
public void Override(AutoMapping<ContentItemRecord> mapping) {
|
||||
|
||||
// public TPartRecord TPartRecord {get;set;}
|
||||
var name = typeof(TPartRecord).Name;
|
||||
var syntheticMethod = new DynamicMethod(name, typeof(TPartRecord), null, typeof(ContentItemRecord));
|
||||
var syntheticProperty = new SyntheticPropertyInfo(syntheticMethod);
|
||||
|
||||
// record => record.TPartRecord
|
||||
var parameter = Expression.Parameter(typeof(ContentItemRecord), "record");
|
||||
var syntheticExpression = (Expression<Func<ContentItemRecord, TPartRecord>>)Expression.Lambda(
|
||||
typeof(Func<ContentItemRecord, TPartRecord>),
|
||||
Expression.Property(parameter, syntheticProperty),
|
||||
parameter);
|
||||
|
||||
mapping.References(syntheticExpression)
|
||||
.Access.NoOp()
|
||||
.Column("Id")
|
||||
.Unique()
|
||||
.Not.Insert()
|
||||
.Not.Update()
|
||||
.Cascade.All();
|
||||
}
|
||||
|
||||
private class SyntheticPropertyInfo : PropertyInfo {
|
||||
private readonly DynamicMethod _getMethod;
|
||||
|
||||
public SyntheticPropertyInfo(DynamicMethod dynamicMethod) {
|
||||
_getMethod = dynamicMethod;
|
||||
}
|
||||
|
||||
public override object[] GetCustomAttributes(bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool IsDefined(Type attributeType, bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodInfo[] GetAccessors(bool nonPublic) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodInfo GetGetMethod(bool nonPublic) {
|
||||
return _getMethod;
|
||||
}
|
||||
|
||||
public override MethodInfo GetSetMethod(bool nonPublic) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override ParameterInfo[] GetIndexParameters() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string Name {
|
||||
get { return _getMethod.Name; }
|
||||
}
|
||||
|
||||
public override Type DeclaringType {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override Type ReflectedType {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override Type PropertyType {
|
||||
get { return _getMethod.ReturnType; }
|
||||
}
|
||||
|
||||
public override PropertyAttributes Attributes {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override bool CanRead {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanWrite {
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
public class ContentPartAlteration : IAutoMappingAlteration {
|
||||
public void Alter(AutoPersistenceModel model) {
|
||||
|
||||
model.OverrideAll(mapping => {
|
||||
var recordType = mapping.GetType().GetGenericArguments().Single();
|
||||
|
||||
if (Utility.IsPartRecord(recordType)) {
|
||||
var type = typeof(PartAlteration<>).MakeGenericType(recordType);
|
||||
var alteration = (IAlteration)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
}
|
||||
else if (Utility.IsPartVersionRecord(recordType)) {
|
||||
var type = typeof(PartVersionAlteration<>).MakeGenericType(recordType);
|
||||
var alteration = (IAlteration)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
interface IAlteration {
|
||||
void Override(object mapping);
|
||||
}
|
||||
|
||||
class PartAlteration<T> : IAlteration where T : ContentPartRecord {
|
||||
public void Override(object mappingObj) {
|
||||
var mapping = (AutoMapping<T>)mappingObj;
|
||||
|
||||
mapping.Id(x => x.Id)
|
||||
.GeneratedBy.Foreign("ContentItemRecord");
|
||||
|
||||
mapping.HasOne(x => x.ContentItemRecord)
|
||||
.Constrained();
|
||||
}
|
||||
}
|
||||
|
||||
class PartVersionAlteration<T> : IAlteration where T : ContentPartVersionRecord {
|
||||
public void Override(object mappingObj) {
|
||||
var mapping = (AutoMapping<T>)mappingObj;
|
||||
|
||||
mapping.Id(x => x.Id)
|
||||
.GeneratedBy.Foreign("ContentItemVersionRecord");
|
||||
|
||||
mapping.HasOne(x => x.ContentItemVersionRecord)
|
||||
.Constrained();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
public class ContentPartRecordAlteration : IAutoMappingAlteration {
|
||||
public void Alter(AutoPersistenceModel model) {
|
||||
|
||||
model.OverrideAll(mapping => {
|
||||
var genericArguments = mapping.GetType().GetGenericArguments();
|
||||
if (!genericArguments.Single().IsSubclassOf(typeof(ContentPartRecord))) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = typeof(Alteration<>).MakeGenericType(genericArguments);
|
||||
var alteration = (IAlteration)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
interface IAlteration {
|
||||
void Override(object mapping);
|
||||
}
|
||||
|
||||
class Alteration<T> : IAlteration where T : ContentPartRecord {
|
||||
public void Override(object mappingObj) {
|
||||
var mapping = (AutoMapping<T>)mappingObj;
|
||||
|
||||
mapping.Id(x => x.Id)
|
||||
.GeneratedBy.Foreign("ContentItemRecord");
|
||||
|
||||
mapping.HasOne(x => x.ContentItemRecord)
|
||||
.Constrained();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
public abstract class ContentPartVersionRecord {
|
||||
public virtual int Id { get; set; }
|
||||
public virtual ContentItemRecord ContentItemRecord { get; set; }
|
||||
public abstract class ContentPartVersionRecord : ContentPartRecord {
|
||||
public virtual ContentItemVersionRecord ContentItemVersionRecord { get; set; }
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
public class ContentPartVersionRecordAlteration : IAutoMappingAlteration {
|
||||
public void Alter(AutoPersistenceModel model) {
|
||||
|
||||
model.OverrideAll(mapping => {
|
||||
var genericArguments = mapping.GetType().GetGenericArguments();
|
||||
if (!genericArguments.Single().IsSubclassOf(typeof(ContentPartVersionRecord))) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = typeof(Alteration<>).MakeGenericType(genericArguments);
|
||||
var alteration = (IAlteration)Activator.CreateInstance(type);
|
||||
alteration.Override(mapping);
|
||||
});
|
||||
}
|
||||
|
||||
interface IAlteration {
|
||||
void Override(object mapping);
|
||||
}
|
||||
|
||||
class Alteration<T> : IAlteration where T : ContentPartVersionRecord {
|
||||
public void Override(object mappingObj) {
|
||||
var mapping = (AutoMapping<T>)mappingObj;
|
||||
|
||||
mapping.Id(x => x.Id)
|
||||
.GeneratedBy.Foreign("ContentItemVersionRecord");
|
||||
|
||||
mapping.HasOne(x => x.ContentItemVersionRecord)
|
||||
.Constrained();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
src/Orchard/ContentManagement/Records/Utility.cs
Normal file
16
src/Orchard/ContentManagement/Records/Utility.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Orchard.ContentManagement.Records {
|
||||
public static class Utility {
|
||||
public static bool IsPartRecord(Type type) {
|
||||
return type.IsSubclassOf(typeof(ContentPartRecord)) && !type.IsSubclassOf(typeof(ContentPartVersionRecord));
|
||||
}
|
||||
|
||||
public static bool IsPartVersionRecord(Type type) {
|
||||
return type.IsSubclassOf(typeof(ContentPartVersionRecord));
|
||||
}
|
||||
}
|
||||
}
|
@@ -57,7 +57,7 @@ namespace Orchard.Data {
|
||||
alt.Add(new AutoMappingOverrideAlteration(recordAssembly));
|
||||
}
|
||||
alt.AddFromAssemblyOf<DataModule>();
|
||||
alt.Add(new ContentItemRecordAlteration(recordTypes));
|
||||
alt.Add(new ContentItemAlteration(recordTypes));
|
||||
})
|
||||
.Conventions.AddFromAssemblyOf<DataModule>();
|
||||
}
|
||||
|
@@ -110,7 +110,8 @@ namespace Orchard.Data {
|
||||
|
||||
public virtual void Update(T entity) {
|
||||
Logger.Debug("Update {0}", entity);
|
||||
Session.Update(entity);
|
||||
Session.Evict(entity);
|
||||
Session.SaveOrUpdateCopy(entity);
|
||||
}
|
||||
|
||||
public virtual void Delete(T entity) {
|
||||
@@ -119,7 +120,7 @@ namespace Orchard.Data {
|
||||
}
|
||||
|
||||
public virtual void Copy(T source, T target) {
|
||||
Logger.Debug("Delete {0}", source, target);
|
||||
Logger.Debug("Copy {0} {1}", source, target);
|
||||
var metadata = Session.SessionFactory.GetClassMetadata(typeof (T));
|
||||
var values = metadata.GetPropertyValues(source, EntityMode.Poco);
|
||||
metadata.SetPropertyValues(target, values, EntityMode.Poco);
|
||||
|
@@ -170,13 +170,13 @@
|
||||
<Compile Include="ContentManagement\Drivers\PartDriver.cs" />
|
||||
<Compile Include="ContentManagement\IUpdateModel.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentItemRecord.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentItemRecordAlteration.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentItemAlteration.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentItemVersionRecord.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentPartRecord.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentPartRecordAlteration.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentPartAlteration.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentPartVersionRecord.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentPartVersionRecordAlteration.cs" />
|
||||
<Compile Include="ContentManagement\Records\ContentTypeRecord.cs" />
|
||||
<Compile Include="ContentManagement\Records\Utility.cs" />
|
||||
<Compile Include="ContentManagement\ViewModels\ItemDisplayModel.cs" />
|
||||
<Compile Include="ContentManagement\ViewModels\ItemEditorModel.cs" />
|
||||
<Compile Include="ContentManagement\ViewModels\TemplateViewModel.cs" />
|
||||
|
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
namespace Orchard.Utility {
|
||||
public static class ReadOnlyCollectionExtensions {
|
||||
public static IList<T> ToReadOnlyCollection<T>(this IEnumerable<T> enumerable) {
|
||||
return new ReadOnlyCollection<T>(enumerable.ToArray());
|
||||
return new ReadOnlyCollection<T>(enumerable.ToList());
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user