Adding Rollback command to content published events and added logging of the rolled back event.

This commit is contained in:
Sipke Schoorstra
2014-10-21 01:20:40 -07:00
parent 9106dce276
commit 7fd9bc01eb
13 changed files with 128 additions and 21 deletions

View File

@@ -55,7 +55,8 @@ namespace Orchard.AuditTrail.Controllers {
Record = record, Record = record,
EventDescriptor = descriptor, EventDescriptor = descriptor,
CategoryDescriptor = descriptor.CategoryDescriptor, CategoryDescriptor = descriptor.CategoryDescriptor,
SummaryShape = _displayBuilder.BuildDisplay(record, "SummaryAdmin") SummaryShape = _displayBuilder.BuildDisplay(record, "SummaryAdmin"),
ActionsShape = _displayBuilder.BuildActions(record, "SummaryAdmin"),
}; };
var viewModel = new AuditTrailViewModel { var viewModel = new AuditTrailViewModel {

View File

@@ -1,20 +1,28 @@
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Models; using Orchard.AuditTrail.Models;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Security; using Orchard.Security;
using Orchard.UI.Admin; using Orchard.UI.Admin;
using Orchard.UI.Notify;
namespace Orchard.AuditTrail.Controllers { namespace Orchard.AuditTrail.Controllers {
[Admin] [Admin]
public class ContentController : Controller { public class ContentController : Controller {
private readonly IAuthorizer _authorizer; private readonly IAuthorizer _authorizer;
private readonly IContentManager _contentManager; private readonly IContentManager _contentManager;
private readonly INotifier _notifier;
public ContentController(IAuthorizer authorizer, IContentManager contentManager) { public ContentController(IAuthorizer authorizer, IContentManager contentManager, INotifier notifier) {
_authorizer = authorizer; _authorizer = authorizer;
_contentManager = contentManager; _contentManager = contentManager;
_notifier = notifier;
T = NullLocalizer.Instance;
} }
public Localizer T { get; set; }
public ActionResult Detail(int id, int version) { public ActionResult Detail(int id, int version) {
var contentItem = _contentManager.Get(id, VersionOptions.Number(version)); var contentItem = _contentManager.Get(id, VersionOptions.Number(version));
if (!_authorizer.Authorize(Core.Contents.Permissions.ViewContent, contentItem)) if (!_authorizer.Authorize(Core.Contents.Permissions.ViewContent, contentItem))
@@ -29,5 +37,26 @@ namespace Orchard.AuditTrail.Controllers {
var editor = _contentManager.BuildEditor(contentItem); var editor = _contentManager.BuildEditor(contentItem);
return View(editor); return View(editor);
} }
[HttpPost]
public ActionResult Rollback(int id, int version, string returnUrl) {
var contentItem = _contentManager.Get(id);
if (!_authorizer.Authorize(Core.Contents.Permissions.PublishContent, contentItem))
return new HttpUnauthorizedResult();
var title = _contentManager.GetItemMetadata(contentItem).DisplayText;
var currentVersion = contentItem.Version;
var newContentItem = _contentManager.Rollback(contentItem, VersionOptions.Rollback(version, publish: true));
_notifier.Information(T("{0} has been rolled back from version {1} to version {2} as version {3}.", title, currentVersion, version, newContentItem.Version));
returnUrl = Url.IsLocalUrl(returnUrl)
? returnUrl
: Request.UrlReferrer != null
? Request.UrlReferrer.ToString()
: Url.Action("Index", "Admin");
return Redirect(returnUrl);
}
} }
} }

View File

@@ -302,6 +302,12 @@
<ItemGroup> <ItemGroup>
<Content Include="Views\EditorTemplates\Parts.ClientIpAddressSettings.cshtml" /> <Content Include="Views\EditorTemplates\Parts.ClientIpAddressSettings.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Views\AuditTrailEventActions.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\AuditTrailEventActions-Content-Published.SummaryAdmin.cshtml" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -19,6 +19,7 @@ namespace Orchard.AuditTrail.Providers.Content {
public const string Removed = "Removed"; public const string Removed = "Removed";
public const string Imported = "Imported"; public const string Imported = "Imported";
public const string Exported = "Exported"; public const string Exported = "Exported";
public const string RolledBack = "RolledBack";
public static Filters CreateFilters(int contentId, IUpdateModel updateModel) { public static Filters CreateFilters(int contentId, IUpdateModel updateModel) {
return new Filters(updateModel) { return new Filters(updateModel) {
@@ -34,7 +35,8 @@ namespace Orchard.AuditTrail.Providers.Content {
.Event(this, Unpublished, T("Unpublished"), T("A content item was unpublished."), enableByDefault: true) .Event(this, Unpublished, T("Unpublished"), T("A content item was unpublished."), enableByDefault: true)
.Event(this, Removed, T("Removed"), T("A content item was deleted."), enableByDefault: true) .Event(this, Removed, T("Removed"), T("A content item was deleted."), enableByDefault: true)
.Event(this, Imported, T("Imported"), T("A content item was imported."), enableByDefault: true) .Event(this, Imported, T("Imported"), T("A content item was imported."), enableByDefault: true)
.Event(this, Exported, T("Exported"), T("A content item was exported."), enableByDefault: false); .Event(this, Exported, T("Exported"), T("A content item was exported."), enableByDefault: false)
.Event(this, RolledBack, T("Rolled Back"), T("A content item was rolled back to a previous version."), enableByDefault: true);
context.QueryFilter(QueryFilter); context.QueryFilter(QueryFilter);
context.DisplayFilter(DisplayFilter); context.DisplayFilter(DisplayFilter);
@@ -51,7 +53,7 @@ namespace Orchard.AuditTrail.Providers.Content {
private void DisplayFilter(DisplayFilterContext context) { private void DisplayFilter(DisplayFilterContext context) {
var contentItemId = context.Filters.Get("content").ToInt32(); var contentItemId = context.Filters.Get("content").ToInt32();
if (contentItemId != null) { if (contentItemId != null) {
var contentItem = contentItemId != null ? _contentManager.Get(contentItemId.Value, VersionOptions.Latest) : default(ContentItem); var contentItem = _contentManager.Get(contentItemId.Value, VersionOptions.Latest);
var filterDisplay = context.ShapeFactory.AuditTrailFilter__ContentItem(ContentItem: contentItem); var filterDisplay = context.ShapeFactory.AuditTrailFilter__ContentItem(ContentItem: contentItem);
context.FilterDisplay.Add(filterDisplay); context.FilterDisplay.Add(filterDisplay);

View File

@@ -41,6 +41,23 @@ namespace Orchard.AuditTrail.Providers.Content {
context.Shape.ContentItem = contentItem; context.Shape.ContentItem = contentItem;
context.Shape.PreviousVersion = previousVersion; context.Shape.PreviousVersion = previousVersion;
}); });
builder.Describe("AuditTrailEventActions").OnDisplaying(context => {
var record = (AuditTrailEventRecord)context.Shape.Record;
if (record.Category != "Content")
return;
var eventData = (IDictionary<string, object>)context.Shape.EventData;
var contentItemId = eventData.Get<int>("ContentId");
var previousContentItemVersionId = eventData.Get<int>("PreviousVersionId");
var contentItem = _contentManager.Value.Get(contentItemId, VersionOptions.AllVersions);
var previousVersion = previousContentItemVersionId > 0 ? _contentManager.Value.Get(contentItemId, VersionOptions.VersionRecord(previousContentItemVersionId)) : default(ContentItem);
context.Shape.ContentItemId = contentItemId;
context.Shape.ContentItem = contentItem;
context.Shape.PreviousVersion = previousVersion;
});
} }
} }
} }

View File

@@ -33,9 +33,12 @@ namespace Orchard.AuditTrail.Providers.Content {
protected override void Updating(UpdateContentContext context) { protected override void Updating(UpdateContentContext context) {
var contentItem = context.ContentItem; var contentItem = context.ContentItem;
_ignoreExportHandlerFor = contentItem;
_previousVersionXml = _contentItemCreated _previousVersionXml = _contentItemCreated
? default(XElement) // No need to do a diff on a newly created content item. ? default(XElement) // No need to do a diff on a newly created content item.
: _contentManager.Export(contentItem); : _contentManager.Export(contentItem);
_ignoreExportHandlerFor = null;
} }
protected override void Updated(UpdateContentContext context) { protected override void Updated(UpdateContentContext context) {
@@ -54,6 +57,23 @@ namespace Orchard.AuditTrail.Providers.Content {
} }
} }
protected override void RollingBack(RollbackContentContext context) {
_ignoreExportHandlerFor = context.ContentItem;
_previousVersionXml = _contentManager.Export(context.ContentItem);
_ignoreExportHandlerFor = null;
}
protected override void RolledBack(RollbackContentContext context) {
var contentItem = context.ContentItem;
_ignoreExportHandlerFor = contentItem;
var newVersionXml = _contentManager.Export(contentItem);
_ignoreExportHandlerFor = null;
var diffGram = _analyzer.GenerateDiffGram(_previousVersionXml, newVersionXml);
RecordAuditTrailEvent(ContentAuditTrailEventProvider.RolledBack, context.ContentItem, diffGram: diffGram, previousVersionXml: _previousVersionXml);
}
protected override void Published(PublishContentContext context) { protected override void Published(PublishContentContext context) {
var previousVersion = context.PreviousItemVersionRecord; var previousVersion = context.PreviousItemVersionRecord;
RecordAuditTrailEvent(ContentAuditTrailEventProvider.Published, context.ContentItem, previousVersion); RecordAuditTrailEvent(ContentAuditTrailEventProvider.Published, context.ContentItem, previousVersion);

View File

@@ -1,33 +1,44 @@
using System; using System;
using System.Collections.Generic;
using Orchard.AuditTrail.Models; using Orchard.AuditTrail.Models;
using Orchard.DisplayManagement; using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Shapes;
namespace Orchard.AuditTrail.Services { namespace Orchard.AuditTrail.Services {
public class AuditTrailEventDisplayBuilder : IAuditTrailEventDisplayBuilder { public class AuditTrailEventDisplayBuilder : IAuditTrailEventDisplayBuilder {
private readonly IShapeFactory _shapeFactory;
private readonly IEventDataSerializer _serializer; private readonly IEventDataSerializer _serializer;
private readonly IAuditTrailManager _auditTrailManager; private readonly IAuditTrailManager _auditTrailManager;
public AuditTrailEventDisplayBuilder(IShapeFactory shapeFactory, IEventDataSerializer serializer, IAuditTrailManager auditTrailManager) { public AuditTrailEventDisplayBuilder(IShapeFactory shapeFactory, IEventDataSerializer serializer, IAuditTrailManager auditTrailManager) {
_shapeFactory = shapeFactory;
_serializer = serializer; _serializer = serializer;
_auditTrailManager = auditTrailManager; _auditTrailManager = auditTrailManager;
New = shapeFactory;
} }
public dynamic New { get; set; }
public dynamic BuildDisplay(AuditTrailEventRecord record, string displayType) { public dynamic BuildDisplay(AuditTrailEventRecord record, string displayType) {
return BuildEventShape("AuditTrailEvent", record, displayType);
}
public dynamic BuildActions(AuditTrailEventRecord record, string displayType) {
return BuildEventShape("AuditTrailEventActions", record, displayType);
}
private dynamic BuildEventShape(string shapeType, AuditTrailEventRecord record, string displayType) {
var eventData = _serializer.Deserialize(record.EventData); var eventData = _serializer.Deserialize(record.EventData);
var descriptor = _auditTrailManager.DescribeEvent(record); var descriptor = _auditTrailManager.DescribeEvent(record);
var auditTrailEventShape = New.AuditTrailEvent(Record: record, EventData: eventData, Descriptor: descriptor); var auditTrailEventActionsShape = _shapeFactory.Create(shapeType, Arguments.From(new Dictionary<string, object> {
var metaData = (ShapeMetadata)auditTrailEventShape.Metadata; {"Record", record},
{"EventData", eventData},
{"Descriptor", descriptor}
}));
var metaData = auditTrailEventActionsShape.Metadata;
metaData.DisplayType = displayType; metaData.DisplayType = displayType;
metaData.Alternates.Add(String.Format("AuditTrailEvent_{0}", displayType)); metaData.Alternates.Add(String.Format("{0}_{1}", shapeType, displayType));
metaData.Alternates.Add(String.Format("AuditTrailEvent__{0}", record.Category)); metaData.Alternates.Add(String.Format("{0}__{1}", shapeType, record.Category));
metaData.Alternates.Add(String.Format("AuditTrailEvent_{0}__{1}", displayType, record.Category)); metaData.Alternates.Add(String.Format("{0}_{1}__{2}", shapeType, displayType, record.Category));
metaData.Alternates.Add(String.Format("AuditTrailEvent__{0}__{1}", record.Category, record.EventName)); metaData.Alternates.Add(String.Format("{0}__{1}__{2}", shapeType, record.Category, record.EventName));
metaData.Alternates.Add(String.Format("AuditTrailEvent_{0}__{1}__{2}", displayType, record.Category, record.EventName)); metaData.Alternates.Add(String.Format("{0}_{1}__{2}__{3}", shapeType, displayType, record.Category, record.EventName));
return auditTrailEventShape; return auditTrailEventActionsShape;
} }
} }
} }

View File

@@ -3,5 +3,6 @@ using Orchard.AuditTrail.Models;
namespace Orchard.AuditTrail.Services { namespace Orchard.AuditTrail.Services {
public interface IAuditTrailEventDisplayBuilder : IDependency { public interface IAuditTrailEventDisplayBuilder : IDependency {
dynamic BuildDisplay(AuditTrailEventRecord record, string displayType); dynamic BuildDisplay(AuditTrailEventRecord record, string displayType);
dynamic BuildActions(AuditTrailEventRecord record, string displayType);
} }
} }

View File

@@ -7,5 +7,6 @@ namespace Orchard.AuditTrail.ViewModels {
public AuditTrailEventDescriptor EventDescriptor { get; set; } public AuditTrailEventDescriptor EventDescriptor { get; set; }
public AuditTrailCategoryDescriptor CategoryDescriptor { get; set; } public AuditTrailCategoryDescriptor CategoryDescriptor { get; set; }
public dynamic SummaryShape { get; set; } public dynamic SummaryShape { get; set; }
public dynamic ActionsShape { get; set; }
} }
} }

View File

@@ -1,7 +1,5 @@
@using Orchard.AuditTrail.Helpers @using Orchard.AuditTrail.Helpers
@using Orchard.AuditTrail.Models
@using Orchard.AuditTrail.Services.Models @using Orchard.AuditTrail.Services.Models
@using Orchard.ContentManagement
@model Orchard.AuditTrail.ViewModels.AuditTrailViewModel @model Orchard.AuditTrail.ViewModels.AuditTrailViewModel
@{ @{
Style.Include("audittrail-display.css"); Style.Include("audittrail-display.css");
@@ -16,7 +14,7 @@
Layout.Title = T("Audit Trail"); Layout.Title = T("Audit Trail");
} }
@Html.ValidationSummary() @Html.ValidationSummary()
@using (Html.BeginForm("Index", "Admin", new { area = "Orchard.AuditTrail" }, FormMethod.Get)) { @using (Html.BeginFormAntiForgeryPost(Url.Action("Index", "Admin", new { area = "Orchard.AuditTrail" }), FormMethod.Get)) {
<section class="audittrail-filter-section"> <section class="audittrail-filter-section">
@Display(Model.FilterDisplay) @Display(Model.FilterDisplay)
<div class="filter-control-group"> <div class="filter-control-group">
@@ -57,7 +55,7 @@
<td class="timestamp-column">@Display.DateTime(DateTimeUtc: record.Record.CreatedUtc)</td> <td class="timestamp-column">@Display.DateTime(DateTimeUtc: record.Record.CreatedUtc)</td>
<td class="summary-column">@Display(record.SummaryShape)</td> <td class="summary-column">@Display(record.SummaryShape)</td>
<td class="comment-column">@Html.Raw(record.Record.Comment.NewlinesToHtml())</td> <td class="comment-column">@Html.Raw(record.Record.Comment.NewlinesToHtml())</td>
<td class="actions-column">@Html.ActionLink(T("Details").Text, "Detail", "Admin", new { id = record.Record.Id, area = "Orchard.AuditTrail" }, null)</td> <td class="actions-column">@Display(record.ActionsShape)</td>
</tr> </tr>
} }
</tbody> </tbody>

View File

@@ -1,6 +1,5 @@
@using Orchard.AuditTrail.Helpers @using Orchard.AuditTrail.Helpers
@using Orchard.AuditTrail.Models @using Orchard.AuditTrail.Models
@using Orchard.AuditTrail.Providers.Content
@using Orchard.AuditTrail.Services.Models @using Orchard.AuditTrail.Services.Models
@using Orchard.ContentManagement @using Orchard.ContentManagement
@{ @{

View File

@@ -0,0 +1,17 @@
@using Orchard.AuditTrail.Helpers
@using Orchard.AuditTrail.Models
@using Orchard.ContentManagement
@{
var record = (AuditTrailEventRecord)Model.Record;
var eventData = (IDictionary<string, object>) Model.EventData;
var versionNumber = eventData.Get<int>("VersionNumber");
var contentItem = (ContentItem) Model.ContentItem;
}
@Html.ActionLink(T("Details").Text, "Detail", "Admin", new { id = record.Id, area = "Orchard.AuditTrail" }, null)
@if (contentItem != null) {
var isLatest = contentItem.VersionRecord.Number == versionNumber;
if (!isLatest) {
@T(" | ")
@Html.ActionLink(T("Rollback").Text, "Rollback", "Content", new {id = contentItem.Id, version = versionNumber, area = "Orchard.AuditTrail"}, new { data_unsafe_url = T("Are you sure you want to rollback to version {0}?", versionNumber) })
}
}

View File

@@ -0,0 +1,5 @@
@using Orchard.AuditTrail.Models
@{
var record = (AuditTrailEventRecord)Model.Record;
}
@Html.ActionLink(T("Details").Text, "Detail", "Admin", new { id = record.Id, area = "Orchard.AuditTrail" }, null)