From c93485bc6df49ea768ceee1c97246e7429211514 Mon Sep 17 00:00:00 2001 From: Jacob O'Mara Date: Wed, 5 Nov 2025 14:47:18 +0000 Subject: [PATCH] Bug 17387: (follow-up) take library groups in to account for permissions check --- Koha/Old/Biblio.pm | 48 ++++++++++++++++++- Koha/REST/V1/DeletedBiblios.pm | 23 ++++++++- Koha/REST/V1/DeletedItems.pm | 9 ++++ api/v1/swagger/paths/deleted_biblios.yaml | 35 ++++++++++++++ .../prog/en/modules/tools/restore-records.tt | 43 +++++++++++++---- tools/restore-records.pl | 11 ++++- 6 files changed, 156 insertions(+), 13 deletions(-) diff --git a/Koha/Old/Biblio.pm b/Koha/Old/Biblio.pm index 754f0b0316d..a8fd3be7193 100644 --- a/Koha/Old/Biblio.pm +++ b/Koha/Old/Biblio.pm @@ -154,18 +154,32 @@ sub to_api_mapping { =head3 restore my $biblio = $deleted_biblio->restore; + my $biblio = $deleted_biblio->restore({ + patron => $patron, + item_ids => \@item_ids, + restore_all => 0 + }); Restores the deleted biblio record back to the biblio table along with its biblioitems and metadata. This removes the record from the deleted tables and re-inserts it into the active tables. The biblio record will be reindexed after restoration. +Optional parameters: + patron - Koha::Patron object. If provided, only items the patron can edit will be restored. + item_ids - Arrayref of item IDs to restore. If not provided and restore_all is false, + no items will be restored. + restore_all - Boolean. If true and no item_ids provided, restore all items the patron + has permission to edit. + Returns the newly restored Koha::Biblio object. =cut sub restore { - my ($self) = @_; + my ( $self, $params ) = @_; + + $params //= {}; my $biblio_data = $self->unblessed; my $biblioitem = $self->biblioitem; @@ -187,13 +201,43 @@ sub restore { $biblioitem->delete; $self->delete; + my $patron = $params->{patron}; + my $item_ids = $params->{item_ids}; + my $restore_all = $params->{restore_all} // 0; + + my @restored_items; + my @skipped_items; + + if ( $restore_all || $item_ids ) { + my $deleted_items = $self->items; + while ( my $deleted_item = $deleted_items->next ) { + my $should_restore = 0; + + if ($item_ids) { + $should_restore = grep { $_ == $deleted_item->itemnumber } @$item_ids; + } elsif ($restore_all) { + $should_restore = 1; + } + + if ($should_restore) { + if ( $patron && !$patron->can_edit_items_from( $deleted_item->homebranch ) ) { + push @skipped_items, $deleted_item->itemnumber; + next; + } + + $deleted_item->restore; + push @restored_items, $deleted_item->itemnumber; + } + } + } + my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); $indexer->index_records( $new_biblio->biblionumber, "specialUpdate", "biblioserver" ); logaction( "CATALOGUING", "RESTORE", $new_biblio->biblionumber, "biblio", undef, $new_biblio ) if C4::Context->preference("CataloguingLog"); - return $new_biblio; + return wantarray ? ( $new_biblio, \@restored_items, \@skipped_items ) : $new_biblio; } =head2 Internal methods diff --git a/Koha/REST/V1/DeletedBiblios.pm b/Koha/REST/V1/DeletedBiblios.pm index 6e8f03d1a95..8ba90026c03 100644 --- a/Koha/REST/V1/DeletedBiblios.pm +++ b/Koha/REST/V1/DeletedBiblios.pm @@ -180,12 +180,31 @@ sub restore { return $c->render_resource_not_found("Bibliographic record") unless $deleted_biblio; + my $body = $c->req->json; + my $item_ids = $body->{item_ids}; + my $restore_all = $body->{restore_all} // 0; + return try { - my $biblio = $deleted_biblio->restore; + my $patron = $c->stash('koha.user'); + + my ( $biblio, $restored_items, $skipped_items ) = $deleted_biblio->restore( + { + patron => $patron, + item_ids => $item_ids, + restore_all => $restore_all + } + ); + + my $response = { biblio_id => $biblio->biblionumber }; + + if ( $item_ids || $restore_all ) { + $response->{restored_items} = $restored_items if @$restored_items; + $response->{skipped_items} = $skipped_items if @$skipped_items; + } return $c->render( status => 200, - openapi => { biblio_id => $biblio->biblionumber } + openapi => $response ); } catch { $c->unhandled_exception($_); diff --git a/Koha/REST/V1/DeletedItems.pm b/Koha/REST/V1/DeletedItems.pm index fa3522477c1..a6f297b396a 100644 --- a/Koha/REST/V1/DeletedItems.pm +++ b/Koha/REST/V1/DeletedItems.pm @@ -85,6 +85,15 @@ sub restore { unless $deleted_item; return try { + my $patron = $c->stash('koha.user'); + + unless ( $patron->can_edit_items_from( $deleted_item->homebranch ) ) { + return $c->render( + status => 403, + openapi => { error => "You do not have permission to restore items from this library." } + ); + } + my $item = $deleted_item->restore; return $c->render( diff --git a/api/v1/swagger/paths/deleted_biblios.yaml b/api/v1/swagger/paths/deleted_biblios.yaml index 4f62a0e3907..117ac1521a6 100644 --- a/api/v1/swagger/paths/deleted_biblios.yaml +++ b/api/v1/swagger/paths/deleted_biblios.yaml @@ -135,6 +135,24 @@ summary: Restore deleted biblio parameters: - $ref: "../swagger.yaml#/parameters/biblio_id_pp" + - name: body + in: body + description: Restore options for items + required: false + schema: + type: object + properties: + item_ids: + type: array + description: Array of item IDs to restore with the biblio + items: + type: integer + restore_all: + type: boolean + description: Restore all items the user has permission to edit + additionalProperties: false + consumes: + - application/json produces: - application/json responses: @@ -142,6 +160,23 @@ description: Biblio restored successfully schema: type: object + properties: + biblio_id: + type: integer + description: The ID of the restored biblio + restored_items: + type: array + description: List of item IDs that were successfully restored + items: + type: integer + skipped_items: + type: array + description: List of item IDs that were skipped during restoration due to insufficient permissions + items: + type: integer + required: + - biblio_id + additionalProperties: false "400": description: Bad request schema: diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/restore-records.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/restore-records.tt index bd73665c189..6bc918b4f41 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/restore-records.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/restore-records.tt @@ -190,6 +190,16 @@ } $(document).ready(function () { + var librariesWhereCanEdit = [% libraries_where_can_edit_json | $raw %]; + var canEditAnyLibrary = librariesWhereCanEdit.length === 0; + + function canEditItem(homebranch) { + if (canEditAnyLibrary) { + return true; + } + return librariesWhereCanEdit.includes(homebranch); + } + var oneYearAgo = new Date(); oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); var today = new Date(); @@ -365,17 +375,22 @@ { data: function (row, type) { if (type === "display") { - return ( - '" - ); + ""; + + if (!canEdit) { + return '' + button + ''; + } + return button; } return ""; }, @@ -422,7 +437,14 @@ { data: function (row, type) { if (type === "display") { - return ''; + var canEdit = canEditItem(row.home_library_id); + var disabled = canEdit ? '' : ' disabled'; + var checkbox = ''; + + if (!canEdit) { + return '' + checkbox + ''; + } + return checkbox; } return ""; }, @@ -462,7 +484,12 @@ orderable: true, render: function (data, type, row) { if (type === "display") { - return data ? $("
").text(data).html() : ""; + var text = data ? $("
").text(data).html() : ""; + var canEdit = canEditItem(data); + if (!canEdit) { + text += ' ' + _("No permission") + ''; + } + return text; } return data || ""; }, @@ -570,7 +597,7 @@ // Select all items checkbox $("#select-all-items").on("change", function () { var checked = $(this).prop("checked"); - $(".item-checkbox").prop("checked", checked); + $(".item-checkbox:not(:disabled)").prop("checked", checked); }); // Restore biblio with items diff --git a/tools/restore-records.pl b/tools/restore-records.pl index 41d5dbb5888..ef83c80957b 100755 --- a/tools/restore-records.pl +++ b/tools/restore-records.pl @@ -17,10 +17,12 @@ use Modern::Perl; -use CGI qw ( -utf8 ); +use CGI qw ( -utf8 ); +use JSON qw( encode_json ); use C4::Auth qw( get_template_and_user ); use C4::Output qw( output_html_with_http_headers ); +use Koha::Patrons; my $input = CGI->new; @@ -33,4 +35,11 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); +my $patron = Koha::Patrons->find($loggedinuser); +my @libraries_where_can_edit = $patron->libraries_where_can_edit_items; + +$template->param( + libraries_where_can_edit_json => encode_json( \@libraries_where_can_edit ), +); + output_html_with_http_headers $input, $cookie, $template->output; -- 2.39.5