diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/AdminController.cs index ffdaa918e..c3d73d672 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/AdminController.cs @@ -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 { diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/ContentController.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/ContentController.cs index 6ae87c618..98f99ec33 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/ContentController.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Controllers/ContentController.cs @@ -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); + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Orchard.AuditTrail.csproj b/src/Orchard.Web/Modules/Orchard.AuditTrail/Orchard.AuditTrail.csproj index b86b1e33c..adc50b69e 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Orchard.AuditTrail.csproj +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Orchard.AuditTrail.csproj @@ -302,6 +302,12 @@ + + + + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventProvider.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventProvider.cs index 8da739190..18cdb9f8e 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventProvider.cs @@ -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); diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventShapes.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventShapes.cs index da7123478..8cb43ffeb 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventShapes.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/ContentAuditTrailEventShapes.cs @@ -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)context.Shape.EventData; + var contentItemId = eventData.Get("ContentId"); + var previousContentItemVersionId = eventData.Get("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; + }); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/GlobalContentHandler.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/GlobalContentHandler.cs index 0c1ab71e6..e1091bc48 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/GlobalContentHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Providers/Content/GlobalContentHandler.cs @@ -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); diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailEventDisplayBuilder.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailEventDisplayBuilder.cs index 6a25cabff..a1e6dda46 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailEventDisplayBuilder.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailEventDisplayBuilder.cs @@ -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 { + {"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; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/IAuditTrailEventDisplayBuilder.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/IAuditTrailEventDisplayBuilder.cs index 85f6f94b4..e49ff95db 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/IAuditTrailEventDisplayBuilder.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/IAuditTrailEventDisplayBuilder.cs @@ -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); } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/ViewModels/AuditTrailEventSummaryViewModel.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/ViewModels/AuditTrailEventSummaryViewModel.cs index 3b9b5d2cb..5a49cf7f6 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/ViewModels/AuditTrailEventSummaryViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/ViewModels/AuditTrailEventSummaryViewModel.cs @@ -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; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/Admin/Index.cshtml index edcb208f7..912ba38eb 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/Admin/Index.cshtml @@ -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)) {
@Display(Model.FilterDisplay)
@@ -57,7 +55,7 @@ @Display.DateTime(DateTimeUtc: record.Record.CreatedUtc) @Display(record.SummaryShape) @Html.Raw(record.Record.Comment.NewlinesToHtml()) - @Html.ActionLink(T("Details").Text, "Detail", "Admin", new { id = record.Record.Id, area = "Orchard.AuditTrail" }, null) + @Display(record.ActionsShape) } diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEvent-Content.cshtml b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEvent-Content.cshtml index 91ec8f147..ef7e94ae7 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEvent-Content.cshtml +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEvent-Content.cshtml @@ -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 @{ diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions-Content-Published.SummaryAdmin.cshtml b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions-Content-Published.SummaryAdmin.cshtml new file mode 100644 index 000000000..99cebb032 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions-Content-Published.SummaryAdmin.cshtml @@ -0,0 +1,17 @@ +@using Orchard.AuditTrail.Helpers +@using Orchard.AuditTrail.Models +@using Orchard.ContentManagement +@{ + var record = (AuditTrailEventRecord)Model.Record; + var eventData = (IDictionary) Model.EventData; + var versionNumber = eventData.Get("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) }) + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions.cshtml b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions.cshtml new file mode 100644 index 000000000..a2b0887ea --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Views/AuditTrailEventActions.cshtml @@ -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) \ No newline at end of file