Recycle Bin improvements (Lombiq Technologies: COLI-1711) (#8890)
Some checks failed
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

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Web.Mvc;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.AuditTrail.ViewModels;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
@@ -28,21 +27,19 @@ namespace Orchard.AuditTrail.Controllers
private readonly IOrchardServices _services;
private readonly IRecycleBin _recycleBin;
public RecycleBinController(IAuthorizer authorizer, IContentManager contentManager, INotifier notifier, IOrchardServices services, IRecycleBin recycleBin)
public RecycleBinController(IOrchardServices services, IRecycleBin recycleBin)
{
_authorizer = authorizer;
_contentManager = contentManager;
_notifier = notifier;
_authorizer = services.Authorizer;
_contentManager = services.ContentManager;
_notifier = services.Notifier;
_services = services;
_recycleBin = recycleBin;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
public ILogger Logger { get; set; }
public Localizer T { get; set; } = NullLocalizer.Instance;
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))
return new HttpUnauthorizedResult();
@@ -76,7 +73,7 @@ namespace Orchard.AuditTrail.Controllers
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);
}
@@ -108,10 +105,10 @@ namespace Orchard.AuditTrail.Controllers
{
var pager = new Pager(_services.WorkContext.CurrentSite, pagerParameters);
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.Pager = pagershape;
viewModel.Pager = pagerShape;
return viewModel;
}
@@ -143,7 +140,9 @@ namespace Orchard.AuditTrail.Controllers
var contentItemTitle = _contentManager.GetItemMetadata(contentItem).DisplayText;
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;
}

View File

@@ -57,13 +57,12 @@ namespace Orchard.AuditTrail.Services
public ContentItem Restore(ContentItem contentItem)
{
var versions = contentItem.Record.Versions.OrderBy(x => x.Number).ToArray();
var lastVersion = versions.Last();
var lastVersion = contentItem.Record.Versions.OrderBy(x => x.Number).ToArray().Last();
if (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));
return _contentManager.Restore(contentItem, VersionOptions.Restore(lastVersion.Number, publish: false));
return lastVersion.Latest || lastVersion.Published
? throw new InvalidOperationException(
$"Cannot restore content item with ID {contentItem.Id} from the recycle bin, since that item is not deleted.")
: _contentManager.Restore(contentItem, VersionOptions.Restore(lastVersion.Number, publish: false));
}
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) " +
"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 =
"group by ContentItemVersionRecord.ContentItemRecord.Id " +

View File

@@ -1,25 +1,29 @@
@using Orchard.AuditTrail.ViewModels
@using Orchard.AuditTrail.ViewModels
@using Orchard.ContentManagement
@using Orchard.Core.Common.Models
@using Orchard.Localization.Services
@model RecycleBinViewModel
@{
Style.Include("audittrail-recycle-bin.css");
Script.Require("ShapesBase");
Script.Include(("audittrail-recyclebin.js"));
Script.Include("audittrail-recyclebin.js");
Layout.Title = T("Audit Trail");
var contentItems = Model.ContentItems;
var dateLocalizationServices = WorkContext.Resolve<IDateLocalizationServices>();
}
<div id="recycle-bin">
@Html.ValidationSummary()
@using (Html.BeginFormAntiForgeryPost()) {
@using (Html.BeginFormAntiForgeryPost())
{
<fieldset class="bulk-actions">
<label>@T("Actions:")</label>
<select name="RecycleBinCommand">
<option></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="">@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>
@**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>*@
</select>
@@ -28,10 +32,12 @@
<button type="submit" class="filter-apply-button" name="ExecuteActionButton" value="ExecuteActionButton">@T("Execute")</button>
</div>
<section class="recycle-bin-list-section">
@if (!contentItems.Any()) {
@if (!contentItems.Any())
{
<p class="info">@T("There are no records to display.")</p>
}
else {
else
{
<table class="items">
<thead>
<tr>
@@ -44,17 +50,18 @@
<tbody>
@{
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 commonPart = contentItem.As<CommonPart>();
var removedText = commonPart != null ? dateLocalizationServices.ConvertToLocalizedString(commonPart.VersionModifiedUtc) : T("-").Text;
var contentDisplayTextHtmlString = Html.ItemDisplayText(contentItem);
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>
<td>
<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 class="content-column"><a href="@contentDisplayUrl">@contentDisplayText</a></td>
<td class="content-removed-column">@removedText</td>