Recycle Bin improvements (Lombiq Technologies: COLI-1711) (#8890)
Build Crowdin Translation Packages / build-crowdin-translation-packages (push) Has been cancelled
Compile / Compile .NET solution (push) Has been cancelled
Compile / Compile Client-side Assets (push) Has been cancelled
SpecFlow Tests / SpecFlow Tests (push) Has been cancelled

This commit is contained in:
Benedek Farkas
2026-01-25 17:54:35 +01:00
committed by GitHub
parent 8687ca0a98
commit d58037bd8f
3 changed files with 38 additions and 33 deletions
@@ -4,7 +4,6 @@ using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.AuditTrail.Helpers; using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services; using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.AuditTrail.ViewModels; using Orchard.AuditTrail.ViewModels;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Environment.Extensions; using Orchard.Environment.Extensions;
@@ -28,21 +27,19 @@ namespace Orchard.AuditTrail.Controllers
private readonly IOrchardServices _services; private readonly IOrchardServices _services;
private readonly IRecycleBin _recycleBin; private readonly IRecycleBin _recycleBin;
public RecycleBinController(IAuthorizer authorizer, IContentManager contentManager, INotifier notifier, IOrchardServices services, IRecycleBin recycleBin) public RecycleBinController(IOrchardServices services, IRecycleBin recycleBin)
{ {
_authorizer = authorizer; _authorizer = services.Authorizer;
_contentManager = contentManager; _contentManager = services.ContentManager;
_notifier = notifier; _notifier = services.Notifier;
_services = services; _services = services;
_recycleBin = recycleBin; _recycleBin = recycleBin;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
} }
public Localizer T { get; set; } public Localizer T { get; set; } = NullLocalizer.Instance;
public ILogger Logger { get; set; } public ILogger Logger { get; set; } = NullLogger.Instance;
public ActionResult Index(PagerParameters pagerParameters, AuditTrailOrderBy? orderBy = null) public ActionResult Index(PagerParameters pagerParameters)
{ {
if (!_authorizer.Authorize(Permissions.ViewAuditTrail)) if (!_authorizer.Authorize(Permissions.ViewAuditTrail))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
@@ -76,7 +73,7 @@ namespace Orchard.AuditTrail.Controllers
ModelState.AddModelError("RecycleBinCommand", T("Please select an action to execute.").Text); ModelState.AddModelError("RecycleBinCommand", T("Please select an action to execute.").Text);
} }
if (viewModel.SelectedContentItems == null || !viewModel.SelectedContentItems.Any()) if (viewModel.SelectedContentItems == null || !viewModel.SelectedContentItems.Any(item => item.Selected))
{ {
ModelState.AddModelError("SelectedContentItems", T("Please select one or more content items.").Text); ModelState.AddModelError("SelectedContentItems", T("Please select one or more content items.").Text);
} }
@@ -108,10 +105,10 @@ namespace Orchard.AuditTrail.Controllers
{ {
var pager = new Pager(_services.WorkContext.CurrentSite, pagerParameters); var pager = new Pager(_services.WorkContext.CurrentSite, pagerParameters);
var removedContentItems = _recycleBin.List(pager.Page, pager.PageSize); var removedContentItems = _recycleBin.List(pager.Page, pager.PageSize);
var pagershape = _services.New.Pager(pager).TotalItemCount(removedContentItems.TotalItemCount); var pagerShape = _services.New.Pager(pager).TotalItemCount(removedContentItems.TotalItemCount);
viewModel.ContentItems = removedContentItems; viewModel.ContentItems = removedContentItems;
viewModel.Pager = pagershape; viewModel.Pager = pagerShape;
return viewModel; return viewModel;
} }
@@ -143,7 +140,9 @@ namespace Orchard.AuditTrail.Controllers
var contentItemTitle = _contentManager.GetItemMetadata(contentItem).DisplayText; var contentItemTitle = _contentManager.GetItemMetadata(contentItem).DisplayText;
if (!_authorizer.Authorize(Core.Contents.Permissions.DeleteContent, contentItem)) if (!_authorizer.Authorize(Core.Contents.Permissions.DeleteContent, contentItem))
{ {
_notifier.Error(T("You need to have permission to delete <strong>{0}</strong> to be able to permanently delete it.", contentItemTitle)); _notifier.Error(
T("You need to have permission to delete <strong>{0}</strong> to be able to permanently delete it.",
contentItemTitle));
continue; continue;
} }
@@ -162,4 +161,4 @@ namespace Orchard.AuditTrail.Controllers
} }
} }
} }
@@ -57,13 +57,12 @@ namespace Orchard.AuditTrail.Services
public ContentItem Restore(ContentItem contentItem) public ContentItem Restore(ContentItem contentItem)
{ {
var versions = contentItem.Record.Versions.OrderBy(x => x.Number).ToArray(); var lastVersion = contentItem.Record.Versions.OrderBy(x => x.Number).ToArray().Last();
var lastVersion = versions.Last();
if (lastVersion.Latest || lastVersion.Published) return lastVersion.Latest || lastVersion.Published
throw new InvalidOperationException(string.Format("Cannot restore content item with ID {0} ftom the recycle bin, since that item is not deleted", contentItem.Id)); ? throw new InvalidOperationException(
$"Cannot restore content item with ID {contentItem.Id} from the recycle bin, since that item is not deleted.")
return _contentManager.Restore(contentItem, VersionOptions.Restore(lastVersion.Number, publish: false)); : _contentManager.Restore(contentItem, VersionOptions.Restore(lastVersion.Number, publish: false));
} }
private IEnumerable<T> LoadContentItems<T>(IQuery query, QueryHints hints = null) where T : class, IContent private IEnumerable<T> LoadContentItems<T>(IQuery query, QueryHints hints = null) where T : class, IContent
@@ -82,7 +81,7 @@ namespace Orchard.AuditTrail.Services
"select max(ContentItemVersionRecord.Id), ContentItemVersionRecord.ContentItemRecord.Id, max(ContentItemVersionRecord.Number) " + "select max(ContentItemVersionRecord.Id), ContentItemVersionRecord.ContentItemRecord.Id, max(ContentItemVersionRecord.Number) " +
"from Orchard.ContentManagement.Records.ContentItemVersionRecord ContentItemVersionRecord "; "from Orchard.ContentManagement.Records.ContentItemVersionRecord ContentItemVersionRecord ";
var filter = contentItemIds != null ? "where ContentItemVersionRecord.ContentItemRecord.Id in (:ids) " : default(string); var filter = contentItemIds != null ? "where ContentItemVersionRecord.ContentItemRecord.Id in (:ids) " : default;
var group = var group =
"group by ContentItemVersionRecord.ContentItemRecord.Id " + "group by ContentItemVersionRecord.ContentItemRecord.Id " +
@@ -99,4 +98,4 @@ namespace Orchard.AuditTrail.Services
return query; return query;
} }
} }
} }
@@ -1,25 +1,29 @@
@using Orchard.AuditTrail.ViewModels @using Orchard.AuditTrail.ViewModels
@using Orchard.ContentManagement @using Orchard.ContentManagement
@using Orchard.Core.Common.Models @using Orchard.Core.Common.Models
@using Orchard.Localization.Services @using Orchard.Localization.Services
@model RecycleBinViewModel @model RecycleBinViewModel
@{ @{
Style.Include("audittrail-recycle-bin.css"); Style.Include("audittrail-recycle-bin.css");
Script.Require("ShapesBase"); Script.Require("ShapesBase");
Script.Include(("audittrail-recyclebin.js")); Script.Include("audittrail-recyclebin.js");
Layout.Title = T("Audit Trail"); Layout.Title = T("Audit Trail");
var contentItems = Model.ContentItems; var contentItems = Model.ContentItems;
var dateLocalizationServices = WorkContext.Resolve<IDateLocalizationServices>(); var dateLocalizationServices = WorkContext.Resolve<IDateLocalizationServices>();
} }
<div id="recycle-bin"> <div id="recycle-bin">
@Html.ValidationSummary() @Html.ValidationSummary()
@using (Html.BeginFormAntiForgeryPost()) { @using (Html.BeginFormAntiForgeryPost())
{
<fieldset class="bulk-actions"> <fieldset class="bulk-actions">
<label>@T("Actions:")</label> <label>@T("Actions:")</label>
<select name="RecycleBinCommand"> <select name="RecycleBinCommand">
<option></option> <option value="">@T("Choose action...")</option>
<option value="@RecycleBinCommand.Restore" data-unsafe-action="@T("Are you sure you want to restore the selected items?")" @if(Model.RecycleBinCommand == RecycleBinCommand.Restore){<text>selected="selected"</text>}>@T("Restore")</option> <option value="@RecycleBinCommand.Restore" data-unsafe-action="@T("Are you sure you want to restore the selected items?")" @if (Model.RecycleBinCommand == RecycleBinCommand.Restore) { <text> selected="selected" </text> }>@T("Restore")</option>
@**TODO: Decide wether or not to allow users to permanently delete items. Commented out for now.*@ @**TODO: Decide wether or not to allow users to permanently delete items. Commented out for now.*@
@*<option value="@RecycleBinCommand.Destroy" data-unsafe-action="@T("WARNING: This will PERMANENTLY delete the selected content items, including related content part records, never to be seen again. Are you sure you want to do this?")" @if (Model.RecycleBinCommand == RecycleBinCommand.Destroy) { <text> selected="selected" </text> }>@T("Remove Permanently ☠")</option>*@ @*<option value="@RecycleBinCommand.Destroy" data-unsafe-action="@T("WARNING: This will PERMANENTLY delete the selected content items, including related content part records, never to be seen again. Are you sure you want to do this?")" @if (Model.RecycleBinCommand == RecycleBinCommand.Destroy) { <text> selected="selected" </text> }>@T("Remove Permanently ☠")</option>*@
</select> </select>
@@ -28,10 +32,12 @@
<button type="submit" class="filter-apply-button" name="ExecuteActionButton" value="ExecuteActionButton">@T("Execute")</button> <button type="submit" class="filter-apply-button" name="ExecuteActionButton" value="ExecuteActionButton">@T("Execute")</button>
</div> </div>
<section class="recycle-bin-list-section"> <section class="recycle-bin-list-section">
@if (!contentItems.Any()) { @if (!contentItems.Any())
{
<p class="info">@T("There are no records to display.")</p> <p class="info">@T("There are no records to display.")</p>
} }
else { else
{
<table class="items"> <table class="items">
<thead> <thead>
<tr> <tr>
@@ -44,17 +50,18 @@
<tbody> <tbody>
@{ @{
var index = 0; var index = 0;
foreach (var contentItem in contentItems) { foreach (var contentItem in contentItems)
{
var isSelected = Model.SelectedContentItems.Where(x => x.Id == contentItem.Id && x.Selected).Select(x => x.Id).Any(); var isSelected = Model.SelectedContentItems.Where(x => x.Id == contentItem.Id && x.Selected).Select(x => x.Id).Any();
var commonPart = contentItem.As<CommonPart>(); var commonPart = contentItem.As<CommonPart>();
var removedText = commonPart != null ? dateLocalizationServices.ConvertToLocalizedString(commonPart.VersionModifiedUtc) : T("-").Text; var removedText = commonPart != null ? dateLocalizationServices.ConvertToLocalizedString(commonPart.VersionModifiedUtc) : T("-").Text;
var contentDisplayTextHtmlString = Html.ItemDisplayText(contentItem); var contentDisplayTextHtmlString = Html.ItemDisplayText(contentItem);
var contentDisplayText = contentDisplayTextHtmlString != null ? contentDisplayTextHtmlString.ToString() : contentItem.ContentType; var contentDisplayText = contentDisplayTextHtmlString != null ? contentDisplayTextHtmlString.ToString() : contentItem.ContentType;
var contentDisplayUrl = Url.Action("Detail", "Content", new {id = contentItem.Id, version = contentItem.Version, area = "Orchard.AuditTrail"}); var contentDisplayUrl = Url.Action("Detail", "Content", new { id = contentItem.Id, version = contentItem.Version, area = "Orchard.AuditTrail" });
<tr> <tr>
<td> <td>
<input type="hidden" name="SelectedContentItems[@index].Id" value="@contentItem.Id" /> <input type="hidden" name="SelectedContentItems[@index].Id" value="@contentItem.Id" />
<input type="checkbox" name="SelectedContentItems[@index].Selected" value="true" @if(isSelected){<text>checked="checked"</text>} /> <input type="checkbox" name="SelectedContentItems[@index].Selected" value="true" @if (isSelected) { <text> checked="checked" </text> } />
</td> </td>
<td class="content-column"><a href="@contentDisplayUrl">@contentDisplayText</a></td> <td class="content-column"><a href="@contentDisplayUrl">@contentDisplayText</a></td>
<td class="content-removed-column">@removedText</td> <td class="content-removed-column">@removedText</td>