mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-27 04:19:04 +08:00
Adding Rollback command to content published events and added logging of the rolled back event.
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
@{
|
@{
|
||||||
|
|||||||
@@ -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) })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user