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,
EventDescriptor = descriptor,
CategoryDescriptor = descriptor.CategoryDescriptor,
SummaryShape = _displayBuilder.BuildDisplay(record, "SummaryAdmin")
SummaryShape = _displayBuilder.BuildDisplay(record, "SummaryAdmin"),
ActionsShape = _displayBuilder.BuildActions(record, "SummaryAdmin"),
};
var viewModel = new AuditTrailViewModel {

View File

@@ -1,20 +1,28 @@
using System.Web.Mvc;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Models;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Admin;
using Orchard.UI.Notify;
namespace Orchard.AuditTrail.Controllers {
[Admin]
public class ContentController : Controller {
private readonly IAuthorizer _authorizer;
private readonly IContentManager _contentManager;
private readonly INotifier _notifier;
public ContentController(IAuthorizer authorizer, IContentManager contentManager) {
public ContentController(IAuthorizer authorizer, IContentManager contentManager, INotifier notifier) {
_authorizer = authorizer;
_contentManager = contentManager;
_notifier = notifier;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public ActionResult Detail(int id, int version) {
var contentItem = _contentManager.Get(id, VersionOptions.Number(version));
if (!_authorizer.Authorize(Core.Contents.Permissions.ViewContent, contentItem))
@@ -29,5 +37,26 @@ namespace Orchard.AuditTrail.Controllers {
var editor = _contentManager.BuildEditor(contentItem);
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>
<Content Include="Views\EditorTemplates\Parts.ClientIpAddressSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\AuditTrailEventActions.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\AuditTrailEventActions-Content-Published.SummaryAdmin.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<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 Imported = "Imported";
public const string Exported = "Exported";
public const string RolledBack = "RolledBack";
public static Filters CreateFilters(int contentId, IUpdateModel 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, 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, 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.DisplayFilter(DisplayFilter);
@@ -51,7 +53,7 @@ namespace Orchard.AuditTrail.Providers.Content {
private void DisplayFilter(DisplayFilterContext context) {
var contentItemId = context.Filters.Get("content").ToInt32();
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);
context.FilterDisplay.Add(filterDisplay);

View File

@@ -41,6 +41,23 @@ namespace Orchard.AuditTrail.Providers.Content {
context.Shape.ContentItem = contentItem;
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) {
var contentItem = context.ContentItem;
_ignoreExportHandlerFor = contentItem;
_previousVersionXml = _contentItemCreated
? default(XElement) // No need to do a diff on a newly created content item.
: _contentManager.Export(contentItem);
_ignoreExportHandlerFor = null;
}
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) {
var previousVersion = context.PreviousItemVersionRecord;
RecordAuditTrailEvent(ContentAuditTrailEventProvider.Published, context.ContentItem, previousVersion);

View File

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

View File

@@ -3,5 +3,6 @@ using Orchard.AuditTrail.Models;
namespace Orchard.AuditTrail.Services {
public interface IAuditTrailEventDisplayBuilder : IDependency {
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 AuditTrailCategoryDescriptor CategoryDescriptor { 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.Models
@using Orchard.AuditTrail.Services.Models
@using Orchard.ContentManagement
@model Orchard.AuditTrail.ViewModels.AuditTrailViewModel
@{
Style.Include("audittrail-display.css");
@@ -16,7 +14,7 @@
Layout.Title = T("Audit Trail");
}
@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">
@Display(Model.FilterDisplay)
<div class="filter-control-group">
@@ -57,7 +55,7 @@
<td class="timestamp-column">@Display.DateTime(DateTimeUtc: record.Record.CreatedUtc)</td>
<td class="summary-column">@Display(record.SummaryShape)</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>
}
</tbody>

View File

@@ -1,6 +1,5 @@
@using Orchard.AuditTrail.Helpers
@using Orchard.AuditTrail.Models
@using Orchard.AuditTrail.Providers.Content
@using Orchard.AuditTrail.Services.Models
@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)