From 812ebf0e1dda2aca2f5d8d31d4364fcf8b82b87f Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Wed, 11 Sep 2024 14:04:17 +0000 Subject: [PATCH 001/429] Bug 37892: Fix guarantor restriction, add tests [SQUASHED IN QA] These patches will alter the checks for a patron that prevent a category with 'can_be_guarantee' from being a guarantor. Two patrons in the same category should be allowed to have a guarantee/guarantor relationship The tests below assume you are using the KTD sample data. Update borrowernumbers if not. To test: 0 - Apply tests patch 1 - Set the 'Patron' category as 'Can be a guarantee' 2 - Add a relationship between two patrons of the same category This is restricted from the staff interface perl -e 'use Koha::Patrons; my $p = Koha::Patrons->find(5)->add_guarantor({ guarantor_id => 23, relationship => 'father'});' 3 - Note there is no warning or exception. This should be allowed. 4 - Checkout an item to Edna (borrowernumber 5) 5 - Set 'TrackLastPatronActivityTriggers' to 'Checking in an item' 6 - Try to check the item in, KABOOM 7 - Set 'TrackLastPatronActivityTriggers' to 'Checking out an item' 8 - Try to issue an item to Enda, KABOOM 9 - prove -v t/db_dependent/Koha/Patron.t, fail 10 - Apply second patch 11 - prove -v t/db_dependent/Koha/Patron.t, one more test passes, but then fail 12 - Apply third patch 13 - prove -v t/db_dependent/Koha/Patron.t, pass! 14 - restart_all 15 - Checkout to Enda, OK! 16 - Checkin from Edna, OK! 17 - Find two more patrons in the category and attempt to link them 18 - 'Guarantor cannot be a guarantee' 19 - Apply fourth patch 20 - You can add a guarantor from the same category in interface 21 - Try to add a guarantor to the guarantor assigned in 20 22 - Confirm you cannot add a guarantor - "Guarantor cannot be a guarantee" Signed-off-by: Olivier V Signed-off-by: Brendan Lawlor Signed-off-by: Baptiste Wojtkowski Bug 37892: Fix patron updates Signed-off-by: Olivier V Signed-off-by: Brendan Lawlor Signed-off-by: Baptiste Wojtkowski Bug 37892: Fix patron creation Signed-off-by: Olivier V Signed-off-by: Brendan Lawlor Signed-off-by: Baptiste Wojtkowski Bug 37892: Fix memberentry.pl Signed-off-by: Olivier V Signed-off-by: Brendan Lawlor Signed-off-by: Baptiste Wojtkowski Bug 37892: (follow-up) Fix patron creation This patch fixes the 22. of the test plan (22 - Confirm you cannot add a guarantor - "Guarantor cannot be a guarantee" ) Signed-off-by: Olivier V Signed-off-by: Brendan Lawlor Bug 37892: (follow-up) Fix patron creation TEST PLAN: 1 - Do the 22 parts of the test plan 2 - Add a guarantor to one patron not selected before (let's say A is the guarantee, B the guarantor) 3 - Try and add a guarantor to B -> you will success 4 - Remove B's guarantor 5 - Apply this patch 6 - Repeat 3 -> you will not be able to Signed-off-by: Brendan Lawlor Bug 37892: (follow-up) Tidyness Signed-off-by: Marcel de Rooy [EDIT] Renamed a subtest to patron creation tests in Patron.t. Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer (cherry picked from commit 391e150e0755b9c5e9a59d369da029a5bc9fc7e3) Signed-off-by: Lucas Gass --- Koha/Patron.pm | 28 +++++++++++++++++++-- members/memberentry.pl | 2 +- t/db_dependent/Koha/Patron.t | 47 +++++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 70cbe3ab7f..104c3a00e6 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -315,7 +315,7 @@ sub store { } foreach my $guarantor (@$guarantors) { - if ( $guarantor->is_child or $guarantor->category->can_be_guarantee ) { + if ( $guarantor->is_child ) { Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 ); } } @@ -372,7 +372,7 @@ sub store { } foreach my $guarantor (@$guarantors) { - if ( $guarantor->is_child or $guarantor->category->can_be_guarantee ) { + if ( $guarantor->is_child ) { Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 ); } } @@ -533,6 +533,18 @@ sub guarantor_relationships { return Koha::Patron::Relationships->search( { guarantee_id => $self->id } ); } +=head3 is_guarantee + +Returns true if the patron has a guarantor. + +=cut + +sub is_guarantee { + my ($self) = @_; + return $self->guarantor_relationships()->count(); +} + + =head3 guarantee_relationships Returns Koha::Patron::Relationships object for this patron's guarantors @@ -557,6 +569,18 @@ sub guarantee_relationships { ); } +=head3 is_guarantor + +Returns true if the patron is a guarantor. + +=cut + +sub is_guarantor { + my ($self) = @_; + return $self->guarantee_relationships()->count(); +} + + =head3 relationships_debt Returns the amount owed by the patron's guarantors *and* the other guarantees of those guarantors diff --git a/members/memberentry.pl b/members/memberentry.pl index f647234691..17c1f0305a 100755 --- a/members/memberentry.pl +++ b/members/memberentry.pl @@ -280,7 +280,7 @@ if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) } foreach my $guarantor (@guarantors) { - if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) && $guarantor->is_child || $guarantor->category->can_be_guarantee ) { + if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) && ($guarantor->is_child ) || $guarantor->is_guarantee || $patron->is_guarantor) { push @errors, 'ERROR_guarantor_is_guarantee'; } } diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index bad1cc1bfa..8df5658384 100755 --- a/t/db_dependent/Koha/Patron.t +++ b/t/db_dependent/Koha/Patron.t @@ -19,7 +19,7 @@ use Modern::Perl; -use Test::More tests => 33; +use Test::More tests => 34; use Test::Exception; use Test::Warn; use Time::Fake; @@ -410,6 +410,51 @@ subtest 'add_guarantor() tests' => sub { $schema->storage->txn_rollback; }; +subtest 'guarantor checks on patron creation / update tests' => sub { + + plan tests => 2; + + $schema->storage->txn_begin; + + t::lib::Mocks::mock_preference( 'borrowerRelationship', 'guarantor' ); + my $category = $builder->build_object( + { class => 'Koha::Patron::Categories', value => { can_be_guarantee => 1, category_type => 'A' } } ); + + my $guarantor = + $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $category->categorycode } } ); + my $guarantee = + $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $category->categorycode } } ); + + subtest 'patron update tests' => sub { + plan tests => 4; + ok( + $guarantee->add_guarantor( { guarantor_id => $guarantor->borrowernumber, relationship => 'guarantor' } ), + "Relationship is added, no problem" + ); + is( $guarantor->guarantee_relationships->count, 1, 'Relationship added' ); + ok( $guarantor->surname("Duck")->store(), "Updating guarantor is okay" ); + ok( $guarantee->surname("Duck")->store(), "Updating guarantee is okay" ); + }; + + subtest 'patron creation tests' => sub { + plan tests => 1; + + my $new_guarantee = $builder->build_object( + { class => 'Koha::Patrons', value => { categorycode => $category->categorycode } } ); + my $new_guarantee_hash = $new_guarantee->unblessed; + $new_guarantee->delete(); + + delete $new_guarantee_hash->{borrowernumber}; + + ok( + Koha::Patron->new($new_guarantee_hash)->store( { guarantors => [$guarantor] } ), + "We can add the new patron and indicate guarantor" + ); + }; + + $schema->storage->txn_rollback; +}; + subtest 'relationships_debt() tests' => sub { plan tests => 168; From 74e4173dd8fe9729c7ec1076050ac8ec9b66d032 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy Date: Fri, 20 Sep 2024 08:44:30 +0000 Subject: [PATCH 002/429] Bug 37892: (QA follow-up) Add tests for ->is_guarantor/ee Test plan: Run t/db_dependent/Koha/Patron.t Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer (cherry picked from commit bc68a17c548c6c63ab0a487e479cb0f1648ff4c5) Signed-off-by: Lucas Gass --- t/db_dependent/Koha/Patron.t | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index 8df5658384..2e11ab3d4a 100755 --- a/t/db_dependent/Koha/Patron.t +++ b/t/db_dependent/Koha/Patron.t @@ -426,12 +426,16 @@ subtest 'guarantor checks on patron creation / update tests' => sub { $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $category->categorycode } } ); subtest 'patron update tests' => sub { - plan tests => 4; + plan tests => 7; ok( $guarantee->add_guarantor( { guarantor_id => $guarantor->borrowernumber, relationship => 'guarantor' } ), "Relationship is added, no problem" ); - is( $guarantor->guarantee_relationships->count, 1, 'Relationship added' ); + is( $guarantor->is_guarantor, 1, 'Is a guarantor' ); + is( $guarantor->is_guarantee, 0, 'Is no guarantee' ); + is( $guarantee->is_guarantor, 0, 'Is no guarantor' ); + is( $guarantee->is_guarantee, 1, 'Is a guarantee' ); + ok( $guarantor->surname("Duck")->store(), "Updating guarantor is okay" ); ok( $guarantee->surname("Duck")->store(), "Updating guarantee is okay" ); }; From 5e07ef7ae703e272c162324fa5a1d9ff676f2be9 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy Date: Fri, 20 Sep 2024 09:07:42 +0000 Subject: [PATCH 003/429] Bug 37892: (QA follow-up) Fix condition in memberentry Patron may be undefined. So the test may crash. Also there is an issue with operator precedence. Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer (cherry picked from commit cfc953a411448c814328b26841c496369604408d) Signed-off-by: Lucas Gass --- members/memberentry.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/members/memberentry.pl b/members/memberentry.pl index 17c1f0305a..8903ecfcfa 100755 --- a/members/memberentry.pl +++ b/members/memberentry.pl @@ -280,7 +280,9 @@ if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) } foreach my $guarantor (@guarantors) { - if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) && ($guarantor->is_child ) || $guarantor->is_guarantee || $patron->is_guarantor) { + if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) + && ( $guarantor->is_child || $guarantor->is_guarantee || ( $patron && $patron->is_guarantor ) ) ) + { push @errors, 'ERROR_guarantor_is_guarantee'; } } From 36d2eb2fa79c13453180e14a4c662704726d83cf Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Tue, 6 Aug 2024 09:41:32 +0000 Subject: [PATCH 004/429] Bug 31143: Work around subtracting datetime if date_of_birth undef To test: 1) Run the following SQL: update borrowers set dateofbirth = '0000-00-00' where borrowernumber = 45; 2) Access the borrower edit page through staff UI: http://localhost:8081/cgi-bin/koha/members/memberentry.pl?op=edit_form&destination=circ&borrowernumber=45 3) Notice you get an error 4) Run the script: perl misc/maintenance/search_for_data_inconsistencies.pl 5) Notice you get an error 6) Apply patch restart plack and repeat -> no errors This is not perfect but its all I have time for, for now. Follow-ups are welcomed as always. Signed-off-by: David Nind Signed-off-by: Shi Yao Wang Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer (cherry picked from commit e048144d80097f45f43475f5730acbee328110cc) Signed-off-by: Lucas Gass --- Koha/Patron.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 104c3a00e6..d0f5c76acc 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -1551,6 +1551,7 @@ sub get_age { my $date_of_birth = dt_from_string( $self->dateofbirth, undef, 'floating' ); my $today = dt_from_string(undef, undef, 'floating')->truncate( to => 'day' ); + return 0 unless $date_of_birth; return $today->subtract_datetime( $date_of_birth )->years; } From 0abf2ede21c8acdd9a19b904f96600ce600882f3 Mon Sep 17 00:00:00 2001 From: wainuiwitikapark Date: Tue, 24 Sep 2024 04:53:05 +0000 Subject: [PATCH 005/429] Bug 31143: Fix/identify all cases where '0000-00-00' may still remain in the database This patch identifies date fields in the database (timestamp, datetime, date) that are 0000-00-00 and sets them to NULL Test plan - Examples of setting up some 0000-00-00 date fields: e.g. Borrowers table update borrowers set dateexpiry="0000-00-00" where borrowernumber=49; e.g. Items table update items set datelastseen="0000-00-00" where itemnumber=12; To identify the cases: 1) Set up some 0000-00-00 date fields 2) Run the misc/maintenance/search_for_data_inconsistencies.pl script 3) Run fix_invalid_dates.pl -v To fix the cases: 1) Run fix_invalid_dates.pl -c -v 2) Notice the value has changed from 0000-00-00 to NULL e.g. Borrowers table select dateexpiry from borrowers where borrowernumber=49; e.g. Items table select datelastseen from items where itemnumber=12; Signed-off-by: Shi Yao Wang Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer (cherry picked from commit d4bfae09778ef9f251b5a55b9446a50d66becd92) Signed-off-by: Lucas Gass --- misc/cronjobs/fix_invalid_dates.pl | 66 +++++++++++++++++++ .../search_for_data_inconsistencies.pl | 32 +++++++++ 2 files changed, 98 insertions(+) create mode 100755 misc/cronjobs/fix_invalid_dates.pl diff --git a/misc/cronjobs/fix_invalid_dates.pl b/misc/cronjobs/fix_invalid_dates.pl new file mode 100755 index 0000000000..a7f43b671d --- /dev/null +++ b/misc/cronjobs/fix_invalid_dates.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use C4::Context; +use C4::Log qw(cronlogaction); +use Getopt::Long qw( GetOptions ); +use Pod::Usage qw( pod2usage ); +use Koha::Logger; +use Koha::DateUtils qw( dt_from_string ); +use Koha::Script -cron; +use Data::Dumper; +use Koha::Database; + +my $verbose = 0; +my $doit = 0; + +GetOptions( + 'v|verbose' => \$verbose, + 'c|confirm' => \$doit, +); + +my $schema = Koha::Database->new->schema; +# Loop over all the DBIx::Class classes +for my $class ( sort values %{$schema->{class_mappings}} ) { + # Retrieve the resultset so we can access the columns info + my $rs = $schema->resultset($class); + my $columns = $rs->result_source->columns_info; + + # Loop over the columns + while ( my ( $column, $info ) = each %$columns ) { + # Next if data type is not date/datetime/timestamp + my $data_type = $info->{data_type}; + next unless grep { $data_type =~ m{^$_$} } qw( timestamp datetime date ); + + # Count the invalid dates + my $invalid_dates = $rs->search({ $column => '0000-00-00' })->count; + + next unless $invalid_dates; + + if ($verbose) { + say sprintf "Column %s.%s contains %s invalid dates", $rs->result_source->name, $column, $invalid_dates; + } + + if ($doit) { + $rs->search({ $column => '0000-00-00' })->update({ $column => undef }); + say sprintf "Column %s.%s contains %s invalid dates that have been fixed", $rs->result_source->name, $column, $invalid_dates; + } + + } +} diff --git a/misc/maintenance/search_for_data_inconsistencies.pl b/misc/maintenance/search_for_data_inconsistencies.pl index 6719cecedc..ed29e4134f 100755 --- a/misc/maintenance/search_for_data_inconsistencies.pl +++ b/misc/maintenance/search_for_data_inconsistencies.pl @@ -326,6 +326,37 @@ use C4::Biblio qw( GetMarcFromKohaField ); } } +{ + use Koha::Database; + my $schema = Koha::Database->new->schema; + + # Loop over all the DBIx::Class classes + for my $class ( sort values %{$schema->{class_mappings}} ) { + # Retrieve the resultset so we can access the columns info + my $rs = $schema->resultset($class); + my $columns = $rs->result_source->columns_info; + + # Loop over the columns + while ( my ( $column, $info ) = each %$columns ) { + # Next if data type is not date/datetime/timestamp + my $data_type = $info->{data_type}; + next unless grep { $data_type =~ m{^$_$} } qw( timestamp datetime date ); + + # Count the invalid dates + my $invalid_dates = $rs->search({ $column => '0000-00-00' })->count; + + next unless $invalid_dates; + + new_section("Column " . $rs->result_source->name . "." . $column . " contains $invalid_dates invalid dates"); + + if ($invalid_dates > 0) { + new_hint("You may change the dates with script: misc/cronjobs/fix_invalid_dates.pl (-c -v)"); + } + + } + } +} + { my @loop_borrowers_relationships; my @guarantor_ids = Koha::Patron::Relationships->_resultset->get_column('guarantor_id')->all(); @@ -450,5 +481,6 @@ Catch data inconsistencies in Koha database * Item types defined in items or biblioitems must be defined in the itemtypes table * Invalid MARCXML in bibliographic records * Patrons with invalid category types due to lower and upper age limits +* Any date fields in the database (timestamp, datetime, date) set to 0000-00-00 =cut From 698056d183b20ebc45f59de9232219b4333fdb06 Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Fri, 27 Sep 2024 17:43:16 +0000 Subject: [PATCH 006/429] Bug 31143: (QA follow-up) move script to maintenance Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer (cherry picked from commit b598154fbfc06261bee16beaa6c42709451d3a63) Signed-off-by: Lucas Gass --- misc/{cronjobs => maintenance}/fix_invalid_dates.pl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename misc/{cronjobs => maintenance}/fix_invalid_dates.pl (100%) diff --git a/misc/cronjobs/fix_invalid_dates.pl b/misc/maintenance/fix_invalid_dates.pl similarity index 100% rename from misc/cronjobs/fix_invalid_dates.pl rename to misc/maintenance/fix_invalid_dates.pl From c90b470c3e937542ce076f94b9112f36f8e51133 Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Fri, 27 Sep 2024 17:45:01 +0000 Subject: [PATCH 007/429] Bug 31143: (QA follow-up) Tidy Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer (cherry picked from commit a900e9307bd2b497bbaa6619de4098b2ea519626) Signed-off-by: Lucas Gass --- misc/maintenance/fix_invalid_dates.pl | 26 +++++++++++-------- .../search_for_data_inconsistencies.pl | 13 ++++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/misc/maintenance/fix_invalid_dates.pl b/misc/maintenance/fix_invalid_dates.pl index a7f43b671d..2da8b1c457 100755 --- a/misc/maintenance/fix_invalid_dates.pl +++ b/misc/maintenance/fix_invalid_dates.pl @@ -18,9 +18,9 @@ use Modern::Perl; use C4::Context; -use C4::Log qw(cronlogaction); +use C4::Log qw(cronlogaction); use Getopt::Long qw( GetOptions ); -use Pod::Usage qw( pod2usage ); +use Pod::Usage qw( pod2usage ); use Koha::Logger; use Koha::DateUtils qw( dt_from_string ); use Koha::Script -cron; @@ -28,38 +28,42 @@ use Data::Dumper; use Koha::Database; my $verbose = 0; -my $doit = 0; +my $doit = 0; GetOptions( - 'v|verbose' => \$verbose, - 'c|confirm' => \$doit, + 'v|verbose' => \$verbose, + 'c|confirm' => \$doit, ); my $schema = Koha::Database->new->schema; + # Loop over all the DBIx::Class classes -for my $class ( sort values %{$schema->{class_mappings}} ) { +for my $class ( sort values %{ $schema->{class_mappings} } ) { + # Retrieve the resultset so we can access the columns info - my $rs = $schema->resultset($class); + my $rs = $schema->resultset($class); my $columns = $rs->result_source->columns_info; # Loop over the columns while ( my ( $column, $info ) = each %$columns ) { + # Next if data type is not date/datetime/timestamp my $data_type = $info->{data_type}; next unless grep { $data_type =~ m{^$_$} } qw( timestamp datetime date ); # Count the invalid dates - my $invalid_dates = $rs->search({ $column => '0000-00-00' })->count; + my $invalid_dates = $rs->search( { $column => '0000-00-00' } )->count; next unless $invalid_dates; - if ($verbose) { + if ($verbose) { say sprintf "Column %s.%s contains %s invalid dates", $rs->result_source->name, $column, $invalid_dates; } if ($doit) { - $rs->search({ $column => '0000-00-00' })->update({ $column => undef }); - say sprintf "Column %s.%s contains %s invalid dates that have been fixed", $rs->result_source->name, $column, $invalid_dates; + $rs->search( { $column => '0000-00-00' } )->update( { $column => undef } ); + say sprintf "Column %s.%s contains %s invalid dates that have been fixed", $rs->result_source->name, + $column, $invalid_dates; } } diff --git a/misc/maintenance/search_for_data_inconsistencies.pl b/misc/maintenance/search_for_data_inconsistencies.pl index ed29e4134f..4c0eddf792 100755 --- a/misc/maintenance/search_for_data_inconsistencies.pl +++ b/misc/maintenance/search_for_data_inconsistencies.pl @@ -331,25 +331,28 @@ use C4::Biblio qw( GetMarcFromKohaField ); my $schema = Koha::Database->new->schema; # Loop over all the DBIx::Class classes - for my $class ( sort values %{$schema->{class_mappings}} ) { + for my $class ( sort values %{ $schema->{class_mappings} } ) { + # Retrieve the resultset so we can access the columns info - my $rs = $schema->resultset($class); + my $rs = $schema->resultset($class); my $columns = $rs->result_source->columns_info; # Loop over the columns while ( my ( $column, $info ) = each %$columns ) { + # Next if data type is not date/datetime/timestamp my $data_type = $info->{data_type}; next unless grep { $data_type =~ m{^$_$} } qw( timestamp datetime date ); # Count the invalid dates - my $invalid_dates = $rs->search({ $column => '0000-00-00' })->count; + my $invalid_dates = $rs->search( { $column => '0000-00-00' } )->count; next unless $invalid_dates; - new_section("Column " . $rs->result_source->name . "." . $column . " contains $invalid_dates invalid dates"); + new_section( + "Column " . $rs->result_source->name . "." . $column . " contains $invalid_dates invalid dates" ); - if ($invalid_dates > 0) { + if ( $invalid_dates > 0 ) { new_hint("You may change the dates with script: misc/cronjobs/fix_invalid_dates.pl (-c -v)"); } From 4bb4a27fcb3d6eb7dda9df95d7f3f2980f03f51d Mon Sep 17 00:00:00 2001 From: Wainui Witika-Park Date: Mon, 4 Nov 2024 18:31:25 +1300 Subject: [PATCH 008/429] Bug 31143: (follow-up) Add description to fix_invalid_dates.pl Signed-off-by: Katrin Fischer (cherry picked from commit 769b3942925e9d03d8ff053fdc8dc349f5c03d21) Signed-off-by: Lucas Gass --- misc/maintenance/fix_invalid_dates.pl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/misc/maintenance/fix_invalid_dates.pl b/misc/maintenance/fix_invalid_dates.pl index 2da8b1c457..4a4cf99319 100755 --- a/misc/maintenance/fix_invalid_dates.pl +++ b/misc/maintenance/fix_invalid_dates.pl @@ -27,6 +27,16 @@ use Koha::Script -cron; use Data::Dumper; use Koha::Database; +=head1 NAME + +fix_invalid_dates.pl - Fix invalid dates + +=head1 DESCRIPTION + +This script fixes any date fields in the database that have '0000-00-00' values (for example: dateofbirth) by updating them to 'NULL'. + +=cut + my $verbose = 0; my $doit = 0; From 701aca8307b5601c402e780f1976fcef8f85dc73 Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Oct 2024 14:35:39 +0000 Subject: [PATCH 009/429] Bug 36822: Shibboleth - Add tests prove t/db_dependent/Auth_with_shibboleth.t Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit 01d149b48afdab11f2b8d7621930373f7bc280bd) Signed-off-by: Lucas Gass --- t/db_dependent/Auth_with_shibboleth.t | 35 +++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t index 926d32bf97..f650d58143 100755 --- a/t/db_dependent/Auth_with_shibboleth.t +++ b/t/db_dependent/Auth_with_shibboleth.t @@ -20,7 +20,7 @@ use Modern::Perl; use utf8; -use Test::More tests => 5; +use Test::More tests => 6; use Test::MockModule; use Test::Warn; use CGI qw(-utf8 ); @@ -32,6 +32,14 @@ use t::lib::TestBuilder; use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib ); use Koha::Database; +use Koha::DateUtils qw( dt_from_string ); + +BEGIN { + use_ok( + 'C4::Auth', + qw( checkpw ) + ); +} my $schema = Koha::Database->new->schema; $schema->storage->txn_begin; @@ -155,7 +163,7 @@ subtest "get_login_shib tests" => sub { }; subtest "checkpw_shib tests" => sub { - plan tests => 33; + plan tests => 35; # Test borrower data my $test_borrowers = [ @@ -267,6 +275,29 @@ subtest "checkpw_shib tests" => sub { ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login); is( $retval, "0", "user not authenticated" ); $logger->info_is("No users with userid of martin found and autocreate is disabled", "Missing matchpoint warned to info"); + + # autocreate user from checkpw + change_config( { autocreate => 1, welcome => 1 } ); + $shib_login = 'test43210'; + $ENV{'uid'} = 'test43210'; + $ENV{'sn'} = "pika"; + $ENV{'exp'} = "2017-01-01"; + $ENV{'cat'} = $category->categorycode; + $ENV{'add'} = 'Address'; + $ENV{'city'} = 'City'; + $ENV{'emailpro'} = 'me@myemail.com'; + $ENV{branchcode} = $library->branchcode; # needed since T::D::C does no longer hides the FK constraint + + checkpw($shib_login); + ok my $new_user_autocreated = $schema->resultset('Borrower')->search( { 'userid' => 'test43210' }, { rows => 1 } ), + "new user found"; + + my $rec_autocreated = $new_user_autocreated->next; + is_deeply( + [ map { $rec_autocreated->$_ } qw/updated_on/ ], + [ dt_from_string()->ymd . ' ' . dt_from_string()->hms ], + 'updated_on correctly saved on newly created user' + ); }; subtest 'get_uri' => sub { From a9b9f587ce7fa77f5f6a315b61c19275f1812dbf Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Oct 2024 14:36:23 +0000 Subject: [PATCH 010/429] Bug 36822: Shibboleth - Add discard_changes Test plan: 1) Apply only Shibboleth tests patch 2) Run that test file: $ prove t/db_dependent/Auth_with_shibboleth.t 3) Verify tests fail with the updated_on error message. 4) Apply this patch. Repeat 2) Verify tests now pass. Co-authored-by: Martin Renvoize Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit ce9c97cda30ef32d4fa22a51c1a96f69ea9f18b0) Signed-off-by: Lucas Gass --- C4/Auth_with_shibboleth.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/C4/Auth_with_shibboleth.pm b/C4/Auth_with_shibboleth.pm index 3e6f42b57c..e3eb281cfc 100644 --- a/C4/Auth_with_shibboleth.pm +++ b/C4/Auth_with_shibboleth.pm @@ -135,6 +135,8 @@ sub _autocreate { } my $patron = Koha::Patron->new( \%borrower )->store; + $patron->discard_changes; + C4::Members::Messaging::SetMessagingPreferencesFromDefaults( { borrowernumber => $patron->borrowernumber, From 8d9d78493d8528fce6e938ecfb781d95694f770f Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Oct 2024 14:38:09 +0000 Subject: [PATCH 011/429] Bug 36822: LDAP - Rearrange Auth_with_ldap.pm so it picks up on mock config from test file Without this rearrangement, C4::Context->config('useldapserver') is always 0. It seems that because its inside a BEGIN block, the mock coming from .t file does not get picked up Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit e3d071ac5409030742b5b41805559e083c6c0140) Signed-off-by: Lucas Gass --- C4/Auth.pm | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/C4/Auth.pm b/C4/Auth.pm index 1a6141c372..fe97202f6f 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -82,14 +82,9 @@ BEGIN { get_template_and_user haspermission create_basic_session ); - $ldap = C4::Context->config('useldapserver') || 0; $cas = C4::Context->preference('casAuthentication'); $caslogout = C4::Context->preference('casLogout'); - if ($ldap) { - require C4::Auth_with_ldap; - import C4::Auth_with_ldap qw(checkpw_ldap); - } if ($cas) { require C4::Auth_with_cas; # no import import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url logout_if_required multipleAuth getMultipleAuth); @@ -1986,15 +1981,17 @@ sub checkpw { my $passwd_ok = 0; my $patron; - # Note: checkpw_* routines returns: # 1 if auth is ok # 0 if auth is nok # -1 if user bind failed (LDAP only) - + $ldap = C4::Context->config('useldapserver') || 0; if ( $ldap && defined($password) ) { my ( $retval, $retcard, $retuserid ); - ( $retval, $retcard, $retuserid, $patron ) = checkpw_ldap(@_); # EXTERNAL AUTH + require C4::Auth_with_ldap; + import C4::Auth_with_ldap qw(checkpw_ldap); + + ( $retval, $retcard, $retuserid, $patron ) = C4::Auth_with_ldap::checkpw_ldap(@_); # EXTERNAL AUTH if ( $retval == 1 ) { @return = ( $retval, $retcard, $retuserid, $patron ); $passwd_ok = 1; From dc8219cfb3ccac41b70e425986c0928a125c6729 Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Oct 2024 14:41:38 +0000 Subject: [PATCH 012/429] Bug 36822: LDAP - Add test Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit e567f78e27a9cfaf1fca6bbc3654de3e19c2e216) Signed-off-by: Lucas Gass --- t/db_dependent/Auth_with_ldap.t | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/t/db_dependent/Auth_with_ldap.t b/t/db_dependent/Auth_with_ldap.t index 19e95d24fb..601820df1e 100755 --- a/t/db_dependent/Auth_with_ldap.t +++ b/t/db_dependent/Auth_with_ldap.t @@ -25,8 +25,10 @@ use t::lib::TestBuilder; use Test::Warn; use C4::Context; +use C4::Auth; use Koha::Patrons; +use Koha::DateUtils qw( dt_from_string ); # Hide all the subrouteine redefined warnings when running this test.. # We reload the ldap module lots in the test and each reload triggers the @@ -155,7 +157,7 @@ subtest 'checkpw_ldap tests' => sub { subtest 'auth_by_bind = 1 tests' => sub { - plan tests => 14; + plan tests => 15; $auth_by_bind = 1; @@ -272,6 +274,17 @@ subtest 'checkpw_ldap tests' => sub { $patron->delete; $replicate = 0; $welcome = 0; + + # replicate testing with checkpw + C4::Auth::checkpw( 'hola', password => 'hey' ); + my $patron_replicated_from_auth = Koha::Patrons->search( { userid => 'hola' } )->next; + is( + $patron_replicated_from_auth->updated_on, dt_from_string()->ymd . ' ' . dt_from_string()->hms, + "updated_on correctly saved on newly created user" + ); + + $patron_replicated_from_auth->delete; + $auth->unmock('ldap_entry_2_hash'); # end replicate testing @@ -464,6 +477,9 @@ sub mockedC4Config { my $class = shift; my $param = shift; + if ( $param eq 'useldapserver' ) { + return 1; + } if ( $param eq 'useshibboleth' ) { return 0; } From bc5556acd3cec9d405b4b845c8a68e0f20a5adf2 Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Oct 2024 14:41:56 +0000 Subject: [PATCH 013/429] Bug 36822: LDAP - Add discard_changes Test plan: 1) Apply only LDAP rearrangement patch 2) Run that test file (to prove the rearrangement does not break tests): $ prove t/db_dependent/Auth_with_ldap.t 3) Verify tests pass 4) Apply LDAP test patch on top 5) Run the test file. Notice it fails. 6) Apply this patch. Run LDAP tests again. Verify tests pass. Co-authored-by: Martin Renvoize Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit 4c1e6b5e71e1fd033de8b289d0693edca7d28d01) Signed-off-by: Lucas Gass --- C4/Auth_with_ldap.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/C4/Auth_with_ldap.pm b/C4/Auth_with_ldap.pm index f0993c89ba..406e0d9a19 100644 --- a/C4/Auth_with_ldap.pm +++ b/C4/Auth_with_ldap.pm @@ -227,6 +227,7 @@ sub checkpw_ldap { map { exists( $borrower{$_} ) ? ( $_ => $borrower{$_} ) : () } @columns } )->store; + $patron->discard_changes; die "Insert of new patron failed" unless $patron; $borrowernumber = $patron->borrowernumber; C4::Members::Messaging::SetMessagingPreferencesFromDefaults( From 4c15fb996db0cecc8b161ba82f9aafafaaed3e43 Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Tue, 29 Oct 2024 13:14:54 +0000 Subject: [PATCH 014/429] Bug 36822: atomicupdate Fix any existing updated_on issues Signed-off-by: Katrin Fischer (cherry picked from commit 473f918ba876b7343ced24b20b21d80d7a33abaf) Signed-off-by: Lucas Gass --- .../data/mysql/atomicupdate/bug_36822.pl | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 installer/data/mysql/atomicupdate/bug_36822.pl diff --git a/installer/data/mysql/atomicupdate/bug_36822.pl b/installer/data/mysql/atomicupdate/bug_36822.pl new file mode 100755 index 0000000000..a6527d8eca --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_36822.pl @@ -0,0 +1,28 @@ +use Modern::Perl; +use Koha::Installer::Output qw(say_warning say_failure say_success say_info); + +return { + bug_number => "36822", + description => "Sanitize borrowers.updated_on", + up => sub { + my ($args) = @_; + my ( $dbh, $out ) = @$args{qw(dbh out)}; + + my $sth = $dbh->prepare( + q{ + SELECT borrowernumber + FROM borrowers + WHERE updated_on = '0000-00-00 00:00:00' + } + ); + $sth->execute; + my $results = $sth->fetchall_arrayref( {} ); + + if (@$results) { + sanitize_zero_date( 'borrowers', 'updated_on' ); + say_info( $out, "The following borrowers' updated_on has been sanitized: " + . join( ', ', map { $_->{borrowernumber} } @$results ) ); + } + + }, +}; From 5ea41dd50796211d4ba55dafc52cc274242f56b3 Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Mon, 4 Nov 2024 11:32:34 +0000 Subject: [PATCH 015/429] Bug 36822: (QA follow-up): Fix now time in tests This is to prevent the scenario of tests failing if they take long to run, e.g.: # Failed test 'updated_on correctly saved on newly created user' # at t/db_dependent/Auth_with_shibboleth.t line 317. # Structures begin differing at: # ->[0] = '2024-11-04 11:20:13' # ->[0] = '2024-11-04 11:20:14 Signed-off-by: Katrin Fischer (cherry picked from commit f945b29ca1ad7b6df178c46f080ae1e9ca05e1c4) Signed-off-by: Lucas Gass --- t/db_dependent/Auth_with_ldap.t | 3 ++- t/db_dependent/Auth_with_shibboleth.t | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/t/db_dependent/Auth_with_ldap.t b/t/db_dependent/Auth_with_ldap.t index 601820df1e..f6e47fb450 100755 --- a/t/db_dependent/Auth_with_ldap.t +++ b/t/db_dependent/Auth_with_ldap.t @@ -276,10 +276,11 @@ subtest 'checkpw_ldap tests' => sub { $welcome = 0; # replicate testing with checkpw + my $time_now = dt_from_string()->ymd . ' ' . dt_from_string()->hms; C4::Auth::checkpw( 'hola', password => 'hey' ); my $patron_replicated_from_auth = Koha::Patrons->search( { userid => 'hola' } )->next; is( - $patron_replicated_from_auth->updated_on, dt_from_string()->ymd . ' ' . dt_from_string()->hms, + $patron_replicated_from_auth->updated_on, $time_now, "updated_on correctly saved on newly created user" ); diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t index f650d58143..2e518f9e85 100755 --- a/t/db_dependent/Auth_with_shibboleth.t +++ b/t/db_dependent/Auth_with_shibboleth.t @@ -288,6 +288,7 @@ subtest "checkpw_shib tests" => sub { $ENV{'emailpro'} = 'me@myemail.com'; $ENV{branchcode} = $library->branchcode; # needed since T::D::C does no longer hides the FK constraint + my $time_now = dt_from_string()->ymd . ' ' . dt_from_string()->hms; checkpw($shib_login); ok my $new_user_autocreated = $schema->resultset('Borrower')->search( { 'userid' => 'test43210' }, { rows => 1 } ), "new user found"; @@ -295,7 +296,7 @@ subtest "checkpw_shib tests" => sub { my $rec_autocreated = $new_user_autocreated->next; is_deeply( [ map { $rec_autocreated->$_ } qw/updated_on/ ], - [ dt_from_string()->ymd . ' ' . dt_from_string()->hms ], + [$time_now], 'updated_on correctly saved on newly created user' ); }; From 3b2019442823eda3a719649cb2ff643a37ab0089 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 14 Nov 2024 13:08:16 +0000 Subject: [PATCH 016/429] Bug 36822: (follow-up) Catch MySQL 8 failure in update We add a CAST to the fetch of 0000-00-00 dates in the database. This prevents an error in MySQL 8.0 throws htat aborts the update. Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer (cherry picked from commit 36a387db22b43f0e8302eea42e3d279f28d7c076) Signed-off-by: Lucas Gass --- installer/data/mysql/atomicupdate/bug_36822.pl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/installer/data/mysql/atomicupdate/bug_36822.pl b/installer/data/mysql/atomicupdate/bug_36822.pl index a6527d8eca..5812382d4a 100755 --- a/installer/data/mysql/atomicupdate/bug_36822.pl +++ b/installer/data/mysql/atomicupdate/bug_36822.pl @@ -12,17 +12,15 @@ return { q{ SELECT borrowernumber FROM borrowers - WHERE updated_on = '0000-00-00 00:00:00' + WHERE CAST(updated_on AS CHAR(19)) = '0000-00-00 00:00:00' } ); $sth->execute; my $results = $sth->fetchall_arrayref( {} ); - if (@$results) { sanitize_zero_date( 'borrowers', 'updated_on' ); say_info( $out, "The following borrowers' updated_on has been sanitized: " . join( ', ', map { $_->{borrowernumber} } @$results ) ); } - }, }; From b82e05de36eeb0e5918841cad971995f4d32dbf4 Mon Sep 17 00:00:00 2001 From: Lucas Gass Date: Fri, 15 Nov 2024 16:10:40 +0000 Subject: [PATCH 017/429] Bug 36822: DBRev 24.05.05.001 Signed-off-by: Lucas Gass --- Koha.pm | 2 +- .../mysql/{atomicupdate/bug_36822.pl => db_revs/240505001.pl} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename installer/data/mysql/{atomicupdate/bug_36822.pl => db_revs/240505001.pl} (100%) diff --git a/Koha.pm b/Koha.pm index 2e0f5073fb..0ed0ed1fc4 100644 --- a/Koha.pm +++ b/Koha.pm @@ -29,7 +29,7 @@ use vars qw{ $VERSION }; # - #4 : the developer version. The 4th number is the database subversion. # used by developers when the database changes. updatedatabase take care of the changes itself # and is automatically called by Auth.pm when needed. -$VERSION = "24.05.05.000"; +$VERSION = "24.05.05.001"; sub version { return $VERSION; diff --git a/installer/data/mysql/atomicupdate/bug_36822.pl b/installer/data/mysql/db_revs/240505001.pl similarity index 100% rename from installer/data/mysql/atomicupdate/bug_36822.pl rename to installer/data/mysql/db_revs/240505001.pl From 42e42bced4ca381554ff655e2f4b985c8f5896dc Mon Sep 17 00:00:00 2001 From: Emmi Takkinen Date: Tue, 24 Sep 2024 15:00:10 +0300 Subject: [PATCH 018/429] Bug 37987: Speed up downloading reports in .tab format Downloading SQL report in .tab format was significantly slower after changes made in bug 37382. Apparently using HTML::Scrubber to redeclare content variable after mapping it is slow. Instead sending whole map function as parameter to module speeds up downloading to couple of seconds as it was before. To test: 1. Find or create report that returns several thousand rows (e.g. SELECT * FROM items LIMIT 4000). 2. Run report and attempt to download it as .tab file. => Notice that this takes a lot of time (around 20 seconds). 3. Apply this patch. 4. Download report again. => Dowloading should now take couple second. => Confirm that the content of .tab file still contains table data. Sponsored-by: Koha-Suomi Oy Signed-off-by: Paul Derscheid Signed-off-by: Katrin Fischer (cherry picked from commit 5002a0dac9f2d45e3f781c245ca50f4468d08e37) Signed-off-by: Lucas Gass --- reports/guided_reports.pl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/reports/guided_reports.pl b/reports/guided_reports.pl index 483a984a95..324c83ee5d 100755 --- a/reports/guided_reports.pl +++ b/reports/guided_reports.pl @@ -639,8 +639,7 @@ elsif ($op eq 'export'){ $content .= join("\t", header_cell_values($sth)) . "\n"; $content = $scrubber->scrub( Encode::decode( 'UTF-8', $content ) ); while ( my $row = $sth->fetchrow_arrayref() ) { - $content .= join( "\t", map { $_ // '' } @$row ) . "\n"; - $content = $scrubber->scrub( $content ); + $content .= $scrubber->scrub( join( "\t", map { $_ // '' } @$row ) ) . "\n"; } } else { if ( $format eq 'csv' ) { From 242b345311b21e951f0ea10be945f0539d44106b Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Mon, 7 Oct 2024 10:49:36 +0200 Subject: [PATCH 019/429] Bug 38071: Ensure search.dt is triggered I don't really understand why the previous code is not working, but the search.dt event handler is not correctly set when initComplete is passed to the constructor (for instance from the patrons search) Signed-off-by: Pedro Amorim Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer (cherry picked from commit 503f310a14c19a66c11fe6a9b310f57aa912025c) Signed-off-by: Lucas Gass --- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 39040059ac..2e791f604f 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -55,12 +55,6 @@ var dataTablesDefaults = { var tableId = settings.nTable.id var state = settings.oLoadedState; state && toggledClearFilter(state.search.search, tableId); - // When the DataTables search function is triggered, - // enable or disable the "Clear filter" button based on - // the presence of a search string - $(this).on( 'search.dt', function ( e, settings ) { - toggledClearFilter(settings.oPreviousSearch.sSearch, tableId); - }); if (settings.ajax) { let table_node = $("#" + tableId); @@ -1001,9 +995,16 @@ function _dt_add_delay(table_dt, table_node, delay_ms) { _dt_add_filters(this, table_dt, filters_options); } - table.DataTable().on("column-visibility.dt", function(){_dt_on_visibility(add_filters, table, table_dt);}) + table_dt.on("column-visibility.dt", function(){_dt_on_visibility(add_filters, table, table_dt);}) .columns( hidden_ids ).visible( false ); + table_dt.on( 'search.dt', function ( e, settings ) { + // When the DataTables search function is triggered, + // enable or disable the "Clear filter" button based on + // the presence of a search string + toggledClearFilter(settings.oPreviousSearch.sSearch, settings.nTable.id); + }); + return table; }; From 613e8137e60387fe35a2d1832782b665814aa920 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Tue, 20 Aug 2024 13:49:15 +0000 Subject: [PATCH 020/429] Bug 37687: Restore -not_in operator Test plan: 1) Compare the API documentation on query parameters and note that'-not_in' is listed as a valid operator. 2) Look at the patch diff - you will see that previously '-not_in' was not included in the list and now is Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Katrin Fischer (cherry picked from commit 7c3c36f2bb14c18aa8c18e6cd27d952c0200f0c0) Signed-off-by: Lucas Gass --- Koha/REST/Plugin/Query.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Koha/REST/Plugin/Query.pm b/Koha/REST/Plugin/Query.pm index 01fa0e8cd4..c8ba09e8d4 100644 --- a/Koha/REST/Plugin/Query.pm +++ b/Koha/REST/Plugin/Query.pm @@ -512,7 +512,7 @@ sub _validate_operator { my ($operator) = @_; my %allowed_operators = map { $_ => 1 } - qw(= != < > <= >= -in -ident -bool -not_bool -or like -like -not_like -between -not_between -not -and -regexp); + qw(= != < > <= >= -in -ident -bool -not_bool -or like -like -not_like -between -not_between -not -and -regexp -not_in); Koha::Exceptions::REST::Query::InvalidOperator->throw( operator => $operator ) unless exists $allowed_operators{$operator}; return; From 37c5a96f564d40197e833fc81098c9922bedc54e Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Mon, 19 Aug 2024 09:27:38 -0300 Subject: [PATCH 021/429] Bug 37672: Make V1/RecordSources.pm use more helpers This patch adapts the controller class to match the current guidelines by makiing use of the provided helpers. The tests are adapted. Signed-off-by: Tomas Cohen Arazi Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Katrin Fischer (cherry picked from commit e6ef792e10ae7a8fe1097c79a62f5388591a422f) Signed-off-by: Lucas Gass --- Koha/REST/V1/RecordSources.pm | 29 +++++++------------------- t/db_dependent/api/v1/record_sources.t | 2 +- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Koha/REST/V1/RecordSources.pm b/Koha/REST/V1/RecordSources.pm index 522b6943d3..46ce01b50f 100644 --- a/Koha/REST/V1/RecordSources.pm +++ b/Koha/REST/V1/RecordSources.pm @@ -55,12 +55,8 @@ sub get { return try { my $source = $c->objects->find( Koha::RecordSources->new, $c->param('record_source_id') ); - unless ($source) { - return $c->render( - status => 404, - openapi => { error => "Object not found" } - ); - } + return $c->render_resource_not_found("Record source") + unless $source; return $c->render( status => 200, openapi => $source ); } catch { @@ -97,12 +93,8 @@ sub update { my $source = $c->objects->find_rs( Koha::RecordSources->new, $c->param('record_source_id') ); - unless ($source) { - return $c->render( - status => 404, - openapi => { error => "Object not found" } - ); - } + return $c->render_resource_not_found("Record source") + unless $source; return try { $source->set_from_api( $c->req->json )->store; @@ -122,19 +114,12 @@ sub delete { my $source = $c->objects->find_rs( Koha::RecordSources->new, $c->param('record_source_id') ); - unless ($source) { - return $c->render( - status => 404, - openapi => { error => "Object not found" } - ); - } + return $c->render_resource_not_found("Record source") + unless $source; return try { $source->delete; - return $c->render( - status => 204, - openapi => q{} - ); + return $c->render_resource_deleted; } catch { $c->unhandled_exception($_); }; diff --git a/t/db_dependent/api/v1/record_sources.t b/t/db_dependent/api/v1/record_sources.t index f82264a714..09f3578375 100755 --- a/t/db_dependent/api/v1/record_sources.t +++ b/t/db_dependent/api/v1/record_sources.t @@ -129,7 +129,7 @@ subtest 'get() tests' => sub { $source->delete; $t->get_ok("//$userid:$password@/api/v1/record_sources/$id")->status_is(404) - ->json_is( '/error' => 'Object not found' ); + ->json_is( '/error' => 'Record source not found' ); $schema->storage->txn_rollback; }; From 94764f4d7064056da1b57c9ad87a47ab9ec88d79 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Mon, 19 Aug 2024 09:55:06 -0300 Subject: [PATCH 022/429] Bug 37672: Add tests Signed-off-by: Tomas Cohen Arazi Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Katrin Fischer (cherry picked from commit 765c724f98ddad70659fc62e6a7ef76c5960786e) Signed-off-by: Lucas Gass --- t/db_dependent/api/v1/record_sources.t | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/t/db_dependent/api/v1/record_sources.t b/t/db_dependent/api/v1/record_sources.t index 09f3578375..0fb37a9091 100755 --- a/t/db_dependent/api/v1/record_sources.t +++ b/t/db_dependent/api/v1/record_sources.t @@ -136,7 +136,7 @@ subtest 'get() tests' => sub { subtest 'delete() tests' => sub { - plan tests => 6; + plan tests => 10; $schema->storage->txn_begin; @@ -168,7 +168,15 @@ subtest 'delete() tests' => sub { $patron->set_password( { password => $password, skip_validation => 1 } ); $userid = $patron->userid; - $t->delete_ok("//$userid:$password@/api/v1/record_sources/$id")->status_is( 204, 'SWAGGER3.2.2' ); + $source->delete(); + $t->delete_ok("//$userid:$password@/api/v1/record_sources/$id")->status_is( 404, 'REST4.3' ) + ->json_is( { error => q{Record source not found}, error_code => q{not_found} } ); + + $source = $builder->build_object( { class => 'Koha::RecordSources' } ); + $id = $source->id; + + $t->delete_ok("//$userid:$password@/api/v1/record_sources/$id")->status_is( 204, 'REST3.2.4' ) + ->content_is( q{}, 'REST3.3.4' ); my $deleted_source = Koha::RecordSources->search( { record_source_id => $id } ); From e69480bfb7fb03926cd7e4f6ac8393df84eae1fd Mon Sep 17 00:00:00 2001 From: Baptiste Wojtkowski Date: Wed, 14 Aug 2024 10:55:16 +0200 Subject: [PATCH 023/429] Bug 37642: Generated letter should use https in header _wrap_html returns a header referring to http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd and http://www.w3.org/1999/xhtml. These should be https links. Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Katrin Fischer (cherry picked from commit e27c3a6090523549509ced1cc3d5f27d9be0e7aa) Signed-off-by: Lucas Gass --- C4/Letters.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/C4/Letters.pm b/C4/Letters.pm index 273b1fef21..10c7e2ee9a 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -1569,8 +1569,8 @@ sub _wrap_html { $css = qq{} if $css; return < - + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + $title From 0a906ca5b8b624b58466b5d66fd943b61372333c Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Wed, 12 Jun 2024 13:36:42 +0000 Subject: [PATCH 024/429] Bug 37076: Fix incorrect needsconfirmation code This patch corrects two instances of RESERVED_WAITING to RESERVE_WAITING Test plan: 1) Navigate to system preferences and search for AllowItemsOnHoldCheckoutSCO 2) Observe from the description that the correct status code is RESERVE_WAITING 3) Search the repository for RESERVED_WAITING - you should find two instances, one in Checkouts.pm and one in checkout.js 4) Apply patch 5) Repeat step 3 - there should be 0 instances Signed-off-by: Lucas Signed-off-by: Emily Lamancusa Signed-off-by: Katrin Fischer (cherry picked from commit 2b7e770429e4e85f2cebbf2d2b4a56f13fc0df6e) Signed-off-by: Lucas Gass --- Koha/REST/V1/Checkouts.pm | 2 +- koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Koha/REST/V1/Checkouts.pm b/Koha/REST/V1/Checkouts.pm index c1b874f35d..301369a45c 100644 --- a/Koha/REST/V1/Checkouts.pm +++ b/Koha/REST/V1/Checkouts.pm @@ -120,7 +120,7 @@ sub _check_availability { # Upgrade some confirmations to blockers my @should_block = - qw/TOO_MANY ISSUED_TO_ANOTHER RESERVED RESERVED_WAITING TRANSFERRED PROCESSING AGE_RESTRICTION/; + qw/TOO_MANY ISSUED_TO_ANOTHER RESERVED RESERVE_WAITING TRANSFERRED PROCESSING AGE_RESTRICTION/; for my $block (@should_block) { if ( exists( $confirmation->{$block} ) ) { $impossible->{$block} = $confirmation->{$block}; diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js b/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js index 9495d02ee4..9c9ece5c0f 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js +++ b/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js @@ -25,7 +25,7 @@ $(document).ready(function() { result += _("Your account is currently in debt by '%s'").format(data); } else if (code == 'ISSUED_TO_ANOTHER') { result += _("This item appears to be checked out to another patron, please return it to the desk"); - } else if (code == 'RESERVED' || code == 'RESERVED_WAITING') { + } else if (code == 'RESERVED' || code == 'RESERVE_WAITING') { result += _("This item appears to be on hold for another patron, please return it to the desk"); } else if (code == 'TOO_MANY') { result += _("You have reached the maximum number of checkouts allowed on your account"); From a5d2239a528afbcf3f2a8a61898bbb200ff4e660 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Tue, 27 Aug 2024 09:21:30 +0000 Subject: [PATCH 025/429] Bug 37620: Fix Total-Count headers Signed-off-by: Jonathan Druart Signed-off-by: Katrin Fischer (cherry picked from commit e2538b5e8312c4a8b4a0f1ce279423371770cbf8) Signed-off-by: Lucas Gass --- .../integration/InfiniteScrollSelect_spec.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/t/cypress/integration/InfiniteScrollSelect_spec.ts b/t/cypress/integration/InfiniteScrollSelect_spec.ts index bc928cfffd..214400c011 100644 --- a/t/cypress/integration/InfiniteScrollSelect_spec.ts +++ b/t/cypress/integration/InfiniteScrollSelect_spec.ts @@ -63,8 +63,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageOne, headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }); @@ -81,8 +81,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageTwo, headers: { - "X-Base-Total-Count": "40", - "X-Total-Count": "40", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("getPageTwo"); // Scroll the dropdown @@ -127,8 +127,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageOne, headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }); @@ -190,8 +190,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageOne, headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("getPageOne"); @@ -208,8 +208,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageTwo, headers: { - "X-Base-Total-Count": "40", - "X-Total-Count": "40", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("getPageTwo"); // Scroll the dropdown @@ -245,8 +245,8 @@ describe("Infinite scroll", () => { }, ], headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("searchFilter"); // Select a license that is not in the first page of results @@ -279,8 +279,8 @@ describe("Infinite scroll", () => { statusCode: 200, body: pageOne, headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("resetDropdown"); cy.get("#license_id_0 .vs__open-indicator").click(); @@ -306,8 +306,8 @@ describe("Infinite scroll", () => { }, ], headers: { - "X-Base-Total-Count": "20", - "X-Total-Count": "20", + "X-Base-Total-Count": "60", + "X-Total-Count": "60", }, }).as("secondSearchFilter"); cy.get("#license_id_0 .vs__open-indicator").click(); From 86ca184501f7f7d07376df9838b947477ca5a1de Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 22 May 2024 11:22:06 +0200 Subject: [PATCH 026/429] Bug 36919: Remove a warning from Koha/Object.t Use of uninitialized value in numeric eq (==) at /kohadevbox/koha/Koha/Patron.pm line 446. Signed-off-by: David Nind Signed-off-by: Lucas Gass Signed-off-by: Katrin Fischer (cherry picked from commit 5c905109dbeaae6aaadbcdd0f2670e7c0bd437fe) Signed-off-by: Lucas Gass --- Koha/Patron.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Koha/Patron.pm b/Koha/Patron.pm index d0f5c76acc..f5d4aaf8ac 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -443,7 +443,7 @@ sub delete { Koha::Exceptions::Patron::FailedDeleteAnonymousPatron->throw() if $anonymous_patron && $self->id eq $anonymous_patron; # Check if patron is protected - Koha::Exceptions::Patron::FailedDeleteProtectedPatron->throw() if $self->protected == 1; + Koha::Exceptions::Patron::FailedDeleteProtectedPatron->throw() if defined $self->protected && $self->protected == 1; $self->_result->result_source->schema->txn_do( sub { From 43829ee9286cd5a7e036059c13d2d2a6e989131c Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 22 May 2024 11:22:44 +0200 Subject: [PATCH 027/429] Bug 36919: Remove another warning from Koha/Object.t C4::Context->userenv not defined! at /kohadevbox/koha/Koha/Patron.pm line 1890. Signed-off-by: David Nind Signed-off-by: Lucas Gass Signed-off-by: Katrin Fischer (cherry picked from commit f14db22d8ab39714fd71854c146b7472041ee7a3) Signed-off-by: Lucas Gass --- t/db_dependent/Koha/Object.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/db_dependent/Koha/Object.t b/t/db_dependent/Koha/Object.t index 4794fb1b70..8d11feb3ee 100755 --- a/t/db_dependent/Koha/Object.t +++ b/t/db_dependent/Koha/Object.t @@ -383,6 +383,8 @@ subtest "to_api() tests" => sub { } ); + t::lib::Mocks::mock_userenv( { patron => $patron } ); + my $patron_api = $patron->to_api( { embed => { holds_count => { is_count => 1 } }, From 386a823a8462533471f80f21ac133f6a2fbf3ac8 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 22 May 2024 11:23:07 +0200 Subject: [PATCH 028/429] Bug 36919: Remove yet another warning from Koha/Object.t DBIx::Class::Storage::DBI::insert(): Missing value for primary key column 'currency' on Currency - perhaps you forgot to set its 'is_auto_increment' attribute during add_columns()? Treating 'currency' implicitly as an autoinc, and attempting value retrieval at /kohadevbox/koha/Koha/Object.pm line 172 Signed-off-by: David Nind Signed-off-by: Lucas Gass Signed-off-by: Katrin Fischer (cherry picked from commit 34ff421c1c7ee82dd6454138008398f7382eac57) Signed-off-by: Lucas Gass --- t/db_dependent/Koha/Object.t | 1 + 1 file changed, 1 insertion(+) diff --git a/t/db_dependent/Koha/Object.t b/t/db_dependent/Koha/Object.t index 8d11feb3ee..ab8b7b1d97 100755 --- a/t/db_dependent/Koha/Object.t +++ b/t/db_dependent/Koha/Object.t @@ -1008,6 +1008,7 @@ subtest 'store() tests' => sub { my $currency = eval { Koha::Acquisition::Currency->new( { + currency => 'Cur_test', active => 'IT4test', } )->store; From b86cd3ec62530ff60b17ba7aec5b93e09c85f652 Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 27 Aug 2024 01:48:58 +0000 Subject: [PATCH 029/429] Bug 37741: Do not send cookie back from error pages This change stops the cookie from being sent back from error pages, so that backcalls that cause errors don't overwrite the existing cookie used by the foreground request page. Test plan: 0. Apply the patch and koha-plack --reload kohadev 1. Set syspref IntranetFavicon to http://localhost:8081/cgi-bin/koha/bad.jpg 2. Open browser developer tools 3. Go to http://localhost:8081/cgi-bin/koha/mainpage.pl 4. Open the Network tab, disable the cache, and shift refresh 5. Notice that bad.jpg fails to load with a 404 6. Try to login to Koha 7. Confirm login works *. Extra points if you note that the cookie returned by the first mainpage.pl request is used for the bad.jpg lookup and the second mainpage.pl request. Signed-off-by: David Nind Signed-off-by: Chris Cormack Signed-off-by: Katrin Fischer (cherry picked from commit e6a1d65c9a0fe36c7c604613f9791e2b25149c08) Signed-off-by: Lucas Gass --- errors/400.pl | 3 +++ errors/401.pl | 2 ++ errors/402.pl | 2 ++ errors/403.pl | 2 ++ errors/404.pl | 3 +++ errors/500.pl | 2 ++ opac/errors/400.pl | 2 ++ opac/errors/401.pl | 2 ++ opac/errors/402.pl | 2 ++ opac/errors/403.pl | 2 ++ opac/errors/404.pl | 2 ++ opac/errors/500.pl | 2 ++ 12 files changed, 26 insertions(+) diff --git a/errors/400.pl b/errors/400.pl index 0bddd49fc8..8ae328dcce 100755 --- a/errors/400.pl +++ b/errors/400.pl @@ -41,4 +41,7 @@ my $status = '400 Bad Request'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } + +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/errors/401.pl b/errors/401.pl index f5761fcb35..4b8f748cf5 100755 --- a/errors/401.pl +++ b/errors/401.pl @@ -40,4 +40,6 @@ my $status = '401 Unauthorized'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/errors/402.pl b/errors/402.pl index 835aed3ea3..b9d3444368 100755 --- a/errors/402.pl +++ b/errors/402.pl @@ -41,4 +41,6 @@ my $status = '402 Payment Required'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/errors/403.pl b/errors/403.pl index b83c014de8..c75c2a5293 100755 --- a/errors/403.pl +++ b/errors/403.pl @@ -43,4 +43,6 @@ my $status = '403 Forbidden'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/errors/404.pl b/errors/404.pl index 049db4aa40..a375d8d8cb 100755 --- a/errors/404.pl +++ b/errors/404.pl @@ -41,4 +41,7 @@ my $status = '404 Not Found'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } + +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/errors/500.pl b/errors/500.pl index 26dab6f93b..5f216dd61b 100755 --- a/errors/500.pl +++ b/errors/500.pl @@ -41,4 +41,6 @@ my $status = '500 Internal Server Error'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/400.pl b/opac/errors/400.pl index 7d3a63ca2d..e34e18c092 100755 --- a/opac/errors/400.pl +++ b/opac/errors/400.pl @@ -41,4 +41,6 @@ my $status = '400 Bad Request'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/401.pl b/opac/errors/401.pl index abd65d72a6..432691859b 100755 --- a/opac/errors/401.pl +++ b/opac/errors/401.pl @@ -41,4 +41,6 @@ my $status = '401 Unauthorized'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/402.pl b/opac/errors/402.pl index 3ba2d62b50..edc710860b 100755 --- a/opac/errors/402.pl +++ b/opac/errors/402.pl @@ -41,4 +41,6 @@ my $status = '402 Payment Required'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/403.pl b/opac/errors/403.pl index 4d7dc16d7f..3a3fb24118 100755 --- a/opac/errors/403.pl +++ b/opac/errors/403.pl @@ -43,4 +43,6 @@ my $status = '403 Forbidden'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/404.pl b/opac/errors/404.pl index e07e056853..4d4b687385 100755 --- a/opac/errors/404.pl +++ b/opac/errors/404.pl @@ -41,4 +41,6 @@ my $status = '404 Not Found'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; diff --git a/opac/errors/500.pl b/opac/errors/500.pl index c25f848211..b0985be6bf 100755 --- a/opac/errors/500.pl +++ b/opac/errors/500.pl @@ -41,4 +41,6 @@ my $status = '500 Internal Server Error'; if ( C4::Context->is_internal_PSGI_request() ) { $status = '200 OK'; } +#NOTE: We're not setting/updating the cookie here +$cookie = ''; output_with_http_headers $query, $cookie, $template->output, 'html', $status; From dba73d1cdbd3863e31ef6aea80c3fd902fe16f01 Mon Sep 17 00:00:00 2001 From: Catalyst Bug Squasher Date: Thu, 25 Jul 2024 23:01:04 +0000 Subject: [PATCH 030/429] Bug 35239: Missing form field ids in batch patron modification template Test Plan 1. Tools > Batch patron modification 2. List valid card number in text box 3. [Continue] 4. Edit patrons form, click on input box label 5. Matching input box gets focus Signed-off-by: Owen Leonard Signed-off-by: David Nind Signed-off-by: Katrin Fischer (cherry picked from commit d5388670c3d3158cd9b6131a630ec0edadb0fe46) Signed-off-by: Lucas Gass --- koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt index 566b9e59ad..a1058b1c2c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt @@ -366,7 +366,7 @@ [% END %] [% IF ( field.type == 'text' ) %] - + [% END %] [% IF ( field.type == 'select' ) %] [% IF field.option.size %] From 4b1ebe2a5bd93f83f280b976bfbd622916dd3370 Mon Sep 17 00:00:00 2001 From: Owen Leonard Date: Fri, 26 Jul 2024 18:17:11 +0000 Subject: [PATCH 031/429] Bug 35239: (follow-up) Correct more missing label inconsistencies This patch corrects a couple more labels and modifies the process of cloning a patron attribute field so that each new cloned row has matching label and input. To test, apply the patch and, if necessary, create some patron attribute types: - Go to Administration -> Patron attribute types - Create at least two new patron attribute types. - Go to Tools -> Batch patron modification. - Submit a batch of patrons for batch modification - In the list of fields to modify, test that clicking each label gives focus to the corresponding field (except date fields, we need to fix that globally). - Click "New" next to the patron attribute field. - A copy of the field should be created. Test that the new field also has matching label and + + [% END %] + +
@@ -491,6 +502,8 @@ $(document).ready(function() { [% END %]
Numbered
+
+
[% UNLESS ( serialsadditems ) %]   diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-home.tt index 22e8960ea7..4b6b7abe8d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-home.tt @@ -3,9 +3,13 @@ [% USE Asset %] [% USE KohaDates %] [% USE Branches %] +[% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% INCLUDE 'doc-head-open.inc' %] -Serials [% biblionumber | html %] › Koha +[% FILTER collapse %] + [% tx("Serials {biblionumber}", { biblionumber = biblionumber }) | html %] › + [% t("Koha") | html %] +[% END %] [% INCLUDE 'doc-head-close.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-search.tt index 972a1b7567..64a7800182 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-search.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/serials-search.tt @@ -5,9 +5,14 @@ [% USE CGI %] [% USE KohaDates %] [% USE Koha %] +[% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% INCLUDE 'doc-head-open.inc' %] -Serials [% biblionumber | html %] › Koha +[% FILTER collapse %] + [% t("Search results") | html %] › + [% t("Serials") | html %] › + [% t("Koha") | html %] +[% END %] [% INCLUDE 'doc-head-close.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-add.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-add.tt index 2c6705656e..3c827d34ed 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-add.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-add.tt @@ -4,9 +4,19 @@ [% USE KohaDates %] [% USE Branches %] [% USE Koha %] +[% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% INCLUDE 'doc-head-open.inc' %] -[% IF ( modify ) %][% bibliotitle | html %] › Modify subscription[% ELSE %]New subscription[% END %] › Serials › Koha +[% FILTER collapse %] + [% IF( modify ) %] + [% t("Modify subscription") | html %] › + [% bibliotitle | html %] + [% ELSE %] + [% t("New subscription") | html %] + [% END %] › + [% t("Serials") | html %] › + [% t("Koha") | html %] +[% END %] [% INCLUDE 'doc-head-close.inc' %] [% END %] +[% END %] [% INCLUDE 'slip-print.inc' #printThenClose %] @@ -39,7 +42,6 @@ [% SET slips = [{content => slip, is_html => !plain, style => style }] %] [% END %] [% FOR slip IN slips %] -[% IF slip.style %][% END %]
[% UNLESS slip.is_html %]

From 160fd0724e223c4b0d4c012073e34dc32e880e1f Mon Sep 17 00:00:00 2001
From: Nick Clemens 
Date: Fri, 4 Oct 2024 16:43:44 +0000
Subject: [PATCH 185/429] Bug 38089: (QA follow-up) Ensure style works on
 members/printslip.pl

When moving the style up, it was moved before the code that handles printing a single slip, i.e.
taking the variables and forming a slip hash.

This patch moves that code to the top and additionally passes through the CODE of the notice so the ID is not blank

Signed-off-by: Martin Renvoize 
Signed-off-by: Katrin Fischer 
(cherry picked from commit 03e17cda38e69150538de5cf623664abaab77201)
Signed-off-by: Lucas Gass 
---
 .../prog/en/modules/circ/printslip.tt         | 37 ++++++++++---------
 members/printslip.pl                          |  1 +
 2 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/printslip.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/printslip.tt
index 938c52f41e..fd36a77f88 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/printslip.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/printslip.tt
@@ -3,6 +3,9 @@
 [% USE Koha %]
 [% PROCESS 'i18n.inc' %]
 [% INCLUDE 'doc-head-open.inc' %]
+[% UNLESS slips && slips.size %]
+    [% SET slips = [{content => slip, is_html => !plain, style => style, id => id }] %]
+[% END %]
 [% FILTER collapse %]
 [% IF ( caller == 'hold-transfer' ) %]
     [% t("Hold transfer print receipt") | html %] ›
@@ -30,28 +33,26 @@
 <link rel="stylesheet" type="text/css" href="[% Koha.Preference('SlipCSS') | $raw %]" />
 [% END %]
 [% FOR slip IN slips %]
-[% IF slip.style %]<style>[% slip.style.replace('([^\{\}]+?)\s*\{', '#slip_' _ slip.id _ ' $1 {') | $raw %]</style>[% END %]
+    [% IF slip.style %]<style>[% slip.style.replace('([^\{\}]+?)\s*\{', '#slip_' _ slip.id _ ' $1 {') | $raw %]</style>[% END %]
 [% END %]
 
 [% INCLUDE 'slip-print.inc' #printThenClose %]
 </head>
 <body id="circ_printslip" class="circ">
-<div id="receipt">
-
-[% UNLESS slips && slips.size %]
-    [% SET slips = [{content => slip, is_html => !plain, style => style }] %]
-[% END %]
-[% FOR slip IN slips %]
-<div id="slip_[% slip.id | html %]">
-[% UNLESS slip.is_html %]
-<pre>
-[% IF ( slip.content ) %][% slip.content | html %][% ELSE %]No slip template found[% END %]
-</pre>
-[% ELSE %]
-[% IF ( slip.content ) %][% slip.content | $raw %][% ELSE %]No slip template found[% END %]
-[% END %]
-</div>
-[% IF slips.size > 1 && !loop.last%]<div class="pagebreak"></div>[% END %]
-[% END %]
+    <div id="receipt">
+    [% FOR slip IN slips %]
+        <div id="slip_[% slip.id | html %]">
+        [% UNLESS slip.is_html %]
+            <pre>
+                [% IF ( slip.content ) %][% slip.content | html %][% ELSE %]No slip template found[% END %]
+            </pre>
+        [% ELSE %]
+            [% IF ( slip.content ) %][% slip.content | $raw %][% ELSE %]No slip template found[% END %]
+        [% END %]
+        </div>
+        [% IF slips.size > 1 && !loop.last%]
+        <div class="pagebreak"></div>
+        [% END %]
+    [% END %]
 
 [% INCLUDE 'intranet-bottom.inc' %]
diff --git a/members/printslip.pl b/members/printslip.pl
index 67df3fd808..9cf130c22f 100755
--- a/members/printslip.pl
+++ b/members/printslip.pl
@@ -120,6 +120,7 @@ $template->param(
     stylesheet     => C4::Context->preference("SlipCSS"),
     error          => $error,
     style          => $letter->{style},
+    id             => $print,
 );
 
 $template->param( IntranetSlipPrinterJS => C4::Context->preference('IntranetSlipPrinterJS' ) );

From 41fad6a308f3d200fd7322b5cc1b5b88b0fdf05e Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Mon, 7 Oct 2024 15:26:35 +0100
Subject: [PATCH 186/429] Bug 38089: (follow-up) More fixes for errors in
 original submission

The original submission missed a number of slip types that are print on
demand without queueing.

These all need an "id" passing in to the template and in some cases we
were also missing passing in the new style parameter too (preservation)

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 3ab343b67e358eda8e9b0e07d9ebc2dfd12b3b22)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/article-request-slip.pl  | 1 +
 circ/hold-transfer-slip.pl    | 1 +
 circ/transfer-slip.pl         | 1 +
 members/print_overdues.pl     | 1 +
 preservation/print_slip.pl    | 3 +++
 recalls/recall_pickup_slip.pl | 1 +
 6 files changed, 8 insertions(+)

diff --git a/circ/article-request-slip.pl b/circ/article-request-slip.pl
index 5ab59576c9..ed8bf8aa4e 100755
--- a/circ/article-request-slip.pl
+++ b/circ/article-request-slip.pl
@@ -81,6 +81,7 @@ $template->param(
     caller => 'article-request',
     plain  => 0,
     style  => $style,
+    id     => 'ar_slip',
 );
 
 output_html_with_http_headers $cgi, $cookie, $template->output;
diff --git a/circ/hold-transfer-slip.pl b/circ/hold-transfer-slip.pl
index 435d1c14e0..dab16202d0 100755
--- a/circ/hold-transfer-slip.pl
+++ b/circ/hold-transfer-slip.pl
@@ -54,6 +54,7 @@ $template->param( slip   => $slip ) if ($slip);
 $template->param( caller => 'hold-transfer' );
 $template->param( plain  => !$is_html );
 $template->param( style  => $style );
+$template->param( id     => 'reserve_slip' );
 
 output_html_with_http_headers $input, $cookie, $template->output;
 
diff --git a/circ/transfer-slip.pl b/circ/transfer-slip.pl
index 5282bbe44e..b8f8a5031f 100755
--- a/circ/transfer-slip.pl
+++ b/circ/transfer-slip.pl
@@ -60,6 +60,7 @@ $template->param(
     caller => 'cud-transfer',
     stylesheet => C4::Context->preference("SlipCSS"),
     style      => $style,
+    id         => 'transfer_slip',
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/members/print_overdues.pl b/members/print_overdues.pl
index 135e1df39d..d5bb612e15 100755
--- a/members/print_overdues.pl
+++ b/members/print_overdues.pl
@@ -68,6 +68,7 @@ $template->param(
     plain          => !$letter->{is_html},
     borrowernumber => $borrowernumber,
     style          => $letter->{style},
+    id             => 'overdues_slip',
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/preservation/print_slip.pl b/preservation/print_slip.pl
index 285319e036..153508c3d6 100755
--- a/preservation/print_slip.pl
+++ b/preservation/print_slip.pl
@@ -40,6 +40,7 @@ my $logged_in_user = Koha::Patrons->find($loggedinuser);
 my $branch         = C4::Context->userenv->{'branch'};
 
 my @slips;
+my $count = 0;
 for my $train_item_id (@train_item_ids) {
     my $train_item = Koha::Preservation::Train::Items->find($train_item_id);
     my $letter     = C4::Letters::GetPreparedLetter(
@@ -55,6 +56,8 @@ for my $train_item_id (@train_item_ids) {
     push @slips, {
         content => $letter->{content},
         is_html => $letter->{is_html},
+        style   => $letter->{style},
+        id      => ++$count,
     };
 }
 
diff --git a/recalls/recall_pickup_slip.pl b/recalls/recall_pickup_slip.pl
index 19e439ce29..247e03686b 100755
--- a/recalls/recall_pickup_slip.pl
+++ b/recalls/recall_pickup_slip.pl
@@ -70,6 +70,7 @@ $template->param(
     plain  => !$is_html,
     caller => 'recall',
     style  => $style,
+    id     => 'recall_request_det',
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;

From d3db300f3d58ab62a42a50a402707e47a4ef3f96 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Mon, 7 Oct 2024 19:58:17 +0000
Subject: [PATCH 187/429] Bug 38109: Sort patron category types before display

To test
1 - Patrons -> New patron -> {any category}
2 - Find the category dropdown
3 - Note the order of categories types
4 - Reload the page
5 - Note a new order
6 - Apply patch
7 - Refresh page, category types now ordered (by code)
8 - Refresh again, no change!

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 3b438c2b891bb16e53f752e64799545546559750)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/members/memberentrygen.tt     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index daefdf6774..e430f5e45b 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -991,7 +991,7 @@ legend.collapsed i.fa.fa-caret-down::before {
                                         <li>
                                             <label for="categorycode_entry" class="required">Category: </label>
                                             <select id="categorycode_entry" name="categorycode">
-                                                [% FOREACH category_type IN patron_categories.keys %]
+                                                [% FOREACH category_type IN patron_categories.keys.sort %]
                                                     [% SWITCH category_type %]
                                                     [% CASE 'C' %]
                                                         <optgroup label="Child">

From 464978912bed3a48745bd7cd541a8a82b2ede589 Mon Sep 17 00:00:00 2001
From: Laura Escamilla <laura.escamilla@bywatersolutions.com>
Date: Wed, 15 May 2024 15:34:22 +0000
Subject: [PATCH 188/429] Bug 13945: Prevent multiple dialog modals from
 popping up when capturing a hold at checkin

1. Select/Create Library Branches:
    - Select or create two library branches: Library A and Library B.

2. Locate/Create Item:
    - Locate or create an item with the "Current library" and "Home library" set to Library A.

3. Place Hold:
    - Place a hold on the item for a patron whose pickup location is Library B.

4. Check-in at Library A - First Attempt:
    - Check in the item at Library A.
    - Verify that the 'Hold found' modal pops up.
    - Click on 'Confirm hold and transfer'.

5. Check-in at Library A - Second Attempt:
    - Check in the item at Library A again.
    - Verify that the 'Hold found' modal pops up.
    - Click on 'Ignore'.
    - Verify that the 'Please return this item to (Library B)' modal has popped up behind it.

6. Apply Patch and Restart:
    - Apply the patch to the system.
    - Restart all relevant services.

7. Repeat Check-in Process:
    - Repeat steps 4 and 5.
    - Verify that no matter how many times you check in the item and hit 'Ignore' or 'Confirm the hold', the second modal does not pop up.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 87a2aa8a62cb1d3018329c0123ad6f8e908aa3b3)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
index 9d38faf322..aa0b3e1c13 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
@@ -739,7 +739,7 @@
                                 </div> <!-- /#hold-found1 -->
                             [% END # /IF waiting %]
 
-                            [% IF transfer || needstransfer %]
+                            [% IF (transfer || needstransfer) && !(reserved and !recalled and !waitingrecall) %]
                                 [% IF Koha.Preference('TransfersBlockCirc') %]
                                     <div id="item-transfer-modal" class="modal fade audio-alert-action block">
                                 [% ELSE %]

From c46e5c782d3e020a417d4ae61af087774c05af4f Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleisha@catalyst.net.nz>
Date: Wed, 1 May 2024 02:18:19 +0000
Subject: [PATCH 189/429] Bug 36435: Catch harmless errors in koha-dump
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

tar has exit status 1 if any file has changed while being read, which is likely to happen as the logfiles are included. this patch will catch these harmless errors so that koha-dump will still return a success code.

Test plan:
1. Create a number of backups and edit their modify time to being older than 2 days
2. run the koha-run-backups script and try and write to one of the files it includes while it's being backed up (you'll trigger a warning from tar saying the file changed as it was being read)
3. verify that the older backups are not being cleaned up and deleted despite new backups being made.
4. Apply this patch
5. Complete steps 1-2 again and then go and look to see if the older backups are being deleted.

Sponsored-by: Catalyst IT
Signed-off-by: David Nind <david@davidnind.com>
Co-authored-by: Michał Kula <148193449+mkibp@users.noreply.github.com>
Signed-off-by: Michał Kula <148193449+mkibp@users.noreply.github.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0119429ac5588fa5aaa3b75a3634a754d617d3e1)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 debian/scripts/koha-dump | 45 +++++++++++++++++++++++-----------------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/debian/scripts/koha-dump b/debian/scripts/koha-dump
index f58c4b2022..6ed0f1b683 100755
--- a/debian/scripts/koha-dump
+++ b/debian/scripts/koha-dump
@@ -124,30 +124,37 @@ dump_instance()
         output="$output to $metadump"
         [ "$quiet" = "no" ] && echo "$output"
 
+        # tar has exit status 1 if any file has changed while being read,
+        # which is likely to happen as the logfiles are included.
+        set +e
+
         if [ "$exclude_logs" = "yes" ]; then
-        tar -czf "$metadump" -C / $excludes \
-            "etc/koha/sites/$name" \
-            "etc/apache2/sites-available/$instancefile" \
-            "etc/apache2/sites-enabled/$instancefile" \
-            "var/lib/koha/$name" \
-            $uploaded_files_dir \
-            $uploaded_temp_files_dir
-
-        chown "root:$name-koha" "$metadump"
-        chmod g+r "$metadump"
+            tar -czf "$metadump" -C / $excludes \
+                "etc/koha/sites/$name" \
+                "etc/apache2/sites-available/$instancefile" \
+                "etc/apache2/sites-enabled/$instancefile" \
+                "var/lib/koha/$name" \
+                $uploaded_files_dir \
+                $uploaded_temp_files_dir
         else
-        tar -czf "$metadump" -C / $excludes \
-            "etc/koha/sites/$name" \
-            "etc/apache2/sites-available/$instancefile" \
-            "etc/apache2/sites-enabled/$instancefile" \
-            "var/lib/koha/$name" \
-            "var/log/koha/$name" \
-            $uploaded_files_dir \
-            $uploaded_temp_files_dir
+            tar -czf "$metadump" -C / $excludes \
+                "etc/koha/sites/$name" \
+                "etc/apache2/sites-available/$instancefile" \
+                "etc/apache2/sites-enabled/$instancefile" \
+                "var/lib/koha/$name" \
+                "var/log/koha/$name" \
+                $uploaded_files_dir \
+                $uploaded_temp_files_dir
+        fi
+
+        if [ "$?" != "1" -a "$?" != "0" ]; then
+            exit $?
+        fi
+
+        set -e
 
         chown "root:$name-koha" "$metadump"
         chmod g+r "$metadump"
-      fi
 
         [ "$quiet" = "no" ] && echo "Done."
     fi

From 7712993222a6e1a5df8911acfc4dd9ec79838892 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Wed, 18 Sep 2024 10:22:51 +0000
Subject: [PATCH 190/429] Bug 37954: Fix barcode column name

Patch https://github.com/Koha-Community/Koha/commit/6cbbfa2 changed
holdings_barcode to holdings_barcodes

I believe this caused the configuration from the table to no longer
work (barcodes!=barcode)

Test plan:
1) Access any bibllio:
   <staff_url>/cgi-bin/koha/catalogue/detail.pl?biblionumber=76
2) On the top right of the table, click 'Configure'
3) Expand 'Catalog', search for 'holdings_barcode'. Tick 'is hidden by
   default'. Save.
4) Visit the biblio again, notice the column is still showing.
5) Apply patch. Restart all + flush_memcached. Repeat.
6) Bonus: On the table, click 'Show filters'. Make sure barcode
   filtering still works.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e89dc927df4d4cf7706a73ef054b95d7796fe89a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 admin/columns_settings.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml
index 78f54079c1..f459878dd3 100644
--- a/admin/columns_settings.yml
+++ b/admin/columns_settings.yml
@@ -411,7 +411,7 @@ modules:
             -
               columnname: holdings_datelastborrowed
             -
-              columnname: holdings_barcodes
+              columnname: holdings_barcode
             -
               columnname: holdings_uri
             -

From a58c132a1646453d68281b15ce0ff0f525a488a3 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 19 Sep 2024 11:40:24 +0000
Subject: [PATCH 191/429] Bug 37954: Atomic update

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4355f54f5ca00de9a32bd5c6c1c036657ca7a926)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../data/mysql/atomicupdate/bug_37954.pl      | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100755 installer/data/mysql/atomicupdate/bug_37954.pl

diff --git a/installer/data/mysql/atomicupdate/bug_37954.pl b/installer/data/mysql/atomicupdate/bug_37954.pl
new file mode 100755
index 0000000000..1c75e5b3ed
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_37954.pl
@@ -0,0 +1,44 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
+
+return {
+    bug_number  => "37954",
+    description => "Move holdings_barcodes setting to holdings_barcode",
+    up          => sub {
+        my ($args) = @_;
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+        my ($existing_holdings_barcode_setting) = $dbh->selectrow_array(
+            q{
+                SELECT COUNT(*) FROM columns_settings WHERE module='catalogue' AND page='detail' AND tablename='holdings_table' AND columnname='holdings_barcode'
+        }
+        );
+        if ($existing_holdings_barcode_setting) {
+            say_info( $out, "Settings already exist" );
+        } else {
+            my ( $cannot_be_toggled, $is_hidden ) = $dbh->selectrow_array(
+                q{
+                    SELECT cannot_be_toggled,is_hidden FROM columns_settings WHERE module='catalogue' AND page='detail' AND tablename='holdings_table' AND columnname='holdings_barcodes'
+            }
+            );
+
+            if ( defined $cannot_be_toggled || defined $is_hidden ) {
+                $dbh->do(
+                    qq{
+                    INSERT IGNORE INTO columns_settings VALUES
+                    ('catalogue', 'detail', 'holdings_table', 'holdings_barcode', $cannot_be_toggled, $is_hidden )
+                }
+                );
+                say_success( $out, "Settings moved to holdings_barcode" );
+            } else {
+                say_info( $out, "No settings for holdings_barcodes found" );
+            }
+        }
+        $dbh->do(
+            q{
+            DELETE FROM columns_settings WHERE module='catalogue' AND page='detail' AND tablename='holdings_table' AND columnname='holdings_barcodes'
+        }
+        );
+        say $out "Removed settings for holdings_barcodes";
+    },
+};

From be96ec063f2bf887e18c365a2a579e7c7b333061 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Mon, 18 Nov 2024 23:15:46 +0000
Subject: [PATCH 192/429] Bug 37954: DBRev 24.05.05.004

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha.pm                                                         | 2 +-
 .../mysql/{atomicupdate/bug_37954.pl => db_revs/240505004.pl}   | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_37954.pl => db_revs/240505004.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index 8246922c43..00b1536fd2 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.003";
+$VERSION = "24.05.05.004";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_37954.pl b/installer/data/mysql/db_revs/240505004.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_37954.pl
rename to installer/data/mysql/db_revs/240505004.pl

From 718ecb53288b99cf37e9ef873703b9061f768c7b Mon Sep 17 00:00:00 2001
From: Andrew Fuerste Henry <andrewfh@dubcolib.org>
Date: Fri, 4 Oct 2024 16:31:52 +0000
Subject: [PATCH 193/429] Bug 38097: Add class to not_retuned message

To test:
1: Enable ShowAllCheckins
2: Check in an item that was not checked out
3: Find "Item was not checked in" message in checkin table, confirm its only class is "problem"
4: apply patch, restart_all
5: repeat 2
6: confirm "Item was not checked in" now has the class "not_returned"

Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 01ebb27b5ea598408bb5584124ad9ad77320db9b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
index aa0b3e1c13..4b88809d4a 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
@@ -1267,7 +1267,7 @@
                                         <tr>
                                             <td class="ci-duedate">
                                                 [% IF ( riloo.not_returned ) %]
-                                                    <span class="problem">Item was not checked in</span>
+                                                    <span class="problem not_returned">Item was not checked in</span>
                                                 [% END %]
                                                 [% IF ( riloo.duedate ) %]
                                                     [% IF ( riloo.return_overdue ) %]

From e198c2afde084be361af8917cb8e8a78176fc3eb Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Wed, 25 Sep 2024 21:17:22 +0000
Subject: [PATCH 194/429] Bug 38005: Pass opac_editable to search params when
 interface is OPAC

1. Create a patron attribute.
2. Check it as Mandatory.
3. Do NOT make it display or editable in the OPAC.
4. Go to OPAC self reg form
5. You will not see the attribute.
6. Submit the form and see the 500 error.
7. Apply patch, restart_all.
8. Try again, now you should be able to submit the OPAC form.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit fbdd43a6a148366402930d259a684d2de680728a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Patron.pm | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/Koha/Patron.pm b/Koha/Patron.pm
index 5ec4bac303..cbce7c3f42 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -2208,17 +2208,22 @@ sub extended_attributes {
                 }
 
                 # Check globally mandatory types
-                my @required_attribute_types =
-                    Koha::Patron::Attribute::Types->search(
-                        {
-                            mandatory => 1,
-                            category_code => [ undef, $self->categorycode ],
-                            'borrower_attribute_types_branches.b_branchcode' =>
-                              undef,
-                        },
-                        { join => 'borrower_attribute_types_branches' }
-                    )->get_column('code');
-                for my $type ( @required_attribute_types ) {
+                my $interface = C4::Context->interface;
+                my $params = {
+                    mandatory                                        => 1,
+                    category_code                                    => [ undef, $self->categorycode ],
+                    'borrower_attribute_types_branches.b_branchcode' => undef,
+                };
+
+                if ( $interface eq 'opac' ) {
+                    $params->{opac_editable} = 1;
+                }
+
+                my @required_attribute_types = Koha::Patron::Attribute::Types->search(
+                    $params,
+                    { join => 'borrower_attribute_types_branches' }
+                )->get_column('code');
+                for my $type (@required_attribute_types) {
                     Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute->throw(
                         type => $type,
                     ) if !$new_types->{$type};

From 029635023980628089e7f9fb2c026174e3884b96 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Fri, 11 Oct 2024 19:12:18 +0000
Subject: [PATCH 195/429] Bug 38005: (follow-up) Add/adjust tests

NC amended patch, tidied
Bug 38005: (QA follow-up) Tidy

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0f3d1f3daf9521984e44cf430a01a928659f462a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Koha/Patron.t | 63 ++++++++++++++++++++++++++++--------
 1 file changed, 50 insertions(+), 13 deletions(-)

diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t
index 563ecdc61e..7a75423076 100755
--- a/t/db_dependent/Koha/Patron.t
+++ b/t/db_dependent/Koha/Patron.t
@@ -1073,12 +1073,14 @@ subtest 'extended_attributes' => sub {
 
     subtest 'globally mandatory attributes tests' => sub {
 
-        plan tests => 5;
+        plan tests => 8;
 
         $schema->storage->txn_begin;
         Koha::Patron::Attribute::Types->search->delete;
 
-        my $patron = $builder->build_object({ class => 'Koha::Patrons' });
+        my $context = Test::MockModule->new('C4::Context');
+
+        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
 
         my $attribute_type_1 = $builder->build_object(
             {
@@ -1094,17 +1096,33 @@ subtest 'extended_attributes' => sub {
             }
         );
 
+        my $attribute_type_3 = $builder->build_object(
+            {
+                class => 'Koha::Patron::Attribute::Types',
+                value => { mandatory => 1, class => 'a', category_code => undef, opac_editable => 1 }
+            }
+        );
+
+        my $attribute_type_4 = $builder->build_object(
+            {
+                class => 'Koha::Patron::Attribute::Types',
+                value => { mandatory => 0, class => 'a', category_code => undef, opac_editable => 1 }
+            }
+        );
+
+        #Staff interface tests
+        $context->mock( 'interface', sub { return "intranet" } );
         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
 
-        throws_ok
-            {
-                $patron->extended_attributes(
-                    [
-                        { code => $attribute_type_2->code, attribute => 'b' }
-                    ]
-                );
-            }
-            'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
+        throws_ok {
+            $patron->extended_attributes(
+                [
+                    { code => $attribute_type_2->code, attribute => 'b' },
+                    { code => $attribute_type_3->code, attribute => 'b' },
+                ]
+            );
+        }
+        'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
             'Exception thrown on missing mandatory attribute type';
 
         is( $@->type, $attribute_type_1->code, 'Exception parameters are correct' );
@@ -1113,11 +1131,30 @@ subtest 'extended_attributes' => sub {
 
         $patron->extended_attributes(
             [
-                { code => $attribute_type_1->code, attribute => 'b' }
+                { code => $attribute_type_1->code, attribute => 'b' },
+                { code => $attribute_type_3->code, attribute => 'b' },
             ]
         );
 
-        is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
+        is( $patron->extended_attributes->count, 2, 'Extended attributes succeeded' );
+
+        # OPAC interface tests
+        $context->mock( 'interface', sub { return "opac" } );
+
+        is( $patron->extended_attributes->count, 2, 'Patron still has 2 extended attributes in OPAC' );
+
+        throws_ok {
+            $patron->extended_attributes(
+                [
+                    { code => $attribute_type_1->code, attribute => 'b' },
+                    { code => $attribute_type_2->code, attribute => 'b' },
+                ]
+            );
+        }
+        'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
+            'Exception thrown on missing mandatory OPAC-editable attribute';
+
+        is( $@->type, $attribute_type_3->code, 'Exception parameters are correct for OPAC-editable attribute' );
 
         $schema->storage->txn_rollback;
 

From b5ac0ad18b0fa1df64990de226a27ea579dd6db4 Mon Sep 17 00:00:00 2001
From: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
Date: Tue, 10 Sep 2024 03:06:15 +0000
Subject: [PATCH 196/429] Bug 37838: Fix broken remove button in course
 reserves

To test:
1) Go to staff client -> Course reserves
2) Create a course or go to an existing course
3) Add reserves to the course (will need at least 21 items to be able to go to a second page of results)
4) Go to the second page of results on the course details page
5) Click the Remove button next to a result
6) Notice how the button just makes the page move to the top
7) Apply patch
8) Repeat steps 1-5
9) Notice how the remove button is working as expected

Sponsored-by: Toi Ohomai Institute of Technology
Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit bbd644444f0fb344922112dd447c18a4a86525d7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/course_reserves/course-details.tt           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course-details.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course-details.tt
index 9174459162..61099da882 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course-details.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course-details.tt
@@ -342,7 +342,7 @@
                 "autoWidth": false,
             }, table_settings );
 
-            $(".delete_item").on( 'click', function(e){
+            $("#course_reserves_table").on( 'click', " .delete_item", function(e){
                 e.preventDefault();
                 if (confirmDelete(_("Are you sure you want to remove this item from the course?") )) {
                     cr_id = $(this).data('cr-id');

From 0cc899ac3deac15eb51baebf54496116b04ebed0 Mon Sep 17 00:00:00 2001
From: Hammat Wele <hammat.wele@inlibro.com>
Date: Tue, 11 Jun 2024 14:08:45 +0000
Subject: [PATCH 197/429] Bug 37070: Incorrect barcode generation when adding
 orders to basket
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the autoBarcode preference is set to « generated in the form 1, 2, 3 » and the maximum barcode is length 16 (ex 1000000000000000),
when adding orders to the basket,the numbers generated are in the hexadecimal form.

to reproduce:

1- Set the system preference autoBarcode to « generated in the form 1, 2, 3 »
2- Search for a biblio record
3- In the record details, click on New -> New item
4- fill the Barcode field to a number with length 16 (1000000000000000) and add the item
5- Create a suggestion
    5-1- Go to Acquisitions and click on suggestions
    5-2- Create a suggestion and accept it
6- Add a new order to a basket
    6-1- Go to Acquisitions and find a vendor
    6-2- Create a new Basket
    6-3- on the Basket click on 'Add to basket' and select 'From a suggestion'
    6-4- Add the order created on 5-2- to the basket
    6-5- On the item form click on Add item
    6-6- Select a Fund and save the order
7- In the orders table click on the record and check the item Barcode
    ---> the Barcode is in hexadecimal form
8- Cancel order and delete the catalog record
9- Apply the patch
10- Repeat step 6-4, 6-5, 6-6
11- Check the item Barcode
    ---> the Barcode is not in hexadecimal form
12- run prove t/db_dependent/Barcodes.t

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 898cb34fefa5bcfa02b8822bab01cba4fb256898)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Barcodes.pm            |  2 +-
 t/db_dependent/Barcodes.t | 24 +++++++++++++++++++++++-
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/C4/Barcodes.pm b/C4/Barcodes.pm
index 957d60f283..dfca945a33 100644
--- a/C4/Barcodes.pm
+++ b/C4/Barcodes.pm
@@ -95,7 +95,7 @@ sub max {
 }
 sub db_max {
 	my $self = shift;
-	my $query = "SELECT max(abs(barcode)) FROM items LIMIT 1"; # Possible problem if multiple barcode types populated
+	my $query = "SELECT max(abs(CAST(barcode AS UNSIGNED))) FROM items LIMIT 1"; # Possible problem if multiple barcode types populated
 	my $sth = C4::Context->dbh->prepare($query);
 	$sth->execute();
 	return $sth->fetchrow_array || $self->initial;
diff --git a/t/db_dependent/Barcodes.t b/t/db_dependent/Barcodes.t
index 2846f4fe89..b5a976b841 100755
--- a/t/db_dependent/Barcodes.t
+++ b/t/db_dependent/Barcodes.t
@@ -17,7 +17,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 75;
+use Test::More tests => 76;
 use Test::Warn;
 use Test::MockModule;
 use t::lib::TestBuilder;
@@ -132,6 +132,28 @@ subtest 'Test generation of hbyymmincr barcodes from DB values' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'Test generation of incremental barcodes from DB values' => sub {
+
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+    $builder->schema->resultset('Issue')->delete_all;
+    $builder->schema->resultset('Item')->delete_all;
+    my $barcodeobj;
+
+    my $item_1 = $builder->build_sample_item(
+        {
+            barcode => '1000000000000000'
+        }
+    );
+
+    $barcodeobj = C4::Barcodes->new('incremental');
+
+    is( $barcodeobj->db_max(), '1000000000000000', "(hbyymmincr) First barcode saved to db is equal to db_max" );
+    is( $barcodeobj->value(),  '1000000000000001', 'incremental barcode' );
+
+    $schema->storage->txn_rollback;
+};
 
 $schema->storage->txn_begin;
 

From 580cf78aff82e9369611155df09b4cd4c94eb142 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Fri, 4 Oct 2024 13:00:26 -0400
Subject: [PATCH 198/429] Bug 37070: (QA follow-up) Fix qa script issues

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit bfe67da62d939240eed64decab48633284c6c41f)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Barcodes.pm            | 11 ++++++-----
 t/db_dependent/Barcodes.t |  6 +-----
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/C4/Barcodes.pm b/C4/Barcodes.pm
index dfca945a33..333a8eb8bc 100644
--- a/C4/Barcodes.pm
+++ b/C4/Barcodes.pm
@@ -94,11 +94,12 @@ sub max {
 	return $self->db_max;
 }
 sub db_max {
-	my $self = shift;
-	my $query = "SELECT max(abs(CAST(barcode AS UNSIGNED))) FROM items LIMIT 1"; # Possible problem if multiple barcode types populated
-	my $sth = C4::Context->dbh->prepare($query);
-	$sth->execute();
-	return $sth->fetchrow_array || $self->initial;
+    my $self  = shift;
+    my $query = "SELECT max(abs(CAST(barcode AS UNSIGNED))) FROM items LIMIT 1"
+        ;    # Possible problem if multiple barcode types populated
+    my $sth = C4::Context->dbh->prepare($query);
+    $sth->execute();
+    return $sth->fetchrow_array || $self->initial;
 }
 sub next_value {
 	my $self = shift;
diff --git a/t/db_dependent/Barcodes.t b/t/db_dependent/Barcodes.t
index b5a976b841..0232a4a9c9 100755
--- a/t/db_dependent/Barcodes.t
+++ b/t/db_dependent/Barcodes.t
@@ -141,11 +141,7 @@ subtest 'Test generation of incremental barcodes from DB values' => sub {
     $builder->schema->resultset('Item')->delete_all;
     my $barcodeobj;
 
-    my $item_1 = $builder->build_sample_item(
-        {
-            barcode => '1000000000000000'
-        }
-    );
+    my $item_1 = $builder->build_sample_item( { barcode => '1000000000000000' } );
 
     $barcodeobj = C4::Barcodes->new('incremental');
 

From d59cc9176cca170780ffa031a1addef2df4942cf Mon Sep 17 00:00:00 2001
From: Brendan Lawlor <blawlor@clamsnet.org>
Date: Mon, 22 Jul 2024 20:07:18 +0000
Subject: [PATCH 199/429] Bug 37304: Fix created by filter in Acquisitions
 advanced search

This patch updates the field for the created by filter from
ui.item.borrowernumber to ui.item.patron_id

Test plan:
1. Add a vendor and a basket
2. Use advanced order search to search for created by 'koha'
3. Use the autofill dropdown to make the selection and click 'Search'
4. Your search returned no results.
5. Apply patch and repeat steps 2 and 3
6. Confirm the search returns the basket you created earlier

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit c18f368411a1525f401739bc0e586ce8e0f7929b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/histsearch.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/histsearch.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/histsearch.tt
index 4c2e5b17fc..37404fd64e 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/histsearch.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/histsearch.tt
@@ -184,7 +184,7 @@
             }
             patron_autocomplete($("#find_patron"), {
                 'on-select-callback': function( event, ui ) {
-                    var field = ui.item.borrowernumber;
+                    var field = ui.item.patron_id;
                     AddPatron( ui.item.firstname + " " + ( ui.item.middle_name || "" ) + " " + ui.item.surname, field, $("#basket_creators"), 'created_by' );
                     $("#find_patron").val('').focus();
                     return false;

From 491ad084ba5294b68bc0826d8dfd71409a209188 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Sat, 12 Oct 2024 14:28:57 +0000
Subject: [PATCH 200/429] Bug 38162: Pass the rota_id correctly when deleting

To test:

1. Enable StockRotation
2. Got to Cataloging -> Stock rotation
3. Create a new Rota
4. Try to delete it
5. See the error "Can't call method "delete" on an undefined value at /kohadevbox/koha/tools/stockrotation.pl line 231"
6. APPLY PATCH
7. Try again, the rota should be deleted properly.

Signed-off-by: Laura ONeil <laura@bywatersolutions.com>
Signed-off-by: Alyssa <alyssa.drake@bywatersolutions.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit dd4822e400449921ff0c602bd3bd9c57ec95287c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/tools/stockrotation.tt      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stockrotation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stockrotation.tt
index dec382dc8b..62d646898a 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stockrotation.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stockrotation.tt
@@ -407,10 +407,10 @@
                             [% END %]
                         [% END %]
                         <p>
-                            <form id="delete_rota_[% rota.rota_id | html %]" method="post" action="/cgi-bin/koha/tools/stockrotation.pl">
+                            <form id="delete_rota_[% rota_id | html %]" method="post" action="/cgi-bin/koha/tools/stockrotation.pl">
                                 [% INCLUDE 'csrf-token.inc' %]
                                 <input type="hidden" name="op" value="cud-delete_rota" />
-                                <input type="hidden" name="rota_id" value="[% rota.rota_id | html %]" />
+                                <input type="hidden" name="rota_id" value="[% rota_id | html %]" />
                                 <button class="btn btn-default btn-xs approve" type="submit"><i class="fa fa-fw fa-check"></i> Yes</button>
                             </form>
                             <a class="btn btn-default btn-xs deny" href="/cgi-bin/koha/tools/stockrotation.pl"><i class="fa fa-fw fa-times"></i> No</a>

From f70f31ed5d09de09c0fa870826a23783d173409c Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 8 Oct 2024 15:38:18 +0200
Subject: [PATCH 201/429] Bug 38118: Display columns if not empty in
 drawCallback

Instead of updating only when DT init is complete we are going to update
the visibility when the table will be redrawn.

Test plan:
1 - Find/Add a bib record with no items
2 - Add 20 items from Centerville
    Edit ->Edit items -> enter CENTER1 into barcode ->'Add multiple copies' -> 20
3 - Add 20 items from Fairview as above but enter 'FAIR1' for barcode and 'FAIR ITEM' as callnumber
4 - Add 1 item from Midway with barcode MIDWAY1
5 - Edit a midway patron to grant 'catalogue' and 'edit_items' permission - NOT edit_any_item
6 - Sign in as the patron above
7 - Find the record
8 - Note there is callnumber and actions/edit columns
9 - Go to page 2 of items - callnumber visible
10 - Go to page 3 - edit column

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../tables/items/catalogue_detail.inc         | 34 +++++++++++--------
 1 file changed, 20 insertions(+), 14 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index 9a1db9f781..90d80fca34 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -120,6 +120,24 @@
             }
         }
 
+        function update_columns_visibility(settings){
+            let table = settings.oInstance.api();
+            table.columns().visible(true);
+            table.columns().every(function(i){
+                let is_empty = true;
+                let nodes = this.nodes();
+                nodes.each((td, ii) => {
+                    if ( $(td).html() !== '' ) {
+                        is_empty = false;
+                        return;
+                    }
+                });
+                if ( is_empty ) {
+                    table.columns(i).visible(false);
+                }
+            });
+        }
+
         $(document).ready(function() {
 
             $(".SelectAll").on("click",function(e){
@@ -779,20 +797,6 @@
             [% END %]
             ],
             initComplete: function( settings, json ){
-                let table = settings.oInstance.api();
-                table.columns().every(function(i){
-                    let is_empty = true;
-                    let nodes = this.nodes();
-                    nodes.each((td, ii) => {
-                        if ( $(td).html() !== '' ) {
-                            is_empty = false;
-                            return;
-                        }
-                    });
-                    if ( is_empty ) {
-                        table.columns(i).visible(false);
-                    }
-                });
                 [% IF StaffDetailItemSelection %]
                 itemSelectionBuildActionLinks(tab_id);
                 [% END %]
@@ -835,6 +839,8 @@
                     });
                 [% END %]
 
+                update_columns_visibility(settings);
+
                 if ( drawcallback ) { drawcallback(this); }
             },
             [% END %]

From a3206ef0d5962bae895a1f5f6e74ffca5cc8cb14 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 23 Oct 2024 15:34:09 +0200
Subject: [PATCH 202/429] Bug 38240: Do not hide columns if no items to display

If you filter items using the column filters and that no item are displayed, all columns will be hidden (because no data for those columns) and it is then impossible to reset the filters show items again.

Test plan:
http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=4
All items are from CPL
Select another library
=> No items displayed
Without this patch the headers are hidden and it's impossible to modify
the filters
With this patch the columns that were displayed on the last draw are
still there, letting you modify the filters

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7c9ae2d83bbf6b3c8594e2a363e8eed623b1e1d7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../includes/html_helpers/tables/items/catalogue_detail.inc   | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index 90d80fca34..cff5387cd0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -839,7 +839,9 @@
                     });
                 [% END %]
 
-                update_columns_visibility(settings);
+                if ( api.data().length ) {
+                    update_columns_visibility(settings);
+                }
 
                 if ( drawcallback ) { drawcallback(this); }
             },

From 9bfea2a66b7ee1194bae1acad244ce90cf754397 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 8 Oct 2024 10:43:41 +0200
Subject: [PATCH 203/429] Bug 38112: Restore description of patrons search

Certainly caused by bug 35329.

We used to display a description of the patrons search, but it is no longer displayed.

Test plan:
On the main patrons search select some values in the form on the left
and search. You should see a "Patrons found for: " h3 that will be
displayed and is supposed to describe the current search.
I don't think it's working very well to be honest, maybe a candidate for
candidate, especially if nobody else noticed its disappearance.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 913052d23ca5a99791363b3ea964a6b80fc423b1)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
index d2e8d8cc44..995907d3fb 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
@@ -786,8 +786,7 @@
                     searched += paron_search_form.find("select[name='sort2_filter']").val();
                 }
             }
-            patron_search_form.find(".searchpattern").text(searched);
-            patron_search_form.find(".searchpattern").parent().show();
+            parent_block.find(".searchpattern").text(searched).parent().show();
         }
 
         function filter() {

From c9c44b5c6966a1d45701c9fbb04692ce60fda875 Mon Sep 17 00:00:00 2001
From: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Date: Tue, 15 Oct 2024 16:50:03 -0400
Subject: [PATCH 204/429] Bug 38112: (QA follow-up) Hide description when
 search is cleared

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ad4ce38551bbde5eae9abf541d2779cd0d111790)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
index 995907d3fb..095d129671 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
@@ -712,7 +712,7 @@
             patron_search_form.find(".clear_search").on("click",function(e){
                 e.preventDefault();
                 clearFilters();
-                patron_search_form.find(".searchpattern").parent().hide();
+                parent_block.find(".searchpattern").parent().hide();
             });
 
             if ( !defer_loading ) {

From 75e1b19ce5345bf952944d0cb427466862d53376 Mon Sep 17 00:00:00 2001
From: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Date: Thu, 10 Oct 2024 14:31:45 -0400
Subject: [PATCH 205/429] Bug 38146: Display full datetime of datelastseen in
 item holdings

To test:
1. Check in an item
2. Look at the item holdings table for the bib record that item is on
--> Note the date last seen column displays dates, but not the time
3. Apply patch and restart_all
4. Refresh the page
--> Date last seen column now shows the time you checked the item in

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d60057b429de3a522c5140002746a610150dc540)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../en/includes/html_helpers/tables/items/catalogue_detail.inc  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index cff5387cd0..9f9189c590 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -574,7 +574,7 @@
                 searchable: true,
                 orderable: true,
                 render: function (data, type, row, meta) {
-                    return $date(row.last_seen_date);
+                    return $datetime(row.last_seen_date);
                 }
             },
             {

From b99b51deef834023089609eed069e1bed9653df4 Mon Sep 17 00:00:00 2001
From: Hinemoea Viault <hinemoea.viault@inlibro.com>
Date: Wed, 24 Jul 2024 12:35:28 -0400
Subject: [PATCH 206/429] Bug 37403: Wrong progress quantity in job details

In fact the system adds a previous value to the progress. As if the system keeps a previous value and adds the correct count of records in addition.
This patch removes the previously stored value.

To test:
1. Go to Cataloging > Stage records for import
2. Choose a file with bibliographic records and click Upload file
3. In "Look for existing records in catalog?" choose a record matching rule (e.g. ISBN)
4. Click Stage for import
5. Click View detail of the enqueued job
   --> Progress show 2/1
6. Apply the patch
7. Repeat steps 1 to 4
8. Click View detail of the enqueued job
   --> Progress show 1/1

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

qa script:
Commit title does not start with 'Bug XXXXX: ' - d1bebb34d7

Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Fixed patch subject line
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 929e906187eca4454604d6082da5d8abb8a5a8b5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/BackgroundJob/StageMARCForImport.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Koha/BackgroundJob/StageMARCForImport.pm b/Koha/BackgroundJob/StageMARCForImport.pm
index 1272378279..e536ded50b 100644
--- a/Koha/BackgroundJob/StageMARCForImport.pm
+++ b/Koha/BackgroundJob/StageMARCForImport.pm
@@ -144,7 +144,7 @@ sub process {
                 $matcher_code    = $matcher->code();
                 $num_with_matches =
                   BatchFindDuplicates( $batch_id, $matcher, 10, 50,
-                    sub { my $job_progress = shift; $self->progress( $self->progress + $job_progress )->store } );
+                    sub { my $job_progress = shift; $self->progress( $job_progress )->store } );
                 SetImportBatchMatcher( $batch_id, $matcher_id );
                 SetImportBatchOverlayAction( $batch_id, $overlay_action );
                 SetImportBatchNoMatchAction( $batch_id, $nomatch_action );

From 2e7e5c465916f54c0158872408b730a3369a795e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johanna=20R=C3=A4is=C3=A4?= <johanna.raisa@gmail.com>
Date: Thu, 1 Aug 2024 08:46:09 +0300
Subject: [PATCH 207/429] Bug 37528: check if selected relationship is valid

This patch checks if the selected relationship is valid before trying to save the patron record.
It takes the list of valid relationships from borrowerRelationships syspref and checks if the selected relationship is in the list.

Also this patch fixes relationship field required message when BorrowerMandatoryField is not set.
The required message is shown when adding the guarantee from guarantor's detail page.

Test plan:
1) Add at least one option to borrowerRelationships syspref.
2) Leave the relationship unchecked from BorrowerMandatoryField syspref.
3) Create a new guarantee patron.
4) Add a guarantor to the guarantee patron.
5) Leave the relationship field empty and try to save the patron record.
6) Notice the 500 error page.
7) Apply the patch.
8) Repeat steps 3-5.
9) Notice the error message "Guarantor relationship is invalid".

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Olivier V <olivier.vezina@inLibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 9ea82b05d59d6430cffa448d94be076f3fe20f1c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/members/memberentrygen.tt    |  5 ++++-
 members/memberentry.pl                           | 16 ++++++++++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index e430f5e45b..dde1efaf4d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -217,6 +217,9 @@ legend.collapsed i.fa.fa-caret-down::before {
                                     [% IF ( ERROR_cannot_delete_guarantor ) %]
                                         <li id="ERROR_cannot_delete_guarantor">Cannot delete guarantor(s). A child patron needs a guarantor.</li>
                                     [% END %]
+                                    [% IF ( ERROR_invalid_relationship ) %]
+                                        <li id="ERROR_invalid_relationship">Guarantor relationship is invalid.</li>
+                                    [% END %]
                                 </ul>
                             </div>
                         [% END %]
@@ -554,7 +557,7 @@ legend.collapsed i.fa.fa-caret-down::before {
                                                                             [% END %]
                                                                         [% END %]
                                                                     </select>
-                                                                    [% UNLESS mandatoryrelationship %]
+                                                                    [% IF mandatoryrelationship %]
                                                                         <span class="required">Required</span>
                                                                     [% END %]
                                                                 </li>
diff --git a/members/memberentry.pl b/members/memberentry.pl
index 3e75eeebde..7ee45c5eed 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -289,6 +289,22 @@ foreach my $guarantor (@guarantors) {
     }
 }
 
+my @valid_relationships = split(/\|/, C4::Context->preference('borrowerRelationship'), -1);
+if (@valid_relationships) {
+    my @new_guarantor_id           = $input->multi_param('new_guarantor_id');
+    my @new_guarantor_relationship = $input->multi_param('new_guarantor_relationship');
+
+    for ( my $i = 0 ; $i < scalar @new_guarantor_id; $i++ ) {
+        my $guarantor_id = $new_guarantor_id[$i];
+        my $relationship = $new_guarantor_relationship[$i];
+
+        next unless $guarantor_id;
+        unless ( grep { $_ eq $relationship } @valid_relationships ) {
+            push @errors, 'ERROR_invalid_relationship';
+        }
+    }
+}
+
 ###############test to take the right zipcode, country and city name ##############
 # set only if parameter was passed from the form
 $newdata{'city'}    = $input->param('city')    if defined($input->param('city'));

From b4855d5c09d4593fe0ae6a00a28007808beee3c3 Mon Sep 17 00:00:00 2001
From: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Date: Mon, 16 Sep 2024 15:32:14 +0200
Subject: [PATCH 208/429] Bug 37528: (follow-up) Tidyness

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ec5d2c9ff990c00273a9cdb3fb9c1e24a9691cb6)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 members/memberentry.pl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/members/memberentry.pl b/members/memberentry.pl
index 7ee45c5eed..98f985d41e 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -289,12 +289,12 @@ foreach my $guarantor (@guarantors) {
     }
 }
 
-my @valid_relationships = split(/\|/, C4::Context->preference('borrowerRelationship'), -1);
+my @valid_relationships = split( /\|/, C4::Context->preference('borrowerRelationship'), -1 );
 if (@valid_relationships) {
     my @new_guarantor_id           = $input->multi_param('new_guarantor_id');
     my @new_guarantor_relationship = $input->multi_param('new_guarantor_relationship');
 
-    for ( my $i = 0 ; $i < scalar @new_guarantor_id; $i++ ) {
+    for ( my $i = 0 ; $i < scalar @new_guarantor_id ; $i++ ) {
         my $guarantor_id = $new_guarantor_id[$i];
         my $relationship = $new_guarantor_relationship[$i];
 

From 0b824af4ed1dd271341d076e089b578884f504fe Mon Sep 17 00:00:00 2001
From: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Date: Wed, 9 Oct 2024 09:39:07 +0200
Subject: [PATCH 209/429] Bug 37528: (follow-up) Add help strings for blank
 borrowerrelationship

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit dd1bef0ea82f534acb83a70262a8cbdd29cd98e9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/admin/preferences/patrons.pref              | 2 +-
 .../intranet-tmpl/prog/en/modules/members/memberentrygen.tt     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
index 21a1b37505..cd598a1862 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
@@ -338,7 +338,7 @@ Patrons:
          - "Guarantors can be the following of those they guarantee:"
          - pref: borrowerRelationship
            class: multi
-         - (input multiple choices separated by |).
+         - (input multiple choices separated by |, leave a | at the end to allow to librarians to leave blank).
      -
          - "When adding a guarantee to a guarantor, whether it's from the guarantor's form or the guarantee's form, fill the following fields in the guarantee's member entry form from the guarantors record:"
          - pref: PrefillGuaranteeField
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index dde1efaf4d..102ce7abbb 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -218,7 +218,7 @@ legend.collapsed i.fa.fa-caret-down::before {
                                         <li id="ERROR_cannot_delete_guarantor">Cannot delete guarantor(s). A child patron needs a guarantor.</li>
                                     [% END %]
                                     [% IF ( ERROR_invalid_relationship ) %]
-                                        <li id="ERROR_invalid_relationship">Guarantor relationship is invalid.</li>
+                                        <li id="ERROR_invalid_relationship">Administrators do not allow to leave guarantor relationship blank</li>
                                     [% END %]
                                 </ul>
                             </div>

From 89507ca8d5ceda80ba417ba988189ca5521e77c7 Mon Sep 17 00:00:00 2001
From: Katrin Fischer <katrin.fischer@bsz-bw.de>
Date: Fri, 18 Oct 2024 11:41:01 +0000
Subject: [PATCH 210/429] Bug 37528: (QA follow-up) Slight rephrase of system
 preference and warning message

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 55c6df46b7123ab8bb3c06526df69b90a2f73cf4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/admin/preferences/patrons.pref              | 2 +-
 .../intranet-tmpl/prog/en/modules/members/memberentrygen.tt     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
index cd598a1862..1efef1c696 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
@@ -338,7 +338,7 @@ Patrons:
          - "Guarantors can be the following of those they guarantee:"
          - pref: borrowerRelationship
            class: multi
-         - (input multiple choices separated by |, leave a | at the end to allow to librarians to leave blank).
+         - (input multiple choices separated by |, leave a | at the end to allow for librarians to leave it blank).
      -
          - "When adding a guarantee to a guarantor, whether it's from the guarantor's form or the guarantee's form, fill the following fields in the guarantee's member entry form from the guarantors record:"
          - pref: PrefillGuaranteeField
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index 102ce7abbb..b310d2490d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -218,7 +218,7 @@ legend.collapsed i.fa.fa-caret-down::before {
                                         <li id="ERROR_cannot_delete_guarantor">Cannot delete guarantor(s). A child patron needs a guarantor.</li>
                                     [% END %]
                                     [% IF ( ERROR_invalid_relationship ) %]
-                                        <li id="ERROR_invalid_relationship">Administrators do not allow to leave guarantor relationship blank</li>
+                                        <li id="ERROR_invalid_relationship">Guarantor relationship cannot be left blank according to configuration.</li>
                                     [% END %]
                                 </ul>
                             </div>

From 9cc99b3eaf2e6ae993c6fe38b361d70be9231f05 Mon Sep 17 00:00:00 2001
From: Baptiste Wojtkowski <bski@laposte.net>
Date: Fri, 9 Aug 2024 16:33:37 +0200
Subject: [PATCH 211/429] Bug 37606: Fix framework export module to escape
 double quotes

When exporting a framework as csv, the exporter does not check the presence of double quote in the fields. Hence, if there is one double quote, the csv is broken.

TEST PLAN:
1 - Change a framework to add a field containing double quote in name
2 - Export it in csv
3 - Create a new framework
4 - Import the csv in the new framework -> every fields after the one
containing double quotes should be broke. Every other fields should have
no subfield
5 - APPLY PATCH
6 - Repeat 2-5 -> everything should be correctly exported

Signed-off-by: Sukhmandeep Benipal <sukhmandeep.benipal@inLibro.com>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5f45818cf795d5456b0498269b33ed7d09e59dd7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/ImportExportFramework.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/C4/ImportExportFramework.pm b/C4/ImportExportFramework.pm
index d02ec43d57..ad626b679f 100644
--- a/C4/ImportExportFramework.pm
+++ b/C4/ImportExportFramework.pm
@@ -291,6 +291,7 @@ sub _export_table_csv
             for my $field (@fields) {
                 my $value = $hashRef->{$field} // q||;
                 $value =~ s/[\r\n]//g;
+                $value =~ s/"/""/g;
                 $$strCSV .= '"' . $value . '",';
             }
             chop $$strCSV;

From 26cbf2492d1d7d5d991b34e8dcb113ca68b0c717 Mon Sep 17 00:00:00 2001
From: Bo Gustavsson <bosse@gustavsson.one>
Date: Wed, 4 Sep 2024 21:49:47 +0200
Subject: [PATCH 212/429] Bug 37836: Prevent submitting empty barcodes in Self
 check-in

This patch disbles the "Submit" button when the barcode field is empty.

To test this patch:

Add the patch to your koha clone
Enable the "SelfCheckInModule".
Open the page and the "Submit" button should be disabled when the barcode field is empty.

Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 07d212c02413263696bcd7b3e3468d0879b26a74)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../bootstrap/en/modules/sci/sci-main.tt      | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt
index 2d941ba168..9509ead702 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt
@@ -298,6 +298,33 @@
                 }
             }
         }
+
+
+        function checkBarcodeInput() {
+            var inputField = document.getElementById("barcode_input");
+            var button = document.getElementById("sci_append_button");
+
+            if (inputField.value.trim() === "") {
+                button.disabled = true;
+            } else {
+                button.disabled = false;
+            }
+        }
+
+checkBarcodeInput();
+
+document.getElementById("barcode_input").addEventListener("input", checkBarcodeInput);
+
+document.getElementById("sci_append_button").addEventListener("click", function() {
+    setTimeout(checkBarcodeInput, 10);
+});
+
+
+
+
+
+
+
     </script>
 
     [% IF ( Koha.Preference('SelfCheckInUserJS') ) %]

From c2284dbaf993705e4b6286593761078c84ac969a Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 4 Sep 2024 14:20:33 +0200
Subject: [PATCH 213/429] Bug 37905: Use correct RFC3339 formatted date to the
 server

This patch suggests to revert the previous attempt to fix this problem.
The date was using the client-side's tz and so the "last hour" was not
matching the one from the server.

With bug 37831 with simply need to pass a rfc3339-formatted date and the
filtering will be done as expected.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit caca6791ced4a3ddfcead57d90e5671fcee22107)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/background_jobs.tt  | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
index e23f04ba41..bbc778e66f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
@@ -261,12 +261,10 @@
 
             let additional_filters = {
                 enqueued_on: function(){
-                    let now = new Date();
                     if ( $("#include_last_hour").is(":checked") ) {
-                        now.setHours(now.getHours() - 1);
-                        return { ">": now.toISOString() };
+                        return { ">": dayjs().subtract(1, 'hour').format() };
                     } else {
-                        return { "<": now.toISOString() };
+                        return { "<": dayjs().format()};
                     }
                 }
             };

From 5d390464da98328a1e2aa47275fa2857cc39bd4f Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 4 Sep 2024 14:24:45 +0200
Subject: [PATCH 214/429] Bug 37905: Fix parameter name

It's named enqueued_date on the API side, not enqueued_on (which is the DB
column's name)

No behaviour change expected here.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit fac259d2b91e600c64136c40afca5cc5863feba2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/background_jobs.tt      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
index bbc778e66f..b85793db50 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
@@ -260,7 +260,7 @@
             [% END %]
 
             let additional_filters = {
-                enqueued_on: function(){
+                enqueued_date: function(){
                     if ( $("#include_last_hour").is(":checked") ) {
                         return { ">": dayjs().subtract(1, 'hour').format() };
                     } else {

From 40f8e3fb8c49a82507b8e9f2dbc1dceab64ca787 Mon Sep 17 00:00:00 2001
From: Andrew Fuerste Henry <andrewfh@dubcolib.org>
Date: Fri, 4 Oct 2024 15:11:49 +0000
Subject: [PATCH 215/429] Bug 38012: Remove ispermanent from returns and
 branchtransfers

To test:
There should be no change in behavior, as this code is not referred to elsewhere in Koha. Make sure you can still do checkeckins and transfers.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6760de2d880f0c468250f989c20e9b337a7aa941)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/circ/branchtransfers.tt      | 3 ---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt        | 3 ---
 2 files changed, 6 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/branchtransfers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/branchtransfers.tt
index 7ce124d600..e2f5902e3b 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/branchtransfers.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/branchtransfers.tt
@@ -191,9 +191,6 @@
                                         [% IF ( errmsgloo.errbadcode ) %]
                                             <li>No item with barcode: [% errmsgloo.msg | html %]</li>
                                         [% END %]
-                                        [% IF ( errmsgloo.errispermanent ) %]
-                                            <li>Please return item to home library: [% Branches.GetName( errmsgloo.msg ) | html %]</li>
-                                        [% END %]
                                         [% IF ( errmsgloo.errnotallowed ) %]
                                             <li>Transfer is not allowed for:
                                                 <ol>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
index 4b88809d4a..7480e69386 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
@@ -326,9 +326,6 @@
                                         [% IF ( errmsgloo.badbarcode ) %]
                                             <p class="problem ret_badbarcode">No item with barcode: [% errmsgloo.msg | html %]</p>
                                         [% END %]
-                                        [% IF ( errmsgloo.ispermanent ) %]
-                                            <p class="problem ret_ispermenant">Please return item to: [% Branches.GetName( errmsgloo.msg ) | html %]</p>
-                                        [% END %]
                                         [% IF ( errmsgloo.notissued ) %]
                                             <p class="problem ret_notissued">Not checked out.</p>
                                         [% END %]

From 6bd354f74ddfbbf310666776ce7207f5a7cb67b8 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 29 Aug 2024 21:54:57 +0000
Subject: [PATCH 216/429] Bug 37505: Remove onloan check when checking out to
 stats patron

To test:
1.  Create a Statistical Patron
2.  Set BlockReturnOfLostItems to Block.
3..  Mark an item as lost
4. Check it out to the Statistical Patron. You should see the message "Item was lost, cannot be returned."

5. Set BlockReturnOfWithdrawnItems to Block.
6.  Mark an item as withdrawn
7. Check it out to the Statistical Patron. You should see the message "Item was withdrawn, cannot be returned."
8. Turn off BlockReturnOfWithdrawnItems, check out the same item to the Statistical Patron. You should see a message "Item was withdrawn."

9. Place an item on hold.
10. Check it out to the Statistical Patron
11. See the message "Item on hold, please checkin."

12. Make sure  ClaimReturnedLostValue is set to a lost value
13. Claim a return, making sure it is not still checked out to the patron
14. Checkit it out to the Statistical Patron.
15. See the message "Item claimed returned, please checkin."

16. Have an item on a regular patron account that has been recalled.
17. Checkit it out to the Statistical Patron.
18. See the message "Item can fill a recall, please checkin."

Signed-off-by: Kelly <kelly@bywatersolutions.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5ee142c7f7b4c7a1071cfd92a410aa11b59d2bee)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/circulation.pl | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/circ/circulation.pl b/circ/circulation.pl
index d1c2b3b0cf..f44005593d 100755
--- a/circ/circulation.pl
+++ b/circ/circulation.pl
@@ -364,19 +364,16 @@ if (@$barcodes && $op eq 'cud-checkout') {
     }
 
     if ( $issuingimpossible->{'STATS'} ) {
-        $template->param( STATS => 1 );
+        my ( $stats_return, $stats_messages, $stats_iteminformation, $stats_borrower ) =
+            AddReturn( $item->barcode, C4::Context->userenv->{'branch'}, undef, undef, 1 );
 
-        if ( $item->onloan ) {
-            my ( $stats_return, $stats_messages, $stats_iteminformation, $stats_borrower ) =
-                AddReturn( $item->barcode, C4::Context->userenv->{'branch'}, undef, undef, 1 );
-
-            $template->param(
-                CHECKEDIN => $stats_return,
-                MESSAGES  => $stats_messages,
-                ITEM      => $stats_iteminformation,
-                BORROWER  => $stats_borrower,
-            );
-        }
+        $template->param(
+            STATS     => 1,
+            CHECKEDIN => $stats_return,
+            MESSAGES  => $stats_messages,
+            ITEM      => $stats_iteminformation,
+            BORROWER  => $stats_borrower,
+        );
 
         #increment items.localuse
         my $localuse_count = $item->localuse;

From 5166fb9d3cef597da9468533b1562ca83db9ea55 Mon Sep 17 00:00:00 2001
From: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Date: Mon, 21 Oct 2024 08:52:21 -0400
Subject: [PATCH 217/429] Bug 37505: (QA follow-up) Don't log return stat if
 ignore_localuse and no checkout

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 42ba50e1eb8c5fc9572514d9bbc32689a3302c6f)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Circulation.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/C4/Circulation.pm b/C4/Circulation.pm
index 2bebdc0de4..5844172556 100644
--- a/C4/Circulation.pm
+++ b/C4/Circulation.pm
@@ -2460,7 +2460,7 @@ sub AddReturn {
         ccode          => $item->ccode,
         categorycode   => $categorycode,
         interface      => C4::Context->interface,
-    }) unless ( $skip_localuse && $stat_type eq 'localuse' );
+    }) unless ( $skip_localuse && !$issue );
 
     # Send a check-in slip. # NOTE: borrower may be undef. Do not try to send messages then.
     if ( $patron ) {

From 3060f2613175e24a4ba63fc8e0f8a0f354d1dda0 Mon Sep 17 00:00:00 2001
From: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Date: Mon, 21 Oct 2024 09:32:11 -0400
Subject: [PATCH 218/429] Bug 37505: (QA follow-up) Adjust tests

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit c4ac5eb8e8793d865cc1367a58e4a3d78de78bc4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Circulation.t | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/t/db_dependent/Circulation.t b/t/db_dependent/Circulation.t
index a893d4dcc6..0e58c946c3 100755
--- a/t/db_dependent/Circulation.t
+++ b/t/db_dependent/Circulation.t
@@ -2600,8 +2600,8 @@ subtest 'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
     is( $error->{USERBLOCKEDNOENDDATE},    '9999-12-31', 'USERBLOCKEDNOENDDATE should be 9999-12-31 for unlimited debarments' );
 };
 
-subtest 'CanBookBeIssued + Statistic patrons "X"' => sub {
-    plan tests => 13;
+subtest 'Statistic patrons "X"' => sub {
+    plan tests => 15;
 
     my $library           = $builder->build_object( { class => 'Koha::Libraries' } );
     my $patron_category_x = $builder->build_object(
@@ -2660,6 +2660,13 @@ subtest 'CanBookBeIssued + Statistic patrons "X"' => sub {
         'Single entry recorded in the stats table'
     );
 
+    AddReturn( $item_3->barcode, C4::Context->userenv->{'branch'}, undef, undef, 1 );
+    $item_3->discard_changes;
+    is(
+        Koha::Statistics->search( { itemnumber => $item_3->itemnumber } )->count, 1,
+        'Stats skipped in AddReturn when there was no checkout and skip_localuse was passed'
+    );
+
     my $item_4 = $builder->build_sample_item( { library => $library->branchcode } );
     AddIssue( $patron_2, $item_4->barcode );
     $item_4->discard_changes;
@@ -2671,6 +2678,13 @@ subtest 'CanBookBeIssued + Statistic patrons "X"' => sub {
     $item_4->discard_changes;
     is(
         Koha::Statistics->search( { itemnumber => $item_4->itemnumber } )->count, 2,
+        'Issue and localuse should be recorded in statistics table for item 4.'
+    );
+
+    AddReturn( $item_4->barcode, C4::Context->userenv->{'branch'}, undef, undef, 1 );
+    $item_4->discard_changes;
+    is(
+        Koha::Statistics->search( { itemnumber => $item_4->itemnumber } )->count, 3,
         'Issue, return, and localuse should be recorded in statistics table for item 4.'
     );
 

From ac1172bf6a15c6498a703c82f166ecc2c9e1f047 Mon Sep 17 00:00:00 2001
From: Hammat Wele <hammat.wele@inlibro.com>
Date: Thu, 14 Dec 2023 13:59:42 +0000
Subject: [PATCH 219/429] Bug 35508: Update borrowers.updated_on when modifying
 a patron's attribute

Currently, if you have patron attributes and modify the values in a patron's account, the patron's updated_on date is not updated. This patch makes the "Updated on" change when a patron attribute is updated.

To test:

1. Create a patron attribute type
   1.1. Go to Administration > Patron attribute types
   1.2. Click New patron attribute
   1.3. Fill out the code and description
   1.4. Click Save

2. Edit a patron (normal)
   2.1. Go to Patrons and find a patron account
   2.2. Click Edit
   2.3. Change a regular field (e.g. Middle name)
   2.4. Click Save

--> Notice the "Updated on" date in the left column has been updated to now

3. Edit a patron attribute
   3.1. In another patron account*, click Edit
   3.2. Change the value of an attribute
   3.3. Click Save

--> Notice the "Updated on" date did not change

4. Apply the patch
   4.1 Repeat step 3.1, 3.2, 3.3

--> Notice the "Updated on" date has now changed

Signed-off-by: Esther <esther@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ac99bc6db2bfcb278d7ec6b07b2032dd12398603)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 members/memberentry.pl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/members/memberentry.pl b/members/memberentry.pl
index 98f985d41e..941dc34c82 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -591,6 +591,7 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){
         if (C4::Context->preference('ExtendedPatronAttributes') and $input->param('setting_extended_patron_attributes')) {
             $patron->extended_attributes->filter_by_branch_limitations->delete;
             $patron->extended_attributes($extended_patron_attributes);
+            $patron->set( { updated_on => "" } )->store;
         }
 
         if ( $destination eq 'circ' and not C4::Auth::haspermission( C4::Context->userenv->{id}, { circulate => 'circulate_remaining_permissions' } ) ) {

From 2d340da8f805666d68bbf29ea8f43854415a7ee2 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Wed, 17 Apr 2024 09:29:29 -0400
Subject: [PATCH 220/429] Bug 35508: Move logic to
 Koha::Patron::Attribute::store()

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4624425bf800cba280a0afc69c2c9e97a7dbda62)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Patron/Attribute.pm               |  4 +++
 members/memberentry.pl                 |  1 -
 t/db_dependent/Koha/Patron/Attribute.t | 41 +++++++++++++++++++++++++-
 3 files changed, 44 insertions(+), 2 deletions(-)

diff --git a/Koha/Patron/Attribute.pm b/Koha/Patron/Attribute.pm
index 5fc1b9111d..863d55850b 100644
--- a/Koha/Patron/Attribute.pm
+++ b/Koha/Patron/Attribute.pm
@@ -58,6 +58,10 @@ sub store {
 
     Koha::Exceptions::Patron::Attribute::InvalidAttributeValue->throw( attribute => $self )
         unless $self->value_ok();
+    C4::Context->dbh->do(
+        "UPDATE borrowers SET updated_on = NOW() WHERE borrowernumber = ?", undef,
+        $self->borrowernumber
+    );
 
     return $self->SUPER::store();
 }
diff --git a/members/memberentry.pl b/members/memberentry.pl
index 941dc34c82..98f985d41e 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -591,7 +591,6 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){
         if (C4::Context->preference('ExtendedPatronAttributes') and $input->param('setting_extended_patron_attributes')) {
             $patron->extended_attributes->filter_by_branch_limitations->delete;
             $patron->extended_attributes($extended_patron_attributes);
-            $patron->set( { updated_on => "" } )->store;
         }
 
         if ( $destination eq 'circ' and not C4::Auth::haspermission( C4::Context->userenv->{id}, { circulate => 'circulate_remaining_permissions' } ) ) {
diff --git a/t/db_dependent/Koha/Patron/Attribute.t b/t/db_dependent/Koha/Patron/Attribute.t
index f68692e57b..e9e9f9c7fb 100755
--- a/t/db_dependent/Koha/Patron/Attribute.t
+++ b/t/db_dependent/Koha/Patron/Attribute.t
@@ -33,7 +33,46 @@ my $builder = t::lib::TestBuilder->new;
 
 subtest 'store() tests' => sub {
 
-    plan tests => 5;
+    plan tests => 6;
+
+    subtest 'Update an attribute should update the patron "updated_on" field' => sub {
+
+        plan tests => 1;
+
+        $schema->storage->txn_begin;
+
+        my $patron     = $builder->build_object( { class => 'Koha::Patrons' } );
+        my $updated_on = $patron->updated_on;
+
+        sleep 1;    # Timestamps are in one second increments, so we need to wait one second
+
+        my $type = $builder->build_object(
+            {
+                class => 'Koha::Patron::Attribute::Types',
+                value => {
+                    mandatory     => 0,
+                    repeatable    => 0,
+                    unique_id     => 0,
+                    category_code => undef
+                }
+            }
+        );
+
+        my $attr = $patron->add_extended_attribute(
+            {
+                code      => $type->code,
+                attribute => 'TEST'
+            }
+        );
+
+        $attr->set( { attribute => 'TEST' } )->store();
+
+        $patron->discard_changes;
+
+        isnt( $updated_on, $patron->updated_on, "Updated on was updated by attribute storage" );
+
+        $schema->storage->txn_rollback;
+    };
 
     subtest 'repeatable attributes tests' => sub {
 

From e5ed5e05f9b24a9bc0e2712048cfd67709ae666f Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Tue, 28 May 2024 13:26:03 -0400
Subject: [PATCH 221/429] Bug 35508: Don't update patron attributes if new list
 of attributes matches list of existing attributes

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit eed4b0940dd3beaa26f63d3e8d936f1efff9a9e9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Patron/Attribute.pm |  1 +
 members/memberentry.pl   | 44 +++++++++++++++++++++++++++++++++++++---
 2 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/Koha/Patron/Attribute.pm b/Koha/Patron/Attribute.pm
index 863d55850b..93b25ece02 100644
--- a/Koha/Patron/Attribute.pm
+++ b/Koha/Patron/Attribute.pm
@@ -58,6 +58,7 @@ sub store {
 
     Koha::Exceptions::Patron::Attribute::InvalidAttributeValue->throw( attribute => $self )
         unless $self->value_ok();
+
     C4::Context->dbh->do(
         "UPDATE borrowers SET updated_on = NOW() WHERE borrowernumber = ?", undef,
         $self->borrowernumber
diff --git a/members/memberentry.pl b/members/memberentry.pl
index 98f985d41e..d40ee6885c 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -589,9 +589,47 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){
 
     if ( $success ) {
         if (C4::Context->preference('ExtendedPatronAttributes') and $input->param('setting_extended_patron_attributes')) {
-            $patron->extended_attributes->filter_by_branch_limitations->delete;
-            $patron->extended_attributes($extended_patron_attributes);
-        }
+            my $existing_attributes = $patron->extended_attributes->filter_by_branch_limitations->unblessed;
+
+            my $needs_update = 1;
+
+            # If there are an unqueunal number of new and old patron attributes they definietely need updated
+            if ( scalar @{$existing_attributes} == scalar @{$extended_patron_attributes} ) {
+                my $seen = 0;
+                for ( my $i = 0 ; $i <= scalar @{$extended_patron_attributes} ; $i++ ) {
+                    my $new_attr = $extended_patron_attributes->[$i];
+                    next unless $new_attr;
+                    for ( my $j = 0 ; $j <= scalar @{$existing_attributes} ; $j++ ) {
+                        my $existing_attr = $existing_attributes->[$j];
+                        next unless $existing_attr;
+
+                        if (   $new_attr->{attribute} eq $existing_attr->{attribute}
+                            && $new_attr->{borrowernumber} eq $existing_attr->{borrowernumber}
+                            && $new_attr->{code} eq $existing_attr->{code} )
+                        {
+                            $seen++;
+
+                            # Remove the match from the "old" attribute
+                            splice( @{$existing_attributes}, $j, 1 );
+
+                            # Move on to look at the next "new" attribute
+                            last;
+                        }
+                    }
+                }
+
+                # If we found a match for each existing attribute and the number of see attributes matches the number seen
+                # we don't need to update the attributes
+                if ( scalar @{$existing_attributes} == 0 && $seen == @{$extended_patron_attributes} ) {
+                    $needs_update = 0;
+                }
+            }
+
+                if ($needs_update) {
+                    $patron->extended_attributes->filter_by_branch_limitations->delete;
+                    $patron->extended_attributes($extended_patron_attributes);
+                }
+            }
 
         if ( $destination eq 'circ' and not C4::Auth::haspermission( C4::Context->userenv->{id}, { circulate => 'circulate_remaining_permissions' } ) ) {
             # If we want to redirect to circulation.pl and need to check if the logged in user has the necessary permission

From cf0a222fb2173c8184fb01400d3bd2f6f1c7a777 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Fri, 18 Oct 2024 10:31:32 +0000
Subject: [PATCH 222/429] Bug 35508: (QA follow-up) Fix qa script issues

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 056abc53217e081d36119b2efb2e1f96de4a69f5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 members/memberentry.pl | 34 ++++++++++++++++++++--------------
 1 file changed, 20 insertions(+), 14 deletions(-)

diff --git a/members/memberentry.pl b/members/memberentry.pl
index d40ee6885c..ed6df4b133 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -587,13 +587,15 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){
         }
     }
 
-    if ( $success ) {
-        if (C4::Context->preference('ExtendedPatronAttributes') and $input->param('setting_extended_patron_attributes')) {
+    if ($success) {
+        if ( C4::Context->preference('ExtendedPatronAttributes')
+            and $input->param('setting_extended_patron_attributes') )
+        {
             my $existing_attributes = $patron->extended_attributes->filter_by_branch_limitations->unblessed;
 
             my $needs_update = 1;
 
-            # If there are an unqueunal number of new and old patron attributes they definietely need updated
+            # If there are an unqueunal number of new and old patron attributes they definitely need updated
             if ( scalar @{$existing_attributes} == scalar @{$extended_patron_attributes} ) {
                 my $seen = 0;
                 for ( my $i = 0 ; $i <= scalar @{$extended_patron_attributes} ; $i++ ) {
@@ -625,23 +627,27 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){
                 }
             }
 
-                if ($needs_update) {
-                    $patron->extended_attributes->filter_by_branch_limitations->delete;
-                    $patron->extended_attributes($extended_patron_attributes);
-                }
+            if ($needs_update) {
+                $patron->extended_attributes->filter_by_branch_limitations->delete;
+                $patron->extended_attributes($extended_patron_attributes);
             }
+        }
 
-        if ( $destination eq 'circ' and not C4::Auth::haspermission( C4::Context->userenv->{id}, { circulate => 'circulate_remaining_permissions' } ) ) {
+        if (
+            $destination eq 'circ'
+            and not C4::Auth::haspermission(
+                C4::Context->userenv->{id},
+                { circulate => 'circulate_remaining_permissions' }
+            )
+            )
+        {
             # If we want to redirect to circulation.pl and need to check if the logged in user has the necessary permission
             $destination = 'not_circ';
         }
         print scalar( $destination eq "circ" )
-          ? $input->redirect(
-            "/cgi-bin/koha/circ/circulation.pl?borrowernumber=$borrowernumber")
-          : $input->redirect(
-            "/cgi-bin/koha/members/moremember.pl?borrowernumber=$borrowernumber"
-          );
-        exit; # You can only send 1 redirect!  After that, content or other headers don't matter.
+            ? $input->redirect("/cgi-bin/koha/circ/circulation.pl?borrowernumber=$borrowernumber")
+            : $input->redirect("/cgi-bin/koha/members/moremember.pl?borrowernumber=$borrowernumber");
+        exit;    # You can only send 1 redirect!  After that, content or other headers don't matter.
     }
 }
 

From 67bba93fd128999739f865a99b444a8ac35c3ff8 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 9 Oct 2024 09:18:07 +0200
Subject: [PATCH 223/429] Bug 38130: Add the ability to filter item by library
 name

On the item list of the bibliographic record page you actually can filter
items by library, either using the dropdown list on top of the column
or using the library_id (branchcode) in the global search.

But you cannot search using the library's name in the global search.

This patch add yet other embeds to allow this.

I am not sure this is needed, we won't be able to do the same for item
type and AV, so maybe it is not worth 2 additional JOINS.

Maybe we should tell people you should use the column filters when a
dropdown list is there, instead of: "it works, sometimes".

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4212da818025754ab6ff989c70fbde3616df7c81)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../includes/html_helpers/tables/items/catalogue_detail.inc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index 9f9189c590..4f2f2d4f2f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -228,7 +228,7 @@
     [%# In case or SeparateHoldings we may need to display the number of biblios in each tab %]
     [%# Do we need separate/new endpoints or do we hack the somewhere client-side? %]
     let item_table_url = "/api/v1/biblios/[% biblio.biblionumber | uri %]/items?";
-    let embed = ["+strings,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"];
+    let embed = ["+strings,home_library,holding_library,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"];
     [% IF Koha.Preference('LocalCoverImages') %]
         embed.push('cover_image_ids');
     [% END %]
@@ -376,7 +376,7 @@
             },
             [% END %]
             {
-                data: "me.holding_library_id",
+                data: "me.holding_library_id:holding_library.name",
                 className: "location",
                 searchable: true,
                 orderable: true,
@@ -385,7 +385,7 @@
                 }
             },
             {
-                data: "me.home_library_id",
+                data: "me.home_library_id:home_library.name",
                 className: "homebranch",
                 searchable: true,
                 orderable: true,

From db6915c0090e82ae5dc858cf1ff588dc9c16497a Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Thu, 7 Nov 2024 09:40:03 +0100
Subject: [PATCH 224/429] Bug 38130: Allow sorting and filtering on library id
 and name

On the item list of the bib detail page and the patron search we want
the library columns ('holding library', 'home library' and 'library') to
be sorted by library names and to make them searchable on library id and
name.

Concerns about this approach (comment #8):
"""
If we do it for the library names on this view, we need to do it for all the library columns (thinking about patrons right now but I am sure there are others). And to be consistent, for all the other coded values: "special AVs" (item types, cn source), regular AVs, patron category codes, etc.

We will introduce additional JOINs and so maybe performance problems. This needs to be investigated before we take this route IMO.
"""

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1f5e3b66f8a69b10cca12e0a1f8b0bc02f01431e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Patron.pm                                           | 2 +-
 Koha/Schema/Result/Borrower.pm                           | 7 +++++++
 .../html_helpers/tables/items/catalogue_detail.inc       | 4 ++--
 .../intranet-tmpl/prog/en/includes/patron-search.inc     | 9 ++++-----
 4 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/Koha/Patron.pm b/Koha/Patron.pm
index cbce7c3f42..0483b88e16 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -499,7 +499,7 @@ Returns a Koha::Library object representing the patron's home library.
 
 sub library {
     my ( $self ) = @_;
-    return Koha::Library->_new_from_dbic($self->_result->branchcode);
+    return Koha::Library->_new_from_dbic($self->_result->library);
 }
 
 =head3 sms_provider
diff --git a/Koha/Schema/Result/Borrower.pm b/Koha/Schema/Result/Borrower.pm
index bf8ba36030..fddaf77201 100644
--- a/Koha/Schema/Result/Borrower.pm
+++ b/Koha/Schema/Result/Borrower.pm
@@ -2191,6 +2191,13 @@ __PACKAGE__->many_to_many("permissions", "user_permissions", "permission");
 # Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-05-02 11:36:36
 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jUOMoSonNmA3KwtZKVxqOA
 
+__PACKAGE__->belongs_to(
+  "library",
+  "Koha::Schema::Result::Branch",
+  { branchcode => "branchcode" },
+  { is_deferrable => 1, on_delete => "RESTRICT", on_update => "RESTRICT" },
+);
+
 __PACKAGE__->has_many(
   "restrictions",
   "Koha::Schema::Result::BorrowerDebarment",
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index 4f2f2d4f2f..626886ab59 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -376,7 +376,7 @@
             },
             [% END %]
             {
-                data: "me.holding_library_id:holding_library.name",
+                data: "holding_library.name:me.holding_library_id",
                 className: "location",
                 searchable: true,
                 orderable: true,
@@ -385,7 +385,7 @@
                 }
             },
             {
-                data: "me.home_library_id:home_library.name",
+                data: "home_library.name:me.home_library_id",
                 className: "homebranch",
                 searchable: true,
                 orderable: true,
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
index 095d129671..5f73e30f40 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
@@ -532,15 +532,14 @@
                             }
                             [% CASE 'branch' %]
                             {
-                                "data": "library_id",
+                                "data": "library.name:me.library_id",
                                 "searchable": true,
                                 "orderable": true,
                                 "render": function( data, type, row, meta ) {
-                                    let library_name = libraries_map[data].branchname
-                                    if( !singleBranchMode && data == logged_in_library_id ) {
-                                        return "<span class=\"currentlibrary\">" + escape_str(library_name) + "</span>";
+                                    if( !singleBranchMode && row.library.library_id == logged_in_library_id ) {
+                                        return "<span class=\"currentlibrary\">" + escape_str(row.library.name) + "</span>";
                                     } else {
-                                        return escape_str(library_name);
+                                        return escape_str(row.library.name);
                                     }
                                 }
                             }

From 6f02fffe11da104b7f65e86fbb0e88bb0b578ebc Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 8 Nov 2024 16:53:23 -0800
Subject: [PATCH 225/429] Bug 38413: Batch operations from item search need to
 POST to not exceed URL length limits

Batch item modification and deletion accepts either op="show" or op="cud-show"
because sometimes you just have one item and it's saner to GET with one, but
sometimes you've selected 961 items from itemsearch and you need to POST to
not get hit by Apache's "too long" error.

Bug 36630 switched item search from using POST to GET because item search is
confusing. It uses a form with a dummy op and then empties out the form and
adds back contents, and the CSRF protection changed the dummy op to "cud-show"
so it could POST, but didn't notice that the op would be replaced by one that
was still "show." The form needs to go back to POST, the dummy contents need a
comment saying they are just dummies so nobody else gets fooled, and the JS
that replaces the form contents needs to insert a CSRF token and the correct op.

Test plan:
 1. Without the patch, Search - Item search, search with no criteria so you
    get hundreds of items
 2. Change to Show All entries, then Select visible rows
 3. Batch operations - Batch item modification
 4. Apache should give you an error about your overly long URL
 5. Apply patch and repeat steps 1-3
 6. After a long pause while Batch item modification swallows the POST, you
    should get the batch modification form with all your items.

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b2ea96e3512e797013e5667a056f0313829c35c2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/catalogue/itemsearch.tt         | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt
index 9d21f5b2c8..660c7064d2 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt
@@ -279,8 +279,10 @@
         </div>
       </div>
 
-        <form method="GET" action="/cgi-bin/koha/tools/batchMod.pl" id="batch_item_operations">
-            <input type="hidden" name="op" value="show" />
+        <form method="POST" action="/cgi-bin/koha/tools/batchMod.pl" id="batch_item_operations">
+            <!-- dummy values which will be replaced by JavaScript -->
+            [% INCLUDE 'csrf-token.inc' %]
+            <input type="hidden" name="op" value="cud-show" />
             <input type="hidden" id="batch_mod_del" name="del" value="0" />
         </form>
 
@@ -694,7 +696,12 @@
                 batch_mod_form.append(
                     $("<input>").attr("type","hidden")
                     .attr("name", "op")
-                    .val("show")
+                    .val("cud-show")
+                );
+                batch_mod_form.append(
+                    $("<input>").attr("type","hidden")
+                    .attr("name", "csrf_token")
+                    .val($('meta[name="csrf-token"]').attr('content'))
                 );
                 batch_mod_form.append(
                     $("<input>").attr("type","hidden")

From 93d5737493c16fda6765e68e98bf530721bed574 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 20 Sep 2024 14:43:35 -0700
Subject: [PATCH 226/429] Bug 37977: Fix some issues with labels in inventory
 form

The inventory form has several issues with labels with a for attribute that
doesn't match the id of the input, so the label doesn't actually get linked
to anything.

Test plan:
 1. Without the patch, go to Cataloging - Inventory
 2. In the section for "Item location filters" click the label for "Shelving
    location (items.location) is", which will focus the select menu, then
    press the down arrow on the keyboard to see that it's focused by opening
    the select menu.
 3. Click in a blank spot to close the select menu, then click the label for
    "Collection" and press the down arrow key, which will scroll the page
    since the label didn't focus the menu, and repeat with "Call number
    classification scheme" which also will scroll the page when you press
    down arrow.
 4. In the section for "Optional filters for inventory list or comparing
    barcodes" click any of the labels below items.notforloan - clicking a
    checkbox label should toggle whether the checkbox is checked, but it
    will not
 5. Apply patch, refresh page
 6. Repeat steps 3 and 4, but this time you will get the proper behavior,
    focusing the select menus and toggling the checkboxes

Sponsored-by: Chetco Community Public Library
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit fa5b7e8260d718e7a167dfe541862ffbf4acef21)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
index e134c1291c..96684a0e90 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
@@ -138,7 +138,7 @@
                             [% SET ccodes = AuthorisedValues.GetAuthValueDropbox('CCODE') %]
                             [% IF ( ccodes.count > 0 ) %]
                                 <li>
-                                    <label for="ccode">Collection code: </label>
+                                    <label for="ccodeloop">Collection: </label>
                                     <select id="ccodeloop" name="ccode">
                                         <option value="">Filter collection</option>
                                         [% FOREACH cc IN ccodes %]
@@ -161,7 +161,7 @@
                                 <input type="text" name="maxlocation" id="maxlocation" value="[% maxlocation | html %]" />
                                 <li>
                                     <label for="class_source">Call number classification scheme</label>
-                                    <select name="class_source">
+                                    <select name="class_source" id="class_source">
                                         [% FOREACH class_source IN class_sources %]
                                             [% IF class_source.cn_source == pref_class %]
                                                 <option value="[% class_source.cn_source | html %]" selected="selected">[% class_source.description | html %] (default)</option>
@@ -195,7 +195,7 @@
                                                                 [% value.lib | html %]
                                                             [% END %]
                                                         </label>
-                                                        <input type="checkbox" name="status-[% status.fieldname | html %]-[% value.authorised_value | html %]" id="[% value.authorised_value | html %]" />
+                                                        <input type="checkbox" name="status-[% status.fieldname | html %]-[% value.authorised_value | html %]" id="[% value.id | html %]" />
                                                     </li>
                                                 [% END %]
                                             [% END %]

From 76c1387263518ff99b4286c2a45593e3604e4002 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Mon, 5 Aug 2024 12:48:04 +0000
Subject: [PATCH 227/429] Bug 35466: Add skip_indexing parameter to
 bulkmarcimport.pl

This patch adds a new optoin to skip indexing to bulkmarcimport.
It also fixes a bug where authorities were being indexed multiple times
during import.

To test:
 1 - Apply patch
 2 - Download the sample files on this bug
 3 - Perform asearch engine search that should retrieve all records( 'a' in Zebra, '*' in ES)
 4 - Note the total (435 in KTD)
 5 - perl misc/migration_tools/bulkmarcimport.pl -b -v --file=bug_35466_b.mrc
 6 - Search again, note increaed by 100
 7 - perl misc/migration_tools/bulkmarcimport.pl -b -v --file=bug_35466_b.mrc --skip_indexing
 8 - Search again, no increase
 9 - perl misc/search_tools/rebuild_elasticsearch.pl -v
10 - Search again, increase, records were added but not initially indexed
11 - Browse to authorities and search as for biblios
12 - perl misc/migration_tools/bulkmarcimport.pl -a -v --file=bug_35466_a.mrc
13 - Search again, note increase
14 - perl misc/migration_tools/bulkmarcimport.pl -a -v --file=bug_35466_a.mrc --skip_indexing
15 - Search again, no increase
16 - perl misc/search_tools/rebuild_elasticsearch.pl -v
17 - Search again, increase, records were added but not initially indexed
18 - Sign off! Hi5!

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e01fb164554a4ae3a9cb1488400a3a1e058c9d57)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index ade216e3f5..27d719a153 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -75,6 +75,7 @@ my $framework = '';
 my $localcust;
 my $marc_mod_template    = '';
 my $marc_mod_template_id = -1;
+my $skip_indexing        = 0;
 $| = 1;
 
 GetOptions(
@@ -111,6 +112,7 @@ GetOptions(
     'framework=s'       => \$framework,
     'custom:s'          => \$localcust,
     'marcmodtemplate:s' => \$marc_mod_template,
+    'si|skip_indexing'  => \$skip_indexing,
 );
 
 $biblios ||= !$authorities;
@@ -128,13 +130,15 @@ if ($all) {
 my $using_elastic_search = ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' );
 my $mod_biblio_options   = {
     disable_autolink  => $using_elastic_search,
-    skip_record_index => $using_elastic_search,
+    skip_record_index => $using_elastic_search || $skip_indexing,
     overlay_context   => { source => 'bulkmarcimport' }
 };
 my $add_biblio_options = {
     disable_autolink  => $using_elastic_search,
-    skip_record_index => $using_elastic_search
+    skip_record_index => $using_elastic_search || $skip_indexing
 };
+my $mod_authority_options = { skip_record_index => $using_elastic_search || $skip_indexing };
+my $add_authority_options = { skip_record_index => $using_elastic_search || $skip_indexing };
 
 my @search_engine_record_ids;
 my @search_engine_records;
@@ -467,7 +471,12 @@ RECORD: foreach my $record ( @{$marc_records} ) {
             if ($matched_record_id) {
                 if ($update) {
                     ## Authority has an id and is in database: update
-                    eval { ($authid) = ModAuthority( $matched_record_id, $record, $authtypecode ) };
+                    eval {
+                        ($authid) = ModAuthority(
+                            $matched_record_id, $record, $authtypecode,
+                            $mod_authority_options,
+                        );
+                    };
                     if ($@) {
                         warn "ERROR: Update authority $matched_record_id failed: $@\n";
                         printlog( { id => $matched_record_id, op => "update", status => "ERROR" } ) if ($logfile);
@@ -487,7 +496,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                 }
             } elsif ($insert) {
                 ## An authid is defined but no authority in database: insert
-                eval { ($authid) = AddAuthority( $record, undef, $authtypecode ) };
+                eval { ($authid) = AddAuthority( $record, undef, $authtypecode, $add_authority_options ); };
                 if ($@) {
                     warn "ERROR: Insert authority $originalid failed: $@\n";
                     printlog( { id => $originalid, op => "insert", status => "ERROR" } ) if ($logfile);
@@ -682,7 +691,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
             $schema->txn_commit;
             $schema->txn_begin;
             if ($indexer) {
-                $indexer->update_index( \@search_engine_record_ids, \@search_engine_records );
+                $indexer->update_index( \@search_engine_record_ids, \@search_engine_records ) unless $skip_indexing;
                 if ( C4::Context->preference('AutoLinkBiblios') ) {
                     foreach my $record (@search_engine_records) {
                         BiblioAutoLink( $record, $framework );

From 9266f2b63f42d437f5d72870a92aab5d6018fd54 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 23 Aug 2024 13:14:58 +0000
Subject: [PATCH 228/429] Bug 35466: (follow-up) Go to next if error, add to
 indexing call if not

Before the previous patch we were indexing with every AddAuthority/ModAuthority call and I assumed
we were also indexing during the commity, however, it appears we were not. This patch ensures we push
the record and ids into the arrays for indexing during commit.

Additionally I add a skip to next record on error, to match biblio behaviour.
I also correct a log referring to biblios during authority importing

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f522fbff329b0a458337f577c1f2117da339a4b2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 27d719a153..2dd095b061 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -480,6 +480,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                     if ($@) {
                         warn "ERROR: Update authority $matched_record_id failed: $@\n";
                         printlog( { id => $matched_record_id, op => "update", status => "ERROR" } ) if ($logfile);
+                        next RECORD;
                     } else {
                         printlog( { id => $authid, op => "update", status => "ok" } ) if ($logfile);
                     }
@@ -500,6 +501,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                 if ($@) {
                     warn "ERROR: Insert authority $originalid failed: $@\n";
                     printlog( { id => $originalid, op => "insert", status => "ERROR" } ) if ($logfile);
+                    next RECORD;
                 } else {
                     printlog( { id => $authid, op => "insert", status => "ok" } ) if ($logfile);
                 }
@@ -508,7 +510,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                 printlog(
                     {
                         id     => $originalid, op => "insert",
-                        status => "warning : biblio not in database and option -insert not enabled, skipping..."
+                        status => "warning : authority not in database and option -insert not enabled, skipping..."
                     }
                 ) if ($logfile);
             }
@@ -520,6 +522,8 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                     1    #@FIXME: Really always updated?
                 );
             }
+            push @search_engine_record_ids, $authid;
+            push @search_engine_records,    $record;
         } else {
             my ( $biblioitemnumber, $itemnumbers_ref, $errors_ref, $record_id );
 

From ca526d63c7b405f3fbe763db882b94679a545c1c Mon Sep 17 00:00:00 2001
From: Thomas Klausner <domm@plix.at>
Date: Sat, 19 Oct 2024 12:32:34 +0000
Subject: [PATCH 229/429] Bug 35466: (follow-up) document skip_indexing
 parameter to bulkmarciport.pl

Add documentation for the new -si, --skip_indexing parameter

Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e200988cb8aabaf6f42a4457aa7ecaa33a7ef4c8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 2dd095b061..8bf2b5e71a 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -1020,6 +1020,12 @@ modification template to apply as the MARC records are imported (these
 templates are created in the "MARC modification templates" tool in Koha).
 If not specified, no MARC modification templates are used (default).
 
+=item B<-si, --skip_indexing>
+
+If set, do not index the imported records with Zebra or Elasticsearch.
+Use this when you plan to do a complete reindex of your data after running
+bulkmarciport. This can increase performance and avoid unnecessary load.
+
 =back
 
 =cut

From 71f0dd6ba97080105af9fdbc5718f21902df5f92 Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Wed, 16 Oct 2024 17:24:55 +0000
Subject: [PATCH 230/429] Bug 36320: Clicking Edit items from
 catalogue/detail.pl leads to 'Add item' screen

This patch updates the catalog toolbar so that the "Edit items"
choice under the "Edit" menu is replaced with a new option: "Manage
items," which takes you to the top of the add/edit items page. The "New
item" menu item has been modified to add the anchor so that the link
jumps the user directly to add item form (past the existing items).

The patch also adds a "Jump to add item form" link at the top of the
add/edit items page.

To test, apply the patch and search for a bibliographic record in the
staff interface.

- View the detail page for the record.
- Click the "Edit" menu button in the toolbar.
- You should see the new menu item, "Manage items."
- Clicking the "Manage items" button should take you to the top
  of the add/edit items page in cataloging.
  - You should see a "Jump to add item form" limk under the page
    heading.
  - Clicking it should jump you to the "Add item" form.
- Clicking the "New item" menu item from the bibliographic detail "New"
  menu should take you directly to the add item form, scrolling down
  past the existing items.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 119e5561a354a97d170ed224623dde0963f7c98d)
---
 .../intranet-tmpl/prog/en/includes/cat-toolbar.inc  |  2 +-
 .../prog/en/modules/cataloguing/additem.tt          |  1 +
 .../intranet-tmpl/prog/js/cataloging_additem.js     | 13 +++++++++++++
 3 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
index 37a7d05ee0..9ccc4d8f7f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
@@ -60,7 +60,7 @@
             [% END %]
 
             [% IF CAN_user_editcatalogue_edit_items or ( frameworkcode == 'FA' and CAN_user_editcatalogue_fast_cataloging ) %]
-            <li><a id="edititems" href="/cgi-bin/koha/cataloguing/additem.pl?biblionumber=[% biblionumber | html %]">Edit items</a></li>
+                <li><a class="dropdown-item" id="manageitems" href="/cgi-bin/koha/cataloguing/additem.pl?biblionumber=[% biblionumber | html %]">Manage items</a></li>
             [% END %]
 
             [% IF ( CAN_user_tools_items_batchmod ) %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
index 57e8b73173..f4536d28b9 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
@@ -77,6 +77,7 @@
                     </div>
                 [% END %]
 <h1>Items for [% biblio.title | html %] [% IF ( biblio.author ) %] by [% biblio.author | html %][% END %] (Record #[% biblio.biblionumber | html %])</h1>
+<a id="newitem_jump" href="#additema"><i class="fa fa-arrow-down"></i> Jump to add item form</a>
 
 [% IF ( barcode_not_unique ) %]<div class="dialog alert"><strong>Error saving item</strong>: Barcode must be unique.</div>[% END %]
 [% IF ( no_next_barcode ) %]<div class="dialog alert"><strong>Error saving items</strong>: Unable to automatically determine values for barcodes. No item has been inserted.</div>[% END %]
diff --git a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
index c8a2189b83..d4f28bbf84 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
@@ -138,6 +138,19 @@ $(document).ready(function(){
             return $("#" + $(this).data("item") + "-delete-item-form").submit();
         }
     }) ;
+
+    /* On page load, check for location.hash in the page URL */
+    /* If present the location hash will be used to scroll to the relevant anchor */
+    var hash = location.hash;
+    var hashPieces = hash.split('?');
+    if( hashPieces[0] !== "" ){
+        $( hashPieces[0] )[0].scrollIntoView();
+    }
+
+    $("#newitem_jump").on("click", function(e){
+        e.preventDefault();
+        document.getElementById("additema").scrollIntoView();
+    });
 });
 
 function CheckTemplateForm(f) {

From 0acc0dffb1503036615a5621b9ecedd01d7365f5 Mon Sep 17 00:00:00 2001
From: Matthias Meusburger <matthias.meusburger@biblibre.com>
Date: Wed, 15 Mar 2017 16:10:45 +0000
Subject: [PATCH 231/429] Bug 18273: Do not insert authority if there already
 are duplicates in koha

Test plan:

1) Create an authority with a unique value in a searchable field, for instance a PERSO_NAME authority with 'BZ18273' in the 100$a field.

2) Create a second authority with the same value in the same field.

3) Export this second authority as iso2709 (Save -> MARC (Unicode/UTF-8))

4) Run bulkmarcimport.pl with the following parameters:
perl bulkmarcimport.pl -a --file=/kohadevbox/koha/auth-xxxx.utf8 --match='Heading,100$a' -l /kohadevbox/koha/bulk.log

5) Check in the log file and/or through search that a third authority has been added (despite using the --match option)

6) Apply the patch

7) Delete the 3 authorities and repeat step 1 to 4.

8) Check that a third authority has not been added (--match worked)

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 2e2fba91bfbaab3bd60870059c7dd5bc75f8bb57)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 8bf2b5e71a..efcac9589f 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -428,6 +428,7 @@ RECORD: foreach my $record ( @{$marc_records} ) {
             }
         } elsif ( @{$results} > 1 ) {
             $logger->debug("More than one match for: $query");
+            next;
         } else {
             $logger->debug("No match for: $query");
         }

From 4ac03f9d873ab7c0abe7170c918256eccbfce141 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Wed, 20 Nov 2024 18:19:53 +0000
Subject: [PATCH 232/429] Revert "Bug 36320: Clicking Edit items from
 catalogue/detail.pl leads to 'Add item' screen"

This reverts commit 71f0dd6ba97080105af9fdbc5718f21902df5f92.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/includes/cat-toolbar.inc  |  2 +-
 .../prog/en/modules/cataloguing/additem.tt          |  1 -
 .../intranet-tmpl/prog/js/cataloging_additem.js     | 13 -------------
 3 files changed, 1 insertion(+), 15 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
index 9ccc4d8f7f..37a7d05ee0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
@@ -60,7 +60,7 @@
             [% END %]
 
             [% IF CAN_user_editcatalogue_edit_items or ( frameworkcode == 'FA' and CAN_user_editcatalogue_fast_cataloging ) %]
-                <li><a class="dropdown-item" id="manageitems" href="/cgi-bin/koha/cataloguing/additem.pl?biblionumber=[% biblionumber | html %]">Manage items</a></li>
+            <li><a id="edititems" href="/cgi-bin/koha/cataloguing/additem.pl?biblionumber=[% biblionumber | html %]">Edit items</a></li>
             [% END %]
 
             [% IF ( CAN_user_tools_items_batchmod ) %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
index f4536d28b9..57e8b73173 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
@@ -77,7 +77,6 @@
                     </div>
                 [% END %]
 <h1>Items for [% biblio.title | html %] [% IF ( biblio.author ) %] by [% biblio.author | html %][% END %] (Record #[% biblio.biblionumber | html %])</h1>
-<a id="newitem_jump" href="#additema"><i class="fa fa-arrow-down"></i> Jump to add item form</a>
 
 [% IF ( barcode_not_unique ) %]<div class="dialog alert"><strong>Error saving item</strong>: Barcode must be unique.</div>[% END %]
 [% IF ( no_next_barcode ) %]<div class="dialog alert"><strong>Error saving items</strong>: Unable to automatically determine values for barcodes. No item has been inserted.</div>[% END %]
diff --git a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
index d4f28bbf84..c8a2189b83 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js
@@ -138,19 +138,6 @@ $(document).ready(function(){
             return $("#" + $(this).data("item") + "-delete-item-form").submit();
         }
     }) ;
-
-    /* On page load, check for location.hash in the page URL */
-    /* If present the location hash will be used to scroll to the relevant anchor */
-    var hash = location.hash;
-    var hashPieces = hash.split('?');
-    if( hashPieces[0] !== "" ){
-        $( hashPieces[0] )[0].scrollIntoView();
-    }
-
-    $("#newitem_jump").on("click", function(e){
-        e.preventDefault();
-        document.getElementById("additema").scrollIntoView();
-    });
 });
 
 function CheckTemplateForm(f) {

From 9acd02725cf0be2ffd58c6a25d9e9176c3271c49 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Wed, 2 Oct 2024 18:04:56 +0000
Subject: [PATCH 233/429] Bug 38065: Auto control number (001) widget in
 advanced editor does not work under CSRF protection

Auto control number (001) widget in advanced editor does not work under
CSRF protection.  A token should be passed to POST ...
control_num_sequences call.

Test plan:
==========

1. Verify that the CONTROL_NUM_SEQUENCE category in authorized values is
   present.
3. Add a new authorized value for CONTROL_NUM_SEQUENCE:
     b) authorised value: sprLib0001
     c) in Description - a short string indicating the type of control
     number i.e. "Springfield Library"
4. Create a new bib record using the advanced editor, insert a 001
   field, note that the 001 widget is there.  Click on "Assign next". There
   should be no effect (and you could see [HTTP/1.1 403 Forbidden 706ms] in
   the browser console).
5. Apply the patch ; restart_all ; reload the browser.
6. Repeat p. 4.  You should get the content of the 001 field generated
   by the widget.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit bc1af60e1ac8c9b7de5aab79239899ad2a437345)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/includes/cateditor-widgets-marc21.inc              | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
index 26cadeda16..67a9c120f2 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
@@ -152,8 +152,9 @@ require( [ 'koha-backend', 'widget', 'text-marc' ], function( KohaBackend, Widge
                 $( this.node )
                     .find('.control-number-widget-assign').click( function() {
                         var sequence = $( widget.node ).find('.control-number-widget-sequence').val();
+                        const token = $("input[name='csrf_token']").first().val();
                         $.post(
-                            '/cgi-bin/koha/svc/cataloguing/control_num_sequences/' + sequence
+                            '/cgi-bin/koha/svc/cataloguing/control_num_sequences/' + sequence + '?csrf_token=' + token
                         ).done( function( result ) {
                             if ( result.next_value ) widget.setControlNumber( result.next_value );
                         } );

From 6bde7f52b9412e4dcb01a5a92a7e48045c4805f9 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 22 Oct 2024 00:31:23 +0000
Subject: [PATCH 234/429] Bug 38065: (QA follow-up) Move csrf_token to POST
 body from URL

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a369ace161db801fdff1becb8f3f0c4b4aa7ccba)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/includes/cateditor-widgets-marc21.inc            | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
index 67a9c120f2..1a87de386f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc
@@ -152,9 +152,10 @@ require( [ 'koha-backend', 'widget', 'text-marc' ], function( KohaBackend, Widge
                 $( this.node )
                     .find('.control-number-widget-assign').click( function() {
                         var sequence = $( widget.node ).find('.control-number-widget-sequence').val();
-                        const token = $("input[name='csrf_token']").first().val();
+                        const csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
                         $.post(
-                            '/cgi-bin/koha/svc/cataloguing/control_num_sequences/' + sequence + '?csrf_token=' + token
+                            '/cgi-bin/koha/svc/cataloguing/control_num_sequences/' + sequence,
+                            { csrf_token: csrf_token }
                         ).done( function( result ) {
                             if ( result.next_value ) widget.setControlNumber( result.next_value );
                         } );

From f1c9a95c3acd3fa6d6c6b5d3a7dec59a7f1d54c8 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Thu, 3 Oct 2024 18:22:39 +0000
Subject: [PATCH 235/429] Bug 38082: Advanced editor does not save the selected
 framework

When creating a fresh record in the advanced editor, it does not
save the framework selected from the Settings dropdown.

Test plan:
==========

1. Enable the advanced editor feature (EnableAdvancedCatalogingEditor).
2. Start editing a new record in the advanced editor.  Fill in the
   mandatory fields, select a framework (other than Default) from the
   Settings dropdown, save the record.
3. Select view -- note that the record has been saved with the Default
   framework.
4. Apply the patch; restart_all; refresh the browser window.
5. Repeat p. 2.
6. Select view -- you should now see that the record has been saved with
   the correct, selected framework.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 145bc398c12f6b02e4de9d9e917f28190a344762)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/js/fetch/cataloguing-api-client.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/cataloguing-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/cataloguing-api-client.js
index 00959be93c..7bae56d62a 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/fetch/cataloguing-api-client.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/cataloguing-api-client.js
@@ -12,7 +12,7 @@ export class CataloguingAPIClient extends HttpClient {
         return {
             create: bib_info =>
                 this.post({
-                    endpoint: "new_bib/frameworkcode=%s".format(
+                    endpoint: "new_bib/?frameworkcode=%s".format(
                         bib_info.frameworkcode
                     ),
                     body: bib_info.record.toXML(),

From 006f94a718a5bd30b561281eb1492fe28c67dd9f Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 22 Oct 2024 10:02:56 +0200
Subject: [PATCH 236/429] Bug 31581: Remove zebra files for NORMARC

Those are leftovers, we should have removed them on bug 18984.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Magnus Enger <magnus@libriotech.no>
Looks good!
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8e5774cdfe24de1ec9a15d836a71a72039e8ca3d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../normarc/biblios/biblio-koha-indexdefs.xml | 1019 ---------
 .../biblios/biblio-zebra-indexdefs.xsl        | 1909 -----------------
 .../normarc/biblios/preprocess_marcxml.xsl    |   23 -
 3 files changed, 2951 deletions(-)
 delete mode 100644 etc/zebradb/marc_defs/normarc/biblios/biblio-koha-indexdefs.xml
 delete mode 100644 etc/zebradb/marc_defs/normarc/biblios/biblio-zebra-indexdefs.xsl
 delete mode 100644 etc/zebradb/marc_defs/normarc/biblios/preprocess_marcxml.xsl

diff --git a/etc/zebradb/marc_defs/normarc/biblios/biblio-koha-indexdefs.xml b/etc/zebradb/marc_defs/normarc/biblios/biblio-koha-indexdefs.xml
deleted file mode 100644
index 161ce6a9f9..0000000000
--- a/etc/zebradb/marc_defs/normarc/biblios/biblio-koha-indexdefs.xml
+++ /dev/null
@@ -1,1019 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<kohaidx:index_defs xmlns:kohaidx="http://www.koha-community.org/schemas/index-defs"
-                    xmlns="http://www.koha-community.org/schemas/index-defs">
-  <id>marc:datafield[@tag='999']/marc:subfield[@code='c']</id>
-  <!--record.abs line 64: xelm /record/leader llength:w:range(data,0,5),rtype:w:range(data,6,1),Bib-level:w:range(data,7,1)-->
-  <index_leader offset="0" length="5">
-    <target_index>llength:w</target_index>
-  </index_leader>
-  <index_leader offset="6" length="1">
-    <target_index>rtype:w</target_index>
-  </index_leader>
-  <index_leader offset="7" length="1">
-    <target_index>Bib-level:w</target_index>
-  </index_leader>
-  <!--record.abs line 67: melm 001        Control-number-->
-  <index_control_field tag="001">
-    <target_index>Control-number:w</target_index>
-  </index_control_field>
-  <!--record.abs line 71: melm 007        Microform-generation:n:range(data,11,1),Material-type,ff7-00:w:range(data,0,1),ff7-01:w:range(data,1,1),ff7-02:w:range(data,2,1),ff7-01-02:w:range(data,0,2)-->
-  <index_control_field tag="007" offset="11" length="1">
-    <target_index>Microform-generation:n</target_index>
-  </index_control_field>
-  <index_control_field tag="007">
-    <target_index>Material-type:w</target_index>
-  </index_control_field>
-  <index_control_field tag="007" offset="0" length="1">
-    <target_index>ff7-00:w</target_index>
-  </index_control_field>
-  <index_control_field tag="007" offset="1" length="1">
-    <target_index>ff7-01:w</target_index>
-  </index_control_field>
-  <index_control_field tag="007" offset="2" length="1">
-    <target_index>ff7-02:w</target_index>
-  </index_control_field>
-  <index_control_field tag="007" offset="0" length="2">
-    <target_index>ff7-01-02:w</target_index>
-  </index_control_field>
-  <!--record.abs line 74: melm 008        date-entered-on-file:n:range(data,0,5),date-entered-on-file:s:range(data,0,5),pubdate:w:range(data,7,4),pubdate:n:range(data,7,4),pubdate:y:range(data,7,4),pubdate:s:range(data,7,4),pl:w:range(data,15,3),ta:w:range(data,22,1),ff8-23:w:range(data,23,1),ff8-29:w:range(data,29,1),lf:w:range(data,33,1),bio:w:range(data,34,1),ln:w:range(data,35,3),ctype:w:range(data,24,4),Record-source:w:range(data,39,0)-->
-  <index_control_field tag="008" offset="0" length="5">
-    <target_index>date-entered-on-file:n</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="0" length="5">
-    <target_index>date-entered-on-file:s</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="7" length="4">
-    <target_index>pubdate:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="7" length="4">
-    <target_index>pubdate:n</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="7" length="4">
-    <target_index>pubdate:y</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="7" length="4">
-    <target_index>pubdate:s</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="15" length="3">
-    <target_index>pl:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="22" length="1">
-    <target_index>ta:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="23" length="1">
-    <target_index>ff8-23:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="29" length="1">
-    <target_index>ff8-29:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="33" length="1">
-    <target_index>lf:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="34" length="1">
-    <target_index>bio:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="35" length="3">
-    <target_index>ln:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="24" length="4">
-    <target_index>ctype:w</target_index>
-  </index_control_field>
-  <index_control_field tag="008" offset="39" length="0">
-    <target_index>Record-source:w</target_index>
-  </index_control_field>
-  <!--record.abs line 76: melm 010        LC-card-number,Identifier-standard-->
-  <index_data_field tag="010">
-    <target_index>LC-card-number:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 78: melm 015        BNB-card-number,BGF-number,Number-db,Number-natl-biblio,Identifier-standard-->
-  <index_data_field tag="015">
-    <target_index>BNB-card-number:w</target_index>
-    <target_index>BGF-number:w</target_index>
-    <target_index>Number-db:w</target_index>
-    <target_index>Number-natl-biblio:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 81: melm 020$a      ISBN:w,Identifier-standard:w-->
-  <index_subfields tag="020" subfields="a">
-    <target_index>ISBN:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_subfields>
-  <!--record.abs line 82: melm 020        ISBN,Identifier-standard-->
-  <index_data_field tag="020">
-    <target_index>ISBN:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 83: melm 022$a      ISSN:w,ISBN:w-->
-  <index_subfields tag="022" subfields="a">
-    <target_index>ISSN:w</target_index>
-    <target_index>ISBN:w</target_index>
-  </index_subfields>
-  <!--record.abs line 84: melm 022        ISSN,Identifier-standard-->
-  <index_data_field tag="022">
-    <target_index>ISSN:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 86: melm 024        Identifier-standard-->
-  <index_data_field tag="024">
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 87: melm 025        Identifier-standard-->
-  <index_data_field tag="025">
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 88: melm 027        Report-number,Identifier-standard-->
-  <index_data_field tag="027">
-    <target_index>Report-number:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 89: melm 028        Identifier-publisher-for-music,Identifier-standard-->
-  <index_data_field tag="028">
-    <target_index>Identifier-publisher-for-music:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 90: melm 030        CODEN,Identifier-standard-->
-  <index_data_field tag="030">
-    <target_index>CODEN:w</target_index>
-    <target_index>Identifier-standard:w</target_index>
-  </index_data_field>
-  <!--record.abs line 95: melm 040        Code-institution,Record-source-->
-  <index_data_field tag="040">
-    <target_index>Code-institution:w</target_index>
-    <target_index>Record-source:w</target_index>
-  </index_data_field>
-  <!--record.abs line 96: melm 041        ln-->
-  <index_data_field tag="041">
-    <target_index>ln:w</target_index>
-  </index_data_field>
-  <!--record.abs line 97: melm 043        Code-geographic-->
-  <index_data_field tag="043">
-    <target_index>Code-geographic:w</target_index>
-  </index_data_field>
-  <!--record.abs line 102: melm 060        NLM-call-number-->
-  <index_data_field tag="060">
-    <target_index>NLM-call-number:w</target_index>
-  </index_data_field>
-  <!--record.abs line 104: melm 080        UDC-classification-->
-  <index_data_field tag="080">
-    <target_index>UDC-classification:w</target_index>
-  </index_data_field>
-  <!--record.abs line 105: melm 082        Dewey-classification:w,Dewey-classification:s-->
-  <index_data_field tag="082">
-    <target_index>Dewey-classification:w</target_index>
-    <target_index>Dewey-classification:s</target_index>
-  </index_data_field>
-  <!--record.abs line 113: melm 100$9      Cross-Reference:w,Koha-Auth-Number-->
-  <index_subfields tag="100" subfields="9">
-    <target_index>Cross-Reference:w</target_index>
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 114: melm 100$a      Author,Author:p,Author:s,Editor,Author-personal-bibliography,Author-personal-bibliography:p,Author-personal-bibliography:s-->
-  <index_subfields tag="100" subfields="a">
-    <target_index>Author:w</target_index>
-    <target_index>Author:p</target_index>
-    <target_index>Author:s</target_index>
-    <target_index>Editor:w</target_index>
-    <target_index>Author-personal-bibliography:w</target_index>
-    <target_index>Author-personal-bibliography:p</target_index>
-    <target_index>Author-personal-bibliography:s</target_index>
-  </index_subfields>
-  <!--record.abs line 115: melm 100        Author,Author-title,Author-name-personal,Name,Name-and-title,Personal-name-->
-  <index_data_field tag="100">
-    <target_index>Author:w</target_index>
-    <target_index>Author-title:w</target_index>
-    <target_index>Author-name-personal:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Personal-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 116: melm 110$9      Koha-Auth-Number-->
-  <index_subfields tag="110" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 117: melm 110        Author,Author-title,Author-name-corporate,Name,Name-and-title,Corporate-name-->
-  <index_data_field tag="110">
-    <target_index>Author:w</target_index>
-    <target_index>Author-title:w</target_index>
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Corporate-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 118: melm 111$9      Koha-Auth-Number-->
-  <index_subfields tag="111" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 119: melm 111        Author,Author-title,Author-name-corporate,Name,Name-and-title,Conference-name-->
-  <index_data_field tag="111">
-    <target_index>Author:w</target_index>
-    <target_index>Author-title:w</target_index>
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Conference-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 120: melm 130$i      Thematic-number-->
-  <index_subfields tag="130" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 121: melm 130$n      Thematic-number-->
-  <index_subfields tag="130" subfields="n">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 122: melm 130$r      Music-key-->
-  <index_subfields tag="130" subfields="r">
-    <target_index>Music-key:w</target_index>
-  </index_subfields>
-  <!--record.abs line 123: melm 130$9      Koha-Auth-Number-->
-  <index_subfields tag="130" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 124: melm 130        Title,Title-uniform-->
-  <index_data_field tag="130">
-    <target_index>Title:w</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_data_field>
-  <!--record.abs line 126: melm 210        Title,Title-abbreviated-->
-  <index_data_field tag="210">
-    <target_index>Title:w</target_index>
-    <target_index>Title-abbreviated:w</target_index>
-  </index_data_field>
-  <!--record.abs line 130: melm 222        Title,Title-key-->
-  <index_data_field tag="222">
-    <target_index>Title:w</target_index>
-    <target_index>Title-key:w</target_index>
-  </index_data_field>
-  <!--record.abs line 131: melm 240$i      Thematic-number-->
-  <index_subfields tag="240" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 132: melm 240$r      Music-key-->
-  <index_subfields tag="240" subfields="r">
-    <target_index>Music-key:w</target_index>
-  </index_subfields>
-  <!--record.abs line 133: melm 240$n      Thematic-number-->
-  <index_subfields tag="240" subfields="n">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 134: melm 240        Title:w,Title:p,Title-uniform-->
-  <index_data_field tag="240">
-    <target_index>Title:w</target_index>
-    <target_index>Title:p</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_data_field>
-  <!--record.abs line 138: melm 245$a      Title-cover:w,Title-cover:p,Title-cover:s,Title:w,Title:p,Title:s-->
-  <index_subfields tag="245" subfields="a">
-    <target_index>Title-cover:w</target_index>
-    <target_index>Title-cover:p</target_index>
-    <target_index>Title-cover:s</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title:p</target_index>
-    <target_index>Title:s</target_index>
-  </index_subfields>
-  <!--record.abs line 139: melm 245$c      Author,Author-in-order:w,Author-in-order:p,Author-in-order:s-->
-  <index_subfields tag="245" subfields="c">
-    <target_index>Author:w</target_index>
-    <target_index>Author-in-order:w</target_index>
-    <target_index>Author-in-order:p</target_index>
-    <target_index>Author-in-order:s</target_index>
-  </index_subfields>
-  <!--record.abs line 140: melm 245$9      Cross-Reference:w,Koha-Auth-Number-->
-  <index_subfields tag="245" subfields="9">
-    <target_index>Cross-Reference:w</target_index>
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 141: melm 245        Title:w,Title:p-->
-  <index_data_field tag="245">
-    <target_index>Title:w</target_index>
-    <target_index>Title:p</target_index>
-  </index_data_field>
-  <!--record.abs line 142: melm 246        Title,Title:p,Title-abbreviated,Title-expanded,Title-former-->
-  <index_data_field tag="246">
-    <target_index>Title:w</target_index>
-    <target_index>Title:p</target_index>
-    <target_index>Title-abbreviated:w</target_index>
-    <target_index>Title-expanded:w</target_index>
-    <target_index>Title-former:w</target_index>
-  </index_data_field>
-  <!--record.abs line 144: melm 260$a      pl:w,pl:p-->
-  <index_subfields tag="260" subfields="a">
-    <target_index>pl:w</target_index>
-    <target_index>pl:p</target_index>
-  </index_subfields>
-  <!--record.abs line 145: melm 260$b      Publisher:w,Publisher:p-->
-  <index_subfields tag="260" subfields="b">
-    <target_index>Publisher:w</target_index>
-    <target_index>Publisher:p</target_index>
-  </index_subfields>
-  <!--record.abs line 146: melm 260$c      copydate,copydate:s-->
-  <index_subfields tag="260" subfields="c">
-    <target_index>copydate:w</target_index>
-    <target_index>copydate:s</target_index>
-  </index_subfields>
-  <!--record.abs line 147: melm 260        pl-->
-  <index_data_field tag="260">
-    <target_index>pl:w</target_index>
-  </index_data_field>
-  <!--record.abs line 148: melm 300        Extent:w,Extent:p-->
-  <index_data_field tag="300">
-    <target_index>Extent:w</target_index>
-    <target_index>Extent:p</target_index>
-  </index_data_field>
-  <!--record.abs line 164: melm 440$a      Title-series:w,Title-series:p-->
-  <index_subfields tag="440" subfields="a">
-    <target_index>Title-series:w</target_index>
-    <target_index>Title-series:p</target_index>
-  </index_subfields>
-  <!--record.abs line 165: melm 440$9      Koha-Auth-Number-->
-  <index_subfields tag="440" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 166: melm 440        Title-series:w,Title-series:p,Title,Title-series-->
-  <index_data_field tag="440">
-    <target_index>Title-series:w</target_index>
-    <target_index>Title-series:p</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_data_field>
-  <!--record.abs line 167: melm 490$a      Title-series:w,Title-series:p-->
-  <index_subfields tag="490" subfields="a">
-    <target_index>Title-series:w</target_index>
-    <target_index>Title-series:p</target_index>
-  </index_subfields>
-  <!--record.abs line 168: melm 490        Title,Title-series-->
-  <index_data_field tag="490">
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_data_field>
-  <!--record.abs line 169: melm 490$9      Koha-Auth-Number-->
-  <index_subfields tag="490" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 171: melm 502        Material-type-->
-  <index_data_field tag="502">
-    <target_index>Material-type:w</target_index>
-  </index_data_field>
-  <!--record.abs line 174: melm 505        Note:w,Note:p-->
-  <index_data_field tag="505">
-    <target_index>Note:w</target_index>
-    <target_index>Note:p</target_index>
-  </index_data_field>
-  <!--record.abs line 175: melm 510        Indexed-by-->
-  <index_data_field tag="510">
-    <target_index>Indexed-by:w</target_index>
-  </index_data_field>
-  <!--record.abs line 176: melm 520        Abstract:w,Abstract:p-->
-  <index_data_field tag="520">
-    <target_index>Abstract:w</target_index>
-    <target_index>Abstract:p</target_index>
-  </index_data_field>
-  <!--record.abs line 180: melm 600$a      Name-and-title,Name,Personal-name,Subject-name-personal,Subject,Subject:p-->
-  <index_subfields tag="600" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Personal-name:w</target_index>
-    <target_index>Subject-name-personal:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 181: melm 600$t      Name-and-title,Title,Subject,Subject:p-->
-  <index_subfields tag="600" subfields="t">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 182: melm 600$9      Koha-Auth-Number-->
-  <index_subfields tag="600" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 184: melm 600        Name,Personal-name,Subject-name-personal,Subject,Subject:p-->
-  <index_data_field tag="600">
-    <target_index>Name:w</target_index>
-    <target_index>Personal-name:w</target_index>
-    <target_index>Subject-name-personal:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 185: melm 610$a      Name-and-title,Subject,Subject:p-->
-  <index_subfields tag="610" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 186: melm 610$t      Name-and-title,Title,Subject,Subject:p-->
-  <index_subfields tag="610" subfields="t">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 187: melm 610$9      Koha-Auth-Number-->
-  <index_subfields tag="610" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 188: melm 610        Name,Subject,Corporate-name,Subject,Subject:p-->
-  <index_data_field tag="610">
-    <target_index>Name:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Corporate-name:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 189: melm 611        Conference-name,Subject,Subject:p-->
-  <index_data_field tag="611">
-    <target_index>Conference-name:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 190: melm 611$a      Name-and-title,Subject,Subject:p-->
-  <index_subfields tag="611" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 191: melm 611$t      Name-and-title,Title,Subject,Subject:p-->
-  <index_subfields tag="611" subfields="t">
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 192: melm 611$9      Koha-Auth-Number-->
-  <index_subfields tag="611" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 193: melm 611        Name,Subject,Subject:p-->
-  <index_data_field tag="611">
-    <target_index>Name:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 194: melm 630$i      Thematic-number,Subject,Subject:p-->
-  <index_subfields tag="630" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 195: melm 630$n      Thematic-number,Subject,Subject:p-->
-  <index_subfields tag="630" subfields="n">
-    <target_index>Thematic-number:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 196: melm 630$r      Music-key,Subject,Subject:p-->
-  <index_subfields tag="630" subfields="r">
-    <target_index>Music-key:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_subfields>
-  <!--record.abs line 197: melm 630$9      Koha-Auth-Number-->
-  <index_subfields tag="630" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 198: melm 630        Subject,Subject:p-->
-  <index_data_field tag="630">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <index_subfields tag="648" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 199: melm 650$9      Koha-Auth-Number-->
-  <index_subfields tag="650" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 200: melm 650        Subject,Subject:p-->
-  <index_data_field tag="650">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 201: melm 651$9      Koha-Auth-Number-->
-  <index_subfields tag="651" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 202: melm 651        Name-geographic,Subject,Subject:p-->
-  <index_data_field tag="651">
-    <target_index>Name-geographic:w</target_index>
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 203: melm 652$9      Koha-Auth-Number-->
-  <index_subfields tag="652" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 205: melm 653$9      Koha-Auth-Number-->
-  <index_subfields tag="653" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 206: melm 653        Subject,Subject:p-->
-  <index_data_field tag="653">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 207: melm 654$9      Koha-Auth-Number-->
-  <index_subfields tag="654" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 208: melm 654        Subject,Subject:p-->
-  <index_data_field tag="654">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 209: melm 655$9      Koha-Auth-Number-->
-  <index_subfields tag="655" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 210: melm 655        Subject,Subject:p-->
-  <index_data_field tag="655">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 211: melm 656$9      Koha-Auth-Number-->
-  <index_subfields tag="656" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 212: melm 656        Subject,Subject:p-->
-  <index_data_field tag="656">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 213: melm 657$9      Koha-Auth-Number-->
-  <index_subfields tag="657" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 214: melm 657        Subject,Subject:p-->
-  <index_data_field tag="657">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 215: melm 690$9      Koha-Auth-Number-->
-  <index_subfields tag="690" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 216: melm 690        Subject,Subject:p-->
-  <index_data_field tag="690">
-    <target_index>Subject:w</target_index>
-    <target_index>Subject:p</target_index>
-  </index_data_field>
-  <!--record.abs line 218: melm 700$9      Cross-Reference,Koha-Auth-Number-->
-  <index_subfields tag="700" subfields="9">
-    <target_index>Cross-Reference:w</target_index>
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 219: melm 700$a      Author,Author:p-->
-  <index_subfields tag="700" subfields="a">
-    <target_index>Author:w</target_index>
-    <target_index>Author:p</target_index>
-  </index_subfields>
-  <!--record.abs line 220: melm 700$i      Thematic-number-->
-  <index_subfields tag="700" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 221: melm 700$n      Thematic-number-->
-  <index_subfields tag="700" subfields="n">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 222: melm 700$r      Music-key-->
-  <index_subfields tag="700" subfields="r">
-    <target_index>Music-key:w</target_index>
-  </index_subfields>
-  <!--record.abs line 224: melm 700$t      Author-title,Name-and-title,Title,Title-uniform-->
-  <index_subfields tag="700" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_subfields>
-  <!--record.abs line 225: melm 700        Author,Author-name-corporate,Author-name-personal,Name,Editor,Personal-name-->
-  <index_data_field tag="700">
-    <target_index>Author:w</target_index>
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Author-name-personal:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Editor:w</target_index>
-    <target_index>Personal-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 226: melm 710        Author,Corporate-name-->
-  <index_data_field tag="710">
-    <target_index>Author:w</target_index>
-    <target_index>Corporate-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 228: melm 710$t      Author-title,Name-and-title,Title,Title-uniform-->
-  <index_subfields tag="710" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_subfields>
-  <!--record.abs line 230: melm 710$a      Name-and-title-->
-  <index_subfields tag="710" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 231: melm 710$i      Thematic-number-->
-  <index_subfields tag="710" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 232: melm 710$9      Koha-Auth-Number-->
-  <index_subfields tag="710" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 234: melm 710        Author,Name-->
-  <index_data_field tag="710">
-    <target_index>Author:w</target_index>
-    <target_index>Name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 235: melm 711$a      Name-and-title-->
-  <index_subfields tag="711" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 237: melm 711$t      Author-title,Title,Title-uniform-->
-  <index_subfields tag="711" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_subfields>
-  <!--record.abs line 238: melm 711$9      Koha-Auth-Number-->
-  <index_subfields tag="711" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 240: melm 711        Author-name-corporate,Name,Conference-name-->
-  <index_data_field tag="711">
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Conference-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 241: melm 730$i      Thematic-number-->
-  <index_subfields tag="730" subfields="i">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 242: melm 730$n      Thematic-number-->
-  <index_subfields tag="730" subfields="n">
-    <target_index>Thematic-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 243: melm 730$r      Music-key-->
-  <index_subfields tag="730" subfields="r">
-    <target_index>Music-key:w</target_index>
-  </index_subfields>
-  <!--record.abs line 244: melm 730$9      Koha-Auth-Number-->
-  <index_subfields tag="730" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 245: melm 730        Title,Title-uniform-->
-  <index_data_field tag="730">
-    <target_index>Title:w</target_index>
-    <target_index>Title-uniform:w</target_index>
-  </index_data_field>
-  <!--record.abs line 246: melm 740        Title,Title-other-variant-->
-  <index_data_field tag="740">
-    <target_index>Title:w</target_index>
-    <target_index>Title-other-variant:w</target_index>
-  </index_data_field>
-  <!--record.abs line 247: melm 773$a      Host-item-->
-  <index_subfields tag="773" subfields="a">
-    <target_index>Host-item:w</target_index>
-  </index_subfields>
-  <!--record.abs line 248: melm 773$9	    Host-Item-Number-->
-  <index_subfields tag="773" subfields="9">
-    <target_index>Host-Item-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 249: melm 773$t      Host-item-->
-  <index_subfields tag="773" subfields="t">
-    <target_index>Host-item:w</target_index>
-  </index_subfields>
-  <!--record.abs line 250: melm 773$w      Record-control-number-->
-  <index_subfields tag="773" subfields="w">
-    <target_index>Record-control-number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 251: melm 780$t      Title-->
-  <index_subfields tag="780" subfields="t">
-    <target_index>Title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 252: melm 780        Title,Title-former,Related-periodical-->
-  <index_data_field tag="780">
-    <target_index>Title:w</target_index>
-    <target_index>Title-former:w</target_index>
-    <target_index>Related-periodical:w</target_index>
-  </index_data_field>
-  <!--record.abs line 253: melm 785        Related-periodical-->
-  <index_data_field tag="785">
-    <target_index>Related-periodical:w</target_index>
-  </index_data_field>
-  <!--record.abs line 255: melm 800$a      Name-and-title-->
-  <index_subfields tag="800" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 257: melm 800$t      Author-title,Name-and-title,Title,Title-series-->
-  <index_subfields tag="800" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_subfields>
-  <!--record.abs line 258: melm 800$9      Koha-Auth-Number-->
-  <index_subfields tag="800" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 259: melm 800        Author,Author-name-personal,Name,Personal-name-->
-  <index_data_field tag="800">
-    <target_index>Author:w</target_index>
-    <target_index>Author-name-personal:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Personal-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 260: melm 810$a      Name-and-title-->
-  <index_subfields tag="810" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 261: melm 810$t      Author-title,Name-and-title,Title,Title-series-->
-  <index_subfields tag="810" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_subfields>
-  <!--record.abs line 262: melm 810$9      Koha-Auth-Number-->
-  <index_subfields tag="810" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 263: melm 810        Author,Corporate-name,Author-name-corporate,Name-->
-  <index_data_field tag="810">
-    <target_index>Author:w</target_index>
-    <target_index>Corporate-name:w</target_index>
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 264: melm 811$a      Name-and-title-->
-  <index_subfields tag="811" subfields="a">
-    <target_index>Name-and-title:w</target_index>
-  </index_subfields>
-  <!--record.abs line 265: melm 811$9      Koha-Auth-Number-->
-  <index_subfields tag="811" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 267: melm 811$t      Author-title,Name-and-title,Title,Title-series-->
-  <index_subfields tag="811" subfields="t">
-    <target_index>Author-title:w</target_index>
-    <target_index>Name-and-title:w</target_index>
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_subfields>
-  <!--record.abs line 268: melm 811        Author,Author-name-corporate,Name,Conference-name-->
-  <index_data_field tag="811">
-    <target_index>Author:w</target_index>
-    <target_index>Author-name-corporate:w</target_index>
-    <target_index>Name:w</target_index>
-    <target_index>Conference-name:w</target_index>
-  </index_data_field>
-  <!--record.abs line 269: melm 830$9      Koha-Auth-Number-->
-  <index_subfields tag="830" subfields="9">
-    <target_index>Koha-Auth-Number:w</target_index>
-  </index_subfields>
-  <!--record.abs line 270: melm 830        Title,Title-series-->
-  <index_data_field tag="830">
-    <target_index>Title:w</target_index>
-    <target_index>Title-series:w</target_index>
-  </index_data_field>
-  <!--record.abs line 275: melm 999$c      Local-Number:n,Local-Number:w,Local-Number:s-->
-  <index_subfields tag="999" subfields="c">
-    <target_index>Local-Number:n</target_index>
-    <target_index>Local-Number:w</target_index>
-    <target_index>Local-Number:s</target_index>
-  </index_subfields>
-  <!--record.abs line 276: melm 999$d      biblioitemnumber:n,biblioitemnumber:w,biblioitemnumber:s-->
-  <index_subfields tag="999" subfields="d">
-    <target_index>biblioitemnumber:n</target_index>
-    <target_index>biblioitemnumber:w</target_index>
-    <target_index>biblioitemnumber:s</target_index>
-  </index_subfields>
-  <!--record.abs line 277: melm 942$0      totalissues:n,totalissues:s-->
-  <index_subfields tag="942" subfields="0">
-    <target_index>totalissues:n</target_index>
-    <target_index>totalissues:s</target_index>
-  </index_subfields>
-  <!--record.abs line 278: melm 942$2      cn-bib-source-->
-  <index_subfields tag="942" subfields="2">
-    <target_index>cn-bib-source:w</target_index>
-  </index_subfields>
-  <!--record.abs line 279: melm 942$6      cn-bib-sort:n,cn-bib-sort:s-->
-  <index_subfields tag="942" subfields="6">
-    <target_index>cn-bib-sort:n</target_index>
-    <target_index>cn-bib-sort:s</target_index>
-  </index_subfields>
-  <!--record.abs line 280: melm 942$c      itemtype:w-->
-  <index_subfields tag="942" subfields="c">
-    <target_index>itemtype:w</target_index>
-  </index_subfields>
-  <!--record.abs line 281: melm 942$n      Suppress:w,Suppress:n-->
-  <index_subfields tag="942" subfields="n">
-    <target_index>Suppress:w</target_index>
-    <target_index>Suppress:n</target_index>
-  </index_subfields>
-  <!--record.abs line 284: melm 942$h      cn-class-->
-  <index_subfields tag="942" subfields="h">
-    <target_index>cn-class:w</target_index>
-  </index_subfields>
-  <!--record.abs line 285: melm 942$i      cn-item-->
-  <index_subfields tag="942" subfields="i">
-    <target_index>cn-item:w</target_index>
-  </index_subfields>
-  <!--record.abs line 286: melm 942$k      cn-prefix-->
-  <index_subfields tag="942" subfields="k">
-    <target_index>cn-prefix:w</target_index>
-  </index_subfields>
-  <!--record.abs line 287: melm 942$m      cn-suffix-->
-  <index_subfields tag="942" subfields="m">
-    <target_index>cn-suffix:w</target_index>
-  </index_subfields>
-  <!--record.abs line 290: melm 952$0      withdrawn:n,withdrawn:w-->
-  <index_subfields tag="952" subfields="0">
-    <target_index>withdrawn:n</target_index>
-    <target_index>withdrawn:w</target_index>
-  </index_subfields>
-  <!--record.abs line 291: melm 952$1      lost,lost:n-->
-  <index_subfields tag="952" subfields="1">
-    <target_index>lost:w</target_index>
-    <target_index>lost:n</target_index>
-  </index_subfields>
-  <!--record.abs line 292: melm 952$2      classification-source-->
-  <index_subfields tag="952" subfields="2">
-    <target_index>classification-source:w</target_index>
-  </index_subfields>
-  <!--record.abs line 293: melm 952$3      materials-specified-->
-  <index_subfields tag="952" subfields="3">
-    <target_index>materials-specified:w</target_index>
-  </index_subfields>
-  <!--record.abs line 294: melm 952$4      damaged:n,damaged:w-->
-  <index_subfields tag="952" subfields="4">
-    <target_index>damaged:n</target_index>
-    <target_index>damaged:w</target_index>
-  </index_subfields>
-  <!--record.abs line 295: melm 952$5      restricted:n,restricted:w-->
-  <index_subfields tag="952" subfields="5">
-    <target_index>restricted:n</target_index>
-    <target_index>restricted:w</target_index>
-  </index_subfields>
-  <!--record.abs line 296: melm 952$6      cn-sort:n,cn-sort:s-->
-  <index_subfields tag="952" subfields="6">
-    <target_index>cn-sort:n</target_index>
-    <target_index>cn-sort:s</target_index>
-  </index_subfields>
-  <!--record.abs line 297: melm 952$7      notforloan:n,notforloan:w-->
-  <index_subfields tag="952" subfields="7">
-    <target_index>notforloan:n</target_index>
-    <target_index>notforloan:w</target_index>
-  </index_subfields>
-  <!--record.abs line 298: melm 952$8      ccode-->
-  <index_subfields tag="952" subfields="8">
-    <target_index>ccode:w</target_index>
-  </index_subfields>
-  <!--record.abs line 299: melm 952$9      itemnumber:n,itemnumber:s-->
-  <index_subfields tag="952" subfields="9">
-    <target_index>itemnumber:n</target_index>
-    <target_index>itemnumber:s</target_index>
-  </index_subfields>
-  <!--record.abs line 300: melm 952$a      homebranch-->
-  <index_subfields tag="952" subfields="a">
-    <target_index>homebranch:w</target_index>
-  </index_subfields>
-  <!--record.abs line 301: melm 952$b      holdingbranch-->
-  <index_subfields tag="952" subfields="b">
-    <target_index>holdingbranch:w</target_index>
-  </index_subfields>
-  <!--record.abs line 302: melm 952$c      location-->
-  <index_subfields tag="952" subfields="c">
-    <target_index>location:w</target_index>
-  </index_subfields>
-  <!--record.abs line 305: melm 952$d      Date-of-acquisition,Date-of-acquisition:d,Date-of-acquisition:s-->
-  <index_subfields tag="952" subfields="d">
-    <target_index>Date-of-acquisition:w</target_index>
-    <target_index>Date-of-acquisition:d</target_index>
-    <target_index>Date-of-acquisition:s</target_index>
-  </index_subfields>
-  <!--record.abs line 306: melm 952$e      acqsource-->
-  <index_subfields tag="952" subfields="e">
-    <target_index>acqsource:w</target_index>
-  </index_subfields>
-  <!--record.abs line 307: melm 952$f      coded-location-qualifier-->
-  <index_subfields tag="952" subfields="f">
-    <target_index>coded-location-qualifier:w</target_index>
-  </index_subfields>
-  <!--record.abs line 308: melm 952$g      price-->
-  <index_subfields tag="952" subfields="g">
-    <target_index>price:w</target_index>
-  </index_subfields>
-  <!--record.abs line 311: melm 952$j      stack:n,stack:w-->
-  <index_subfields tag="952" subfields="j">
-    <target_index>stack:n</target_index>
-    <target_index>stack:w</target_index>
-  </index_subfields>
-  <!--record.abs line 313: melm 952$l      issues:n,issues:w,issues:s-->
-  <index_subfields tag="952" subfields="l">
-    <target_index>issues:n</target_index>
-    <target_index>issues:w</target_index>
-    <target_index>issues:s</target_index>
-  </index_subfields>
-  <!--record.abs line 314: melm 952$m      renewals:n,renewals:w-->
-  <index_subfields tag="952" subfields="m">
-    <target_index>renewals:n</target_index>
-    <target_index>renewals:w</target_index>
-  </index_subfields>
-  <!--record.abs line 315: melm 952$n      reserves:n,reserves:w-->
-  <index_subfields tag="952" subfields="n">
-    <target_index>reserves:n</target_index>
-    <target_index>reserves:w</target_index>
-  </index_subfields>
-  <!--record.abs line 316: melm 952$o      Local-classification:w,Local-classification:p,Local-classification:s-->
-  <index_subfields tag="952" subfields="o">
-    <target_index>Local-classification:w</target_index>
-    <target_index>Local-classification:p</target_index>
-    <target_index>Local-classification:s</target_index>
-  </index_subfields>
-  <!--record.abs line 317: melm 952$p      barcode,barcode:n-->
-  <index_subfields tag="952" subfields="p">
-    <target_index>barcode:w</target_index>
-    <target_index>barcode:n</target_index>
-  </index_subfields>
-  <!--record.abs line 318: melm 952$q      onloan:n,onloan:w-->
-  <index_subfields tag="952" subfields="q">
-    <target_index>onloan:n</target_index>
-    <target_index>onloan:w</target_index>
-  </index_subfields>
-  <!--record.abs line 319: melm 952$r      datelastseen-->
-  <index_subfields tag="952" subfields="r">
-    <target_index>datelastseen:w</target_index>
-  </index_subfields>
-  <!--record.abs line 320: melm 952$s      datelastborrowed-->
-  <index_subfields tag="952" subfields="s">
-    <target_index>datelastborrowed:w</target_index>
-  </index_subfields>
-  <!--record.abs line 321: melm 952$t      copynumber-->
-  <index_subfields tag="952" subfields="t">
-    <target_index>copynumber:w</target_index>
-  </index_subfields>
-  <!--record.abs line 322: melm 952$u      uri:u-->
-  <index_subfields tag="952" subfields="u">
-    <target_index>uri:u</target_index>
-  </index_subfields>
-  <!--record.abs line 323: melm 952$v      replacementprice-->
-  <index_subfields tag="952" subfields="v">
-    <target_index>replacementprice:w</target_index>
-  </index_subfields>
-  <!--record.abs line 324: melm 952$w      replacementpricedate-->
-  <index_subfields tag="952" subfields="w">
-    <target_index>replacementpricedate:w</target_index>
-  </index_subfields>
-  <!--record.abs line 326: melm 952$y      itype:w-->
-  <index_subfields tag="952" subfields="y">
-    <target_index>itype:w</target_index>
-  </index_subfields>
-
-  <index_subfields tag="999" subfields="x">
-    <target_index>not-onloan-count:n</target_index>
-  </index_subfields>
-
-  <facet tag="100" subfields="a">
-    <target_index>au:0</target_index>
-  </facet>
-  <facet tag="110" subfields="a">
-    <target_index>au:0</target_index>
-  </facet>
-  <facet tag="440" subfields="a">
-    <target_index>se:0</target_index>
-  </facet>
-  <facet tag="490" subfields="a">
-    <target_index>>se:0</target_index>
-  </facet>
-  <facet tag="630" subfields="a">
-    <target_index>su-ut:0</target_index>
-  </facet>
-  <facet tag="650" subfields="a">
-    <target_index>su-to:0</target_index>
-  </facet>
-  <facet tag="651" subfields="a">
-    <target_index>su-geo:0</target_index>
-  </facet>
-  <facet tag="700" subfields="a">
-    <target_index>au:0</target_index>
-  </facet>
-  <facet tag="942" subfields="c">
-    <target_index>itype:0</target_index>
-  </facet>
-  <facet tag="952" subfields="a">
-    <target_index>homebranch:0</target_index>
-  </facet>
-  <facet tag="952" subfields="b">
-    <target_index>holdingbranch:0</target_index>
-  </facet>
-  <facet tag="952" subfields="c">
-    <target_index>location:0</target_index>
-  </facet>
-  <facet tag="952" subfields="y">
-    <target_index>itype:0</target_index>
-  </facet>
-  <facet tag="952" subfields="8">
-    <target_index>ccode:0</target_index>
-  </facet>
-
-</kohaidx:index_defs>
diff --git a/etc/zebradb/marc_defs/normarc/biblios/biblio-zebra-indexdefs.xsl b/etc/zebradb/marc_defs/normarc/biblios/biblio-zebra-indexdefs.xsl
deleted file mode 100644
index a7aae13203..0000000000
--- a/etc/zebradb/marc_defs/normarc/biblios/biblio-zebra-indexdefs.xsl
+++ /dev/null
@@ -1,1909 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-This file has been automatically generated from a Koha index definition file
-with the stylesheet koha-indexdefs-to-zebra.xsl. Do not manually edit this file,
-as it may be overwritten. To regenerate, edit the appropriate Koha index
-definition file (probably something like {biblio,authority}-koha-indexdefs.xml) and run:
-`xsltproc koha-indexdefs-to-zebra.xsl {biblio,authority}-koha-indexdefs.xml >
-{biblio,authority}-zebra-indexdefs.xsl` (substituting the appropriate file names).
--->
-<xslo:stylesheet xmlns:xslo="http://www.w3.org/1999/XSL/Transform" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:z="http://indexdata.com/zebra-2.0" xmlns:kohaidx="http://www.koha-community.org/schemas/index-defs" version="1.0">
-  <xslo:output indent="yes" method="xml" version="1.0" encoding="UTF-8"/>
-  <xslo:template match="text()"/>
-  <xslo:template match="text()" mode="index_subfields"/>
-  <xslo:template match="text()" mode="index_data_field"/>
-  <xslo:template match="text()" mode="index_facets"/>
-  <xslo:template match="text()" mode="index_heading"/>
-  <xslo:template match="text()" mode="index_heading_conditional"/>
-  <xslo:template match="text()" mode="index_match_heading"/>
-  <xslo:template match="text()" mode="index_subject_thesaurus"/>
-  <xslo:template match="text()" mode="index_sort_tit"/>
-  <xslo:template match="/">
-    <xslo:if test="marc:collection">
-      <collection>
-        <xslo:apply-templates select="marc:collection/marc:record"/>
-      </collection>
-    </xslo:if>
-    <xslo:if test="marc:record">
-      <xslo:apply-templates select="marc:record"/>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template match="marc:record">
-    <xslo:variable name="idfield" select="normalize-space(marc:datafield[@tag='999']/marc:subfield[@code='c'])"/>
-    <z:record type="update">
-      <xslo:attribute name="z:id">
-        <xslo:value-of select="$idfield"/>
-      </xslo:attribute>
-      <xslo:apply-templates/>
-      <xslo:apply-templates mode="index_subfields"/>
-      <xslo:apply-templates mode="index_data_field"/>
-      <xslo:apply-templates mode="index_facets"/>
-      <xslo:apply-templates mode="index_heading"/>
-      <xslo:apply-templates mode="index_heading_conditional"/>
-      <xslo:apply-templates mode="index_match_heading"/>
-      <xslo:apply-templates mode="index_subject_thesaurus"/>
-      <xslo:apply-templates mode="index_all"/>
-      <xslo:apply-templates mode="index_sort_tit"/>
-    </z:record>
-  </xslo:template>
-  <xslo:template match="marc:leader">
-    <z:index name="llength:w">
-      <xslo:value-of select="substring(., 1, 5)"/>
-    </z:index>
-    <z:index name="rtype:w">
-      <xslo:value-of select="substring(., 7, 1)"/>
-    </z:index>
-    <z:index name="Bib-level:w">
-      <xslo:value-of select="substring(., 8, 1)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template match="marc:controlfield[@tag='001']">
-    <z:index name="Control-number:w">
-      <xslo:value-of select="."/>
-    </z:index>
-  </xslo:template>
-  <xslo:template match="marc:controlfield[@tag='007']">
-    <z:index name="Microform-generation:n">
-      <xslo:value-of select="substring(., 12, 1)"/>
-    </z:index>
-    <z:index name="Material-type:w">
-      <xslo:value-of select="."/>
-    </z:index>
-    <z:index name="ff7-00:w">
-      <xslo:value-of select="substring(., 1, 1)"/>
-    </z:index>
-    <z:index name="ff7-01:w">
-      <xslo:value-of select="substring(., 2, 1)"/>
-    </z:index>
-    <z:index name="ff7-02:w">
-      <xslo:value-of select="substring(., 3, 1)"/>
-    </z:index>
-    <z:index name="ff7-01-02:w">
-      <xslo:value-of select="substring(., 1, 2)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template match="marc:controlfield[@tag='008']">
-    <z:index name="date-entered-on-file:n">
-      <xslo:value-of select="substring(., 1, 5)"/>
-    </z:index>
-    <z:index name="date-entered-on-file:s">
-      <xslo:value-of select="substring(., 1, 5)"/>
-    </z:index>
-    <z:index name="pubdate:w">
-      <xslo:value-of select="substring(., 8, 4)"/>
-    </z:index>
-    <z:index name="pubdate:n">
-      <xslo:value-of select="substring(., 8, 4)"/>
-    </z:index>
-    <z:index name="pubdate:y">
-      <xslo:value-of select="substring(., 8, 4)"/>
-    </z:index>
-    <z:index name="pubdate:s">
-      <xslo:value-of select="substring(., 8, 4)"/>
-    </z:index>
-    <z:index name="pl:w">
-      <xslo:value-of select="substring(., 16, 3)"/>
-    </z:index>
-    <z:index name="ta:w">
-      <xslo:value-of select="substring(., 23, 1)"/>
-    </z:index>
-    <z:index name="ff8-23:w">
-      <xslo:value-of select="substring(., 24, 1)"/>
-    </z:index>
-    <z:index name="ff8-29:w">
-      <xslo:value-of select="substring(., 30, 1)"/>
-    </z:index>
-    <z:index name="lf:w">
-      <xslo:value-of select="substring(., 34, 1)"/>
-    </z:index>
-    <z:index name="bio:w">
-      <xslo:value-of select="substring(., 35, 1)"/>
-    </z:index>
-    <z:index name="ln:w">
-      <xslo:value-of select="substring(., 36, 3)"/>
-    </z:index>
-    <z:index name="ctype:w">
-      <xslo:value-of select="substring(., 25, 4)"/>
-    </z:index>
-    <z:index name="Record-source:w">
-      <xslo:value-of select="substring(., 40, 0)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='020']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="ISBN:w Identifier-standard:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='022']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="ISSN:w ISBN:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='100']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Cross-Reference:w Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Author:w Author:p Author:s Editor:w Author-personal-bibliography:w Author-personal-bibliography:p Author-personal-bibliography:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='110']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='111']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='130']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="Music-key:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='240']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="Music-key:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='245']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Title-cover:w Title-cover:p Title-cover:s Title:w Title:p Title:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('c', @code)">
-        <z:index name="Author:w Author-in-order:w Author-in-order:p Author-in-order:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Cross-Reference:w Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='260']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="pl:w pl:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('b', @code)">
-        <z:index name="Publisher:w Publisher:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('c', @code)">
-        <z:index name="copydate:w copydate:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='440']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Title-series:w Title-series:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='490']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Title-series:w Title-series:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='600']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w Name:w Personal-name:w Subject-name-personal:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Name-and-title:w Title:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='610']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Name-and-title:w Title:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='611']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Name-and-title:w Title:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='630']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Thematic-number:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="Music-key:w Subject:w Subject:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='648']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='650']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='651']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='652']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='653']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='654']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='655']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='656']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='657']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='690']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='700']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Cross-Reference:w Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Author:w Author:p">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="Music-key:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Name-and-title:w Title:w Title-uniform:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='710']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Name-and-title:w Title:w Title-uniform:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='711']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Title:w Title-uniform:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='730']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Thematic-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="Music-key:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='773']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Host-item:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Host-Item-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Host-item:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('w', @code)">
-        <z:index name="Record-control-number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='780']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='800']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Name-and-title:w Title:w Title-series:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='810']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Name-and-title:w Title:w Title-series:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='811']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="Name-and-title:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="Author-title:w Name-and-title:w Title:w Title-series:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='830']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="Koha-Auth-Number:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='999']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('c', @code)">
-        <z:index name="Local-Number:n Local-Number:w Local-Number:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('d', @code)">
-        <z:index name="biblioitemnumber:n biblioitemnumber:w biblioitemnumber:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('x', @code)">
-        <z:index name="not-onloan-count:n">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='942']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('0', @code)">
-        <z:index name="totalissues:n totalissues:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('2', @code)">
-        <z:index name="cn-bib-source:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('6', @code)">
-        <z:index name="cn-bib-sort:n cn-bib-sort:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('c', @code)">
-        <z:index name="itemtype:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="Suppress:w Suppress:n">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('h', @code)">
-        <z:index name="cn-class:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('i', @code)">
-        <z:index name="cn-item:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('k', @code)">
-        <z:index name="cn-prefix:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('m', @code)">
-        <z:index name="cn-suffix:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_subfields" match="marc:datafield[@tag='952']">
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('0', @code)">
-        <z:index name="withdrawn:n withdrawn:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('1', @code)">
-        <z:index name="lost:w lost:n">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('2', @code)">
-        <z:index name="classification-source:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('3', @code)">
-        <z:index name="materials-specified:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('4', @code)">
-        <z:index name="damaged:n damaged:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('5', @code)">
-        <z:index name="restricted:n restricted:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('6', @code)">
-        <z:index name="cn-sort:n cn-sort:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('7', @code)">
-        <z:index name="notforloan:n notforloan:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('8', @code)">
-        <z:index name="ccode:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('9', @code)">
-        <z:index name="itemnumber:n itemnumber:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('a', @code)">
-        <z:index name="homebranch:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('b', @code)">
-        <z:index name="holdingbranch:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('c', @code)">
-        <z:index name="location:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('d', @code)">
-        <z:index name="Date-of-acquisition:w Date-of-acquisition:d Date-of-acquisition:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('e', @code)">
-        <z:index name="acqsource:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('f', @code)">
-        <z:index name="coded-location-qualifier:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('g', @code)">
-        <z:index name="price:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('j', @code)">
-        <z:index name="stack:n stack:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('l', @code)">
-        <z:index name="issues:n issues:w issues:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('m', @code)">
-        <z:index name="renewals:n renewals:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('n', @code)">
-        <z:index name="reserves:n reserves:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('o', @code)">
-        <z:index name="Local-classification:w Local-classification:p Local-classification:s">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('p', @code)">
-        <z:index name="barcode:w barcode:n">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('q', @code)">
-        <z:index name="onloan:n onloan:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('r', @code)">
-        <z:index name="datelastseen:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('s', @code)">
-        <z:index name="datelastborrowed:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('t', @code)">
-        <z:index name="copynumber:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('u', @code)">
-        <z:index name="uri:u">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('v', @code)">
-        <z:index name="replacementprice:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('w', @code)">
-        <z:index name="replacementpricedate:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-    <xslo:for-each select="marc:subfield">
-      <xslo:if test="contains('y', @code)">
-        <z:index name="itype:w">
-          <xslo:value-of select="."/>
-        </z:index>
-      </xslo:if>
-    </xslo:for-each>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='010']">
-    <z:index name="LC-card-number:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='015']">
-    <z:index name="BNB-card-number:w BGF-number:w Number-db:w Number-natl-biblio:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='020']">
-    <z:index name="ISBN:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='022']">
-    <z:index name="ISSN:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='024']">
-    <z:index name="Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='025']">
-    <z:index name="Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='027']">
-    <z:index name="Report-number:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='028']">
-    <z:index name="Identifier-publisher-for-music:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='030']">
-    <z:index name="CODEN:w Identifier-standard:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='040']">
-    <z:index name="Code-institution:w Record-source:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='041']">
-    <z:index name="ln:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='043']">
-    <z:index name="Code-geographic:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='060']">
-    <z:index name="NLM-call-number:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='080']">
-    <z:index name="UDC-classification:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='082']">
-    <z:index name="Dewey-classification:w Dewey-classification:s">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='100']">
-    <z:index name="Author:w Author-title:w Author-name-personal:w Name:w Name-and-title:w Personal-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='110']">
-    <z:index name="Author:w Author-title:w Author-name-corporate:w Name:w Name-and-title:w Corporate-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='111']">
-    <z:index name="Author:w Author-title:w Author-name-corporate:w Name:w Name-and-title:w Conference-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='130']">
-    <z:index name="Title:w Title-uniform:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='210']">
-    <z:index name="Title:w Title-abbreviated:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='222']">
-    <z:index name="Title:w Title-key:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='240']">
-    <z:index name="Title:w Title:p Title-uniform:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='245']">
-    <z:index name="Title:w Title:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='246']">
-    <z:index name="Title:w Title:p Title-abbreviated:w Title-expanded:w Title-former:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='260']">
-    <z:index name="pl:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='300']">
-    <z:index name="Extent:w Extent:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='440']">
-    <z:index name="Title-series:w Title-series:p Title:w Title-series:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='490']">
-    <z:index name="Title:w Title-series:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='502']">
-    <z:index name="Material-type:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='505']">
-    <z:index name="Note:w Note:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='510']">
-    <z:index name="Indexed-by:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='520']">
-    <z:index name="Abstract:w Abstract:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='600']">
-    <z:index name="Name:w Personal-name:w Subject-name-personal:w Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='610']">
-    <z:index name="Name:w Subject:w Corporate-name:w Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='611']">
-    <z:index name="Conference-name:w Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-    <z:index name="Name:w Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='630']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='650']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='651']">
-    <z:index name="Name-geographic:w Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='653']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='654']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='655']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='656']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='657']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='690']">
-    <z:index name="Subject:w Subject:p">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='700']">
-    <z:index name="Author:w Author-name-corporate:w Author-name-personal:w Name:w Editor:w Personal-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='710']">
-    <z:index name="Author:w Corporate-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-    <z:index name="Author:w Name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='711']">
-    <z:index name="Author-name-corporate:w Name:w Conference-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='730']">
-    <z:index name="Title:w Title-uniform:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='740']">
-    <z:index name="Title:w Title-other-variant:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='780']">
-    <z:index name="Title:w Title-former:w Related-periodical:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='785']">
-    <z:index name="Related-periodical:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='800']">
-    <z:index name="Author:w Author-name-personal:w Name:w Personal-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='810']">
-    <z:index name="Author:w Corporate-name:w Author-name-corporate:w Name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='811']">
-    <z:index name="Author:w Author-name-corporate:w Name:w Conference-name:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_data_field" match="marc:datafield[@tag='830']">
-    <z:index name="Title:w Title-series:w">
-      <xslo:variable name="raw_heading">
-        <xslo:for-each select="marc:subfield">
-          <xslo:if test="position() > 1">
-            <xslo:value-of select="substring(' ', 1, 1)"/>
-          </xslo:if>
-          <xslo:value-of select="."/>
-        </xslo:for-each>
-      </xslo:variable>
-      <xslo:value-of select="normalize-space($raw_heading)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='100']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="au:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='110']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="au:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='440']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="se:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='490']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name=">se:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='630']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="su-ut:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='650']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="su-to:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='651']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="su-geo:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='700']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="au:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='942']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="itype:0">
-        <xslo:value-of select="marc:subfield[@code='c']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_facets" match="marc:datafield[@tag='952']">
-    <xslo:if test="not(@ind1='z')">
-      <z:index name="homebranch:0">
-        <xslo:value-of select="marc:subfield[@code='a']"/>
-      </z:index>
-      <z:index name="holdingbranch:0">
-        <xslo:value-of select="marc:subfield[@code='b']"/>
-      </z:index>
-      <z:index name="location:0">
-        <xslo:value-of select="marc:subfield[@code='c']"/>
-      </z:index>
-      <z:index name="itype:0">
-        <xslo:value-of select="marc:subfield[@code='y']"/>
-      </z:index>
-      <z:index name="ccode:0">
-        <xslo:value-of select="marc:subfield[@code='8']"/>
-      </z:index>
-    </xslo:if>
-  </xslo:template>
-  <xslo:template mode="index_all" match="text()">
-    <z:index name="Any:w Any:p">
-      <xslo:value-of select="."/>
-    </z:index>
-  </xslo:template>
-  <xslo:template mode="index_sort_tit" match="marc:datafield[@tag='245']">
-    <xslo:variable name="chop">
-      <xslo:choose>
-        <xslo:when test="not(number(@ind2))">0</xslo:when>
-        <xslo:otherwise>
-          <xslo:value-of select="number(@ind2)"/>
-        </xslo:otherwise>
-      </xslo:choose>
-    </xslo:variable>
-    <z:index name="Title:s">
-      <xslo:value-of select="substring(marc:subfield[@code='a'], $chop+1)"/>
-    </z:index>
-  </xslo:template>
-  <xslo:template name="chopPunctuation">
-    <xslo:param name="chopString"/>
-    <xslo:variable name="length" select="string-length($chopString)"/>
-    <xslo:choose>
-      <xslo:when test="$length=0"/>
-      <xslo:when test="contains('-,.:=;!%/', substring($chopString,$length,1))">
-        <xslo:call-template name="chopPunctuation">
-          <xslo:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
-        </xslo:call-template>
-      </xslo:when>
-      <xslo:when test="not($chopString)"/>
-      <xslo:otherwise>
-        <xslo:value-of select="$chopString"/>
-      </xslo:otherwise>
-    </xslo:choose>
-    <xslo:text/>
-  </xslo:template>
-</xslo:stylesheet>
diff --git a/etc/zebradb/marc_defs/normarc/biblios/preprocess_marcxml.xsl b/etc/zebradb/marc_defs/normarc/biblios/preprocess_marcxml.xsl
deleted file mode 100644
index 8140c0af59..0000000000
--- a/etc/zebradb/marc_defs/normarc/biblios/preprocess_marcxml.xsl
+++ /dev/null
@@ -1,23 +0,0 @@
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:marc="http://www.loc.gov/MARC21/slim"
- version="1.0">
-<xsl:output indent="yes"
-      method="xml"
-      version="1.0"
-      encoding="UTF-8"/>
-
- <xsl:template name="identity" match="node()|@*">
-   <xsl:copy>
-     <xsl:apply-templates select="@*|node()"/>
-   </xsl:copy>
- </xsl:template>
-
- <xsl:template match="marc:datafield[@tag=880]">
-    <xsl:element name="datafield" namespace="http://www.loc.gov/MARC21/slim">
-        <xsl:attribute name="tag"><xsl:value-of select="substring(marc:subfield[@code=6],1,3)"/></xsl:attribute>
-        <xsl:attribute name="ind1"><xsl:value-of select="@ind1"/></xsl:attribute>
-        <xsl:attribute name="ind2"><xsl:value-of select="@ind2"/></xsl:attribute>
-        <xsl:apply-templates select="marc:subfield[@code != '6']"/>
-    </xsl:element>
-</xsl:template>
-
-</xsl:stylesheet>

From b40117a31b244c029bcd3fe2c37cd8a7ff76ee27 Mon Sep 17 00:00:00 2001
From: Katrin Fischer <katrin.fischer.83@web.de>
Date: Fri, 12 Jan 2024 22:52:42 +0000
Subject: [PATCH 237/429] Bug 35769: Fix untranslatable strings in request.tt

This fixes some strings in the JavaScript part of the
template that were missing from the po files.

This is one way to test:
* Apply patch
* Run gulp po:update --lang <language-code>
* Compare strings changed in the patch, with strings
  appearing in <language-code>-staff-prog.po
* Translate the strings and remove "fuzzy" line
* In k-t-d, run:
  sudo koha-translate --install <language-code> --dev <instance>
* Verify that now the translated version of the template contains
  your translated strings
* Verify the translated page works correctly, no errors

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 928b6060830d18f9a6b20510e991ac486836b687)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
index cdcd729bff..a3d34cc8b7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
@@ -1725,7 +1725,7 @@
 
                     if ( selected_items.closest('tr').find(".pickup_locations").val() == '' ) {
 
-                        msg = _("- Please select a pickup location for the item" + "\n")
+                        msg = (_("- Please select a pickup location for the item") + "\n");
                     }
                 }
                 else {
@@ -1743,7 +1743,7 @@
             } else {
                 // Requesting next available
                 if( $("#pickup-next-avail").length < 1 || $("#pickup-next-avail").val() == "" || $('#pickup-next-avail').val() === null ){
-                    msg = _("- Please select a pickup location for this hold" + "\n");
+                    msg = (_("- Please select a pickup location for this hold") + "\n");
                 }
             }
 

From 589cc320faf66a3a48440be3770d3586f93816a4 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 24 Jan 2024 16:20:50 +0100
Subject: [PATCH 238/429] Bug 35769: Remove extra parenthesis

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 07cf21e6b915d34d64dc96995f6df8fce50c6798)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
index a3d34cc8b7..eb2dec81e6 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
@@ -1725,11 +1725,11 @@
 
                     if ( selected_items.closest('tr').find(".pickup_locations").val() == '' ) {
 
-                        msg = (_("- Please select a pickup location for the item") + "\n");
+                        msg = _("- Please select a pickup location for the item") + "\n"
                     }
                 }
                 else {
-                    msg = (_("- Please select an item to place a hold") + "\n");
+                    msg = _("- Please select an item to place a hold") + "\n";
                 }
             } else if ( $("#requestgrp").is(":checked") ) {
                 var selected_group = $('#requestgroup input[type="radio"]:checked');
@@ -1743,7 +1743,7 @@
             } else {
                 // Requesting next available
                 if( $("#pickup-next-avail").length < 1 || $("#pickup-next-avail").val() == "" || $('#pickup-next-avail').val() === null ){
-                    msg = (_("- Please select a pickup location for this hold") + "\n");
+                    msg = _("- Please select a pickup location for this hold") + "\n";
                 }
             }
 

From 106e88f28187472f8a258713a4d29fcd0cbf3a08 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 24 Sep 2024 14:20:59 +0200
Subject: [PATCH 239/429] Bug 37395: Fix columns visibility on Vue tables

On bug 33066 we reworked a lot of datatables.js to make the JS function
reusable from the Vue component (and especially the KohaTable
component). I guess the regression comes from this change, but I am
surprised that we didn't catch it when testing.

The was a mistake for _dt_visibility, we didn't pass the node. Actually
we cannot pass it as we do not have it yet.

This "node" variable is only used when bKohaColumnsUseNames is passed
(ie. we do not rely on the order of the columns but we need to rely on
their data-colname attributes).

There is then a limitation here: we cannot use bKohaColumnsUseNames from
Vue, but we do not yet.

An ideal fix would be too big for now, especially for an hypothetic use
case.

Test plan:
Confirm that the "Columns" button are now back for the tables of the Vue
apps (ERM, Preservation) and that the settings are taken into account
(hide by default, etc.)

Signed-off-by: Olivier V <olivier.vezina@inLibro.com>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1a8542f05dbde8476d634a0da1ac19386358c7d4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/js/datatables.js | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js
index 2cc1792770..d8cf30b90b 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js
@@ -774,16 +774,24 @@ function _dt_buttons(params){
     return buttons;
 }
 
-function _dt_visibility(node, table_settings, settings){
+function _dt_visibility(table_settings, settings, node){
     var counter = 0;
     let hidden_ids = [];
     let included_ids = [];
     if ( table_settings ) {
         var columns_settings = table_settings['columns'];
         $(columns_settings).each( function() {
-            let selector = '#' + node.attr('id');
-            var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', selector ).index( selector + ' th' );
-            var used_id = settings.bKohaColumnsUseNames ? named_id : counter;
+            let used_id = counter;
+            if ( settings.bKohaColumnsUseNames ) {
+                if (!node){
+                    console.err("settings.bKohaColumnsUseNames is set but node not passed");
+                    return;
+                }
+                let selector = '#' + node.attr('id');
+                var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', selector ).index( selector + ' th' );
+                used_id = named_id;
+            }
+
             if ( used_id == -1 ) return;
 
             if ( this['is_hidden'] == "1" ) {
@@ -969,7 +977,7 @@ function _dt_add_delay(table_dt, table_node, delay_ms) {
         }
 
         let hidden_ids, included_ids;
-        [hidden_ids, included_ids] = _dt_visibility(this, table_settings, settings)
+        [hidden_ids, included_ids] = _dt_visibility(table_settings, settings, this)
 
         settings["buttons"] = _dt_buttons({included_ids, settings, table_settings});
 

From 30dc2f94f69fb9c1d6138b0705a6ba315ec128d4 Mon Sep 17 00:00:00 2001
From: Magnus Enger <magnus@libriotech.no>
Date: Fri, 30 Aug 2024 10:00:25 +0200
Subject: [PATCH 240/429] Bug 37787: Undocument koha-worker --queue
 elastic_index

To test:
- Copy the koha-worker script to the standard location:
  $ sudo cp debian/scripts/koha-worker /usr/sbin/koha-worker
- Check "sudo koha-worker --help" and verify elastic_index is
  not mentioned in the list of "current queues"
- See https://wiki.koha-community.org/wiki/Testing_man_pages for
  how to check the "man" page. elastic_index should not be
  mentioned in the list of "Current queues".
- Both places aboce should have a note about elastic_index not
  being active when koha-es-indexer is running.

Signed-off-by: Sukhmandeep Benipal <sukhmandeep.benipal@inLibro.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit dba23e4da1996340eb833ec41f865585c3104e7c)
---
 debian/docs/koha-worker.xml | 3 ++-
 debian/scripts/koha-worker  | 5 ++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/debian/docs/koha-worker.xml b/debian/docs/koha-worker.xml
index f230b68fcf..56a5f0cb82 100644
--- a/debian/docs/koha-worker.xml
+++ b/debian/docs/koha-worker.xml
@@ -63,7 +63,8 @@
         <term><option>--queue</option></term>
         <listitem>
           <para>Allows specifying the queue worker (defaults to 'default').</para>
-          <para>Current queues are: default, long_tasks, elastic_index.</para>
+          <para>Current queues are: default, long_tasks.</para>
+          <para>Note: There used to be a queue called elastic_index, but since the introduction of koha-es-indexer this queue should not be active.</para>
         </listitem>
     </varlistentry>
     <varlistentry>
diff --git a/debian/scripts/koha-worker b/debian/scripts/koha-worker
index d1d1601357..aa51c90c13 100755
--- a/debian/scripts/koha-worker
+++ b/debian/scripts/koha-worker
@@ -48,12 +48,15 @@ $scriptname -h|--help
     --stop                Stop the worker daemon for the specified instances
     --restart             Restart the worker daemon for the specified instances
     --queue               Specify the queue/worker to restart - 'default' is used if not specified
-                          current queues are: default, long_tasks, elastic_index
+                          current queues are: default, long_tasks
     --status              Show the status of the worker for the specified instances
     --quiet|-q            Make the script quiet about non existent instance names
                           (useful for calling from another scripts).
     --help|-h             Display this help message
 
+Note: There used to be a queue called elastic_index, but since the introduction
+of koha-es-indexer this queue should not be active.
+
 EOF
 }
 

From 193f5676bf9dfc9b78fa256b26b608bf6d44021f Mon Sep 17 00:00:00 2001
From: Magnus Enger <magnus@libriotech.no>
Date: Thu, 5 Sep 2024 08:38:44 +0200
Subject: [PATCH 241/429] Bug 37787: Remove mention of elastic_index from POD

After the introduction of misc/workers/es_indexer_daemon.pl,
misc/workers/background_jobs_worker.pl should not handle the
elastic_index queue. But the latter script still mentions this
queue in the POD. This patch removes that mention.

To test:
- Run perldoc misc/workers/background_jobs_worker.pl
- Verify "elastic_index" is mentioned under --queue
- Apply this patch
- Run perldoc again and verify the mention of "elastic_index"
  is gone

Signed-off-by: Sukhmandeep Benipal <sukhmandeep.benipal@inLibro.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 476ae27df0608c54c63759158e6631a4a0879cb2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/workers/background_jobs_worker.pl | 1 -
 1 file changed, 1 deletion(-)

diff --git a/misc/workers/background_jobs_worker.pl b/misc/workers/background_jobs_worker.pl
index e2472d0fcd..c034d22ca2 100755
--- a/misc/workers/background_jobs_worker.pl
+++ b/misc/workers/background_jobs_worker.pl
@@ -47,7 +47,6 @@ The different values available are:
 
     default
     long_tasks
-    elastic_index
 
 =back
 

From 5c702246449dba57c5a383752e314cde02be9241 Mon Sep 17 00:00:00 2001
From: Magnus Enger <magnus@libriotech.no>
Date: Tue, 15 Oct 2024 13:47:25 +0200
Subject: [PATCH 242/429] Bug 38173: Fix description of koha-dump
 --exclude-indexes

To test:
- Look at debian/scripts/koha-dump
- The description of --exclude-indexes reads:
  "Include Zebra indexes on the backup"
- Apply the patch
- Look at the file again. The description is now:
  "Exclude Zebra indexes from the backup"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0c23b98782520794f1d56b72be6dc10180b08b9c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 debian/scripts/koha-dump | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/debian/scripts/koha-dump b/debian/scripts/koha-dump
index 6ed0f1b683..e8e1b06df3 100755
--- a/debian/scripts/koha-dump
+++ b/debian/scripts/koha-dump
@@ -47,7 +47,7 @@ $scriptname [--quiet|-q] [--exclude-indexes] instancename1 [instancename2...]
 $scriptname -h|--help
 
     --schema-only         Dump only the database schema
-    --exclude-indexes     Include Zebra indexes on the backup
+    --exclude-indexes     Exclude Zebra indexes from the backup
     --exclude-logs        Exclude /var/log/koha/name from the backup
     --uploaded_files      Include uploaded files.
     --uploaded_temp_files Include temporary uploaded files.

From 8f96db1238c393e4510f49bad8300b92c767473e Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 16 Oct 2024 10:11:24 +0200
Subject: [PATCH 243/429] Bug 38177: Do not use stash to retrieve parameters

The pagination for EBSCO's titles, packages and resources is broken. We
shouldn't rely on $c->stash but use params from the request instead.

Test plan:
1) Setup all the system preferences in ERM:
<staff_url>/cgi-bin/koha/admin/preferences.pl?tab=erm
2) Enable EBSCO in ERMProviders. Set the ERMProviderEbscoApiKey and ERMProviderEbscoCustomerID prefs.
3) Do a search that returns more than 20 records. Click on a page other than 1. Notice the records do not change (Page is the same).
4) Apply patch. Repeat. Notice pagination now works.

Search titles, packages and resources
Use the pagination and modify the number of results per page

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 61b3aa2aa298b46835d729f77a6a44a0d0faaa05)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/Providers/EBSCO.pm                   | 4 ++--
 Koha/REST/V1/ERM/EHoldings/Packages/EBSCO.pm  | 7 +------
 Koha/REST/V1/ERM/EHoldings/Resources/EBSCO.pm | 7 +------
 Koha/REST/V1/ERM/EHoldings/Titles/EBSCO.pm    | 7 +------
 4 files changed, 5 insertions(+), 20 deletions(-)

diff --git a/Koha/ERM/Providers/EBSCO.pm b/Koha/ERM/Providers/EBSCO.pm
index c0904cb225..5e3aa377ef 100644
--- a/Koha/ERM/Providers/EBSCO.pm
+++ b/Koha/ERM/Providers/EBSCO.pm
@@ -210,10 +210,10 @@ sub embed {
 
 sub build_query_pagination {
     my ( $self, $params ) = @_;
-    my $per_page = $params->{per_page}
+    my $per_page = $params->{_per_page}
       // C4::Context->preference('RESTdefaultPageSize') // 20;
     if ( $per_page == -1 || $per_page > 100 ) { $per_page = 100; }
-    my $page = $params->{page} || 1;
+    my $page = $params->{_page} || 1;
 
     return ( $per_page, $page );
 }
diff --git a/Koha/REST/V1/ERM/EHoldings/Packages/EBSCO.pm b/Koha/REST/V1/ERM/EHoldings/Packages/EBSCO.pm
index b4a59c4128..5f92ba2a6a 100644
--- a/Koha/REST/V1/ERM/EHoldings/Packages/EBSCO.pm
+++ b/Koha/REST/V1/ERM/EHoldings/Packages/EBSCO.pm
@@ -35,12 +35,7 @@ sub list {
         my $result     = $ebsco->request( GET => '/packages' . $params );
         my $base_total = $result->{totalResults};
 
-        my ( $per_page, $page ) = $ebsco->build_query_pagination(
-            {
-                per_page => $c->stash('koha.pagination.per_page'),
-                page     => $c->stash('koha.pagination.page'),
-            }
-        );
+        my ( $per_page, $page ) = $ebsco->build_query_pagination( $c->req->params->to_hash );
         my $additional_params =
           $ebsco->build_additional_params( $c->req->params->to_hash );
 
diff --git a/Koha/REST/V1/ERM/EHoldings/Resources/EBSCO.pm b/Koha/REST/V1/ERM/EHoldings/Resources/EBSCO.pm
index 39ba226752..e8f3c44d20 100644
--- a/Koha/REST/V1/ERM/EHoldings/Resources/EBSCO.pm
+++ b/Koha/REST/V1/ERM/EHoldings/Resources/EBSCO.pm
@@ -73,12 +73,7 @@ sub list {
 
         my $base_total = $result->{totalResults};
 
-        my ( $per_page, $page ) = $ebsco->build_query_pagination(
-            {
-                per_page => $c->stash('koha.pagination.per_page'),
-                page     => $c->stash('koha.pagination.page'),
-            }
-        );
+        my ( $per_page, $page ) = $ebsco->build_query_pagination( $c->req->params->to_hash );
 
         my $additional_params = $ebsco->build_additional_params( $c->req->params->to_hash );
         my $searchfield = 'titlename';
diff --git a/Koha/REST/V1/ERM/EHoldings/Titles/EBSCO.pm b/Koha/REST/V1/ERM/EHoldings/Titles/EBSCO.pm
index cacc1c84b5..d569e75ddc 100644
--- a/Koha/REST/V1/ERM/EHoldings/Titles/EBSCO.pm
+++ b/Koha/REST/V1/ERM/EHoldings/Titles/EBSCO.pm
@@ -41,12 +41,7 @@ sub list {
         my $ebsco = Koha::ERM::Providers::EBSCO->new;
 
         # We cannot get base_total as a search kw is required by the API
-        my ( $per_page, $page ) = $ebsco->build_query_pagination(
-            {
-                per_page => $c->stash('koha.pagination.per_page'),
-                page     => $c->stash('koha.pagination.page'),
-            }
-        );
+        my ( $per_page, $page ) = $ebsco->build_query_pagination( $c->req->params->to_hash );
 
         my $additional_params = $ebsco->build_additional_params( $c->req->params->to_hash );
 

From b2823543f5c35c4925e1ffae000eb098740c9280 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Mon, 21 Oct 2024 15:09:44 +0000
Subject: [PATCH 244/429] Bug 38177: Fix lengthMenu for components using
 KohaTable

EBSCO HoldingsIQ doesnt consider a "All" option so neither should we.
At the moment, if we pick 'All', only 100 are listed due to the logic in Providers/EBSCO/build_query_pagination.
The original code hints at trying to do this but its not working.
I think this got lost sometime during KohaTable.vue

This patch should also have the same entry for EHoldingsEBSCOTitlePackagesList but that isnt using KohaTable and for some reason its not picking up on either aLengthMenu or lengthMeny, always shows 'All'.

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ca0e745c1d573522a19cf62fc38b2241b671c1dd)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../js/vue/components/ERM/EHoldingsEBSCOPackageTitlesList.vue   | 2 +-
 .../prog/js/vue/components/ERM/EHoldingsEBSCOPackagesList.vue   | 2 +-
 .../prog/js/vue/components/ERM/EHoldingsEBSCOTitlesList.vue     | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackageTitlesList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackageTitlesList.vue
index a1f2622bd5..b01a1823ad 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackageTitlesList.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackageTitlesList.vue
@@ -103,7 +103,7 @@ export default {
                     embed: "title",
                     ordering: false,
                     dom: '<"top pager"<"table_entries"ilp>>tr<"bottom pager"ip>',
-                    aLengthMenu: [
+                    lengthMenu: [
                         [10, 20, 50, 100],
                         [10, 20, 50, 100],
                     ],
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackagesList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackagesList.vue
index 64c729a7d8..1bd1b24dc3 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackagesList.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackagesList.vue
@@ -116,7 +116,7 @@ export default {
                     embed: "resources+count,vendor.name",
                     ordering: false,
                     dom: '<"top pager"<"table_entries"ilp>>tr<"bottom pager"ip>',
-                    aLengthMenu: [
+                    lengthMenu: [
                         [10, 20, 50, 100],
                         [10, 20, 50, 100],
                     ],
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlesList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlesList.vue
index aee16c0373..fbc9e0589a 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlesList.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlesList.vue
@@ -116,7 +116,7 @@ export default {
                 options: {
                     ordering: false,
                     dom: '<"top pager"<"table_entries"ilp>>tr<"bottom pager"ip>',
-                    aLengthMenu: [
+                    lengthMenu: [
                         [10, 20, 50, 100],
                         [10, 20, 50, 100],
                     ],

From c746a746742d40d589b6fce954a3e78fe11653ce Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 22 Oct 2024 10:52:46 +0200
Subject: [PATCH 245/429] Bug 38177: Fix lengthMenu by removing extend
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

$.extend is doing a deep copy of the objects.

```
var dataTablesDefaults = { "lengthMenu": [[10, 20, 50, 100, -1], [10, 20, 50, 100, __('All')]] };
var mine = {lengthMenu: [[1, 2, 5, 10], [1, 2, 5, 10]]};
$.extend(true, {}, dataTablesDefaults, mine);
```
Returns:
```
[
  1,
  2,
  5,
  10,
  -1
]
```
Which is ugly and not what we want.

We only need to merge the first level. Using the spread operator (...) will do the trick here.

Note that we certainly want to adjust all the other occurrences of
extend for DT's options.

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b1cd9cccd2918f954b6e675bf1b7bad853548372)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../ERM/EHoldingsEBSCOTitlePackagesList.vue           | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlePackagesList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlePackagesList.vue
index 182a8274ed..752a528610 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlePackagesList.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOTitlePackagesList.vue
@@ -89,13 +89,14 @@ export default {
             $.fn.dataTable.ext.search = $.fn.dataTable.ext.search.filter(
                 search => search.name != "apply_filter"
             )
-            $("#" + table_id).dataTable(
-                $.extend(true, {}, dataTablesDefaults, {
+            $("#" + table_id).dataTable({
+                ...dataTablesDefaults,
+                ...{
                     data: resources,
                     embed: ["package.name"],
                     ordering: false,
                     dom: '<"top pager"<"table_entries"ilp>>tr<"bottom pager"ip>',
-                    aLengthMenu: [
+                    lengthMenu: [
                         [10, 20, 50, 100],
                         [10, 20, 50, 100],
                     ],
@@ -177,8 +178,8 @@ export default {
                             )
                         })
                     },
-                })
-            )
+                },
+            })
         },
     },
     mounted() {

From e890457245b8cf9662b6c7a59dd1828a587c77dc Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Thu, 17 Oct 2024 20:16:14 -0700
Subject: [PATCH 246/429] Bug 38200: Remove dead code to delete authorities in
 authorities/authorities.pl

It has probably never been used. There's no sign of any UI to delete from
the authority editor or auth_finder in the last 13 years of templates, and
it was unused when it landed; the patch that actually made the first patch
work, a month later, put deleting authorities in authorities_home.pl, where
it has been ever since.

It certainly hasn't been used since bug 17600 landed in 2021, since it
doesn't 'use C4::AuthoritiesMarc qw( DelAuthority );' and so if you try to
use it you just get an error for your trouble.

Sponsored-by: Chetco Community Public Library
Signed-off-by: Yanjun Li <yanjun.li@bywatersolutions.com>

Amended-by: Jonathan Druart
tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 3fdf4ebcc185958c47ed47a90c273a3979fd11a6)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 authorities/authorities.pl | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/authorities/authorities.pl b/authorities/authorities.pl
index 761c1f529d..2133008f4f 100755
--- a/authorities/authorities.pl
+++ b/authorities/authorities.pl
@@ -540,7 +540,6 @@ my $error = $input->param('error');
 my $authid = $input->param('authid') =~ s/\D//gr
     ; # if authid exists, it's a modif, not a new authority. We remove from authid all non-digit characters just in case the CGI parameter contains weird characters like spaces
 my $op = $input->param('op');
-my $nonav = $input->param('nonav');
 my $myindex = $input->param('index');
 my $linkid=$input->param('linkid');
 my $authtypecode = $input->param('authtypecode');
@@ -570,7 +569,7 @@ my ($template, $loggedinuser, $cookie)
                             type => "intranet",
                             flagsrequired => {editauthorities => 1},
                             });
-$template->param(nonav   => $nonav,index=>$myindex,authtypecode=>$authtypecode,breedingid=>$breedingid, count=>$count);
+$template->param( index => $myindex, authtypecode => $authtypecode, breedingid => $breedingid, count => $count );
 
 $tagslib = GetTagsLabels(1,$authtypecode);
 $mandatory_z3950 = GetMandatoryFieldZ3950($authtypecode);
@@ -632,15 +631,6 @@ if ($op eq "cud-add") {
                         duplicateauthvalue  => $duplicateauthvalue->{'authorized'}->[0]->{'heading'},
                         );
     }
-} elsif ($op eq "cud-delete") {
-#------------------------------------------------------------------------------------------------------------------------------
-        DelAuthority({ authid => $authid });
-        if ($nonav){
-            print $input->redirect("auth_finder.pl");
-        }else{
-            print $input->redirect("authorities-home.pl?authid=0");
-        }
-                exit;
 } else {
     if ( $op eq "duplicate" ) {
         $authid = "";

From 3bdb9a96242fb6e3e5531c3c0624ca2afcf28ccd Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Wed, 23 Oct 2024 12:22:16 +0000
Subject: [PATCH 247/429] Bug 38237: Add logging to erm_run_harvester cronjob

This patch simply adds logging to the cronjob

To test:
1 - Enable CronjobLog
    http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=CronjobLog
2 - Enable ERMModule
    http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ERMModule
3 - Home -> E-resource Management -> Data providers -> New data provider
    Fill from registry or create your own, but change the URL and put in any required numbers
    We don't need a real test, just to run the ob
4 - perl misc/cronjobs/erm_run_harvester.pl --begin-date 2024-01-01 --dry-run --debug
5 - View the logs, no log of the script
6 - Apply patches
7 - Run the job again
8 - Check logs
9 - Success!

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6ce5df7f8e6862edc5a19f80b52a2360ee07d6c7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/cronjobs/erm_run_harvester.pl | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/misc/cronjobs/erm_run_harvester.pl b/misc/cronjobs/erm_run_harvester.pl
index fa8f0b3828..f637d43166 100755
--- a/misc/cronjobs/erm_run_harvester.pl
+++ b/misc/cronjobs/erm_run_harvester.pl
@@ -22,9 +22,12 @@ use Getopt::Long qw( GetOptions );
 use Koha::DateUtils qw( dt_from_string );
 use POSIX;
 
+use C4::Log qw( cronlogaction );
 use Koha::Script;
 use Koha::ERM::EUsage::UsageDataProviders;
 
+my $command_line_options = join(" ",@ARGV);
+
 # Command line option values
 my $get_help   = 0;
 my $begin_date = 0;
@@ -54,6 +57,7 @@ unless ($begin_date) {
     die "ERROR: Please specify a begin-date";
 }
 
+cronlogaction({ info => $command_line_options });
 debug_msg("Dry run: Harvests will not be enqueued") if $dry_run;
 while ( my $udprovider = $udproviders->next ) {
     debug_msg(
@@ -94,6 +98,8 @@ while ( my $udprovider = $udproviders->next ) {
 
 }
 
+cronlogaction({ action => 'End', info => "COMPLETED" });
+
 sub debug_msg {
     my ($msg) = @_;
 

From 6264f861a3270f6546580d4ea3abeac892ca0524 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Wed, 23 Oct 2024 12:50:48 +0000
Subject: [PATCH 248/429] Bug 38237: Add POD

This patch adds POD to the script and rpelaces the custom help script with pod2usage

TO test:
perl misc/cronjobs/erm_run_harvester.pl
Confirm you get helpful usage data

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0ff61ab9d92dd0f276aed03b49a2b28483f38acb)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/cronjobs/erm_run_harvester.pl | 120 +++++++++++++++++++++--------
 1 file changed, 88 insertions(+), 32 deletions(-)

diff --git a/misc/cronjobs/erm_run_harvester.pl b/misc/cronjobs/erm_run_harvester.pl
index f637d43166..cbb999fb1f 100755
--- a/misc/cronjobs/erm_run_harvester.pl
+++ b/misc/cronjobs/erm_run_harvester.pl
@@ -18,35 +18,108 @@
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
+
 use Getopt::Long qw( GetOptions );
-use Koha::DateUtils qw( dt_from_string );
+use Pod::Usage   qw( pod2usage );
 use POSIX;
 
-use C4::Log qw( cronlogaction );
-use Koha::Script;
+use C4::Log         qw( cronlogaction );
+use Koha::DateUtils qw( dt_from_string );
 use Koha::ERM::EUsage::UsageDataProviders;
+use Koha::Script -cron;
 
-my $command_line_options = join(" ",@ARGV);
+=head1 NAME
+
+erm_run_harvester.pl This script will run the SUSHI harvesting for usage data providers
+
+=cut
+
+=head1 SYNOPSIS
+
+erm_run_harvester.pl
+  --begin-date <YYYY-MM-DD>
+  [ --dry-run ][ --debug ][ --end-date <YYYY-MM-DD> ]
+
+ Options:
+   --help                         brief help message
+   --man                          detailed help message
+   --debug                        print additional debug messages during run
+   --dry-run                      test run only, do not harvest data
+   --begin-date <YYYY-MM-DD>      date to harvest from
+   --end-date <YYYY-MM-DD>        date to harvest until, defaults to today if not set
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-help>
+
+Print a brief help message and exits.
+
+=item B<-man>
+
+Print a detailed help message and exits.
+
+=item B<--debug>
+
+Add debug statements to run
+
+=item B<--begin-date>
+
+Date from which to harvest, previously harvested data will be ignored
+
+=item B<--end-date>
+
+Date to harvest until, defaults to today if not set
+
+=item B<--dry-run>
+
+Test run only, do not harvest
+
+=back
+
+=head1 DESCRIPTION
+
+This script fetches usage data from ERM data providers defined in the interface.
+
+=head2 Configuration
+
+This script harvests from the given date to the specified end date, or until today
+
+=head1 USAGE EXAMPLES
+
+C<erm_run_harvester.pl> - With no arguments help is printed
+
+
+C<erm_run_harvester.pl --begin-date 2000-01-01> - Harvest from the given date until today
+
+C<erm_run_harvester.pl --begin-date 2000-01-01 --end-date 2024-01-01> - Harvest from the given date until the end date
+
+C<erm_run_harvester.pl --begin-date 2000-01-01 --end-date 2024-01-01 --debug --dry-run> - Dry run, with debuig information
+
+=cut
+
+my $command_line_options = join( " ", @ARGV );
 
 # Command line option values
-my $get_help   = 0;
+my $help       = 0;
+my $man        = 0;
 my $begin_date = 0;
 my $end_date   = 0;
 my $dry_run    = 0;
 my $debug      = 0;
 
 my $options = GetOptions(
-    'h|help'       => \$get_help,
+    'h|help'       => \$help,
+    'm|man'        => \$man,
     'begin-date=s' => \$begin_date,
     'end-date=s'   => \$end_date,
     'dry-run'      => \$dry_run,
     'debug'        => \$debug
 );
 
-if ($get_help) {
-    get_help();
-    exit 1;
-}
+pod2usage(1) if $help;
+pod2usage( -verbose => 2 ) if $man;
 
 my $udproviders = Koha::ERM::EUsage::UsageDataProviders->search( { active => 1 } );
 unless ( scalar @{ $udproviders->as_list() } ) {
@@ -54,10 +127,12 @@ unless ( scalar @{ $udproviders->as_list() } ) {
 }
 
 unless ($begin_date) {
-    die "ERROR: Please specify a begin-date";
+    print "ERROR: You must specify a begin-date\n\n";
+    pod2usage(1);
 }
 
-cronlogaction({ info => $command_line_options });
+cronlogaction( { info => $command_line_options } );
+
 debug_msg("Dry run: Harvests will not be enqueued") if $dry_run;
 while ( my $udprovider = $udproviders->next ) {
     debug_msg(
@@ -98,7 +173,7 @@ while ( my $udprovider = $udproviders->next ) {
 
 }
 
-cronlogaction({ action => 'End', info => "COMPLETED" });
+cronlogaction( { action => 'End', info => "COMPLETED" } );
 
 sub debug_msg {
     my ($msg) = @_;
@@ -113,22 +188,3 @@ sub debug_msg {
     }
     print STDERR "$msg\n";
 }
-
-sub get_help {
-    print <<"HELP";
-$0: Run a SUSHI harvesting for a ERM usage data provider
-
-This script will run the SUSHI harvesting for usage data providers
-
-Parameters:
-    --help or -h                         get help
-    --begin-date                         begin date for the harvest in yyyy-mm-dd format (e.g.: '2023-08-21')
-    --end-date                           end date for the harvest in yyyy-mm-dd format (e.g.: '2023-08-21')
-    --dry-run                            only produce a run report, without actually doing anything permanent
-    --debug                              print additional debugging info during run
-
-Usage example:
-./misc/cronjobs/erm_run_harvester.pl --begin-date 2023-06-21 --debug
-
-HELP
-}

From 2d2e2bbb5aca8ccfcb6b7cc7fbd2ca34f6579377 Mon Sep 17 00:00:00 2001
From: Kevin Carnes <kevin.carnes@ub.lu.se>
Date: Mon, 12 Aug 2024 10:55:57 +0200
Subject: [PATCH 249/429] Bug 36171: Fix template toolkit tags split by HTML
 parser

It appears that the template parser parses HTML tags first and then template
toolkit tags. This can cause a template toolkit tag that contains an HTML tag
to have its start and end markers separated. This causes it to be treated as
text that can be translated instead of a template toolkit tag.

This patch searches for text sections that have a template toolkit end marker
without a start and then searches for the start in previous sections. It then
creates a new template toolkit section with the start and end markers.

The action buttons in the staff Lists view have been changed to allow
translation.

To test:
1. You will need to edit the staff-prog.po for a language and modify the
   the translation for "SET line_break". You can change SET to FOO.
2. Install the translation for the language.
3. View the details of a patron in the staff interface with the language.
4. The page will have an error.
5. Apply the patch.
6. Install the translation for the language.
7. View the details of a patron in the staff interface with the language.
8. The page should now display correctly.

If you want to, you can run "gulp po:update --lang LANGUAGE-CODE" to make sure
that the template tag is no longer included in .po file.

You can also add translations for "Edit", "Delete", and "Transfer" in
messages.po to make sure that the action buttons are translated in the Lists
view.

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Amended-by: Jonathan Druart
Tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit cf634fa014d72645c24e868a70cb55c63166e1d4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/TTParser.pm                                | 20 +++++++++++++++++++
 .../virtualshelves/tables/shelves_results.tt  |  6 +++---
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/C4/TTParser.pm b/C4/TTParser.pm
index 7007fb6604..269d868a77 100644
--- a/C4/TTParser.pm
+++ b/C4/TTParser.pm
@@ -73,6 +73,26 @@ sub text{
     my $line = shift;
     my $work = shift; # original text
     my $is_cdata = shift;
+    # If there is a split template toolkit tag
+    if ( $work =~ m/^(?:(?!\[%).)*?%\]/s ) {
+        my @strings = ($&);
+        my $token;
+        $work = $';
+        while ( $token = pop @tokens ) {
+            if ( $token->string =~ m/\[%.*?$/s ) {
+                push @tokens,  C4::TmplToken->new( $`, $token->type, $token->line_number, $token->pathname );
+                push @strings, $&;
+                last;
+            } else {
+                push @strings, $token->string;
+            }
+        }
+        push @tokens,
+            C4::TmplToken->new(
+            join( '', reverse @strings ),         C4::TmplTokenType::DIRECTIVE,
+            $token ? $token->line_number : $line, $self->{filename}
+            );
+    }
     while($work){
         # if there is a template_toolkit tag
         if( $work =~ m/\[%.*?%\]/ ){
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/tables/shelves_results.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/tables/shelves_results.tt
index de301479f3..abc6272c1d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/tables/shelves_results.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/tables/shelves_results.tt
@@ -43,7 +43,7 @@
         [%~ action_block = action_block _ '<input type="hidden" name="op" value="edit_form" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="public" value="' _ public _ '" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="referer" value="list" />' ~%]
-        [%~ action_block = action_block _ '<button class="editshelf btn btn-default btn-xs"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Edit</button>' ~%]
+        [%~ action_block = action_block _ '<button class="editshelf btn btn-default btn-xs"><i class="fa-solid fa-pencil" aria-hidden="true"></i> ' _ t("Edit") _ '</button>' ~%]
         [%~ action_block = action_block _ '</form> ' ~%]
     [%~ END ~%]
     [%~ IF can_delete_shelf ~%]
@@ -54,7 +54,7 @@
         [%~ action_block = action_block _ '<input type="hidden" name="shelfnumber" value="' _ shelfnumber  _ '" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="public" value="' _ public _ '" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="referer" value="list" />' ~%]
-        [%~ action_block = action_block _ '<button type="submit" class="delete-list btn btn-default btn-xs"><i class="fa fa-trash-can"></i> Delete</button>' ~%]
+        [%~ action_block = action_block _ '<button type="submit" class="delete-list btn btn-default btn-xs"><i class="fa fa-trash-can"></i> ' _ t("Delete") _ '</button>' ~%]
         [%~ action_block = action_block _ '</form>' ~%]
     [%~ END ~%]
     [%~ IF public AND allow_transfer ~%]
@@ -64,7 +64,7 @@
         [%~ action_block = action_block _ '<input type="hidden" name="shelfnumber" value="' _ shelfnumber  _ '" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="public" value="1" />' ~%]
         [%~ action_block = action_block _ '<input type="hidden" name="referer" value="list" />' ~%]
-        [%~ action_block = action_block _ '<button type="submit" class="btn btn-default btn-xs"><i class="fa"></i> Transfer</button>' ~%]
+        [%~ action_block = action_block _ '<button type="submit" class="btn btn-default btn-xs"><i class="fa"></i> ' _ t("Transfer") _ '</button>' ~%]
         [%~ action_block = action_block _ '</form>' ~%]
     [%~ END ~%]
 [%~ ELSE ~%]

From 5bb19beff8a88926e263660cbe175f7882f0b1bd Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Tue, 8 Oct 2024 11:11:54 +0000
Subject: [PATCH 250/429] Bug 38117: Only show 'not checked in' message if item
 is checked out

To test:
 1 - Enable system preference 'ShowAllCheckins'
 2 - Set system preference 'BlockReturnOfWithdrawnItems' to 'Block'
 3 - Check in an item
 4 - Note 'Item was not checked in message'
 5 - Confirm it happens for any item that is not currently issued
 6 - Check out an item to a patron
 7 - Mark the item as withdrawn
 8 - Check the item in
 9 - Note message 'Item was not checked in' appears too
10 - Apply patch
11 - Confirm checking in an item not checked out only shows 'Not checked out'
12 - Confirm the withdrawn item still shows 'Item was not checked in'
13 - Confirm above message remains whne checking in more items that were not issued

Signed-off-by: Michelle Spinney <mspinney@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 91ba4fcfd722196f66c730c781c62941ff9e86a8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
index 7480e69386..db44030902 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
@@ -1263,7 +1263,7 @@
                                     [% FOREACH riloo IN riloop %]
                                         <tr>
                                             <td class="ci-duedate">
-                                                [% IF ( riloo.not_returned ) %]
+                                                [% IF ( riloo.not_returned AND riloo.duedate ) %]
                                                     <span class="problem not_returned">Item was not checked in</span>
                                                 [% END %]
                                                 [% IF ( riloo.duedate ) %]

From ec284141689800d262ea2a24a5a1762ac9a8a48c Mon Sep 17 00:00:00 2001
From: Tomas Cohen Arazi <tomascohen@theke.io>
Date: Wed, 23 Oct 2024 18:01:08 -0300
Subject: [PATCH 251/429] Bug 38249: Fix typo in koha-list --help

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 85239f0fbfd246938ab04d628990c819921d9ef2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 debian/scripts/koha-list | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/debian/scripts/koha-list b/debian/scripts/koha-list
index 3f3fcc90f2..8cfa034700 100755
--- a/debian/scripts/koha-list
+++ b/debian/scripts/koha-list
@@ -280,21 +280,21 @@ email turned on.
     
 Usage: $scriptname [--enabled|--disabled] [--email|--noemail] [--sip|--nosip] [-h]
 Options:
-    --enabled        Show enabled instances
-    --disabled       Show disabled instances
-    --elasticsearch  Show instances with Elasticsearch enabled
-    --noelasticsarch Show instances with Elasticsearch disabled
-    --email          Show instances with email enabled
-    --noemail        Show instances with email disabled
-    --sip            Show instances with SIP enabled
-    --nosip          Show instances with SIP disabled
-    --plack          Show instances with Plack enabled
-    --noplack        Show instances with Plack disabled
-    --letsencrypt    Show instances with letsencrypt enabled
-    --noletsencrypt  Show instances with letsencrypt disabled
-    --z3950          Show instances with Z39.50/SRU enabled
-    --noz3950        Show instances with Z39.50/SRU disabled
-    --help | -h      Show this help
+    --enabled         Show enabled instances
+    --disabled        Show disabled instances
+    --elasticsearch   Show instances with Elasticsearch enabled
+    --noelasticsearch Show instances with Elasticsearch disabled
+    --email           Show instances with email enabled
+    --noemail         Show instances with email disabled
+    --sip             Show instances with SIP enabled
+    --nosip           Show instances with SIP disabled
+    --plack           Show instances with Plack enabled
+    --noplack         Show instances with Plack disabled
+    --letsencrypt     Show instances with letsencrypt enabled
+    --noletsencrypt   Show instances with letsencrypt disabled
+    --z3950           Show instances with Z39.50/SRU enabled
+    --noz3950         Show instances with Z39.50/SRU disabled
+    --help | -h       Show this help
 
 The filtering options can be combined, and you probably want to do this
 (except --email and --noemail, or --enabled and --disabled, that's just silly.)

From 8de5dd66e6cddf3b603e3c1a70cf9f63cd4a6d1b Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Fri, 25 Oct 2024 12:25:40 +0000
Subject: [PATCH 252/429] Bug 38272: Add permission check for erm permission to
 additional-fields.tt

To test:

1) Create a user with just erm and catalogue permissions.
2) Open additional-fields.pl
3) Notice that nothing is displayed
4) Change the permission of the user to just manage aquisition orders and catalogue
5) Navigate back to additional-fields.pl
6) Notice that nothing is displayed
7) Apply patch
8) Repeat 1-6 but notice this time that the respective entries are displayed
9) Sign off

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 48b938d35c9ee7c5c7e8d2e8265f0d1ec18447d4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/additional-fields.tt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
index c42c266505..2196881ceb 100755
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
@@ -112,7 +112,7 @@
     [% END %]
 
     [% IF op == 'list_tables' %]
-        [% IF CAN_user_acquisitions_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions %]
+        [% IF CAN_user_acquisitions_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions || CAN_user_erm %]
         <h1>Additional fields</h1>
 
         <div class="page-section">

From be478eeb66781c26fa6fc3d9b456a59e43301818 Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Fri, 25 Oct 2024 12:27:40 +0000
Subject: [PATCH 253/429] Bug 38272: Additionally fix typo in condition for
 acquisition orders permission

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 3ff6ded8680d9b6a355d0f099928af0ecf62872d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/additional-fields.tt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
index 2196881ceb..3a9e5fdfe3 100755
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
@@ -112,7 +112,7 @@
     [% END %]
 
     [% IF op == 'list_tables' %]
-        [% IF CAN_user_acquisitions_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions || CAN_user_erm %]
+        [% IF CAN_user_acquisition_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions || CAN_user_erm %]
         <h1>Additional fields</h1>
 
         <div class="page-section">

From b8214ffb4ace9aaddcca94462d958251f0e25f56 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 22 Oct 2024 02:06:18 +0000
Subject: [PATCH 254/429] Bug 36560: Add a CSRF exception for ILS-DI API

This change adds an exception for the ILS-DI API for CSRF prevention since there is no way to acquire a CSRF token
for the ILS-DI API.

1. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ILS-DI
2. Enable "ILS-DI"
3. curl -v localhost:8080/cgi-bin/koha/ilsdi.pl -d "service=AuthenticatePatron&username=REALUSER&password=REALPASSWORD"
4. Note the 403 response

5. Apply the patch
6. sudo koha-plack --restart kohadev

7. curl -v localhost:8080/cgi-bin/koha/ilsdi.pl -d "service=AuthenticatePatron&username=REALUSER&password=REALPASSWORD"
8. Note the 200 response

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8472279d5c91b277e1bca006887be8f9e6d4c7fa)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Middleware/CSRF.pm | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/Koha/Middleware/CSRF.pm b/Koha/Middleware/CSRF.pm
index 58e75c948e..a41da53054 100644
--- a/Koha/Middleware/CSRF.pm
+++ b/Koha/Middleware/CSRF.pm
@@ -44,11 +44,20 @@ sub call {
     my $request_method = $req->method  // q{};
     my $uri            = $req->uri     // q{};
     my $referer        = $req->referer // q{No referer};
+    my $request_path   = $req->path // q{};
+
+    my %path_exceptions = (
+        '/ilsdi.pl' => 1,
+    );
 
     #NOTE: Ignore ErrorDocument requests for CSRF
     if ( $env->{'psgix.errordocument.SCRIPT_NAME'} ) {
         return $self->app->($env);
     }
+    elsif ( $path_exceptions{$request_path} ){
+        #NOTE: Ignore path exceptions for CSRF
+        return $self->app->($env);
+    }
 
     my ($error);
     if ( $stateless_methods{$request_method} && defined $original_op && $original_op =~ m{^cud-} ) {

From 400435d4fbbec0853f9e24898d33eb0a1dee6c0f Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Fri, 25 Oct 2024 11:45:32 +0000
Subject: [PATCH 255/429] Bug 36560: (QA follow-up) Tidy code

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5f2807b3703a29c55a0f61d653aa983b8d6123c4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Middleware/CSRF.pm | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Koha/Middleware/CSRF.pm b/Koha/Middleware/CSRF.pm
index a41da53054..811c212386 100644
--- a/Koha/Middleware/CSRF.pm
+++ b/Koha/Middleware/CSRF.pm
@@ -44,7 +44,7 @@ sub call {
     my $request_method = $req->method  // q{};
     my $uri            = $req->uri     // q{};
     my $referer        = $req->referer // q{No referer};
-    my $request_path   = $req->path // q{};
+    my $request_path   = $req->path    // q{};
 
     my %path_exceptions = (
         '/ilsdi.pl' => 1,
@@ -53,8 +53,8 @@ sub call {
     #NOTE: Ignore ErrorDocument requests for CSRF
     if ( $env->{'psgix.errordocument.SCRIPT_NAME'} ) {
         return $self->app->($env);
-    }
-    elsif ( $path_exceptions{$request_path} ){
+    } elsif ( $path_exceptions{$request_path} ) {
+
         #NOTE: Ignore path exceptions for CSRF
         return $self->app->($env);
     }

From 82ecc318f9211ab7ac0af6238c095a4d99daca01 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Mon, 7 Oct 2024 11:31:36 +0200
Subject: [PATCH 256/429] Bug 38085: Pick multiple_sortable options for
 translation

Bug 29948 added a "multiple_sortable" type for sysprefs, to have the
options sortable. But the options were not picked for translation.

This patch fixes it.

Test plan:
Run `cd misc/translator && perl translate update LANG` before and after
this patch. Notice that with this patch you see the different options of
OPACAuthorIdentifiersAndInformation in the LANG-pref.po file (eg.
"Associated group (373$a$s$t$u$v$0)")
Translate the options
Install the translated version of the templates and confirm that the
strings appear translated on the interface.

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 738c641bccd7c65a7a3a22a5a898085a899b08cb)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/translator/LangInstaller.pm | 2 +-
 misc/translator/xgettext-pref    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/misc/translator/LangInstaller.pm b/misc/translator/LangInstaller.pm
index a0f323121e..15bee8355a 100644
--- a/misc/translator/LangInstaller.pm
+++ b/misc/translator/LangInstaller.pm
@@ -225,7 +225,7 @@ sub get_translated_pref {
             my $value = $syspref->{$key};
 
             my $translated_value = $value;
-            if (($key eq 'choices' || $key eq 'multiple') && ref($value) eq 'HASH') {
+            if (($key eq 'choices' || $key eq 'multiple' || $key eq 'multiple_sortable') && ref($value) eq 'HASH') {
                 $translated_value = {
                     map {
                         my $msgid = sprintf('%s#%s# %s', $file, $pref_name, $value->{$_});
diff --git a/misc/translator/xgettext-pref b/misc/translator/xgettext-pref
index 0068ae6869..77b11610ec 100755
--- a/misc/translator/xgettext-pref
+++ b/misc/translator/xgettext-pref
@@ -120,7 +120,7 @@ sub add_prefs {
         for my $element (@$pref) {
             if ( ref($element) eq 'HASH' ) {
                 while ( my ( $key, $value ) = each(%$element) ) {
-                    next unless $key eq 'choices' or $key eq 'multiple';
+                    next unless $key eq 'choices' or $key eq 'multiple' or $key eq 'multiple_sortable';
                     next unless ref($value) eq 'HASH';
                     for my $ckey ( keys %$value ) {
                         my $msgid = sprintf('%s#%s# %s', basename($file), $pref_name, $value->{$ckey});

From f7dd1d21a803c52ce7a0cd171c3921455b9e4487 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Mon, 30 Sep 2024 09:22:03 +0000
Subject: [PATCH 257/429] Bug 38030: stocknumberAV.pl fails with CSRF
 protection

The value builder stocknumberAV.pl does not work after applying the CSRF
protection. In console, it generates entries like:

POST
http://localhost:8081/cgi-bin/koha/cataloguing/plugin_launcher.pl
[HTTP/1.1 403 Forbidden 188ms]

Test plan:
==========
1. Modify the MARC bibliographic framework for the default framework by
   choosing stocknumberAV.pl as plugin for subfield 952 $i.
2. In Authorized values, add a new category 'INVENTORY'.  Add a new
   entry there, e.g. 'ABC', with any number in Description (eg. 123).
3. Find any bibliographic record, make sure it uses the default framework.
   If not set the framework accordingly.
4. Edit an item linked to this record.  Go to the 'i - Inventory number'
   subfield.  You should see three dots on the right.  In the input field
   put ABC and click the three dots.
5. Nothing happens.  You can check in the browser console--there should
   be a message like:
   POST http://FQDN:8081/cgi-bin/koha/cataloguing/plugin_launcher.pl
   [HTTP/1.1 403 Forbidden 188ms]
6. Apply the patch; restart_all.  Refresh the browser window.
7. Repeat p. 4.  You should now get the next sequence number next to
   the 'ABC' (i.e. ABC 0000000124 or similar).

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit bba164856c1c4dfd1ec8e346c9b9d32955fdcad9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 cataloguing/value_builder/stocknumberAV.pl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cataloguing/value_builder/stocknumberAV.pl b/cataloguing/value_builder/stocknumberAV.pl
index c158ec7688..92e6e59dc0 100755
--- a/cataloguing/value_builder/stocknumberAV.pl
+++ b/cataloguing/value_builder/stocknumberAV.pl
@@ -50,8 +50,9 @@ my $builder = sub {
         function Click$params->{id}(ev) {
                 ev.preventDefault();
                 var code = document.getElementById(ev.data.id);
+                const token = document.getElementsByName('csrf_token')[0].value;
                 \$.ajax({
-                    url: '/cgi-bin/koha/cataloguing/plugin_launcher.pl',
+                    url: '/cgi-bin/koha/cataloguing/plugin_launcher.pl?csrf_token=' + token,
                     type: 'POST',
                     data: {
                         'plugin_name': 'stocknumberAV.pl',

From 5e7e39e10d385a75c3e84a2b50a854f279924f57 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 22 Oct 2024 00:42:22 +0000
Subject: [PATCH 258/429] Bug 38030: (QA follow-up) Move csrf_token to POST
 body from URL

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit aec1ad5bab8919cdee097ff2d7a52f85d661e905)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 cataloguing/value_builder/stocknumberAV.pl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/cataloguing/value_builder/stocknumberAV.pl b/cataloguing/value_builder/stocknumberAV.pl
index 92e6e59dc0..dedef7968a 100755
--- a/cataloguing/value_builder/stocknumberAV.pl
+++ b/cataloguing/value_builder/stocknumberAV.pl
@@ -50,13 +50,14 @@ my $builder = sub {
         function Click$params->{id}(ev) {
                 ev.preventDefault();
                 var code = document.getElementById(ev.data.id);
-                const token = document.getElementsByName('csrf_token')[0].value;
+                const csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
                 \$.ajax({
-                    url: '/cgi-bin/koha/cataloguing/plugin_launcher.pl?csrf_token=' + token,
+                    url: '/cgi-bin/koha/cataloguing/plugin_launcher.pl',
                     type: 'POST',
                     data: {
                         'plugin_name': 'stocknumberAV.pl',
                         'code'    : code.value,
+                        'csrf_token': csrf_token
                     },
                     success: function(data){
                         var field = document.getElementById(ev.data.id);

From a267e9a25db94fea8512203ffa9165ceeff665c8 Mon Sep 17 00:00:00 2001
From: Fridolin Somers <fridolin.somers@biblibre.com>
Date: Mon, 18 Mar 2024 15:18:27 +0100
Subject: [PATCH 259/429] Bug 36317: Add unit test

If fails it shows :
DBIx::Class::Storage::DBI::_dbh_execute(): DBI Exception: DBD::mysql::st execute failed: Column 'itemnumber' in where clause is ambiguous at /kohadevbox/koha/Koha/Objects.pm line 399

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d193455ec20e75721179c80c9d00c6163d2aef24)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Koha/Biblio.t | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/Biblio.t b/t/db_dependent/Koha/Biblio.t
index 98095b7142..37d528d989 100755
--- a/t/db_dependent/Koha/Biblio.t
+++ b/t/db_dependent/Koha/Biblio.t
@@ -1230,7 +1230,7 @@ subtest 'get_marc_notes() UNIMARC tests' => sub {
 };
 
 subtest 'host_items() tests' => sub {
-    plan tests => 7;
+    plan tests => 8;
 
     $schema->storage->txn_begin;
 
@@ -1261,6 +1261,27 @@ subtest 'host_items() tests' => sub {
     is_deeply( [ $host_items->get_column('itemnumber') ],
         [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
 
+    my $transfer = $builder->build_object(
+        {
+            class => 'Koha::Item::Transfers',
+            value => {
+                itemnumber => $host_item_1->itemnumber,
+                frombranch => $host_item_1->holdingbranch,
+            }
+        }
+    );
+    ok(
+        $host_items->search(
+            {},
+            {
+                join     => 'branchtransfers',
+                order_by => 'branchtransfers.daterequested'
+            }
+        )->as_list,
+        "host_items can be used with a join query on itemnumber"
+    );
+    $transfer->delete;
+
     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
     $host_items = $biblio->host_items;
     is( ref($host_items),   'Koha::Items' );

From 405ddad65ba6d0e22efb079a6542e47f8d6ed309 Mon Sep 17 00:00:00 2001
From: Fridolin Somers <fridolin.somers@biblibre.com>
Date: Thu, 14 Mar 2024 11:24:54 +0100
Subject: [PATCH 260/429] Bug 36317: Koha::Biblio->host_items must use
 me.itemnumber

Koha::Biblio->host_items fails with search_ordered() because of ambiguous column 'itemnumber' in where clause.
It must use me.itemnumber like in Koha::Biblio->items

Test plan :
prove t/db_dependent/Koha/Biblio.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a9964bc8a9f89d8da00e5056ceb3f06cf551ce72)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Biblio.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm
index caecc63a11..eaf9a6d5d7 100644
--- a/Koha/Biblio.pm
+++ b/Koha/Biblio.pm
@@ -651,7 +651,7 @@ sub host_items {
 
     my $host_itemnumbers = $self->_host_itemnumbers;
 
-    return Koha::Items->search( { itemnumber => { -in => $host_itemnumbers } } );
+    return Koha::Items->search( { "me.itemnumber" => { -in => $host_itemnumbers } } );
 }
 
 =head3 _host_itemnumbers

From d76b6a9b42e361e92ebef0e3b7fcaf503e7771df Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Tue, 17 Sep 2024 18:28:11 +0000
Subject: [PATCH 261/429] Bug 37582: Add ability to for SIP to convert control
 and separator characters to spaces

This came up with a SIP vendor recently - when reading responses they were only getting part of a patron's information.
It seemed to have been caused by patrons with multiple restrictions and a new line in the response

This patch also does minor refactoring to write_msg to pass the server variable that contains
all the previously passed parameters, and removes the never used "file" parameter.

Test Plan:
1) Add multiple debarments to a patron
2) Run a sip patron information response, note the presence of newlines
3) Apply this patch
4) Add the new option convert_control_characters to your SIP account,
   set the value to " -- "
5) Restart all the things!
7) Run a sip patron information response, note the newlines have been replaced!

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6a20ac7f99dc636ca598163f6ca675e09d736339)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/Sip.pm         | 19 +++++++++----------
 C4/SIP/Sip/MsgType.pm | 34 +++++++++++++++++-----------------
 etc/SIPconfig.xml     |  2 ++
 3 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/C4/SIP/Sip.pm b/C4/SIP/Sip.pm
index c29bff282f..cfaa202dac 100644
--- a/C4/SIP/Sip.pm
+++ b/C4/SIP/Sip.pm
@@ -187,11 +187,16 @@ sub boolspace {
 #
 
 sub write_msg {
-    my ($self, $msg, $file, $terminator, $encoding) = @_;
+    my ($self, $msg, $server) = @_;
+    my $terminator = $server->{account}->{terminator};
+    my $encoding = $server->{account}->{encoding};
 
     $terminator ||= q{};
     $terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF;
 
+    my $separator = $server->{account}->{convert_nonprinting_characters};
+    $msg =~ s/[^[:print:]]/$separator/g if defined $separator;
+
     $msg = encode($encoding, $msg) if ( $encoding );
 
     my $cksum;
@@ -206,15 +211,9 @@ sub write_msg {
         $msg .= sprintf('%04.4X', $cksum);
     }
 
-
-    if ($file) {
-        $file->autoflush(1);
-        print $file $msg, $terminator;
-    } else {
-        STDOUT->autoflush(1);
-        print $msg, $terminator;
-        siplog("LOG_INFO", "OUTPUT MSG: '$msg'");
-    }
+    STDOUT->autoflush(1);
+    print $msg, $terminator;
+    siplog("LOG_INFO", "OUTPUT MSG: '$msg'");
 
     $last_response = $msg;
 }
diff --git a/C4/SIP/Sip/MsgType.pm b/C4/SIP/Sip/MsgType.pm
index 4fcd4e486a..406159c52a 100644
--- a/C4/SIP/Sip/MsgType.pm
+++ b/C4/SIP/Sip/MsgType.pm
@@ -509,7 +509,7 @@ sub handle_patron_status {
         $koha_patron->update_lastseen('connection');
     }
     $resp = build_patron_status( $patron, $lang, $fields, $server );
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
     return (PATRON_STATUS_REQ);
 }
 
@@ -649,7 +649,7 @@ sub handle_checkout {
         }
     }
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
     return (CHECKOUT);
 }
 
@@ -748,7 +748,7 @@ sub handle_checkin {
     $resp .= maybe_add( FID_SCREEN_MSG, $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (CHECKIN);
 }
@@ -795,7 +795,7 @@ sub handle_block_patron {
     }
 
     $resp = build_patron_status( $patron, $patron->language, $fields, $server );
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
     return (BLOCK_PATRON);
 }
 
@@ -836,7 +836,7 @@ sub handle_request_acs_resend {
 
         # We haven't sent anything yet, so respond with a
         # REQUEST_SC_RESEND msg (p. 16)
-        $self->write_msg( REQUEST_SC_RESEND, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+        $self->write_msg( REQUEST_SC_RESEND, $server );
     } elsif ( ( length($last_response) < 9 )
         || substr( $last_response, -9, 2 ) ne 'AY' ) {
 
@@ -850,7 +850,7 @@ sub handle_request_acs_resend {
         # Cut out the sequence number and checksum, since the old
         # checksum is wrong for the resent message.
         my $rebuilt = substr( $last_response, 0, -9 );
-        $self->write_msg( $rebuilt, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+        $self->write_msg( $rebuilt, $server );
     }
 
     return REQUEST_ACS_RESEND;
@@ -932,7 +932,7 @@ sub handle_login {
         $status = login_core( $server, $uid, $pwd );
     }
 
-    $self->write_msg( LOGIN_RESP . $status, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( LOGIN_RESP . $status, $server );
     return $status ? LOGIN : '';
 }
 
@@ -1101,7 +1101,7 @@ sub handle_patron_info {
         $resp .= maybe_add( FID_SCREEN_MSG, INVALID_CARD, $server );
     }
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
     return (PATRON_INFO);
 }
 
@@ -1128,7 +1128,7 @@ sub handle_end_patron_session {
     $resp .= maybe_add( FID_SCREEN_MSG, $screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (END_PATRON_SESSION);
 }
@@ -1210,7 +1210,7 @@ sub handle_fee_paid {
     $resp .= maybe_add( FID_SCREEN_MSG,     $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE,     $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (FEE_PAID);
 }
@@ -1303,7 +1303,7 @@ sub handle_item_information {
         $resp .= $item->build_custom_field_string( $server );
     }
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (ITEM_INFORMATION);
 }
@@ -1353,7 +1353,7 @@ sub handle_item_status_update {
     $resp .= maybe_add( FID_SCREEN_MSG, $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (ITEM_STATUS_UPDATE);
 }
@@ -1407,7 +1407,7 @@ sub handle_patron_enable {
 
     $resp .= add_field( FID_INST_ID, $ils->institution, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (PATRON_ENABLE);
 }
@@ -1472,7 +1472,7 @@ sub handle_hold {
     $resp .= maybe_add( FID_SCREEN_MSG, $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (HOLD);
 }
@@ -1559,7 +1559,7 @@ sub handle_renew {
     $resp .= maybe_add( FID_SCREEN_MSG, $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (RENEW);
 }
@@ -1612,7 +1612,7 @@ sub handle_renew_all {
     $resp .= maybe_add( FID_SCREEN_MSG, $status->screen_msg, $server );
     $resp .= maybe_add( FID_PRINT_LINE, $status->print_line, $server );
 
-    $self->write_msg( $resp, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $resp, $server );
 
     return (RENEW_ALL);
 }
@@ -1718,7 +1718,7 @@ sub send_acs_status {
 
     # Do we want to tell the terminal its location?
 
-    $self->write_msg( $msg, undef, $server->{account}->{terminator}, $server->{account}->{encoding} );
+    $self->write_msg( $msg, $server );
     return 1;
 }
 
diff --git a/etc/SIPconfig.xml b/etc/SIPconfig.xml
index 1816b91a23..c353cb6b28 100644
--- a/etc/SIPconfig.xml
+++ b/etc/SIPconfig.xml
@@ -56,6 +56,7 @@
       <login id="lpl-sc" password="1234" institution="LPL" allow_fields="AO,AA,AE"/>
       <!-- allow_fields hides all fields not in the list, it is the inverse of hide_fields ( hide_fields takes precedence ) -->
       <login id="lpl-sc-beacock" password="xyzzy"
+             convert_nonprinting_characters=" "
              delimiter="|" error-detect="enabled" institution="LPL"
              send_patron_home_library_in_af="1"
              cv_send_00_on_success="1"
@@ -81,6 +82,7 @@
              lost_status_for_missing="4"
              blocked_item_types="VM|MU"
              seen_on_item_information="mark_found"> <!-- could be "keep_lost", empty to disable -->
+             <!-- convert_nonprinting_characters: Convert control and non-space separator characters into the given string -->
              <!-- show_checkin_message: If enabled, successful checking responses will contain an AF screen message -->
              <!-- lost_block_checkout sets flag if patron has more than the given current checkouts that are lost ( itemlost > 0 by default ) -->
              <!-- lost_block_checkout_value determines the minimum lost item value to count ( that is, the value in items.itemlost ) -->

From 0845639a8f640edf6fdd0a5683242163c2149675 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Tue, 17 Sep 2024 14:52:22 -0400
Subject: [PATCH 262/429] Bug 37582: Tidy write_msg

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 768a5d197c5d020c5d30fc219272524add02f2ac)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/Sip.pm | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/C4/SIP/Sip.pm b/C4/SIP/Sip.pm
index cfaa202dac..667e0c83ac 100644
--- a/C4/SIP/Sip.pm
+++ b/C4/SIP/Sip.pm
@@ -187,9 +187,9 @@ sub boolspace {
 #
 
 sub write_msg {
-    my ($self, $msg, $server) = @_;
+    my ( $self, $msg, $server ) = @_;
     my $terminator = $server->{account}->{terminator};
-    my $encoding = $server->{account}->{encoding};
+    my $encoding   = $server->{account}->{encoding};
 
     $terminator ||= q{};
     $terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF;
@@ -197,23 +197,23 @@ sub write_msg {
     my $separator = $server->{account}->{convert_nonprinting_characters};
     $msg =~ s/[^[:print:]]/$separator/g if defined $separator;
 
-    $msg = encode($encoding, $msg) if ( $encoding );
+    $msg = encode( $encoding, $msg ) if ($encoding);
 
     my $cksum;
 
     # $msg = encode_utf8($msg);
     if ($error_detection) {
-        if (defined($self->{seqno})) {
+        if ( defined( $self->{seqno} ) ) {
             $msg .= 'AY' . $self->{seqno};
         }
         $msg .= 'AZ';
         $cksum = checksum($msg);
-        $msg .= sprintf('%04.4X', $cksum);
+        $msg .= sprintf( '%04.4X', $cksum );
     }
 
     STDOUT->autoflush(1);
     print $msg, $terminator;
-    siplog("LOG_INFO", "OUTPUT MSG: '$msg'");
+    siplog( "LOG_INFO", "OUTPUT MSG: '$msg'" );
 
     $last_response = $msg;
 }

From 5c72e1f96f0d7ac473256f43a17bd1755f2904dc Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Wed, 30 Oct 2024 11:53:35 +0000
Subject: [PATCH 263/429] Bug 37582: Add unit test

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4d1b0303ffdb58f3e4c59ea53cbd55ac3a74ae79)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/SIP/Message.t | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t
index b7c01b4f71..98f92998c2 100755
--- a/t/db_dependent/SIP/Message.t
+++ b/t/db_dependent/SIP/Message.t
@@ -21,7 +21,7 @@
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 19;
+use Test::More tests => 20;
 use Test::Exception;
 use Test::MockObject;
 use Test::MockModule;
@@ -1553,6 +1553,28 @@ sub test_renew_desensitize {
 
 }
 
+subtest 'Test  convert_nonprinting_characters' => sub {
+
+    my $schema = Koha::Database->new->schema;
+    $schema->storage->txn_begin;
+
+    plan tests => 1;
+    my $msg =
+        qq{64YYYY          eng20241030    115433000000000000000000000000AOtDJZ8mxoq|AAF6LO825MxxosppnEpaUQ6a|AEnzsktrzn seoGzB|BLY|CQY|BHUSD|BV0|CC807485965|BDlbbGF5qo AawLbgu5W cD3r4q KVHWEkp KMAEKo en6QWEa5 Ho12sDL5hf|PB20241030|PCTQvaTv45|PIN|AFComment 1\nComment 2\nComment 3|BEZ2f_HGLMji|BFbbV3yXQ};
+    my $server = { account => { convert_nonprinting_characters => ' -- ' } };
+
+    my $output;
+    {
+        local *STDOUT;
+        open STDOUT, '>', \$output;
+        C4::SIP::Sip::write_msg( undef, $msg, $server )
+    }
+
+    like( $output, qr/Comment 1 -- Comment 2 -- Comment 3/, "Response contains converted restriction comment" );
+
+    $schema->storage->txn_rollback;
+};
+
 # Helper routines
 
 sub create_mocks {

From 66ee5cfe9fcaf8c454a5b3c5b78c0af47e76ded2 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Mon, 30 Sep 2024 19:54:30 +0000
Subject: [PATCH 264/429] Bug 38041: Pass hide_main for all needsconfirm
 warnings in SCO

To test:
1. APPLY PATCH and restart_all
2. Make sure the following errors when issuing in SCO show just the error, no other info. on screen:
DEBT
RENEW_ISSUE
ISSUED_TO_ANOTHER
RESERVED
TRANSFERRED
INVALID_DATE ( cant test in SCO )
TOO_MANY
RECALLED

Signed-off-by: CJ Lynce <cj.lynce@westlakelibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f606e90deb76fe436470fc7dcee25d700322ab54)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 opac/sco/sco-main.pl | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/opac/sco/sco-main.pl b/opac/sco/sco-main.pl
index bf923c754f..25503fe916 100755
--- a/opac/sco/sco-main.pl
+++ b/opac/sco/sco-main.pl
@@ -257,6 +257,9 @@ if ( $patron && $op eq "cud-returnbook" && $allowselfcheckreturns ) {
                 $failed_checkout->{DEBT} = $needconfirm->{DEBT};
             }
             push @failed_checkouts, $failed_checkout;
+            $template->param(
+                hide_main => 1,
+            );
         } else {
             if ( $confirmed || $issuenoconfirm ) {    # we'll want to call getpatroninfo again to get updated issues.
                 my ( $hold_existed, $item );

From f10bd3db289f369853bc367374fb7a01c7fd4dcd Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Mon, 21 Oct 2024 18:19:07 +0000
Subject: [PATCH 265/429] Bug 38066: Pop-up window footers can block page
 content

The staff interface's default <body> padding is enough to accommmodate
the langugage-selection footer but not the taller fixed footer on pop-up
window pages. This patch adds some CSS to increase the padding on those
pages to prevent page content from being obscured by the footer
navigation.

The patch also adjusts the CSS of the "Back to top" arrow so that it
isn't hidden by fixed footer.

To test, apply the patch and rebuild the staff interface CSS.

- In the staff client, go to Cataloging -> New record.
- In tab one, click the plugin button on tag 100 subfield a.
- Perform an authority search which will return results.
- Scroll down to the bottom of the window and confirm that the last
  lines of the search results are not obscured by the footer.

This change should apply to all pop-up windows which include the same
fixed footer markup, e.g. any cataloging plugin; the transfer order
window; vendor search when adding a subscription, etc.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b09f3a88205b007a21107747aeaf816462abbaa5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
index 65884323e4..dcae65fd03 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -654,6 +654,10 @@ body {
     padding: 0 0 4em;
     text-align: left;
 
+    &:has( nav.fixed-bottom ) {
+        padding-bottom: 6rem;
+    }
+
     &.nobackdrop {
         .modal-backdrop {
             opacity: 0;
@@ -5018,7 +5022,7 @@ div .suggestion_note {
     position: fixed;
     bottom: 40px;
     right: 40px;
-    z-index: 1000;
+    z-index: $zindex-tooltip;
 }
 
 @media only screen and ( max-width: 1200px ) {

From 3027cba2da0153adc6d756ca738fcf747346c789 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 9 Oct 2024 09:29:47 +0200
Subject: [PATCH 266/429] Bug 38128: (Bug 35329 follow-up) Filter ERM users to
 users with erm permission

There was a silly mistake, we passed the TT var erm_users instead of the
string "erm_users".
It should also be passed to patron_search_modal to display the note.

Test plan:
Confirm that with this patch a patron search in the ERM module returns
only patrons with the erm permission.

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0627c092c2aea76fd03f784185d17e93f4f1ba41)
---
 koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt
index 76746b5cd8..768c8e8596 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt
@@ -51,8 +51,9 @@
     [% Asset.js("js/vue/dist/erm.js") | $raw %]
     [% INCLUDE 'select2.inc' %]
     [% SET columns = ['cardnumber','name','category','branch','action'] %]
+    [% SET filter = "erm_users" %]
     [% PROCESS patron_search_modal columns => columns, modal_title => t("Select user") %]
-    [% PROCESS patron_search_js columns => columns, filter => erm_users, actions => ["select"], preview_on_name_click => 1 %]
+    [% PROCESS patron_search_js columns => columns, actions => ["select"], preview_on_name_click => 1 %]
 
 [% END %]
 [% INCLUDE 'intranet-bottom.inc' %]

From 05627133c59db339d8cd5b4af16161e32e98de69 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Thu, 7 Nov 2024 11:58:40 +0100
Subject: [PATCH 267/429] Bug 38128: Fix cypress test

We were mocking the wrong route.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 89b22cc278b1cc65bf4d3c473c9e5a9fcdce5ac0)
---
 t/cypress/integration/ERM/UserRoles_spec.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/cypress/integration/ERM/UserRoles_spec.ts b/t/cypress/integration/ERM/UserRoles_spec.ts
index 7bc3cd410b..db0224fabe 100644
--- a/t/cypress/integration/ERM/UserRoles_spec.ts
+++ b/t/cypress/integration/ERM/UserRoles_spec.ts
@@ -24,7 +24,7 @@ describe("Add/search user", () => {
         });
 
         //Intercept patrons request
-        cy.intercept("GET", "/api/v1/patrons*", {
+        cy.intercept("GET", "/api/v1/erm/users*", {
             statusCode: 200,
             body: [
                 {

From 4bd535936d16c28715eb15959bc720433d6f1cab Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 16 Oct 2024 10:28:47 +0200
Subject: [PATCH 268/429] Bug 38138: (bug 33398 follow-up) Don't display code
 for primary contact method

We have several choices for "primary_contact_method", we don't want to
display the code but the label
phone => Primary phone
phonepro => Secondary phone
mobile => Other phone
email => Primary email
emailpro => Secondary email
fax => Fax

Bonus: it will make it translatable.

Test plan:
1. Edit a patron record to add a Main contact method
2. Place a hold for this patron
3. Check the item in
   --> Pop up says "Main contact method:" followed by the choice.

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 49e09672b53b024882cc0c363d39b399ef1859d9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/circ/returns.tt             | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
index db44030902..fb48c7d280 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
@@ -853,7 +853,22 @@
                                                         [% UNLESS ( transfertodo) %]
                                                             [% INCLUDE display_bormessagepref %]
                                                             [% IF patron.primary_contact_method %]
-                                                                <li id="main_contact_method">Main contact method: <span>[% patron.primary_contact_method | html %]</span></li>
+                                                                <li id="main_contact_method">Main contact method:
+                                                                    [% SWITCH patron.primary_contact_method %]
+                                                                        [% CASE 'phone' %]
+                                                                            <span>Primary phone</span>
+                                                                        [% CASE 'phonepro' %]
+                                                                            <span>Secondary phone</span>
+                                                                        [% CASE 'mobile' %]
+                                                                            <span>Other phone</span>
+                                                                        [% CASE 'email' %]
+                                                                            <span>Primary email</span>
+                                                                        [% CASE 'emailpro' %]
+                                                                            <span>Secondary email</span>
+                                                                        [% CASE 'fax' %]
+                                                                            <span>Fax</span>
+                                                                    [% END %]
+                                                                </li>
                                                             [% END %]
                                                         [% END %]
 

From 373099a690d1090159217fa0c7c2328591bdacd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johanna=20R=C3=A4is=C3=A4?= <johanna.raisa@gmail.com>
Date: Thu, 17 Oct 2024 09:15:32 +0300
Subject: [PATCH 269/429] Bug 38188: fix populating the borrowenumberlist from
 patron_search_selections

This patch fixes the issue of populating the borrowernumberlist from patron_search_selections.
Adding borrowernumbers to the textarea randomly fails because the textarea is populated with the html() function.

To test:
1) Go to the patron search
2) Select a few patrons
3) Click on the "Batch patron modification" button
4) Use browsers back button to go back to the patron search
5) Find patron_batchmod_form from browser inspector, open it.
6) Check that between the <textarea> tags, there are the borrowernumbers of the selected patrons.
7) Apply the patch
8) Repeat steps 1-6
9) Check that <textarea> tags are not populated with the borrowernumbers of the selected patrons.
10) Remove the display:none from the textarea and check that the borrowernumbers are populated there.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Michelle Spinney <mspinney@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f44a0adb5dc4ccdff4f5435dda1cce51b7a393a5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt
index 13bc58c91f..208bcb1034 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt
@@ -247,7 +247,7 @@
                 e.preventDefault();
                 var patron_search_selections = JSON.parse( localStorage.getItem("patron_search_selections") ) || [];
                 if( patron_search_selections.length > 0 ){
-                    $("#borrowernumberlist").html( patron_search_selections.join('\n') );
+                    $("#borrowernumberlist").val( patron_search_selections.join('\n') );
                     $("#patron_batchmod_form").submit();
                 }
             });

From 5ad20d67825e305d1f883ba18c2fde23f419d635 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Thu, 24 Oct 2024 14:16:45 -0700
Subject: [PATCH 270/429] Bug 38251: Make 'Remove selected items' button work
 when there is one item

There are two ways to remove items from a list: you can remove a single item
by clicking a 'Remove from this list' button below the item, or you can
remove one or more items by checking the checkboxes for them and clicking the
'Remove selected items' button.

Bug 37150 needed to fix 'Remove from this list' for CSRF, because in the wild
old days it just triggered a GET to delete, and instead it needed to POST.
The first patch to do that defined the JS var single_bib, checked the
checkbox for that bib and submitted the form, as though you had used the
checkbox yourself. But if you cancelled the confirmation prompt, it would
need to uncheck the checkbox it checked, so it added a function to do that
if (single_bib). That patch didn't land.

What landed instead was a patch with a separate hidden form that could be
populated with the single biblionumber from 'Remove from this list', but
without single_bib to define that state, it used selected_titles.size() == 1
as the condition to use the hidden form, forgetting that when the user checks
a single checkbox, selected_titles.size() is also 1. The right condition is
the same one used to tell where to get the titles for the confirmation dialog:
if the click event passed along a biblionumber, it's 'Remove from this list.'
Since checking that sets var biblionumber, we can just use that being truthy
as the condition.

Test plan:
 1. Without the patch, open the OPAC and log in
 2. Add any two biblios to a new list
 3. Lists - {your list name}
 4. Click the checkbox for one of the items, click 'Remove selected items'
    and click 'Yes, remove from list' in the confirmation dialog
 5. Note that it wasn't removed
 6. Click the checkboxes for both items, click 'Remove selected items' and
    click 'Yes, remove from list' in the confirmation dialog
 7. Note that both were removed
 8. Apply patch, restart_all
 9. Add any four items to your list
10. Lists - {your list name}
11. For the first item, click the 'Remove from this list' button below the
    details for the item, confirm, make sure it was deleted
12. Click the checkbox for what's now the first item, click 'Remove from
    this list,' confirm, make sure it was deleted
13. Click the checkboxes for what are now the first two items, click 'Remove
    from this list,' confirm, make sure both were deleted

Sponsored-by: Chetco Community Public Library
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f82d8dae0f871aaa8adfd5bcdb9cda1aa9efdacd)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt
index a935eddf62..710080c86d 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt
@@ -953,7 +953,7 @@ $(function() {
                 }
                 confirmModal( message, title, yes_label, no_label, function( result ){
                     if( result ){
-                        if( selected_titles.size() == 1 ){
+                        if( biblionumber ){
                             $("#shelf_delete_biblionumber").val( biblionumber );
                             $("#shelf_delete_title").submit();
                         } else {
@@ -963,12 +963,6 @@ $(function() {
                 });
             }
         });
-        $("body").on("hidden.bs.modal", "#bootstrap-confirm-box-modal", function(){
-            /* If the user clicked a single "Remove from list" link, uncheck the checkbox */
-            if( single_bib ){
-                selected_titles.prop("checked", false );
-            }
-        });
     [% END %]
 
     [% IF OpenLibraryCovers %]KOHA.OpenLibrary.GetCoverFromIsbn();[% END %]

From 4eccb57f064f4e35cad429aa6d5d97e6d2322004 Mon Sep 17 00:00:00 2001
From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Date: Fri, 25 Oct 2024 11:19:24 +0000
Subject: [PATCH 271/429] Bug 38271: Handle missing 008 fields in EDI

This patch adds handling to create a default 008 field on MARC records that are missing one when created through EDI.

Test plan:
1) prove t/db_dependent/Koha/EDI.t

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b00fb3d55755220557127100ccd6d348792f0dfa)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/EDI.pm                        |  24 +++++
 t/db_dependent/Koha/EDI.t          | 168 ++++++++++++++++++++++++++++-
 t/edi_testfiles/QUOTES_SMALL_2.CEQ |   1 +
 3 files changed, 192 insertions(+), 1 deletion(-)
 create mode 100644 t/edi_testfiles/QUOTES_SMALL_2.CEQ

diff --git a/Koha/EDI.pm b/Koha/EDI.pm
index 137a2210c2..4aeb1884d8 100644
--- a/Koha/EDI.pm
+++ b/Koha/EDI.pm
@@ -45,6 +45,7 @@ use Koha::Plugins; # Adds plugin dirs to @INC
 use Koha::Plugins::Handler;
 use Koha::Acquisition::Baskets;
 use Koha::Acquisition::Booksellers;
+use Koha::Util::FrameworkPlugin qw( biblio_008 );
 
 our $VERSION = 1.1;
 
@@ -684,6 +685,10 @@ sub quote_item {
     if ( !defined $bib ) {
         $bib = {};
         my $bib_record = _create_bib_from_quote( $item, $quote );
+
+        # Check for and add default 008 as this is a mandatory field
+        $bib_record = _handle_008_field($bib_record);
+
         ( $bib->{biblionumber}, $bib->{biblioitemnumber} ) =
           AddBiblio( $bib_record, q{} );
         $logger->trace("New biblio added $bib->{biblionumber}");
@@ -1215,6 +1220,17 @@ sub _create_item_from_quote {
     return $item_hash;
 }
 
+sub _handle_008_field {
+    my ( $bib_record ) = @_;
+
+    if ( !$bib_record->field('008') ) {
+        my $default008 = biblio_008();
+        $bib_record->insert_fields_ordered( MARC::Field->new('008', $default008));
+    }
+
+    return $bib_record
+}
+
 1;
 __END__
 
@@ -1373,6 +1389,14 @@ Koha::EDI
      Returns the Aqbudget object for the active budget given the passed budget_code
      or undefined if one does not exist
 
+=head2 _handle_008_field
+
+     bib_record = _handle_008_field($bib_record)
+
+     Checks whether an 008 field exists on the record and adds a default field it does not
+
+     Returns the bib_record
+
 =head1 AUTHOR
 
    Colin Campbell <colin.campbell@ptfs-europe.com>
diff --git a/t/db_dependent/Koha/EDI.t b/t/db_dependent/Koha/EDI.t
index 46c2455760..a82ad168b1 100755
--- a/t/db_dependent/Koha/EDI.t
+++ b/t/db_dependent/Koha/EDI.t
@@ -20,8 +20,9 @@
 use Modern::Perl;
 use FindBin qw( $Bin );
 
-use Test::More tests => 1;
+use Test::More tests => 2;
 use Test::Warn;
+use Test::MockModule;
 
 use t::lib::Mocks;
 use t::lib::TestBuilder;
@@ -123,3 +124,168 @@ subtest 'process_quote' => sub {
 
     $schema->storage->txn_rollback;
 };
+
+subtest '_handle_008_field' => sub {
+    plan tests => 4;
+
+    $schema->storage->txn_begin;
+
+        # Add our test quote file to the database for testing against
+    my $account = $builder->build(
+        {
+            source => 'VendorEdiAccount',
+            value  => {
+                description => 'test vendor', transport => 'FILE',
+            }
+        }
+    );
+    my $dirname  = ( $Bin =~ /^(.*\/t\/)/ ? $1 . 'edi_testfiles/' : q{} );
+    my $filename = 'QUOTES_SMALL.CEQ';
+    ok( -e $dirname . $filename, 'File QUOTES_SMALL.CEQ found' );
+
+    my $trans = Koha::Edifact::Transport->new( $account->{id} );
+    $trans->working_directory($dirname);
+
+    my $mhash = $trans->message_hash();
+    $mhash->{message_type} = 'QUOTE';
+    $trans->ingest( $mhash, $filename );
+
+    my $quote = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
+
+    # Test quote expects REF to be a valid and active fund code
+    my $active_period = $builder->build(
+        {
+            source => 'Aqbudgetperiod',
+            value  => { budget_period_active => 1 }
+        }
+    );
+    my $fund = $builder->build(
+        {
+            source => 'Aqbudget',
+            value  => {
+                budget_code      => 'REF',
+                budget_period_id => $active_period->{budget_period_id}
+            }
+        }
+    );
+
+    # The quote expects a ROT1 stock rotation roata to exist
+    my $rota = $builder->build_object(
+        {
+            class => 'Koha::StockRotationRotas',
+            value => { title => 'ROT1' }
+        }
+    );
+    $builder->build(
+        {
+            source => 'Stockrotationstage',
+            value  => { rota_id => $rota->rota_id },
+        }
+    );
+
+    # Process the test quote file
+    process_quote($quote);
+
+    $quote->get_from_storage;
+
+    # Tests for generated basket for passed quote file
+    my $baskets = Koha::Acquisition::Baskets->search( { booksellerid => $account->{vendor_id} } );
+    my $basket = $baskets->next;
+
+    my $orders = $basket->orders;
+    my $order = $orders->next;
+
+    my $biblio = $order->biblio;
+    my $record = $biblio->record;
+    my $record_field = $record->field('008');
+
+    is(exists($record_field->{_data}), 1, 'Field has been added');
+
+    # Test without calling the 008 handler
+    $account = $builder->build(
+        {
+            source => 'VendorEdiAccount',
+            value  => {
+                description => 'test vendor', transport => 'FILE',
+            }
+        }
+    );
+    $dirname  = ( $Bin =~ /^(.*\/t\/)/ ? $1 . 'edi_testfiles/' : q{} );
+    $filename = 'QUOTES_SMALL_2.CEQ';
+    ok( -e $dirname . $filename, 'File QUOTES_SMALL_2.CEQ found' );
+
+    $trans = Koha::Edifact::Transport->new( $account->{id} );
+    $trans->working_directory($dirname);
+
+    $mhash = $trans->message_hash();
+    $mhash->{message_type} = 'QUOTE';
+    $trans->ingest( $mhash, $filename );
+
+    $quote = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
+
+    # Test quote expects REF to be a valid and active fund code
+    $active_period = $builder->build(
+        {
+            source => 'Aqbudgetperiod',
+            value  => { budget_period_active => 1 }
+        }
+    );
+    $fund = $builder->build(
+        {
+            source => 'Aqbudget',
+            value  => {
+                budget_code      => 'REF2',
+                budget_period_id => $active_period->{budget_period_id}
+            }
+        }
+    );
+
+    # The quote expects a ROT1 stock rotation roata to exist
+    $rota = $builder->build_object(
+        {
+            class => 'Koha::StockRotationRotas',
+            value => { title => 'ROT2' }
+        }
+    );
+    $builder->build(
+        {
+            source => 'Stockrotationstage',
+            value  => { rota_id => $rota->rota_id },
+        }
+    );
+
+    # Process the test quote file
+    my $edi_module = Test::MockModule->new('Koha::EDI');
+    $edi_module->mock(
+        '_check_for_existing_bib',
+        sub {
+            my $bib_record = shift;
+            return undef;
+        }
+    );
+    $edi_module->mock(
+        '_handle_008_field',
+        sub {
+            my $bib_record = shift;
+            return $bib_record;
+        }
+    );
+    process_quote($quote);
+
+    $quote->get_from_storage;
+
+    # Tests for generated basket for passed quote file
+    $baskets = Koha::Acquisition::Baskets->search( { booksellerid => $account->{vendor_id} } );
+    $basket  = $baskets->next;
+
+    $orders = $basket->orders;
+    $order  = $orders->next;
+
+    $biblio       = $order->biblio;
+    $record       = $biblio->record;
+    $record_field = $record->field('008');
+
+    is( $record_field->{_data}, undef, 'Field has not been added' );
+
+    $schema->storage->txn_rollback;
+}
\ No newline at end of file
diff --git a/t/edi_testfiles/QUOTES_SMALL_2.CEQ b/t/edi_testfiles/QUOTES_SMALL_2.CEQ
new file mode 100644
index 0000000000..26126a27a4
--- /dev/null
+++ b/t/edi_testfiles/QUOTES_SMALL_2.CEQ
@@ -0,0 +1 @@
+UNA:+.? 'UNB+UNOC:3+5013546027173+5013546098818+240123:0945+1044416+ASKEDI:+QUOTES++++'UNH+1044416001+QUOTES:D:96A:UN:EAN002'BGM+31C::28+WO0800131+9'DTM+137:20240123:102'RFF+ON:orders 23/1'CUX+2:GBP:12'NAD+BY+5013546098818::9'NAD+SU+5013546027173::9'LIN+1++9781529923766:EN'PIA+5+152992376X:IB'IMD+L+010+:::Thompson'IMD+L+011+:::Louise'IMD+L+050+:::Lucky'IMD+L+060+:::learning to live again'IMD+L+120+:::Ebury Spotlight'IMD+L+170+:::2024'IMD+L+180+:::304'IMD+L+220+:::Hbk'IMD+L+230+:::791.45'IMD+L+250+:::AN'QTY+1:1'GIR+001+BOOK:LST+ANF:LSQ+CPL:LLO+REF:LFN+791.45:LCL'GIR+001+22.00:LCV'GIR+001+ROT1:LRP'PRI+AAE:22.00'RFF+QLI:WO0800131000001'UNS+S'CNT+2:1'UNT+27+1044416001'UNZ+1+1044416'

From 5d0a0197569b6f8415de95ae88a6bc066564c5d4 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Fri, 25 Oct 2024 09:06:11 -0400
Subject: [PATCH 272/429] Bug 38271: (QA follow-up) Tidy code

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0f68688b5810836dede3625c65f6a71e5520989b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/EDI.pm               |  6 +++---
 t/db_dependent/Koha/EDI.t | 14 +++++++-------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/Koha/EDI.pm b/Koha/EDI.pm
index 4aeb1884d8..7b0eeb3657 100644
--- a/Koha/EDI.pm
+++ b/Koha/EDI.pm
@@ -1221,14 +1221,14 @@ sub _create_item_from_quote {
 }
 
 sub _handle_008_field {
-    my ( $bib_record ) = @_;
+    my ($bib_record) = @_;
 
     if ( !$bib_record->field('008') ) {
         my $default008 = biblio_008();
-        $bib_record->insert_fields_ordered( MARC::Field->new('008', $default008));
+        $bib_record->insert_fields_ordered( MARC::Field->new( '008', $default008 ) );
     }
 
-    return $bib_record
+    return $bib_record;
 }
 
 1;
diff --git a/t/db_dependent/Koha/EDI.t b/t/db_dependent/Koha/EDI.t
index a82ad168b1..fe69c304e7 100755
--- a/t/db_dependent/Koha/EDI.t
+++ b/t/db_dependent/Koha/EDI.t
@@ -130,7 +130,7 @@ subtest '_handle_008_field' => sub {
 
     $schema->storage->txn_begin;
 
-        # Add our test quote file to the database for testing against
+    # Add our test quote file to the database for testing against
     my $account = $builder->build(
         {
             source => 'VendorEdiAccount',
@@ -190,16 +190,16 @@ subtest '_handle_008_field' => sub {
 
     # Tests for generated basket for passed quote file
     my $baskets = Koha::Acquisition::Baskets->search( { booksellerid => $account->{vendor_id} } );
-    my $basket = $baskets->next;
+    my $basket  = $baskets->next;
 
     my $orders = $basket->orders;
-    my $order = $orders->next;
+    my $order  = $orders->next;
 
-    my $biblio = $order->biblio;
-    my $record = $biblio->record;
+    my $biblio       = $order->biblio;
+    my $record       = $biblio->record;
     my $record_field = $record->field('008');
 
-    is(exists($record_field->{_data}), 1, 'Field has been added');
+    is( exists( $record_field->{_data} ), 1, 'Field has been added' );
 
     # Test without calling the 008 handler
     $account = $builder->build(
@@ -288,4 +288,4 @@ subtest '_handle_008_field' => sub {
     is( $record_field->{_data}, undef, 'Field has not been added' );
 
     $schema->storage->txn_rollback;
-}
\ No newline at end of file
+    }

From 9bf772dd42e20c3c2f97bafd5a48e193c1308a2c Mon Sep 17 00:00:00 2001
From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Date: Mon, 28 Oct 2024 09:35:04 +0000
Subject: [PATCH 273/429] Bug 38271: (QA follow-up) Fix testcritic

testcritic was failing due to a 'return undef' so this commit amends that

Test plan:
1) prove t/00-testcritic.t

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 640ce0bc6bff99845d019500fa0a69db35545a2a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Koha/EDI.t | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/EDI.t b/t/db_dependent/Koha/EDI.t
index fe69c304e7..e298719456 100755
--- a/t/db_dependent/Koha/EDI.t
+++ b/t/db_dependent/Koha/EDI.t
@@ -260,7 +260,7 @@ subtest '_handle_008_field' => sub {
         '_check_for_existing_bib',
         sub {
             my $bib_record = shift;
-            return undef;
+            return;
         }
     );
     $edi_module->mock(

From ec9391506431dc5529019c05fa3c3531c9add52c Mon Sep 17 00:00:00 2001
From: Fridolin Somers <fridolin.somers@biblibre.com>
Date: Fri, 25 Oct 2024 16:25:10 +0200
Subject: [PATCH 274/429] Bug 38274: Fix typo in arabic language description

In installer/data/mysql/mandatory/subtag_registry.sql we see a missing
char in arabic language description.

Wee see this was correct before Bug 12250 :
العربية

Test plan :
1) Install a fresh Koha with this patch
2) Look in database :
   SELECT * FROM language_descriptions WHERE subtag='ar' AND type='language' AND lang='ar'
=> Check description is correct
3) On a old existing Koha
4) Install patch and run update database
=> Look in database and check description is correct

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4e1dc5e8b79bcca755610ca9628bd9dc5ae8defd)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 installer/data/mysql/atomicupdate/bug_38274.pl     | 13 +++++++++++++
 installer/data/mysql/mandatory/subtag_registry.sql |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100755 installer/data/mysql/atomicupdate/bug_38274.pl

diff --git a/installer/data/mysql/atomicupdate/bug_38274.pl b/installer/data/mysql/atomicupdate/bug_38274.pl
new file mode 100755
index 0000000000..9936d3b335
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_38274.pl
@@ -0,0 +1,13 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
+
+return {
+    bug_number  => "38274",
+    description => "Fix typo in arabic language description",
+    up          => sub {
+        my ($args) = @_;
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+        $dbh->do(q{ UPDATE language_descriptions SET description='العربية' WHERE subtag='ar' AND type='language' AND lang='ar' });
+    },
+};
diff --git a/installer/data/mysql/mandatory/subtag_registry.sql b/installer/data/mysql/mandatory/subtag_registry.sql
index 671c08bff3..6a052965ce 100644
--- a/installer/data/mysql/mandatory/subtag_registry.sql
+++ b/installer/data/mysql/mandatory/subtag_registry.sql
@@ -83,7 +83,7 @@ INSERT INTO language_rfc4646_to_iso639(rfc4646_subtag,iso639_2_code)
 VALUES ( 'ar','ara');
 
 INSERT INTO language_descriptions(subtag, type, lang, description)
-VALUES ( 'ar', 'language', 'ar', 'لعربية');
+VALUES ( 'ar', 'language', 'ar', 'العربية');
 
 INSERT INTO language_descriptions(subtag, type, lang, description)
 VALUES ( 'ar', 'language', 'en', 'Arabic');

From 889c351d17921709910e19abe4448b1480890f23 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Fri, 22 Nov 2024 21:32:57 +0000
Subject: [PATCH 275/429] Bug 38274: DBRev 24.05.05.005

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha.pm                                                         | 2 +-
 .../mysql/{atomicupdate/bug_38274.pl => db_revs/240505005.pl}   | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_38274.pl => db_revs/240505005.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index 00b1536fd2..219c48d681 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.004";
+$VERSION = "24.05.05.005";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_38274.pl b/installer/data/mysql/db_revs/240505005.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_38274.pl
rename to installer/data/mysql/db_revs/240505005.pl

From a87e7a3687eb6510b4f7645b17a2213eef80d3b2 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Sun, 27 Oct 2024 23:19:45 +0000
Subject: [PATCH 276/429] Bug 38275: Fix patron card creator image delete

This change changes the patron card creator image delete from a
GET to a POST, and supports single delete and multi-delete.

Test plan:
1. Apply the patch (and refresh browser JS cache)
2. Go to http://localhost:8081/cgi-bin/koha/patroncards/image-manage.pl
3. Upload multiple images
4. Select all the images using the checkboxes
5. Delete one image using the "Delete" button in the row
6. Note that only the image selected using the "Delete" button is
deleted and the other images are de-selected
7. Delete multiple images using the "Delete selected" button
8. Note that the correct images are noted in the confirm message,
and the correct images are deleted

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f4a25897b0ecbfd883115f3c79c8bdd44e3eb0f0)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../en/modules/patroncards/image-manage.tt    | 53 ++++++++++++-------
 1 file changed, 34 insertions(+), 19 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/image-manage.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/image-manage.tt
index 3ab37875dd..e8b2f848b3 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/image-manage.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/image-manage.tt
@@ -113,9 +113,9 @@
                                     [% FOREACH text_field IN TABL.text_fields %]
                                     [% IF ( text_field.select_field ) %]
                                         <td>
-                                            <a class="delete_image btn btn-default btn-xs" href="/cgi-bin/koha/patroncards/image-manage.pl?op=delete&image_id=[% text_field.field_value | html %]"><i class="fa fa-trash-can"></i> Delete</a>
+                                            <a class="delete_image btn btn-default btn-xs" data-image_id="[% text_field.field_value | html %]" href="/cgi-bin/koha/patroncards/image-manage.pl?op=delete&image_id=[% text_field.field_value | html %]"><i class="fa fa-trash-can"></i> Delete</a>
                                         </td>
-                                        <td align="center"><input type="checkbox" name="action" value="[% text_field.field_value | html %]" /></td>
+                                        <td align="center"><input type="checkbox" id="image_id_[% text_field.field_value | html %]" name="image_id" value="[% text_field.field_value | html %]" /></td>
                                     [% ELSIF ( text_field.field_value ) %]
                                         <td>[% text_field.field_value | html %]</td>
                                     [% ELSE %]
@@ -165,12 +165,15 @@
 [% MACRO jsinclude BLOCK %]
     <script>
         function DeleteConfirm() {
-            var results = selected_images("delete");
-            if (results.images) {
+            var results = selected_images();
+            if (results) {
                 var msg = _("Are you sure you want to delete image(s): %s?").format(results.image_ids);
                 var answer = confirm(msg);
                 if (answer) {
-                    window.location = "/cgi-bin/koha/patroncards/image-manage.pl?op=delete&" + results.images;
+                    const delete_form = document.delete_images;
+                    if (delete_form){
+                        delete_form.submit();
+                    }
                 } else {
                     return; // abort delete
                 }
@@ -179,30 +182,42 @@
             }
         }
 
-        function selected_images(op) {
-            var selected = new Array;
+        function selected_images() {
             var image_ids = new Array;
-            if (document.delete_images.action.length) {
-                for (i=0;i<document.delete_images.action.length;i++){
-                    if (document.delete_images.action[i].checked){
-                        selected.push("image_id=" + document.delete_images.action[i].value);
-                        image_ids.push(document.delete_images.action[i].value);
+            if (document.delete_images.image_id.length) {
+                for (i=0;i<document.delete_images.image_id.length;i++){
+                    if (document.delete_images.image_id[i].checked){
+                        image_ids.push(document.delete_images.image_id[i].value);
                     }
                 }
-                images = selected.join("&");
-                return {images:images, image_ids:image_ids};
             }
-            else if (document.delete_images.action.checked){
-                    return {images:"image_id="+document.delete_images.action.value, image_ids:document.delete_images.action.value};
+            if (image_ids.length){
+                return {image_ids:image_ids};
             }
-            return (-1);
+            return null;
         }
         $(document).ready(function() {
             $("#delete").click(function(){
                 return DeleteConfirm();
             });
-            $(".delete_image").on("click", function(){
-                return confirmDelete( _("Are you sure you want to delete this image?") );
+            $(".delete_image").on("click", function(ev){
+                ev.preventDefault();
+                const this_image = ev.target;
+                const image_id = this_image.dataset.image_id;
+                for (i=0;i<document.delete_images.image_id.length;i++){
+                    const row_image_id = document.delete_images.image_id[i].value;
+                    const row_checkbox = document.querySelector(`#image_id_${row_image_id}`);
+                    if (image_id === row_image_id){
+                        row_checkbox.checked = true;
+                    }
+                    else {
+                        row_checkbox.checked = false;
+                    }
+                }
+                const confirmed = confirmDelete( _("Are you sure you want to delete image: %s?").format(image_id) );
+                if (confirmed){
+                    document.delete_images.submit();
+                }
             });
         });
     </script>

From 3f23c156f2a93d58436e39d3325c5b9e98912250 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Wed, 4 Dec 2024 22:41:27 +0000
Subject: [PATCH 277/429] Revert "Bug 38066: Pop-up window footers can block
 page content"

This reverts commit f10bd3db289f369853bc367374fb7a01c7fd4dcd.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
index dcae65fd03..65884323e4 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -654,10 +654,6 @@ body {
     padding: 0 0 4em;
     text-align: left;
 
-    &:has( nav.fixed-bottom ) {
-        padding-bottom: 6rem;
-    }
-
     &.nobackdrop {
         .modal-backdrop {
             opacity: 0;
@@ -5022,7 +5018,7 @@ div .suggestion_note {
     position: fixed;
     bottom: 40px;
     right: 40px;
-    z-index: $zindex-tooltip;
+    z-index: 1000;
 }
 
 @media only screen and ( max-width: 1200px ) {

From 146c8327c1a8267cfc3a49c8f2e6a450c95e5ac2 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 16:20:02 +0000
Subject: [PATCH 278/429] Revert "Bug 37593: Removed all instances of 'this
 this' in the codebase"

This reverts commit 8297471b3b41e00d19134453ba5721ad3f85877b.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Schema/Result/Deleteditem.pm                    | 2 +-
 Koha/Schema/Result/Item.pm                           | 2 +-
 installer/data/mysql/db_revs/230600061.pl            | 4 ++--
 installer/data/mysql/kohastructure.sql               | 4 ++--
 koha-tmpl/intranet-tmpl/lib/datatables/datatables.js | 2 +-
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/Koha/Schema/Result/Deleteditem.pm b/Koha/Schema/Result/Deleteditem.pm
index 49a47984c8..118b225121 100644
--- a/Koha/Schema/Result/Deleteditem.pm
+++ b/Koha/Schema/Result/Deleteditem.pm
@@ -61,7 +61,7 @@ item barcode (MARC21 952$p)
   default_value: 0
   is_nullable: 0
 
-boolean value defining whether this item is available for bookings or not
+boolean value defining whether this this item is available for bookings or not
 
 =head2 dateaccessioned
 
diff --git a/Koha/Schema/Result/Item.pm b/Koha/Schema/Result/Item.pm
index 1b3612b8ee..dcbd2275a8 100644
--- a/Koha/Schema/Result/Item.pm
+++ b/Koha/Schema/Result/Item.pm
@@ -63,7 +63,7 @@ item barcode (MARC21 952$p)
   default_value: 0
   is_nullable: 0
 
-boolean value defining whether this item is available for bookings or not
+boolean value defining whether this this item is available for bookings or not
 
 =head2 dateaccessioned
 
diff --git a/installer/data/mysql/db_revs/230600061.pl b/installer/data/mysql/db_revs/230600061.pl
index cb1a47cdbf..a9bf4e4596 100755
--- a/installer/data/mysql/db_revs/230600061.pl
+++ b/installer/data/mysql/db_revs/230600061.pl
@@ -80,7 +80,7 @@ return {
         if ( !column_exists( 'items', 'bookable' ) ) {
             $dbh->do(
                 q{
-                ALTER TABLE items ADD COLUMN `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this item is available for bookings or not' AFTER `barcode`
+                ALTER TABLE items ADD COLUMN `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this this item is available for bookings or not' AFTER `barcode`
             }
             );
 
@@ -90,7 +90,7 @@ return {
         if ( !column_exists( 'deleteditems', 'bookable' ) ) {
             $dbh->do(
                 q{
-                ALTER TABLE deleteditems ADD COLUMN `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this item is available for bookings or not' AFTER `barcode`
+                ALTER TABLE deleteditems ADD COLUMN `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this this item is available for bookings or not' AFTER `barcode`
             }
             );
 
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 71ec8ff31c..4d1f1c10ef 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -2750,7 +2750,7 @@ CREATE TABLE `deleteditems` (
   `biblionumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from biblio table used to link this item to the right bib record',
   `biblioitemnumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the biblioitems table to link to item to additional information',
   `barcode` varchar(20) DEFAULT NULL COMMENT 'item barcode (MARC21 952$p)',
-  `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this item is available for bookings or not',
+  `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this this item is available for bookings or not',
   `dateaccessioned` date DEFAULT NULL COMMENT 'date the item was acquired or added to Koha (MARC21 952$d)',
   `booksellerid` longtext DEFAULT NULL COMMENT 'where the item was purchased (MARC21 952$e)',
   `homebranch` varchar(10) DEFAULT NULL COMMENT 'foreign key from the branches table for the library that owns this item (MARC21 952$a)',
@@ -4008,7 +4008,7 @@ CREATE TABLE `items` (
   `biblionumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from biblio table used to link this item to the right bib record',
   `biblioitemnumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the biblioitems table to link to item to additional information',
   `barcode` varchar(20) DEFAULT NULL COMMENT 'item barcode (MARC21 952$p)',
-  `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this item is available for bookings or not',
+  `bookable` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'boolean value defining whether this this item is available for bookings or not',
   `dateaccessioned` date DEFAULT NULL COMMENT 'date the item was acquired or added to Koha (MARC21 952$d)',
   `booksellerid` longtext DEFAULT NULL COMMENT 'where the item was purchased (MARC21 952$e)',
   `homebranch` varchar(10) DEFAULT NULL COMMENT 'foreign key from the branches table for the library that owns this item (MARC21 952$a)',
diff --git a/koha-tmpl/intranet-tmpl/lib/datatables/datatables.js b/koha-tmpl/intranet-tmpl/lib/datatables/datatables.js
index d5de7e72f7..a8ecb4bde3 100644
--- a/koha-tmpl/intranet-tmpl/lib/datatables/datatables.js
+++ b/koha-tmpl/intranet-tmpl/lib/datatables/datatables.js
@@ -100635,7 +100635,7 @@ this.pdfMake = this.pdfMake || {}; this.pdfMake.vfs = {
 				// on destroy, while the `dt` namespaced event is the one we are
 				// listening for
 				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
-					if ( settings !== ctx ) { // need to check this is the host
+					if ( settings !== ctx ) { // need to check this this is the host
 						return;               // table, not a nested one
 					}
 	

From 15734ad2c39ede2fabc70372d4d10c3ddd88e9de Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 16:33:14 +0000
Subject: [PATCH 279/429] Revert "Bug 37870: Remove warning if biblionumber is
 not passed"

This reverts commit 1946bdde6e0cfd507b9fe3c0390051f5cbed03e7.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/UI/Form/Builder/Biblio.pm | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/Koha/UI/Form/Builder/Biblio.pm b/Koha/UI/Form/Builder/Biblio.pm
index 13ecc67ff6..0b4422992b 100644
--- a/Koha/UI/Form/Builder/Biblio.pm
+++ b/Koha/UI/Form/Builder/Biblio.pm
@@ -52,11 +52,8 @@ sub new {
 
     my $self = {};
 
-    if ( exists $self->{biblionumber} ) {
-        $self->{biblionumber} = $params->{biblionumber} =~ s/\D//gr;
-
-        # just in case biblionumber obtained from CGI and passed directly here contains weird characters like spaces
-    }
+    $self->{biblionumber} = $params->{biblionumber} =~ s/\D//gr;
+    # just in case biblionumber obtained from CGI and passed directly here contains weird characters like spaces
 
     bless $self, $class;
     return $self;

From e1c2cf1b6b2e68ab18920cdd822e8e8c7c8949f9 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 16:33:22 +0000
Subject: [PATCH 280/429] Revert "Bug 37870: Fix sort order of class sources"

This reverts commit 6cbe190092290918f6164df610210762773f4555.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/UI/Form/Builder/Biblio.pm               |  4 +---
 Koha/UI/Form/Builder/Item.pm                 |  4 +---
 t/db_dependent/Koha/UI/Form/Builder/Biblio.t | 12 +++++------
 t/db_dependent/Koha/UI/Form/Builder/Item.t   | 21 ++++++++++----------
 4 files changed, 18 insertions(+), 23 deletions(-)

diff --git a/Koha/UI/Form/Builder/Biblio.pm b/Koha/UI/Form/Builder/Biblio.pm
index 0b4422992b..c17d4965e4 100644
--- a/Koha/UI/Form/Builder/Biblio.pm
+++ b/Koha/UI/Form/Builder/Biblio.pm
@@ -16,8 +16,6 @@ package Koha::UI::Form::Builder::Biblio;
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use feature qw(fc);
-
 use C4::Context;
 use C4::ClassSource qw( GetClassSources );
 use Koha::DateUtils qw( dt_from_string );
@@ -332,7 +330,7 @@ sub build_authorized_values_list {
 
         my $default_source = C4::Context->preference("DefaultClassificationSource");
 
-        foreach my $class_source (sort {fc($a) cmp fc($b)} keys %$class_sources) {
+        foreach my $class_source (sort keys %$class_sources) {
             next unless $class_sources->{$class_source}->{'used'} or
                         ($value and $class_source eq $value) or
                         ($class_source eq $default_source);
diff --git a/Koha/UI/Form/Builder/Item.pm b/Koha/UI/Form/Builder/Item.pm
index 1ab305b2fa..2ab9fb3bb5 100644
--- a/Koha/UI/Form/Builder/Item.pm
+++ b/Koha/UI/Form/Builder/Item.pm
@@ -16,10 +16,8 @@ package Koha::UI::Form::Builder::Item;
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use feature qw(fc);
 use List::Util qw( any );
 use MARC::Record;
-
 use C4::Context;
 use C4::Biblio qw( GetFrameworkCode GetMarcStructure IsMarcStructureInternal );
 use C4::Koha qw( GetAuthorisedValues );
@@ -244,7 +242,7 @@ sub generate_subfield_form {
             my $default_source =
               C4::Context->preference("DefaultClassificationSource");
 
-            foreach my $class_source (sort {fc($a) cmp fc($b)} keys %$class_sources) {
+            foreach my $class_source ( sort keys %$class_sources ) {
                 next
                   unless $class_sources->{$class_source}->{'used'}
                   or ( $value and $class_source eq $value )
diff --git a/t/db_dependent/Koha/UI/Form/Builder/Biblio.t b/t/db_dependent/Koha/UI/Form/Builder/Biblio.t
index b331d06b37..ed5d582ef0 100755
--- a/t/db_dependent/Koha/UI/Form/Builder/Biblio.t
+++ b/t/db_dependent/Koha/UI/Form/Builder/Biblio.t
@@ -138,13 +138,13 @@ subtest 'generate_subfield_form class sources' => sub {
         },
     );
 
-    my @class_sources = Koha::ClassSources->search( { used => 1 }, { order_by => 'cn_source' } )->as_list;
-    my %labels        = map { $_->cn_source => $_->description } @class_sources;
-    my @values        = ( '', map { $_->cn_source } @class_sources );
+    my @class_sources = Koha::ClassSources->search({used => 1}, {order_by => 'cn_source'})->as_list;
+    my %labels = map { $_->cn_source => $_->description } @class_sources;
+    my @values = ('', map { $_->cn_source } @class_sources);
 
-    is( $subfield->{marc_value}->{type}, 'select' );
-    is_deeply( $subfield->{marc_value}->{labels}, \%labels );
-    is_deeply( $subfield->{marc_value}->{values}, \@values );
+    is($subfield->{marc_value}->{type}, 'select');
+    is_deeply($subfield->{marc_value}->{labels}, \%labels);
+    is_deeply($subfield->{marc_value}->{values}, \@values);
 };
 
 subtest 'generate_subfield_form authorised value' => sub {
diff --git a/t/db_dependent/Koha/UI/Form/Builder/Item.t b/t/db_dependent/Koha/UI/Form/Builder/Item.t
index 92a7411797..de82c7741c 100755
--- a/t/db_dependent/Koha/UI/Form/Builder/Item.t
+++ b/t/db_dependent/Koha/UI/Form/Builder/Item.t
@@ -23,7 +23,6 @@ use utf8;
 
 use List::MoreUtils qw( uniq );
 
-use Koha::ClassSources;
 use Koha::Libraries;
 use Koha::MarcSubfieldStructures;
 use Koha::UI::Form::Builder::Item;
@@ -100,16 +99,16 @@ subtest 'authorised values' => sub {
     };
 
     subtest 'cn_source' => sub {
-        plan tests => 3;
-        my ($subfield) = grep { $_->{kohafield} eq 'items.cn_source' } @$subfields;
-
-        my @class_sources = Koha::ClassSources->search( { used => 1 }, { order_by => 'cn_source' } )->as_list;
-        my %labels        = map { $_->cn_source => $_->description } @class_sources;
-        my @values        = ( '', map { $_->cn_source } @class_sources );
-
-        is( $subfield->{marc_value}->{type}, 'select' );
-        is_deeply( $subfield->{marc_value}->{labels}, \%labels );
-        is_deeply( $subfield->{marc_value}->{values}, \@values );
+        plan tests => 2;
+        my ( $subfield ) = grep { $_->{kohafield} eq 'items.cn_source' } @$subfields;
+        is_deeply( $subfield->{marc_value}->{values}, [ '', 'ddc', 'lcc' ] );
+        is_deeply(
+            $subfield->{marc_value}->{labels},
+            {
+                ddc => "Dewey Decimal Classification",
+                lcc => "Library of Congress Classification",
+            }
+        );
     };
     subtest 'branches' => sub {
         plan tests => 2;

From d157ef3f7ae35513c6d053847c03f4538b9cbcbb Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 11 Oct 2024 12:05:11 +0000
Subject: [PATCH 281/429] Bug 30648: Add deleted_biblionumber column for
 reserves
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e289a8711147b82391ad24598f82080f0ce621b2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../data/mysql/atomicupdate/bug_30648.pl      | 31 +++++++++++++++++++
 installer/data/mysql/kohastructure.sql        |  2 ++
 2 files changed, 33 insertions(+)
 create mode 100755 installer/data/mysql/atomicupdate/bug_30648.pl

diff --git a/installer/data/mysql/atomicupdate/bug_30648.pl b/installer/data/mysql/atomicupdate/bug_30648.pl
new file mode 100755
index 0000000000..32f7b586bb
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_30648.pl
@@ -0,0 +1,31 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
+
+return {
+    bug_number => "30648",
+    description => "Store biblionumber of deleted record in old reserves",
+    up => sub {
+        my ($args) = @_;
+        my ($dbh, $out) = @$args{qw(dbh out)};
+        if ( !column_exists( 'reserves', 'deleted_biblionumber' ) ) {
+
+            $dbh->do(
+                q{
+                ALTER TABLE `reserves`
+                    ADD COLUMN `deleted_biblionumber` int(11) NULL DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)' AFTER biblionumber
+            }
+            );
+        }
+        say $out "Added column 'reserves.deleted_biblionumber'";
+        if ( !column_exists( 'old_reserves', 'deleted_biblionumber' ) ) {
+
+            $dbh->do(
+                q{
+                ALTER TABLE `old_reserves`
+                    ADD COLUMN `deleted_biblionumber` int(11) NULL DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)' AFTER biblionumber
+            }
+            );
+        }
+        say $out "Added column 'old_reserves.deleted_biblionumber'";
+    },
+};
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 4d1f1c10ef..a48ac559d1 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -4916,6 +4916,7 @@ CREATE TABLE `old_reserves` (
   `borrowernumber` int(11) DEFAULT NULL COMMENT 'foreign key from the borrowers table defining which patron this hold is for',
   `reservedate` date DEFAULT NULL COMMENT 'the date the hold was places',
   `biblionumber` int(11) DEFAULT NULL COMMENT 'foreign key from the biblio table defining which bib record this hold is on',
+  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)',
   `item_group_id` int(11) DEFAULT NULL COMMENT 'foreign key from the item_groups table defining if this is an item group level hold',
   `branchcode` varchar(10) DEFAULT NULL COMMENT 'foreign key from the branches table defining which branch the patron wishes to pick this hold up at',
   `desk_id` int(11) DEFAULT NULL COMMENT 'foreign key from the desks table defining which desk the patron should pick this hold up at',
@@ -5485,6 +5486,7 @@ CREATE TABLE `reserves` (
   `borrowernumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the borrowers table defining which patron this hold is for',
   `reservedate` date DEFAULT NULL COMMENT 'the date the hold was placed',
   `biblionumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the biblio table defining which bib record this hold is on',
+  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)',
   `item_group_id` int(11) DEFAULT NULL COMMENT 'foreign key from the item_groups table defining if this is an item group level hold',
   `branchcode` varchar(10) NOT NULL COMMENT 'foreign key from the branches table defining which branch the patron wishes to pick this hold up at',
   `desk_id` int(11) DEFAULT NULL COMMENT 'foreign key from the desks table defining which desk the patron should pick this hold up at',

From 0b0b1715588fdaee2246465e13900a23766601d5 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 11 Oct 2024 12:28:48 +0000
Subject: [PATCH 282/429] Bug 30648: Record deleted_biblionumber in holds
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch ensures the deleted biblionumber is recorded in current and previous holds
before the record is deleted

To test:
1 - Place and fill a few holds on a biblio, completing checkout to patron
2 - Place a few holds on the biblio and do not fulfill them
3 - Delete the items from the biblio via the DB (otherwise you cannot delete last item when there are holds)
    DELETE FROM items WHERE biblionumber={biblionumber}
4 - Delete the biblio
5 - Check the DB and confirm the deleted_biblionumber column has been filled
    SELECT reserve_id,deleted_biblionumber,biblionumber FROM old_reserves WHERE deleted_biblionumber = {biblionumber};
6 - Sign off!

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7cf1280beec7c9e20e10ed972123790986864420)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Biblio.pm   |  3 +++
 Koha/Biblio.pm | 16 ++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/C4/Biblio.pm b/C4/Biblio.pm
index 3f48dbb8df..8f2f1749a6 100644
--- a/C4/Biblio.pm
+++ b/C4/Biblio.pm
@@ -555,6 +555,9 @@ sub DelBiblio {
 
     # We delete any existing holds
     my $holds = $biblio->holds;
+    $holds->update({ deleted_biblionumber =>  $biblionumber }, { no_triggers => 1 });
+    my $old_holds = $biblio->old_holds;
+    $old_holds->update({ deleted_biblionumber =>  $biblionumber }, { no_triggers => 1 });
     while ( my $hold = $holds->next ) {
         # no need to update the holds queue on each step, we'll do it at the end
         $hold->cancel({ skip_holds_queue => 1 });
diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm
index eaf9a6d5d7..1fdcad53d1 100644
--- a/Koha/Biblio.pm
+++ b/Koha/Biblio.pm
@@ -52,6 +52,7 @@ use Koha::Item::Transfer::Limits;
 use Koha::Items;
 use Koha::Libraries;
 use Koha::Old::Checkouts;
+use Koha::Old::Holds;
 use Koha::Ratings;
 use Koha::Recalls;
 use Koha::RecordProcessor;
@@ -708,6 +709,21 @@ sub holds {
     return Koha::Holds->_new_from_dbic($hold_rs);
 }
 
+=head3 old_holds
+
+my $old_holds = $biblio->old_holds();
+
+return the historic holds placed on this record
+
+=cut
+
+sub old_holds {
+    my ( $self, $params, $attributes ) = @_;
+    $attributes->{order_by} = 'priority' unless exists $attributes->{order_by};
+    my $old_hold_rs = $self->_result->old_reserves->search( $params, $attributes );
+    return Koha::Old::Holds->_new_from_dbic($old_hold_rs);
+}
+
 =head3 current_holds
 
 my $holds = $biblio->current_holds

From 368d2c85a1ee4675cf291caa672d18163e0c0240 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 11 Oct 2024 12:49:23 +0000
Subject: [PATCH 283/429] Bug 30648: Unit tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 69c2abf14bb64fd0a925b01087227b772b7942b4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Biblio.t | 33 ++++++++++++++++++++++++++++++++-
 t/lib/TestBuilder.pm    |  6 ++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Biblio.t b/t/db_dependent/Biblio.t
index 33ef124c1c..42f93ed3c4 100755
--- a/t/db_dependent/Biblio.t
+++ b/t/db_dependent/Biblio.t
@@ -691,10 +691,41 @@ subtest 'deletedbiblio_metadata' => sub {
 
 subtest 'DelBiblio' => sub {
 
-    plan tests => 10;
+    plan tests => 11;
 
     t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
 
+    subtest 'DelBiblio holds handling' => sub {
+
+        plan tests => 3;
+        my $biblio = $builder->build_sample_biblio;
+        my $hold   = $builder->build_object(
+            {
+                class => 'Koha::Holds',
+                value => { biblionumber => $biblio->biblionumber }
+            }
+        );
+        my $old_hold = $builder->build_object(
+            {
+                class => 'Koha::Old::Holds',
+                value => { biblionumber => $biblio->biblionumber }
+            }
+        );
+
+        C4::Biblio::DelBiblio($biblio->biblionumber);
+        $old_hold->discard_changes();
+        $hold = Koha::Old::Holds->find( $hold->reserve_id );
+        ok( $hold, "Hold has been successfully cancelled on deletion of biblio" );
+        is(
+            $old_hold->deleted_biblionumber, $biblio->biblionumber,
+            "Biblionumber has been successfully recorded during deletion for old holds"
+        );
+        is(
+            $hold->deleted_biblionumber, $biblio->biblionumber,
+            "Biblionumber has been successfully recorded during deletion for existing hold that was cancelled"
+        );
+    };
+
     my ($biblionumber, $biblioitemnumber) = C4::Biblio::AddBiblio(MARC::Record->new, '');
     my $deleted = C4::Biblio::DelBiblio( $biblionumber );
     is( $deleted, undef, 'DelBiblio returns undef is the biblio has been deleted correctly - Must be 1 instead'); # FIXME We should return 1 instead!
diff --git a/t/lib/TestBuilder.pm b/t/lib/TestBuilder.pm
index 57506b11d7..63cfe5d049 100644
--- a/t/lib/TestBuilder.pm
+++ b/t/lib/TestBuilder.pm
@@ -630,6 +630,12 @@ sub _gen_default_values {
         Reserve => {
             non_priority  => 0,
             item_group_id => undef,
+            deleted_biblionumber => undef,
+        },
+        OldReserve => {
+            non_priority  => 0,
+            item_group_id => undef,
+            deleted_biblionumber => undef,
         },
         Itemtype => {
             rentalcharge => 0,

From 1577d3833fc499725e32d6241b5cf70db31e1dc9 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 11 Oct 2024 12:51:52 +0000
Subject: [PATCH 284/429] Bug 30648: Add new field to REST API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Maybe we just undef it since ti will always be null except for old_hold obecjts
and we don't have that route yet

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a7ead95c146106781a5e62807bb713fb7ba59b26)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Hold.pm                         | 37 ++++++++++++++--------------
 api/v1/swagger/definitions/hold.yaml |  3 +++
 2 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/Koha/Hold.pm b/Koha/Hold.pm
index 1faec873ec..27e2447eae 100644
--- a/Koha/Hold.pm
+++ b/Koha/Hold.pm
@@ -1026,25 +1026,26 @@ on the API.
 
 sub to_api_mapping {
     return {
-        reserve_id       => 'hold_id',
-        borrowernumber   => 'patron_id',
-        reservedate      => 'hold_date',
-        biblionumber     => 'biblio_id',
-        branchcode       => 'pickup_library_id',
-        notificationdate => undef,
-        reminderdate     => undef,
-        cancellationdate => 'cancellation_date',
-        reservenotes     => 'notes',
-        found            => 'status',
-        itemnumber       => 'item_id',
-        waitingdate      => 'waiting_date',
-        expirationdate   => 'expiration_date',
+        reserve_id             => 'hold_id',
+        borrowernumber         => 'patron_id',
+        reservedate            => 'hold_date',
+        biblionumber           => 'biblio_id',
+        deleted_biblionumber   => 'deleted_biblio_id',
+        branchcode             => 'pickup_library_id',
+        notificationdate       => undef,
+        reminderdate           => undef,
+        cancellationdate       => 'cancellation_date',
+        reservenotes           => 'notes',
+        found                  => 'status',
+        itemnumber             => 'item_id',
+        waitingdate            => 'waiting_date',
+        expirationdate         => 'expiration_date',
         patron_expiration_date => undef,
-        lowestPriority   => 'lowest_priority',
-        suspend          => 'suspended',
-        suspend_until    => 'suspended_until',
-        itemtype         => 'item_type',
-        item_level_hold  => 'item_level',
+        lowestPriority         => 'lowest_priority',
+        suspend                => 'suspended',
+        suspend_until          => 'suspended_until',
+        itemtype               => 'item_type',
+        item_level_hold        => 'item_level',
     };
 }
 
diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml
index 85bcd66900..98d4309cca 100644
--- a/api/v1/swagger/definitions/hold.yaml
+++ b/api/v1/swagger/definitions/hold.yaml
@@ -16,6 +16,9 @@ properties:
   biblio_id:
     type: integer
     description: Internal biblio identifier
+  deleted_biblio_id:
+    type: integer
+    description: Internal deleted biblio identifier
   item_group_id:
     type:
       - string

From ccb75f9815f285e655ff24d1fb5ed86e44415692 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 11 Oct 2024 13:11:10 +0000
Subject: [PATCH 285/429] Bug 30648: Tidy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e5b86bb23a2941ebf9dce50accfd214f7c610058)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Biblio.pm                                   | 7 ++++---
 installer/data/mysql/atomicupdate/bug_30648.pl | 6 +++---
 t/db_dependent/Biblio.t                        | 2 +-
 t/lib/TestBuilder.pm                           | 8 ++++----
 4 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/C4/Biblio.pm b/C4/Biblio.pm
index 8f2f1749a6..ffce275c7a 100644
--- a/C4/Biblio.pm
+++ b/C4/Biblio.pm
@@ -555,12 +555,13 @@ sub DelBiblio {
 
     # We delete any existing holds
     my $holds = $biblio->holds;
-    $holds->update({ deleted_biblionumber =>  $biblionumber }, { no_triggers => 1 });
+    $holds->update( { deleted_biblionumber => $biblionumber }, { no_triggers => 1 } );
     my $old_holds = $biblio->old_holds;
-    $old_holds->update({ deleted_biblionumber =>  $biblionumber }, { no_triggers => 1 });
+    $old_holds->update( { deleted_biblionumber => $biblionumber }, { no_triggers => 1 } );
     while ( my $hold = $holds->next ) {
+
         # no need to update the holds queue on each step, we'll do it at the end
-        $hold->cancel({ skip_holds_queue => 1 });
+        $hold->cancel( { skip_holds_queue => 1 } );
     }
 
     # We update any existing orders
diff --git a/installer/data/mysql/atomicupdate/bug_30648.pl b/installer/data/mysql/atomicupdate/bug_30648.pl
index 32f7b586bb..80d7c842a1 100755
--- a/installer/data/mysql/atomicupdate/bug_30648.pl
+++ b/installer/data/mysql/atomicupdate/bug_30648.pl
@@ -2,11 +2,11 @@ use Modern::Perl;
 use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
 
 return {
-    bug_number => "30648",
+    bug_number  => "30648",
     description => "Store biblionumber of deleted record in old reserves",
-    up => sub {
+    up          => sub {
         my ($args) = @_;
-        my ($dbh, $out) = @$args{qw(dbh out)};
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
         if ( !column_exists( 'reserves', 'deleted_biblionumber' ) ) {
 
             $dbh->do(
diff --git a/t/db_dependent/Biblio.t b/t/db_dependent/Biblio.t
index 42f93ed3c4..76413091ae 100755
--- a/t/db_dependent/Biblio.t
+++ b/t/db_dependent/Biblio.t
@@ -712,7 +712,7 @@ subtest 'DelBiblio' => sub {
             }
         );
 
-        C4::Biblio::DelBiblio($biblio->biblionumber);
+        C4::Biblio::DelBiblio( $biblio->biblionumber );
         $old_hold->discard_changes();
         $hold = Koha::Old::Holds->find( $hold->reserve_id );
         ok( $hold, "Hold has been successfully cancelled on deletion of biblio" );
diff --git a/t/lib/TestBuilder.pm b/t/lib/TestBuilder.pm
index 63cfe5d049..72f2b4c04d 100644
--- a/t/lib/TestBuilder.pm
+++ b/t/lib/TestBuilder.pm
@@ -628,13 +628,13 @@ sub _gen_default_values {
             opacusercss     => q{},
         },
         Reserve => {
-            non_priority  => 0,
-            item_group_id => undef,
+            non_priority         => 0,
+            item_group_id        => undef,
             deleted_biblionumber => undef,
         },
         OldReserve => {
-            non_priority  => 0,
-            item_group_id => undef,
+            non_priority         => 0,
+            item_group_id        => undef,
             deleted_biblionumber => undef,
         },
         Itemtype => {

From 2d06825508ab297267d683942b00360710680180 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 16:44:51 +0000
Subject: [PATCH 286/429] Bug 30648: DBRev 24.05.05.006

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha.pm                                                         | 2 +-
 .../mysql/{atomicupdate/bug_30648.pl => db_revs/240505006.pl}   | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_30648.pl => db_revs/240505006.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index 219c48d681..b5e488ace4 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.005";
+$VERSION = "24.05.05.006";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_30648.pl b/installer/data/mysql/db_revs/240505006.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_30648.pl
rename to installer/data/mysql/db_revs/240505006.pl

From 8ec3887b942da21b8f4e509dd7bdd46adf01c363 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 16:46:55 +0000
Subject: [PATCH 287/429] Bug 30648: DBIC Schema updates

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Schema/Result/OldReserve.pm | 13 +++++++++++--
 Koha/Schema/Result/Reserve.pm    | 13 +++++++++++--
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/Koha/Schema/Result/OldReserve.pm b/Koha/Schema/Result/OldReserve.pm
index cbe46e7881..4f4354c80d 100644
--- a/Koha/Schema/Result/OldReserve.pm
+++ b/Koha/Schema/Result/OldReserve.pm
@@ -54,6 +54,13 @@ the date the hold was places
 
 foreign key from the biblio table defining which bib record this hold is on
 
+=head2 deleted_biblionumber
+
+  data_type: 'integer'
+  is_nullable: 1
+
+links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)
+
 =head2 item_group_id
 
   data_type: 'integer'
@@ -235,6 +242,8 @@ __PACKAGE__->add_columns(
   { data_type => "date", datetime_undef_if_invalid => 1, is_nullable => 1 },
   "biblionumber",
   { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
+  "deleted_biblionumber",
+  { data_type => "integer", is_nullable => 1 },
   "item_group_id",
   { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
   "branchcode",
@@ -428,8 +437,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07049 @ 2024-04-05 06:44:57
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SQp2OEYb3im1x3A+W0h37w
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 16:46:06
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:droS+czhyVcRgVqOF06sQg
 
 __PACKAGE__->belongs_to(
   "item",
diff --git a/Koha/Schema/Result/Reserve.pm b/Koha/Schema/Result/Reserve.pm
index a91c14c9a0..803864d5be 100644
--- a/Koha/Schema/Result/Reserve.pm
+++ b/Koha/Schema/Result/Reserve.pm
@@ -57,6 +57,13 @@ the date the hold was placed
 
 foreign key from the biblio table defining which bib record this hold is on
 
+=head2 deleted_biblionumber
+
+  data_type: 'integer'
+  is_nullable: 1
+
+links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)
+
 =head2 item_group_id
 
   data_type: 'integer'
@@ -243,6 +250,8 @@ __PACKAGE__->add_columns(
     is_foreign_key => 1,
     is_nullable    => 0,
   },
+  "deleted_biblionumber",
+  { data_type => "integer", is_nullable => 1 },
   "item_group_id",
   { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
   "branchcode",
@@ -456,8 +465,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07049 @ 2023-11-01 18:21:38
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6BAjuPSwjtyWJaJN9fcOtg
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 16:46:06
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ljjbd9tYWR6AXmEyrg/sog
 
 __PACKAGE__->belongs_to(
   "item",

From 3a3b2852946d568c93c43350200ff878b9308ba2 Mon Sep 17 00:00:00 2001
From: Katrin Fischer <katrin.fischer@bsz-bw.de>
Date: Mon, 28 Oct 2024 18:56:08 +0000
Subject: [PATCH 288/429] Bug 30648: (QA follow-up) Terminology: reserve =>
 hold

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 9c2ec5798b9f1062b15433554c33e9089460c2cf)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 installer/data/mysql/kohastructure.sql | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index a48ac559d1..6e3f8dfc39 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -4916,7 +4916,7 @@ CREATE TABLE `old_reserves` (
   `borrowernumber` int(11) DEFAULT NULL COMMENT 'foreign key from the borrowers table defining which patron this hold is for',
   `reservedate` date DEFAULT NULL COMMENT 'the date the hold was places',
   `biblionumber` int(11) DEFAULT NULL COMMENT 'foreign key from the biblio table defining which bib record this hold is on',
-  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)',
+  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the hold to the deleted bibliographic record (deletedbiblio.biblionumber)',
   `item_group_id` int(11) DEFAULT NULL COMMENT 'foreign key from the item_groups table defining if this is an item group level hold',
   `branchcode` varchar(10) DEFAULT NULL COMMENT 'foreign key from the branches table defining which branch the patron wishes to pick this hold up at',
   `desk_id` int(11) DEFAULT NULL COMMENT 'foreign key from the desks table defining which desk the patron should pick this hold up at',
@@ -5486,7 +5486,7 @@ CREATE TABLE `reserves` (
   `borrowernumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the borrowers table defining which patron this hold is for',
   `reservedate` date DEFAULT NULL COMMENT 'the date the hold was placed',
   `biblionumber` int(11) NOT NULL DEFAULT 0 COMMENT 'foreign key from the biblio table defining which bib record this hold is on',
-  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)',
+  `deleted_biblionumber` int(11) DEFAULT NULL COMMENT 'links the hold to the deleted bibliographic record (deletedbiblio.biblionumber)',
   `item_group_id` int(11) DEFAULT NULL COMMENT 'foreign key from the item_groups table defining if this is an item group level hold',
   `branchcode` varchar(10) NOT NULL COMMENT 'foreign key from the branches table defining which branch the patron wishes to pick this hold up at',
   `desk_id` int(11) DEFAULT NULL COMMENT 'foreign key from the desks table defining which desk the patron should pick this hold up at',

From 4427257af730be11ab3d3f737e46b80fe42dfed7 Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Mon, 21 Oct 2024 18:19:07 +0000
Subject: [PATCH 289/429] Bug 38066: Pop-up window footers can block page
 content

The staff interface's default <body> padding is enough to accommmodate
the langugage-selection footer but not the taller fixed footer on pop-up
window pages. This patch adds some CSS to increase the padding on those
pages to prevent page content from being obscured by the footer
navigation.

The patch also adjusts the CSS of the "Back to top" arrow so that it
isn't hidden by fixed footer.

To test, apply the patch and rebuild the staff interface CSS.

- In the staff client, go to Cataloging -> New record.
- In tab one, click the plugin button on tag 100 subfield a.
- Perform an authority search which will return results.
- Scroll down to the bottom of the window and confirm that the last
  lines of the search results are not obscured by the footer.

This change should apply to all pop-up windows which include the same
fixed footer markup, e.g. any cataloging plugin; the transfer order
window; vendor search when adding a subscription, etc.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b09f3a88205b007a21107747aeaf816462abbaa5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
index 65884323e4..dcae65fd03 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -654,6 +654,10 @@ body {
     padding: 0 0 4em;
     text-align: left;
 
+    &:has( nav.fixed-bottom ) {
+        padding-bottom: 6rem;
+    }
+
     &.nobackdrop {
         .modal-backdrop {
             opacity: 0;
@@ -5018,7 +5022,7 @@ div .suggestion_note {
     position: fixed;
     bottom: 40px;
     right: 40px;
-    z-index: 1000;
+    z-index: $zindex-tooltip;
 }
 
 @media only screen and ( max-width: 1200px ) {

From 2c36839323d3795a5efadf72149bb58961385f2a Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 29 Oct 2024 11:34:14 +0100
Subject: [PATCH 290/429] Bug 30648: deleted_biblio_id can be null

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 63c3b6833821c8dd955e371ee4f54b49efe936ec)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 api/v1/swagger/definitions/hold.yaml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml
index 98d4309cca..2ba3367e04 100644
--- a/api/v1/swagger/definitions/hold.yaml
+++ b/api/v1/swagger/definitions/hold.yaml
@@ -17,7 +17,9 @@ properties:
     type: integer
     description: Internal biblio identifier
   deleted_biblio_id:
-    type: integer
+    type:
+      - integer
+      - "null"
     description: Internal deleted biblio identifier
   item_group_id:
     type:

From 23adbf215df86ae82564c984d1ad84c15e5e5002 Mon Sep 17 00:00:00 2001
From: Hammat Wele <hammat.wele@inlibro.com>
Date: Mon, 26 Aug 2024 21:21:50 +0000
Subject: [PATCH 291/429] Bug 37184: Special character encoding problem when
 importing MARC file from the Acquisitions module
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. Go to Cataloging > "Stage records for import".
2. Upload the file "ExportMemento2024061010532869Marc8.mrc" or a MARC8 encoded file.
3. In the form, select the options :
   - Record type: Bibliographic
   - Character encoding: MARC8
   - Format: MARC
4. Click "Stage for import".
5. Find or create an open basket in the Acquisitions module.
6. Click "add to basket".
7. Select "From a staged file" and select the previous staged file.
9. Click "Select all" to check all records.
10. In the item information tab, choose the Document type.
11. Verify the titles of the records in the basket:
 --> Koha replaces accents with symbols �

12. Apply the patch.
13. Do the same from step 1 and notice there is no more encoding issues.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e8c8b68672af08b446d1b2d9f8fae76cac22f9ef)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Import/Record.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Koha/Import/Record.pm b/Koha/Import/Record.pm
index ea238e6254..14fab57e54 100644
--- a/Koha/Import/Record.pm
+++ b/Koha/Import/Record.pm
@@ -56,7 +56,7 @@ sub get_marc_record {
         $format = 'UNIMARCAUTH';
     }
 
-    my $record = MARC::Record->new_from_xml($self->marcxml, $self->encoding, $format);
+    my $record = MARC::Record->new_from_xml( $self->marcxml, 'UTF-8', $format );
 
     return $record;
 }

From 24d58e77a4f778d8d4c537aeb2ede7ab7ea315c7 Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Tue, 22 Oct 2024 17:16:07 +0000
Subject: [PATCH 292/429] Bug 38231: Adjust CSS for search result controls in
 the OPAC

This patch makes some markup and CSS changes to the OPAC so that
controls are aligned correctly in the "mobile" view's catalog search
results. The patch also removes the border from the controls... I think
the borders looks out of place.

To test apply the patch and rebuild the OPAC CSS.

- Perform a catalog search.
- Check the styling of the controls shown for each search result, e.g.
  Place hold, Add tg, Save to lists, etc.
  - Make sure the controls work well when the browser window is various
    widths, from ~600px and up.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Yanjun Li <yanjun.li@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b8e69361bd8a0021f272636c61b2be51ab10ba01)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../bootstrap/css/src/_responsive.scss         | 16 +++++-----------
 .../opac-tmpl/bootstrap/css/src/opac.scss      |  6 ------
 .../en/includes/title-actions-menu.inc         | 18 +++++++++---------
 3 files changed, 14 insertions(+), 26 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
index 3339e0c919..082a9cba4c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
@@ -110,17 +110,11 @@
     .actions {
         display: block;
 
-        &:first-child {
-            .btn-link {
-                padding: .375rem .75rem;
-            }
+        .btn-link {
+            padding-left: 0;
         }
 
         a {
-            @include border-radius-all( 4px );
-
-            background-color: #F2F2EF;
-            border: 1px solid #DDD;
             display: block;
             font-size: 120%;
             font-weight: bold;
@@ -210,9 +204,9 @@
     .cart-actions {
         display: flex;
 
-        .btn {
-            flex-grow: 1;
-            padding: .375rem .75rem;
+        .btn + .btn {
+            line-height: 2;
+            margin-left: .5em;
         }
     }
 
diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
index c852515e27..ba62d7a779 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
@@ -280,12 +280,6 @@ th {
 
 .actions {
     white-space: nowrap;
-
-    &:first-child {
-        .btn-link {
-            padding-left: 0;
-        }
-    }
 }
 
 .advsearch_limit {
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
index 79db98d6e4..99fb97ee1c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
@@ -16,18 +16,18 @@
     [% END # / OPACHoldRequests %]
 
     [% IF ( RecallsEnabled ) %]
-        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
+        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-fw fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
     [% END %]
 
     [% IF ( LoginEnabled ) %]
         [% IF items.artreqpossible %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
         [% END %]
     [% END %]
 
     [% IF ( TagsInputEnabled ) %]
         [% IF ( loggedinusername ) %]
-            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-tag" aria-hidden="true"></i> Add tag</a></span>
+            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Add tag</a></span>
             <div id="tagform[% items.biblionumber | html %]" class="tag_results_input form-inline" style="display:none;">
                 <label for="newtag[% items.biblionumber | html %]">New tag(s), separated by a comma:</label>
                 <input type="text" name="newtag[% items.biblionumber | html %]" id="newtag[% items.biblionumber | html %]" class="form-control form-control-sm newtag" maxlength="100" />
@@ -40,7 +40,7 @@
         [% ELSE %]
             <span class="actions">
                 <span class="login4tags">
-                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-tag" aria-hidden="true"></i> Log in to add tags</a>
+                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Log in to add tags</a>
                 </span>
             </span>
         [% END # IF loggedinusername %]
@@ -48,20 +48,20 @@
 
     [% IF ( ListsEnabled ) %]
         [% IF ( shelf AND op == 'view' ) %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-list" aria-hidden="true"></i> Save to another list</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to another list</a></span>
             [% IF can_remove_biblios %]
-                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
+                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
             [% END %]
         [% ELSE %]
-            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-list" aria-hidden="true"></i> Save to lists</a></span>
+            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to lists</a></span>
         [% END # /IF ( shelf AND op == 'view' ) %]
     [% END # /IF Koha.Preference( 'opacuserlogin' ) %]
 
     [% IF ( CartEnabled ) %]
         [% IF ( items.incart ) %]
-            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% ELSE %]
-            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% END %]
     [% END # / IF opacbookbag %]
 </div>

From 49ccb39426c53ce36827a8e3434a5fd6da53e2c8 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 17:05:53 +0000
Subject: [PATCH 293/429] Revert "Bug 38231: Adjust CSS for search result
 controls in the OPAC"

This reverts commit b8e69361bd8a0021f272636c61b2be51ab10ba01.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../bootstrap/css/src/_responsive.scss         | 16 +++++++++++-----
 .../opac-tmpl/bootstrap/css/src/opac.scss      |  6 ++++++
 .../en/includes/title-actions-menu.inc         | 18 +++++++++---------
 3 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
index 082a9cba4c..3339e0c919 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
@@ -110,11 +110,17 @@
     .actions {
         display: block;
 
-        .btn-link {
-            padding-left: 0;
+        &:first-child {
+            .btn-link {
+                padding: .375rem .75rem;
+            }
         }
 
         a {
+            @include border-radius-all( 4px );
+
+            background-color: #F2F2EF;
+            border: 1px solid #DDD;
             display: block;
             font-size: 120%;
             font-weight: bold;
@@ -204,9 +210,9 @@
     .cart-actions {
         display: flex;
 
-        .btn + .btn {
-            line-height: 2;
-            margin-left: .5em;
+        .btn {
+            flex-grow: 1;
+            padding: .375rem .75rem;
         }
     }
 
diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
index ba62d7a779..c852515e27 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
@@ -280,6 +280,12 @@ th {
 
 .actions {
     white-space: nowrap;
+
+    &:first-child {
+        .btn-link {
+            padding-left: 0;
+        }
+    }
 }
 
 .advsearch_limit {
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
index 99fb97ee1c..79db98d6e4 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
@@ -16,18 +16,18 @@
     [% END # / OPACHoldRequests %]
 
     [% IF ( RecallsEnabled ) %]
-        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-fw fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
+        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
     [% END %]
 
     [% IF ( LoginEnabled ) %]
         [% IF items.artreqpossible %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
         [% END %]
     [% END %]
 
     [% IF ( TagsInputEnabled ) %]
         [% IF ( loggedinusername ) %]
-            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Add tag</a></span>
+            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-tag" aria-hidden="true"></i> Add tag</a></span>
             <div id="tagform[% items.biblionumber | html %]" class="tag_results_input form-inline" style="display:none;">
                 <label for="newtag[% items.biblionumber | html %]">New tag(s), separated by a comma:</label>
                 <input type="text" name="newtag[% items.biblionumber | html %]" id="newtag[% items.biblionumber | html %]" class="form-control form-control-sm newtag" maxlength="100" />
@@ -40,7 +40,7 @@
         [% ELSE %]
             <span class="actions">
                 <span class="login4tags">
-                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Log in to add tags</a>
+                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-tag" aria-hidden="true"></i> Log in to add tags</a>
                 </span>
             </span>
         [% END # IF loggedinusername %]
@@ -48,20 +48,20 @@
 
     [% IF ( ListsEnabled ) %]
         [% IF ( shelf AND op == 'view' ) %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to another list</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-list" aria-hidden="true"></i> Save to another list</a></span>
             [% IF can_remove_biblios %]
-                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
+                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
             [% END %]
         [% ELSE %]
-            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to lists</a></span>
+            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-list" aria-hidden="true"></i> Save to lists</a></span>
         [% END # /IF ( shelf AND op == 'view' ) %]
     [% END # /IF Koha.Preference( 'opacuserlogin' ) %]
 
     [% IF ( CartEnabled ) %]
         [% IF ( items.incart ) %]
-            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% ELSE %]
-            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% END %]
     [% END # / IF opacbookbag %]
 </div>

From 5f8a3f89ead3ebf2efc2d1256c20cbec91ac14bf Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 17:07:39 +0000
Subject: [PATCH 294/429] Revert "Bug 38066: Pop-up window footers can block
 page content"

This reverts commit 4427257af730be11ab3d3f737e46b80fe42dfed7.
---
 koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
index dcae65fd03..65884323e4 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -654,10 +654,6 @@ body {
     padding: 0 0 4em;
     text-align: left;
 
-    &:has( nav.fixed-bottom ) {
-        padding-bottom: 6rem;
-    }
-
     &.nobackdrop {
         .modal-backdrop {
             opacity: 0;
@@ -5022,7 +5018,7 @@ div .suggestion_note {
     position: fixed;
     bottom: 40px;
     right: 40px;
-    z-index: $zindex-tooltip;
+    z-index: 1000;
 }
 
 @media only screen and ( max-width: 1200px ) {

From 89b6ef3e92620bb39a9c9ceebb51294981965773 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 22 Oct 2024 23:57:57 +0000
Subject: [PATCH 295/429] Bug 38233: respect OPAC visibility for ILS-DI
 GetRecords

This change makes the ILS-DI GetRecords service use the OPAC
version of the MARCXML and filters items based on their OPAC visibility

Test plan:
1. Apply the patch
2. koha-plack --restart kohadev
3. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ILS-DI
and enable syspref
4. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=OpacHiddenItems
and add the following:
withdrawn: [1]
5. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=hidelostitems
Change syspref to "Don't show"
6. Go to http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=29
7. Add a withdrawn item and a lost item with barcodes of "test1" and "test2" respectively
8. Go to http://localhost:8080/cgi-bin/koha/ilsdi.pl?service=GetRecords&id=29
9. Note the items with barcodes test1 and test2 do not appear in the "items" tree and they don't
appear in the "marcxml" tree.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 369e5ab93a10cd587be557fcb43de628e49f5dfa)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/ILSDI/Services.pm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/C4/ILSDI/Services.pm b/C4/ILSDI/Services.pm
index 48e5bf6e40..8adc0b7d2d 100644
--- a/C4/ILSDI/Services.pm
+++ b/C4/ILSDI/Services.pm
@@ -218,7 +218,7 @@ sub GetRecords {
 
         my $biblioitem = $biblio->biblioitem->unblessed;
 
-        my $record = $biblio->metadata->record({ embed_items => 1 });
+        my $record = $biblio->metadata->record({ embed_items => 1, opac => 1, });
         if ($record) {
             $biblioitem->{marcxml} = $record->as_xml_record( C4::Context->preference('marcflavour') );
         }
@@ -236,7 +236,7 @@ sub GetRecords {
         foreach my $checkout (@$checkouts) {
             delete $checkout->{'borrowernumber'};
         }
-        my @items            = $biblio->items->as_list;
+        my @items            = $biblio->items->filter_by_visible_in_opac->as_list;
 
         $biblioitem->{items}->{item} = [];
 

From 46c9d1de2c4a6676605b603312794e3be6dd6f10 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Tue, 29 Oct 2024 10:20:04 +0000
Subject: [PATCH 296/429] Bug 38233: Add regression tests

prove t/db_dependent/ILSDI_Services.t

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 9ee7673c7ea3e4990c116647d6c077a05e31cc93)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/ILSDI_Services.t | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/ILSDI_Services.t b/t/db_dependent/ILSDI_Services.t
index 442f1c2402..bc8fba067f 100755
--- a/t/db_dependent/ILSDI_Services.t
+++ b/t/db_dependent/ILSDI_Services.t
@@ -760,7 +760,7 @@ subtest 'Holds test with start_date and end_date' => sub {
 
 subtest 'GetRecords' => sub {
 
-    plan tests => 8;
+    plan tests => 10;
 
     $schema->storage->txn_begin;
 
@@ -825,6 +825,30 @@ subtest 'GetRecords' => sub {
     is($reply_reserve->{biblionumber}, $item->biblionumber, 'GetRecords has a reserve tag');
     is($reply_reserve->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in reserve tag');
 
+    # Check for hidelostitems sys pref
+    t::lib::Mocks::mock_preference( 'hidelostitems', 1 );
+    $item->itemlost(1)->store;
+    $cgi->param( service => 'GetRecords' );
+    $cgi->param( id      => $item->biblionumber );
+    my $itemlost_reply = C4::ILSDI::Services::GetRecords($cgi);
+    is(
+        $itemlost_reply->{record}->[0]->{items}->{item}->[0], undef,
+        'GetRecords does not show lost items if hidelostitems = 1'
+    );
+    $item->itemlost(0)->store;
+
+    # Check for OpacHiddenItems sys pref
+    t::lib::Mocks::mock_preference( 'OpacHiddenItems', 'withdrawn: [1]' );
+    $item->withdrawn(1)->store;
+    $cgi->param( service => 'GetRecords' );
+    $cgi->param( id      => $item->biblionumber );
+    my $itemwithdrawn_reply = C4::ILSDI::Services::GetRecords($cgi);
+    is(
+        $itemwithdrawn_reply->{record}->[0]->{items}->{item}->[0],
+        undef,
+        'GetRecords does not show withdrawn items if OpacHiddenItems contains \'withdrawn: [1]\''
+    );
+
     $schema->storage->txn_rollback;
 };
 

From cea77be3c64928b1287442f85bb8e3ac9c0334ec Mon Sep 17 00:00:00 2001
From: Fridolin Somers <fridolin.somers@biblibre.com>
Date: Wed, 9 Oct 2024 14:35:11 +0200
Subject: [PATCH 297/429] Bug 38131: ILS-DI doc must show renewals_count

Since Bug 30275 renamed issues.renewals to issues.renewals_count, the ILS-DI doc needs to be updated in :
koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt
Note that service 'RenewLoan' returns 'renewals' this has not changed.

Test plan :
1) Enable ILS-DI webservice
2) Go to opac : /cgi-bin/koha/ilsdi.pl
3) Click on 'GetRecords'
4) Check you see 'renewals_count' in 'Example response'
5) Click on 'GetPatronInfo'
6) Check you see 'renewals_count' in 'Example response'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 37bd87c6feb9cc3c5659504c99cf606550640a50)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt
index 3fc3bb45ff..2451692a79 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/ilsdi.tt
@@ -349,7 +349,7 @@
         <timestamp>2019-07-18 17:03:03</timestamp>
         <cn_sort>R_ABE</cn_sort>
         <itemlost>0</itemlost>
-        <renewals>28</renewals>
+        <renewals_count>28</renewals_count>
         <barcode>040444587X</barcode>
         <damaged>0</damaged>
         <replacementpricedate>2018-02-01</replacementpricedate>
@@ -656,7 +656,7 @@
       <homebranch>BIB</homebranch>
       <holdingbranch>BIB</holdingbranch>
       <biblionumber>4483</biblionumber>
-      <renewals>3</renewals>
+      <renewals_count>3</renewals_count>
       <damaged>0</damaged>
       <cn_sort>RO_STE</cn_sort>
       <frameworkcode></frameworkcode>
@@ -696,7 +696,7 @@
       <homebranch>BIB</homebranch>
       <holdingbranch>BIB</holdingbranch>
       <biblionumber>4486</biblionumber>
-      <renewals>3</renewals>
+      <renewals_count>3</renewals_count>
       <damaged>0</damaged>
       <cn_sort>78787_BUC</cn_sort>
       <volume>une méthode simple et facile pour apprendre la guitare</volume>

From d288c5a047e126ae3f12364594acf58ae7922370 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 23 Oct 2024 12:12:13 +0200
Subject: [PATCH 298/429] Bug 38234: Remove unused jszip.min.js file

This specific file is not used since Bug 23013 ("Upgrade DataTables in the staff client")

This version is vulnerable and should not be used in the future anyway.

There is a version embedded in our DataTables bundle, which is jszip-3.10.1.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6a006258701fb80cbdacdd3b5b8c8a4c5e7451cf)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/lib/jquery/plugins/jszip.min.js | 15 ---------------
 1 file changed, 15 deletions(-)
 delete mode 100644 koha-tmpl/intranet-tmpl/lib/jquery/plugins/jszip.min.js

diff --git a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jszip.min.js b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jszip.min.js
deleted file mode 100644
index 218a4f7d6c..0000000000
--- a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jszip.min.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/*!
-
-JSZip v3.1.3 - A Javascript class for generating and reading zip files
-<http://stuartk.com/jszip>
-
-(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
-Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
-
-JSZip uses the library pako released under the MIT license :
-https://github.com/nodeca/pako/blob/master/LICENSE
-*/
-!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.JSZip=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";var d=a("./utils"),e=a("./support"),f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";c.encode=function(a){for(var b,c,e,g,h,i,j,k=[],l=0,m=a.length,n=m,o="string"!==d.getTypeOf(a);l<a.length;)n=m-l,o?(b=a[l++],c=l<m?a[l++]:0,e=l<m?a[l++]:0):(b=a.charCodeAt(l++),c=l<m?a.charCodeAt(l++):0,e=l<m?a.charCodeAt(l++):0),g=b>>2,h=(3&b)<<4|c>>4,i=n>1?(15&c)<<2|e>>6:64,j=n>2?63&e:64,k.push(f.charAt(g)+f.charAt(h)+f.charAt(i)+f.charAt(j));return k.join("")},c.decode=function(a){var b,c,d,g,h,i,j,k=0,l=0,m="data:";if(a.substr(0,m.length)===m)throw new Error("Invalid base64 input, it looks like a data url.");a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");var n=3*a.length/4;if(a.charAt(a.length-1)===f.charAt(64)&&n--,a.charAt(a.length-2)===f.charAt(64)&&n--,n%1!==0)throw new Error("Invalid base64 input, bad content length.");var o;for(o=e.uint8array?new Uint8Array(0|n):new Array(0|n);k<a.length;)g=f.indexOf(a.charAt(k++)),h=f.indexOf(a.charAt(k++)),i=f.indexOf(a.charAt(k++)),j=f.indexOf(a.charAt(k++)),b=g<<2|h>>4,c=(15&h)<<4|i>>2,d=(3&i)<<6|j,o[l++]=b,64!==i&&(o[l++]=c),64!==j&&(o[l++]=d);return o}},{"./support":30,"./utils":32}],2:[function(a,b,c){"use strict";function d(a,b,c,d,e){this.compressedSize=a,this.uncompressedSize=b,this.crc32=c,this.compression=d,this.compressedContent=e}var e=a("./external"),f=a("./stream/DataWorker"),g=a("./stream/DataLengthProbe"),h=a("./stream/Crc32Probe"),g=a("./stream/DataLengthProbe");d.prototype={getContentWorker:function(){var a=new f(e.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new g("data_length")),b=this;return a.on("end",function(){if(this.streamInfo.data_length!==b.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),a},getCompressedWorker:function(){return new f(e.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},d.createWorkerFrom=function(a,b,c){return a.pipe(new h).pipe(new g("uncompressedSize")).pipe(b.compressWorker(c)).pipe(new g("compressedSize")).withStreamInfo("compression",b)},b.exports=d},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(a,b,c){"use strict";var d=a("./stream/GenericWorker");c.STORE={magic:"\0\0",compressWorker:function(a){return new d("STORE compression")},uncompressWorker:function(){return new d("STORE decompression")}},c.DEFLATE=a("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(a,b,c){"use strict";function d(){for(var a,b=[],c=0;c<256;c++){a=c;for(var d=0;d<8;d++)a=1&a?3988292384^a>>>1:a>>>1;b[c]=a}return b}function e(a,b,c,d){var e=h,f=d+c;a^=-1;for(var g=d;g<f;g++)a=a>>>8^e[255&(a^b[g])];return a^-1}function f(a,b,c,d){var e=h,f=d+c;a^=-1;for(var g=d;g<f;g++)a=a>>>8^e[255&(a^b.charCodeAt(g))];return a^-1}var g=a("./utils"),h=d();b.exports=function(a,b){if("undefined"==typeof a||!a.length)return 0;var c="string"!==g.getTypeOf(a);return c?e(0|b,a,a.length,0):f(0|b,a,a.length,0)}},{"./utils":32}],5:[function(a,b,c){"use strict";c.base64=!1,c.binary=!1,c.dir=!1,c.createFolders=!0,c.date=null,c.compression=null,c.compressionOptions=null,c.comment=null,c.unixPermissions=null,c.dosPermissions=null},{}],6:[function(a,b,c){"use strict";var d=null;d="undefined"!=typeof Promise?Promise:a("lie"),b.exports={Promise:d}},{lie:58}],7:[function(a,b,c){"use strict";function d(a,b){h.call(this,"FlateWorker/"+a),this._pako=new f[a]({raw:!0,level:b.level||-1}),this.meta={};var c=this;this._pako.onData=function(a){c.push({data:a,meta:c.meta})}}var e="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,f=a("pako"),g=a("./utils"),h=a("./stream/GenericWorker"),i=e?"uint8array":"array";c.magic="\b\0",g.inherits(d,h),d.prototype.processChunk=function(a){this.meta=a.meta,this._pako.push(g.transformTo(i,a.data),!1)},d.prototype.flush=function(){h.prototype.flush.call(this),this._pako.push([],!0)},d.prototype.cleanUp=function(){h.prototype.cleanUp.call(this),this._pako=null},c.compressWorker=function(a){return new d("Deflate",a)},c.uncompressWorker=function(){return new d("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:59}],8:[function(a,b,c){"use strict";function d(a,b,c,d){f.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=b,this.zipPlatform=c,this.encodeFileName=d,this.streamFiles=a,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}var e=a("../utils"),f=a("../stream/GenericWorker"),g=a("../utf8"),h=a("../crc32"),i=a("../signature"),j=function(a,b){var c,d="";for(c=0;c<b;c++)d+=String.fromCharCode(255&a),a>>>=8;return d},k=function(a,b){var c=a;return a||(c=b?16893:33204),(65535&c)<<16},l=function(a,b){return 63&(a||0)},m=function(a,b,c,d,f,m){var n,o,p=a.file,q=a.compression,r=m!==g.utf8encode,s=e.transformTo("string",m(p.name)),t=e.transformTo("string",g.utf8encode(p.name)),u=p.comment,v=e.transformTo("string",m(u)),w=e.transformTo("string",g.utf8encode(u)),x=t.length!==p.name.length,y=w.length!==u.length,z="",A="",B="",C=p.dir,D=p.date,E={crc32:0,compressedSize:0,uncompressedSize:0};b&&!c||(E.crc32=a.crc32,E.compressedSize=a.compressedSize,E.uncompressedSize=a.uncompressedSize);var F=0;b&&(F|=8),r||!x&&!y||(F|=2048);var G=0,H=0;C&&(G|=16),"UNIX"===f?(H=798,G|=k(p.unixPermissions,C)):(H=20,G|=l(p.dosPermissions,C)),n=D.getUTCHours(),n<<=6,n|=D.getUTCMinutes(),n<<=5,n|=D.getUTCSeconds()/2,o=D.getUTCFullYear()-1980,o<<=4,o|=D.getUTCMonth()+1,o<<=5,o|=D.getUTCDate(),x&&(A=j(1,1)+j(h(s),4)+t,z+="up"+j(A.length,2)+A),y&&(B=j(1,1)+j(h(v),4)+w,z+="uc"+j(B.length,2)+B);var I="";I+="\n\0",I+=j(F,2),I+=q.magic,I+=j(n,2),I+=j(o,2),I+=j(E.crc32,4),I+=j(E.compressedSize,4),I+=j(E.uncompressedSize,4),I+=j(s.length,2),I+=j(z.length,2);var J=i.LOCAL_FILE_HEADER+I+s+z,K=i.CENTRAL_FILE_HEADER+j(H,2)+I+j(v.length,2)+"\0\0\0\0"+j(G,4)+j(d,4)+s+z+v;return{fileRecord:J,dirRecord:K}},n=function(a,b,c,d,f){var g="",h=e.transformTo("string",f(d));return g=i.CENTRAL_DIRECTORY_END+"\0\0\0\0"+j(a,2)+j(a,2)+j(b,4)+j(c,4)+j(h.length,2)+h},o=function(a){var b="";return b=i.DATA_DESCRIPTOR+j(a.crc32,4)+j(a.compressedSize,4)+j(a.uncompressedSize,4)};e.inherits(d,f),d.prototype.push=function(a){var b=a.meta.percent||0,c=this.entriesCount,d=this._sources.length;this.accumulate?this.contentBuffer.push(a):(this.bytesWritten+=a.data.length,f.prototype.push.call(this,{data:a.data,meta:{currentFile:this.currentFile,percent:c?(b+100*(c-d-1))/c:100}}))},d.prototype.openedSource=function(a){this.currentSourceOffset=this.bytesWritten,this.currentFile=a.file.name;var b=this.streamFiles&&!a.file.dir;if(b){var c=m(a,b,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:c.fileRecord,meta:{percent:0}})}else this.accumulate=!0},d.prototype.closedSource=function(a){this.accumulate=!1;var b=this.streamFiles&&!a.file.dir,c=m(a,b,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(c.dirRecord),b)this.push({data:o(a),meta:{percent:100}});else for(this.push({data:c.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},d.prototype.flush=function(){for(var a=this.bytesWritten,b=0;b<this.dirRecords.length;b++)this.push({data:this.dirRecords[b],meta:{percent:100}});var c=this.bytesWritten-a,d=n(this.dirRecords.length,c,a,this.zipComment,this.encodeFileName);this.push({data:d,meta:{percent:100}})},d.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),this.isPaused?this.previous.pause():this.previous.resume()},d.prototype.registerPrevious=function(a){this._sources.push(a);var b=this;return a.on("data",function(a){b.processChunk(a)}),a.on("end",function(){b.closedSource(b.previous.streamInfo),b._sources.length?b.prepareNextSource():b.end()}),a.on("error",function(a){b.error(a)}),this},d.prototype.resume=function(){return!!f.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||this.generatedError?void 0:(this.end(),!0))},d.prototype.error=function(a){var b=this._sources;if(!f.prototype.error.call(this,a))return!1;for(var c=0;c<b.length;c++)try{b[c].error(a)}catch(a){}return!0},d.prototype.lock=function(){f.prototype.lock.call(this);for(var a=this._sources,b=0;b<a.length;b++)a[b].lock()},b.exports=d},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(a,b,c){"use strict";var d=a("../compressions"),e=a("./ZipFileWorker"),f=function(a,b){var c=a||b,e=d[c];if(!e)throw new Error(c+" is not a valid compression method !");return e};c.generateWorker=function(a,b,c){var d=new e(b.streamFiles,c,b.platform,b.encodeFileName),g=0;try{a.forEach(function(a,c){g++;var e=f(c.options.compression,b.compression),h=c.options.compressionOptions||b.compressionOptions||{},i=c.dir,j=c.date;c._compressWorker(e,h).withStreamInfo("file",{name:a,dir:i,date:j,comment:c.comment||"",unixPermissions:c.unixPermissions,dosPermissions:c.dosPermissions}).pipe(d)}),d.entriesCount=g}catch(h){d.error(h)}return d}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(a,b,c){"use strict";function d(){if(!(this instanceof d))return new d;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files={},this.comment=null,this.root="",this.clone=function(){var a=new d;for(var b in this)"function"!=typeof this[b]&&(a[b]=this[b]);return a}}d.prototype=a("./object"),d.prototype.loadAsync=a("./load"),d.support=a("./support"),d.defaults=a("./defaults"),d.version="3.1.3",d.loadAsync=function(a,b){return(new d).loadAsync(a,b)},d.external=a("./external"),b.exports=d},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(a,b,c){"use strict";function d(a){return new f.Promise(function(b,c){var d=a.decompressed.getContentWorker().pipe(new i);d.on("error",function(a){c(a)}).on("end",function(){d.streamInfo.crc32!==a.decompressed.crc32?c(new Error("Corrupted zip : CRC32 mismatch")):b()}).resume()})}var e=a("./utils"),f=a("./external"),g=a("./utf8"),e=a("./utils"),h=a("./zipEntries"),i=a("./stream/Crc32Probe"),j=a("./nodejsUtils");b.exports=function(a,b){var c=this;return b=e.extend(b||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:g.utf8decode}),j.isNode&&j.isStream(a)?f.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):e.prepareContent("the loaded zip file",a,!0,b.optimizedBinaryString,b.base64).then(function(a){var c=new h(b);return c.load(a),c}).then(function(a){var c=[f.Promise.resolve(a)],e=a.files;if(b.checkCRC32)for(var g=0;g<e.length;g++)c.push(d(e[g]));return f.Promise.all(c)}).then(function(a){for(var d=a.shift(),e=d.files,f=0;f<e.length;f++){var g=e[f];c.file(g.fileNameStr,g.decompressed,{binary:!0,optimizedBinaryString:!0,date:g.date,dir:g.dir,comment:g.fileCommentStr.length?g.fileCommentStr:null,unixPermissions:g.unixPermissions,dosPermissions:g.dosPermissions,createFolders:b.createFolders})}return d.zipComment.length&&(c.comment=d.zipComment),c})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(a,b,c){"use strict";function d(a,b){f.call(this,"Nodejs stream input adapter for "+a),this._upstreamEnded=!1,this._bindStream(b)}var e=a("../utils"),f=a("../stream/GenericWorker");e.inherits(d,f),d.prototype._bindStream=function(a){var b=this;this._stream=a,a.pause(),a.on("data",function(a){b.push({data:a,meta:{percent:0}})}).on("error",function(a){b.isPaused?this.generatedError=a:b.error(a)}).on("end",function(){b.isPaused?b._upstreamEnded=!0:b.end()})},d.prototype.pause=function(){return!!f.prototype.pause.call(this)&&(this._stream.pause(),!0)},d.prototype.resume=function(){return!!f.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},b.exports=d},{"../stream/GenericWorker":28,"../utils":32}],13:[function(a,b,c){"use strict";function d(a,b,c){e.call(this,b),this._helper=a;var d=this;a.on("data",function(a,b){d.push(a)||d._helper.pause(),c&&c(b)}).on("error",function(a){d.emit("error",a)}).on("end",function(){d.push(null)})}var e=a("readable-stream").Readable,f=a("util");f.inherits(d,e),d.prototype._read=function(){this._helper.resume()},b.exports=d},{"readable-stream":16,util:void 0}],14:[function(a,b,c){"use strict";b.exports={isNode:"undefined"!=typeof Buffer,newBuffer:function(a,b){return new Buffer(a,b)},isBuffer:function(a){return Buffer.isBuffer(a)},isStream:function(a){return a&&"function"==typeof a.on&&"function"==typeof a.pause&&"function"==typeof a.resume}}},{}],15:[function(a,b,c){"use strict";function d(a){return"[object RegExp]"===Object.prototype.toString.call(a)}var e=a("./utf8"),f=a("./utils"),g=a("./stream/GenericWorker"),h=a("./stream/StreamHelper"),i=a("./defaults"),j=a("./compressedObject"),k=a("./zipObject"),l=a("./generate"),m=a("./nodejsUtils"),n=a("./nodejs/NodejsStreamInputAdapter"),o=function(a,b,c){var d,e=f.getTypeOf(b),h=f.extend(c||{},i);h.date=h.date||new Date,null!==h.compression&&(h.compression=h.compression.toUpperCase()),"string"==typeof h.unixPermissions&&(h.unixPermissions=parseInt(h.unixPermissions,8)),h.unixPermissions&&16384&h.unixPermissions&&(h.dir=!0),h.dosPermissions&&16&h.dosPermissions&&(h.dir=!0),h.dir&&(a=q(a)),h.createFolders&&(d=p(a))&&r.call(this,d,!0);var l="string"===e&&h.binary===!1&&h.base64===!1;c&&"undefined"!=typeof c.binary||(h.binary=!l);var o=b instanceof j&&0===b.uncompressedSize;(o||h.dir||!b||0===b.length)&&(h.base64=!1,h.binary=!0,b="",h.compression="STORE",e="string");var s=null;s=b instanceof j||b instanceof g?b:m.isNode&&m.isStream(b)?new n(a,b):f.prepareContent(a,b,h.binary,h.optimizedBinaryString,h.base64);var t=new k(a,s,h);this.files[a]=t},p=function(a){"/"===a.slice(-1)&&(a=a.substring(0,a.length-1));var b=a.lastIndexOf("/");return b>0?a.substring(0,b):""},q=function(a){return"/"!==a.slice(-1)&&(a+="/"),a},r=function(a,b){return b="undefined"!=typeof b?b:i.createFolders,a=q(a),this.files[a]||o.call(this,a,null,{dir:!0,createFolders:b}),this.files[a]},s={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},forEach:function(a){var b,c,d;for(b in this.files)this.files.hasOwnProperty(b)&&(d=this.files[b],c=b.slice(this.root.length,b.length),c&&b.slice(0,this.root.length)===this.root&&a(c,d))},filter:function(a){var b=[];return this.forEach(function(c,d){a(c,d)&&b.push(d)}),b},file:function(a,b,c){if(1===arguments.length){if(d(a)){var e=a;return this.filter(function(a,b){return!b.dir&&e.test(a)})}var f=this.files[this.root+a];return f&&!f.dir?f:null}return a=this.root+a,o.call(this,a,b,c),this},folder:function(a){if(!a)return this;if(d(a))return this.filter(function(b,c){return c.dir&&a.test(b)});var b=this.root+a,c=r.call(this,b),e=this.clone();return e.root=c.name,e},remove:function(a){a=this.root+a;var b=this.files[a];if(b||("/"!==a.slice(-1)&&(a+="/"),b=this.files[a]),b&&!b.dir)delete this.files[a];else for(var c=this.filter(function(b,c){return c.name.slice(0,a.length)===a}),d=0;d<c.length;d++)delete this.files[c[d].name];return this},generate:function(a){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},generateInternalStream:function(a){var b,c={};try{if(c=f.extend(a||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:e.utf8encode}),c.type=c.type.toLowerCase(),c.compression=c.compression.toUpperCase(),"binarystring"===c.type&&(c.type="string"),!c.type)throw new Error("No output type specified.");f.checkSupport(c.type),"darwin"!==c.platform&&"freebsd"!==c.platform&&"linux"!==c.platform&&"sunos"!==c.platform||(c.platform="UNIX"),"win32"===c.platform&&(c.platform="DOS");var d=c.comment||this.comment||"";b=l.generateWorker(this,c,d)}catch(i){b=new g("error"),b.error(i)}return new h(b,c.type||"string",c.mimeType)},generateAsync:function(a,b){return this.generateInternalStream(a).accumulate(b)},generateNodeStream:function(a,b){return a=a||{},a.type||(a.type="nodebuffer"),this.generateInternalStream(a).toNodejsStream(b)}};b.exports=s},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(a,b,c){b.exports=a("stream")},{stream:void 0}],17:[function(a,b,c){"use strict";function d(a){e.call(this,a);for(var b=0;b<this.data.length;b++)a[b]=255&a[b]}var e=a("./DataReader"),f=a("../utils");f.inherits(d,e),d.prototype.byteAt=function(a){return this.data[this.zero+a]},d.prototype.lastIndexOfSignature=function(a){for(var b=a.charCodeAt(0),c=a.charCodeAt(1),d=a.charCodeAt(2),e=a.charCodeAt(3),f=this.length-4;f>=0;--f)if(this.data[f]===b&&this.data[f+1]===c&&this.data[f+2]===d&&this.data[f+3]===e)return f-this.zero;return-1},d.prototype.readAndCheckSignature=function(a){var b=a.charCodeAt(0),c=a.charCodeAt(1),d=a.charCodeAt(2),e=a.charCodeAt(3),f=this.readData(4);return b===f[0]&&c===f[1]&&d===f[2]&&e===f[3]},d.prototype.readData=function(a){if(this.checkOffset(a),0===a)return[];var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"../utils":32,"./DataReader":18}],18:[function(a,b,c){"use strict";function d(a){this.data=a,this.length=a.length,this.index=0,this.zero=0}var e=a("../utils");d.prototype={checkOffset:function(a){this.checkIndex(this.index+a)},checkIndex:function(a){if(this.length<this.zero+a||a<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+a+"). Corrupted zip ?")},setIndex:function(a){this.checkIndex(a),this.index=a},skip:function(a){this.setIndex(this.index+a)},byteAt:function(a){},readInt:function(a){var b,c=0;for(this.checkOffset(a),b=this.index+a-1;b>=this.index;b--)c=(c<<8)+this.byteAt(b);return this.index+=a,c},readString:function(a){return e.transformTo("string",this.readData(a))},readData:function(a){},lastIndexOfSignature:function(a){},readAndCheckSignature:function(a){},readDate:function(){var a=this.readInt(4);return new Date(Date.UTC((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&31,a>>5&63,(31&a)<<1))}},b.exports=d},{"../utils":32}],19:[function(a,b,c){"use strict";function d(a){e.call(this,a)}var e=a("./Uint8ArrayReader"),f=a("../utils");f.inherits(d,e),d.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(a,b,c){"use strict";function d(a){e.call(this,a)}var e=a("./DataReader"),f=a("../utils");f.inherits(d,e),d.prototype.byteAt=function(a){return this.data.charCodeAt(this.zero+a)},d.prototype.lastIndexOfSignature=function(a){return this.data.lastIndexOf(a)-this.zero},d.prototype.readAndCheckSignature=function(a){var b=this.readData(4);return a===b},d.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"../utils":32,"./DataReader":18}],21:[function(a,b,c){"use strict";function d(a){e.call(this,a)}var e=a("./ArrayReader"),f=a("../utils");f.inherits(d,e),d.prototype.readData=function(a){if(this.checkOffset(a),0===a)return new Uint8Array(0);var b=this.data.subarray(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"../utils":32,"./ArrayReader":17}],22:[function(a,b,c){"use strict";var d=a("../utils"),e=a("../support"),f=a("./ArrayReader"),g=a("./StringReader"),h=a("./NodeBufferReader"),i=a("./Uint8ArrayReader");b.exports=function(a){var b=d.getTypeOf(a);return d.checkSupport(b),"string"!==b||e.uint8array?"nodebuffer"===b?new h(a):e.uint8array?new i(d.transformTo("uint8array",a)):new f(d.transformTo("array",a)):new g(a)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(a,b,c){"use strict";c.LOCAL_FILE_HEADER="PK",c.CENTRAL_FILE_HEADER="PK",c.CENTRAL_DIRECTORY_END="PK",c.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",c.ZIP64_CENTRAL_DIRECTORY_END="PK",c.DATA_DESCRIPTOR="PK\b"},{}],24:[function(a,b,c){"use strict";function d(a){e.call(this,"ConvertWorker to "+a),this.destType=a}var e=a("./GenericWorker"),f=a("../utils");f.inherits(d,e),d.prototype.processChunk=function(a){this.push({data:f.transformTo(this.destType,a.data),meta:a.meta})},b.exports=d},{"../utils":32,"./GenericWorker":28}],25:[function(a,b,c){"use strict";function d(){e.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}var e=a("./GenericWorker"),f=a("../crc32"),g=a("../utils");g.inherits(d,e),d.prototype.processChunk=function(a){this.streamInfo.crc32=f(a.data,this.streamInfo.crc32||0),this.push(a)},b.exports=d},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(a,b,c){"use strict";function d(a){f.call(this,"DataLengthProbe for "+a),this.propName=a,this.withStreamInfo(a,0)}var e=a("../utils"),f=a("./GenericWorker");e.inherits(d,f),d.prototype.processChunk=function(a){if(a){var b=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=b+a.data.length}f.prototype.processChunk.call(this,a)},b.exports=d},{"../utils":32,"./GenericWorker":28}],27:[function(a,b,c){"use strict";function d(a){f.call(this,"DataWorker");var b=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,a.then(function(a){b.dataIsReady=!0,b.data=a,b.max=a&&a.length||0,b.type=e.getTypeOf(a),b.isPaused||b._tickAndRepeat()},function(a){b.error(a)})}var e=a("../utils"),f=a("./GenericWorker"),g=16384;e.inherits(d,f),d.prototype.cleanUp=function(){f.prototype.cleanUp.call(this),this.data=null},d.prototype.resume=function(){return!!f.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,e.delay(this._tickAndRepeat,[],this)),!0)},d.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(e.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},d.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var a=g,b=null,c=Math.min(this.max,this.index+a);if(this.index>=this.max)return this.end();switch(this.type){case"string":b=this.data.substring(this.index,c);break;case"uint8array":b=this.data.subarray(this.index,c);break;case"array":case"nodebuffer":b=this.data.slice(this.index,c)}return this.index=c,this.push({data:b,meta:{percent:this.max?this.index/this.max*100:0}})},b.exports=d},{"../utils":32,"./GenericWorker":28}],28:[function(a,b,c){"use strict";function d(a){this.name=a||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}d.prototype={push:function(a){this.emit("data",a)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(a){this.emit("error",a)}return!0},error:function(a){return!this.isFinished&&(this.isPaused?this.generatedError=a:(this.isFinished=!0,this.emit("error",a),this.previous&&this.previous.error(a),this.cleanUp()),!0)},on:function(a,b){return this._listeners[a].push(b),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(a,b){if(this._listeners[a])for(var c=0;c<this._listeners[a].length;c++)this._listeners[a][c].call(this,b)},pipe:function(a){return a.registerPrevious(this)},registerPrevious:function(a){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=a.streamInfo,this.mergeStreamInfo(),this.previous=a;var b=this;return a.on("data",function(a){b.processChunk(a)}),a.on("end",function(){b.end()}),a.on("error",function(a){b.error(a)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;this.isPaused=!1;var a=!1;return this.generatedError&&(this.error(this.generatedError),a=!0),this.previous&&this.previous.resume(),!a},flush:function(){},processChunk:function(a){this.push(a)},withStreamInfo:function(a,b){return this.extraStreamInfo[a]=b,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var a in this.extraStreamInfo)this.extraStreamInfo.hasOwnProperty(a)&&(this.streamInfo[a]=this.extraStreamInfo[a])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var a="Worker "+this.name;return this.previous?this.previous+" -> "+a:a}},b.exports=d},{}],29:[function(a,b,c){"use strict";function d(a,b,c,d){var f=null;switch(a){case"blob":return h.newBlob(c,d);case"base64":return f=e(b,c),k.encode(f);default:return f=e(b,c),h.transformTo(a,f)}}function e(a,b){var c,d=0,e=null,f=0;for(c=0;c<b.length;c++)f+=b[c].length;switch(a){case"string":return b.join("");case"array":return Array.prototype.concat.apply([],b);case"uint8array":for(e=new Uint8Array(f),c=0;c<b.length;c++)e.set(b[c],d),d+=b[c].length;return e;case"nodebuffer":return Buffer.concat(b);default:throw new Error("concat : unsupported type '"+a+"'")}}function f(a,b){return new m.Promise(function(c,e){var f=[],g=a._internalType,h=a._outputType,i=a._mimeType;a.on("data",function(a,c){f.push(a),b&&b(c)}).on("error",function(a){f=[],e(a)}).on("end",function(){try{var a=d(h,g,f,i);c(a)}catch(b){e(b)}f=[]}).resume()})}function g(a,b,c){var d=b;switch(b){case"blob":d="arraybuffer";break;case"arraybuffer":d="uint8array";break;case"base64":d="string"}try{this._internalType=d,this._outputType=b,this._mimeType=c,h.checkSupport(d),this._worker=a.pipe(new i(d)),a.lock()}catch(e){this._worker=new j("error"),this._worker.error(e)}}var h=a("../utils"),i=a("./ConvertWorker"),j=a("./GenericWorker"),k=a("../base64"),l=a("../support"),m=a("../external"),n=null;if(l.nodestream)try{n=a("../nodejs/NodejsStreamOutputAdapter")}catch(o){}g.prototype={accumulate:function(a){return f(this,a)},on:function(a,b){var c=this;return"data"===a?this._worker.on(a,function(a){b.call(c,a.data,a.meta)}):this._worker.on(a,function(){h.delay(b,arguments,c)}),this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(a){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new n(this,{objectMode:"nodebuffer"!==this._outputType},a)}},b.exports=g},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(a,b,c){"use strict";if(c.base64=!0,c.array=!0,c.string=!0,c.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,c.nodebuffer="undefined"!=typeof Buffer,c.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)c.blob=!1;else{var d=new ArrayBuffer(0);try{c.blob=0===new Blob([d],{type:"application/zip"}).size}catch(e){try{var f=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,g=new f;g.append(d),c.blob=0===g.getBlob("application/zip").size}catch(e){c.blob=!1}}}try{c.nodestream=!!a("readable-stream").Readable}catch(e){c.nodestream=!1}},{"readable-stream":16}],31:[function(a,b,c){"use strict";function d(){i.call(this,"utf-8 decode"),this.leftOver=null}function e(){i.call(this,"utf-8 encode")}for(var f=a("./utils"),g=a("./support"),h=a("./nodejsUtils"),i=a("./stream/GenericWorker"),j=new Array(256),k=0;k<256;k++)j[k]=k>=252?6:k>=248?5:k>=240?4:k>=224?3:k>=192?2:1;j[254]=j[254]=1;var l=function(a){var b,c,d,e,f,h=a.length,i=0;for(e=0;e<h;e++)c=a.charCodeAt(e),55296===(64512&c)&&e+1<h&&(d=a.charCodeAt(e+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),e++)),i+=c<128?1:c<2048?2:c<65536?3:4;for(b=g.uint8array?new Uint8Array(i):new Array(i),f=0,e=0;f<i;e++)c=a.charCodeAt(e),55296===(64512&c)&&e+1<h&&(d=a.charCodeAt(e+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),e++)),c<128?b[f++]=c:c<2048?(b[f++]=192|c>>>6,b[f++]=128|63&c):c<65536?(b[f++]=224|c>>>12,b[f++]=128|c>>>6&63,b[f++]=128|63&c):(b[f++]=240|c>>>18,b[f++]=128|c>>>12&63,b[f++]=128|c>>>6&63,b[f++]=128|63&c);return b},m=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return c<0?b:0===c?b:c+j[a[c]]>b?c:b},n=function(a){var b,c,d,e,g=a.length,h=new Array(2*g);for(c=0,b=0;b<g;)if(d=a[b++],d<128)h[c++]=d;else if(e=j[d],e>4)h[c++]=65533,b+=e-1;else{for(d&=2===e?31:3===e?15:7;e>1&&b<g;)d=d<<6|63&a[b++],e--;e>1?h[c++]=65533:d<65536?h[c++]=d:(d-=65536,h[c++]=55296|d>>10&1023,h[c++]=56320|1023&d)}return h.length!==c&&(h.subarray?h=h.subarray(0,c):h.length=c),f.applyFromCharCode(h)};c.utf8encode=function(a){return g.nodebuffer?h.newBuffer(a,"utf-8"):l(a)},c.utf8decode=function(a){return g.nodebuffer?f.transformTo("nodebuffer",a).toString("utf-8"):(a=f.transformTo(g.uint8array?"uint8array":"array",a),n(a))},f.inherits(d,i),d.prototype.processChunk=function(a){var b=f.transformTo(g.uint8array?"uint8array":"array",a.data);if(this.leftOver&&this.leftOver.length){if(g.uint8array){var d=b;b=new Uint8Array(d.length+this.leftOver.length),b.set(this.leftOver,0),b.set(d,this.leftOver.length)}else b=this.leftOver.concat(b);this.leftOver=null}var e=m(b),h=b;e!==b.length&&(g.uint8array?(h=b.subarray(0,e),this.leftOver=b.subarray(e,b.length)):(h=b.slice(0,e),this.leftOver=b.slice(e,b.length))),this.push({data:c.utf8decode(h),meta:a.meta})},d.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:c.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},c.Utf8DecodeWorker=d,f.inherits(e,i),e.prototype.processChunk=function(a){this.push({data:c.utf8encode(a.data),meta:a.meta})},c.Utf8EncodeWorker=e},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(a,b,c){"use strict";function d(a){var b=null;return b=i.uint8array?new Uint8Array(a.length):new Array(a.length),f(a,b)}function e(a){return a}function f(a,b){for(var c=0;c<a.length;++c)b[c]=255&a.charCodeAt(c);return b}function g(a){var b=65536,d=c.getTypeOf(a),e=!0;if("uint8array"===d?e=n.applyCanBeUsed.uint8array:"nodebuffer"===d&&(e=n.applyCanBeUsed.nodebuffer),e)for(;b>1;)try{return n.stringifyByChunk(a,d,b)}catch(f){b=Math.floor(b/2)}return n.stringifyByChar(a)}function h(a,b){for(var c=0;c<a.length;c++)b[c]=a[c];return b}var i=a("./support"),j=a("./base64"),k=a("./nodejsUtils"),l=a("core-js/library/fn/set-immediate"),m=a("./external");c.newBlob=function(a,b){c.checkSupport("blob");try{return new Blob(a,{type:b})}catch(d){try{for(var e=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,f=new e,g=0;g<a.length;g++)f.append(a[g]);
-return f.getBlob(b)}catch(d){throw new Error("Bug : can't construct the Blob.")}}};var n={stringifyByChunk:function(a,b,c){var d=[],e=0,f=a.length;if(f<=c)return String.fromCharCode.apply(null,a);for(;e<f;)"array"===b||"nodebuffer"===b?d.push(String.fromCharCode.apply(null,a.slice(e,Math.min(e+c,f)))):d.push(String.fromCharCode.apply(null,a.subarray(e,Math.min(e+c,f)))),e+=c;return d.join("")},stringifyByChar:function(a){for(var b="",c=0;c<a.length;c++)b+=String.fromCharCode(a[c]);return b},applyCanBeUsed:{uint8array:function(){try{return i.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(a){return!1}}(),nodebuffer:function(){try{return i.nodebuffer&&1===String.fromCharCode.apply(null,k.newBuffer(1)).length}catch(a){return!1}}()}};c.applyFromCharCode=g;var o={};o.string={string:e,array:function(a){return f(a,new Array(a.length))},arraybuffer:function(a){return o.string.uint8array(a).buffer},uint8array:function(a){return f(a,new Uint8Array(a.length))},nodebuffer:function(a){return f(a,k.newBuffer(a.length))}},o.array={string:g,array:e,arraybuffer:function(a){return new Uint8Array(a).buffer},uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return k.newBuffer(a)}},o.arraybuffer={string:function(a){return g(new Uint8Array(a))},array:function(a){return h(new Uint8Array(a),new Array(a.byteLength))},arraybuffer:e,uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return k.newBuffer(new Uint8Array(a))}},o.uint8array={string:g,array:function(a){return h(a,new Array(a.length))},arraybuffer:function(a){var b=new Uint8Array(a.length);return a.length&&b.set(a,0),b.buffer},uint8array:e,nodebuffer:function(a){return k.newBuffer(a)}},o.nodebuffer={string:g,array:function(a){return h(a,new Array(a.length))},arraybuffer:function(a){return o.nodebuffer.uint8array(a).buffer},uint8array:function(a){return h(a,new Uint8Array(a.length))},nodebuffer:e},c.transformTo=function(a,b){if(b||(b=""),!a)return b;c.checkSupport(a);var d=c.getTypeOf(b),e=o[d][a](b);return e},c.getTypeOf=function(a){return"string"==typeof a?"string":"[object Array]"===Object.prototype.toString.call(a)?"array":i.nodebuffer&&k.isBuffer(a)?"nodebuffer":i.uint8array&&a instanceof Uint8Array?"uint8array":i.arraybuffer&&a instanceof ArrayBuffer?"arraybuffer":void 0},c.checkSupport=function(a){var b=i[a.toLowerCase()];if(!b)throw new Error(a+" is not supported by this platform")},c.MAX_VALUE_16BITS=65535,c.MAX_VALUE_32BITS=-1,c.pretty=function(a){var b,c,d="";for(c=0;c<(a||"").length;c++)b=a.charCodeAt(c),d+="\\x"+(b<16?"0":"")+b.toString(16).toUpperCase();return d},c.delay=function(a,b,c){l(function(){a.apply(c||null,b||[])})},c.inherits=function(a,b){var c=function(){};c.prototype=b.prototype,a.prototype=new c},c.extend=function(){var a,b,c={};for(a=0;a<arguments.length;a++)for(b in arguments[a])arguments[a].hasOwnProperty(b)&&"undefined"==typeof c[b]&&(c[b]=arguments[a][b]);return c},c.prepareContent=function(a,b,e,f,g){var h=m.Promise.resolve(b).then(function(a){var b=i.blob&&(a instanceof Blob||["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(a))!==-1);return b&&"undefined"!=typeof FileReader?new m.Promise(function(b,c){var d=new FileReader;d.onload=function(a){b(a.target.result)},d.onerror=function(a){c(a.target.error)},d.readAsArrayBuffer(a)}):a});return h.then(function(b){var h=c.getTypeOf(b);return h?("arraybuffer"===h?b=c.transformTo("uint8array",b):"string"===h&&(g?b=j.decode(b):e&&f!==!0&&(b=d(b))),b):m.Promise.reject(new Error("The data of '"+a+"' is in an unsupported format !"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"core-js/library/fn/set-immediate":36}],33:[function(a,b,c){"use strict";function d(a){this.files=[],this.loadOptions=a}var e=a("./reader/readerFor"),f=a("./utils"),g=a("./signature"),h=a("./zipEntry"),i=(a("./utf8"),a("./support"));d.prototype={checkSignature:function(a){if(!this.reader.readAndCheckSignature(a)){this.reader.index-=4;var b=this.reader.readString(4);throw new Error("Corrupted zip or bug : unexpected signature ("+f.pretty(b)+", expected "+f.pretty(a)+")")}},isSignature:function(a,b){var c=this.reader.index;this.reader.setIndex(a);var d=this.reader.readString(4),e=d===b;return this.reader.setIndex(c),e},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var a=this.reader.readData(this.zipCommentLength),b=i.uint8array?"uint8array":"array",c=f.transformTo(b,a);this.zipComment=this.loadOptions.decodeFileName(c)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var a,b,c,d=this.zip64EndOfCentralSize-44,e=0;e<d;)a=this.reader.readInt(2),b=this.reader.readInt(4),c=this.reader.readData(b),this.zip64ExtensibleData[a]={id:a,length:b,value:c}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),this.disksCount>1)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var a,b;for(a=0;a<this.files.length;a++)b=this.files[a],this.reader.setIndex(b.localHeaderOffset),this.checkSignature(g.LOCAL_FILE_HEADER),b.readLocalPart(this.reader),b.handleUTF8(),b.processAttributes()},readCentralDir:function(){var a;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(g.CENTRAL_FILE_HEADER);)a=new h({zip64:this.zip64},this.loadOptions),a.readCentralPart(this.reader),this.files.push(a);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var a=this.reader.lastIndexOfSignature(g.CENTRAL_DIRECTORY_END);if(a<0){var b=!this.isSignature(0,g.LOCAL_FILE_HEADER);throw b?new Error("Can't find end of central directory : is this a zip file ? If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip : can't find end of central directory")}this.reader.setIndex(a);var c=a;if(this.checkSignature(g.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===f.MAX_VALUE_16BITS||this.diskWithCentralDirStart===f.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===f.MAX_VALUE_16BITS||this.centralDirRecords===f.MAX_VALUE_16BITS||this.centralDirSize===f.MAX_VALUE_32BITS||this.centralDirOffset===f.MAX_VALUE_32BITS){if(this.zip64=!0,a=this.reader.lastIndexOfSignature(g.ZIP64_CENTRAL_DIRECTORY_LOCATOR),a<0)throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(a),this.checkSignature(g.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,g.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(g.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip : can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(g.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var d=this.centralDirOffset+this.centralDirSize;this.zip64&&(d+=20,d+=12+this.zip64EndOfCentralSize);var e=c-d;if(e>0)this.isSignature(c,g.CENTRAL_FILE_HEADER)||(this.reader.zero=e);else if(e<0)throw new Error("Corrupted zip: missing "+Math.abs(e)+" bytes.")},prepareReader:function(a){this.reader=e(a)},load:function(a){this.prepareReader(a),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},b.exports=d},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utf8":31,"./utils":32,"./zipEntry":34}],34:[function(a,b,c){"use strict";function d(a,b){this.options=a,this.loadOptions=b}var e=a("./reader/readerFor"),f=a("./utils"),g=a("./compressedObject"),h=a("./crc32"),i=a("./utf8"),j=a("./compressions"),k=a("./support"),l=0,m=3,n=function(a){for(var b in j)if(j.hasOwnProperty(b)&&j[b].magic===a)return j[b];return null};d.prototype={isEncrypted:function(){return 1===(1&this.bitFlag)},useUTF8:function(){return 2048===(2048&this.bitFlag)},readLocalPart:function(a){var b,c;if(a.skip(22),this.fileNameLength=a.readInt(2),c=a.readInt(2),this.fileName=a.readData(this.fileNameLength),a.skip(c),this.compressedSize===-1||this.uncompressedSize===-1)throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory (compressedSize === -1 || uncompressedSize === -1)");if(b=n(this.compressionMethod),null===b)throw new Error("Corrupted zip : compression "+f.pretty(this.compressionMethod)+" unknown (inner file : "+f.transformTo("string",this.fileName)+")");this.decompressed=new g(this.compressedSize,this.uncompressedSize,this.crc32,b,a.readData(this.compressedSize))},readCentralPart:function(a){this.versionMadeBy=a.readInt(2),a.skip(2),this.bitFlag=a.readInt(2),this.compressionMethod=a.readString(2),this.date=a.readDate(),this.crc32=a.readInt(4),this.compressedSize=a.readInt(4),this.uncompressedSize=a.readInt(4);var b=a.readInt(2);if(this.extraFieldsLength=a.readInt(2),this.fileCommentLength=a.readInt(2),this.diskNumberStart=a.readInt(2),this.internalFileAttributes=a.readInt(2),this.externalFileAttributes=a.readInt(4),this.localHeaderOffset=a.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");a.skip(b),this.readExtraFields(a),this.parseZIP64ExtraField(a),this.fileComment=a.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var a=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),a===l&&(this.dosPermissions=63&this.externalFileAttributes),a===m&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(a){if(this.extraFields[1]){var b=e(this.extraFields[1].value);this.uncompressedSize===f.MAX_VALUE_32BITS&&(this.uncompressedSize=b.readInt(8)),this.compressedSize===f.MAX_VALUE_32BITS&&(this.compressedSize=b.readInt(8)),this.localHeaderOffset===f.MAX_VALUE_32BITS&&(this.localHeaderOffset=b.readInt(8)),this.diskNumberStart===f.MAX_VALUE_32BITS&&(this.diskNumberStart=b.readInt(4))}},readExtraFields:function(a){var b,c,d,e=a.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});a.index<e;)b=a.readInt(2),c=a.readInt(2),d=a.readData(c),this.extraFields[b]={id:b,length:c,value:d}},handleUTF8:function(){var a=k.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=i.utf8decode(this.fileName),this.fileCommentStr=i.utf8decode(this.fileComment);else{var b=this.findExtraFieldUnicodePath();if(null!==b)this.fileNameStr=b;else{var c=f.transformTo(a,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(c)}var d=this.findExtraFieldUnicodeComment();if(null!==d)this.fileCommentStr=d;else{var e=f.transformTo(a,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(e)}}},findExtraFieldUnicodePath:function(){var a=this.extraFields[28789];if(a){var b=e(a.value);return 1!==b.readInt(1)?null:h(this.fileName)!==b.readInt(4)?null:i.utf8decode(b.readData(a.length-5))}return null},findExtraFieldUnicodeComment:function(){var a=this.extraFields[25461];if(a){var b=e(a.value);return 1!==b.readInt(1)?null:h(this.fileComment)!==b.readInt(4)?null:i.utf8decode(b.readData(a.length-5))}return null}},b.exports=d},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(a,b,c){"use strict";var d=a("./stream/StreamHelper"),e=a("./stream/DataWorker"),f=a("./utf8"),g=a("./compressedObject"),h=a("./stream/GenericWorker"),i=function(a,b,c){this.name=a,this.dir=c.dir,this.date=c.date,this.comment=c.comment,this.unixPermissions=c.unixPermissions,this.dosPermissions=c.dosPermissions,this._data=b,this._dataBinary=c.binary,this.options={compression:c.compression,compressionOptions:c.compressionOptions}};i.prototype={internalStream:function(a){var b=a.toLowerCase(),c="string"===b||"text"===b;"binarystring"!==b&&"text"!==b||(b="string");var e=this._decompressWorker(),g=!this._dataBinary;return g&&!c&&(e=e.pipe(new f.Utf8EncodeWorker)),!g&&c&&(e=e.pipe(new f.Utf8DecodeWorker)),new d(e,b,"")},async:function(a,b){return this.internalStream(a).accumulate(b)},nodeStream:function(a,b){return this.internalStream(a||"nodebuffer").toNodejsStream(b)},_compressWorker:function(a,b){if(this._data instanceof g&&this._data.compression.magic===a.magic)return this._data.getCompressedWorker();var c=this._decompressWorker();return this._dataBinary||(c=c.pipe(new f.Utf8EncodeWorker)),g.createWorkerFrom(c,a,b)},_decompressWorker:function(){return this._data instanceof g?this._data.getContentWorker():this._data instanceof h?this._data:new e(this._data)}};for(var j=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],k=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},l=0;l<j.length;l++)i.prototype[j[l]]=k;b.exports=i},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(a,b,c){a("../modules/web.immediate"),b.exports=a("../modules/_core").setImmediate},{"../modules/_core":40,"../modules/web.immediate":56}],37:[function(a,b,c){b.exports=function(a){if("function"!=typeof a)throw TypeError(a+" is not a function!");return a}},{}],38:[function(a,b,c){var d=a("./_is-object");b.exports=function(a){if(!d(a))throw TypeError(a+" is not an object!");return a}},{"./_is-object":51}],39:[function(a,b,c){var d={}.toString;b.exports=function(a){return d.call(a).slice(8,-1)}},{}],40:[function(a,b,c){var d=b.exports={version:"2.3.0"};"number"==typeof __e&&(__e=d)},{}],41:[function(a,b,c){var d=a("./_a-function");b.exports=function(a,b,c){if(d(a),void 0===b)return a;switch(c){case 1:return function(c){return a.call(b,c)};case 2:return function(c,d){return a.call(b,c,d)};case 3:return function(c,d,e){return a.call(b,c,d,e)}}return function(){return a.apply(b,arguments)}}},{"./_a-function":37}],42:[function(a,b,c){b.exports=!a("./_fails")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},{"./_fails":45}],43:[function(a,b,c){var d=a("./_is-object"),e=a("./_global").document,f=d(e)&&d(e.createElement);b.exports=function(a){return f?e.createElement(a):{}}},{"./_global":46,"./_is-object":51}],44:[function(a,b,c){var d=a("./_global"),e=a("./_core"),f=a("./_ctx"),g=a("./_hide"),h="prototype",i=function(a,b,c){var j,k,l,m=a&i.F,n=a&i.G,o=a&i.S,p=a&i.P,q=a&i.B,r=a&i.W,s=n?e:e[b]||(e[b]={}),t=s[h],u=n?d:o?d[b]:(d[b]||{})[h];n&&(c=b);for(j in c)k=!m&&u&&void 0!==u[j],k&&j in s||(l=k?u[j]:c[j],s[j]=n&&"function"!=typeof u[j]?c[j]:q&&k?f(l,d):r&&u[j]==l?function(a){var b=function(b,c,d){if(this instanceof a){switch(arguments.length){case 0:return new a;case 1:return new a(b);case 2:return new a(b,c)}return new a(b,c,d)}return a.apply(this,arguments)};return b[h]=a[h],b}(l):p&&"function"==typeof l?f(Function.call,l):l,p&&((s.virtual||(s.virtual={}))[j]=l,a&i.R&&t&&!t[j]&&g(t,j,l)))};i.F=1,i.G=2,i.S=4,i.P=8,i.B=16,i.W=32,i.U=64,i.R=128,b.exports=i},{"./_core":40,"./_ctx":41,"./_global":46,"./_hide":47}],45:[function(a,b,c){b.exports=function(a){try{return!!a()}catch(b){return!0}}},{}],46:[function(a,b,c){var d=b.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=d)},{}],47:[function(a,b,c){var d=a("./_object-dp"),e=a("./_property-desc");b.exports=a("./_descriptors")?function(a,b,c){return d.f(a,b,e(1,c))}:function(a,b,c){return a[b]=c,a}},{"./_descriptors":42,"./_object-dp":52,"./_property-desc":53}],48:[function(a,b,c){b.exports=a("./_global").document&&document.documentElement},{"./_global":46}],49:[function(a,b,c){b.exports=!a("./_descriptors")&&!a("./_fails")(function(){return 7!=Object.defineProperty(a("./_dom-create")("div"),"a",{get:function(){return 7}}).a})},{"./_descriptors":42,"./_dom-create":43,"./_fails":45}],50:[function(a,b,c){b.exports=function(a,b,c){var d=void 0===c;switch(b.length){case 0:return d?a():a.call(c);case 1:return d?a(b[0]):a.call(c,b[0]);case 2:return d?a(b[0],b[1]):a.call(c,b[0],b[1]);case 3:return d?a(b[0],b[1],b[2]):a.call(c,b[0],b[1],b[2]);case 4:return d?a(b[0],b[1],b[2],b[3]):a.call(c,b[0],b[1],b[2],b[3])}return a.apply(c,b)}},{}],51:[function(a,b,c){b.exports=function(a){return"object"==typeof a?null!==a:"function"==typeof a}},{}],52:[function(a,b,c){var d=a("./_an-object"),e=a("./_ie8-dom-define"),f=a("./_to-primitive"),g=Object.defineProperty;c.f=a("./_descriptors")?Object.defineProperty:function(a,b,c){if(d(a),b=f(b,!0),d(c),e)try{return g(a,b,c)}catch(h){}if("get"in c||"set"in c)throw TypeError("Accessors not supported!");return"value"in c&&(a[b]=c.value),a}},{"./_an-object":38,"./_descriptors":42,"./_ie8-dom-define":49,"./_to-primitive":55}],53:[function(a,b,c){b.exports=function(a,b){return{enumerable:!(1&a),configurable:!(2&a),writable:!(4&a),value:b}}},{}],54:[function(a,b,c){var d,e,f,g=a("./_ctx"),h=a("./_invoke"),i=a("./_html"),j=a("./_dom-create"),k=a("./_global"),l=k.process,m=k.setImmediate,n=k.clearImmediate,o=k.MessageChannel,p=0,q={},r="onreadystatechange",s=function(){var a=+this;if(q.hasOwnProperty(a)){var b=q[a];delete q[a],b()}},t=function(a){s.call(a.data)};m&&n||(m=function(a){for(var b=[],c=1;arguments.length>c;)b.push(arguments[c++]);return q[++p]=function(){h("function"==typeof a?a:Function(a),b)},d(p),p},n=function(a){delete q[a]},"process"==a("./_cof")(l)?d=function(a){l.nextTick(g(s,a,1))}:o?(e=new o,f=e.port2,e.port1.onmessage=t,d=g(f.postMessage,f,1)):k.addEventListener&&"function"==typeof postMessage&&!k.importScripts?(d=function(a){k.postMessage(a+"","*")},k.addEventListener("message",t,!1)):d=r in j("script")?function(a){i.appendChild(j("script"))[r]=function(){i.removeChild(this),s.call(a)}}:function(a){setTimeout(g(s,a,1),0)}),b.exports={set:m,clear:n}},{"./_cof":39,"./_ctx":41,"./_dom-create":43,"./_global":46,"./_html":48,"./_invoke":50}],55:[function(a,b,c){var d=a("./_is-object");b.exports=function(a,b){if(!d(a))return a;var c,e;if(b&&"function"==typeof(c=a.toString)&&!d(e=c.call(a)))return e;if("function"==typeof(c=a.valueOf)&&!d(e=c.call(a)))return e;if(!b&&"function"==typeof(c=a.toString)&&!d(e=c.call(a)))return e;throw TypeError("Can't convert object to primitive value")}},{"./_is-object":51}],56:[function(a,b,c){var d=a("./_export"),e=a("./_task");d(d.G+d.B,{setImmediate:e.set,clearImmediate:e.clear})},{"./_export":44,"./_task":54}],57:[function(a,b,c){(function(a){"use strict";function c(){k=!0;for(var a,b,c=l.length;c;){for(b=l,l=[],a=-1;++a<c;)b[a]();c=l.length}k=!1}function d(a){1!==l.push(a)||k||e()}var e,f=a.MutationObserver||a.WebKitMutationObserver;if(f){var g=0,h=new f(c),i=a.document.createTextNode("");h.observe(i,{characterData:!0}),e=function(){i.data=g=++g%2}}else if(a.setImmediate||"undefined"==typeof a.MessageChannel)e="document"in a&&"onreadystatechange"in a.document.createElement("script")?function(){var b=a.document.createElement("script");b.onreadystatechange=function(){c(),b.onreadystatechange=null,b.parentNode.removeChild(b),b=null},a.document.documentElement.appendChild(b)}:function(){setTimeout(c,0)};else{var j=new a.MessageChannel;j.port1.onmessage=c,e=function(){j.port2.postMessage(0)}}var k,l=[];b.exports=d}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],58:[function(a,b,c){"use strict";function d(){}function e(a){if("function"!=typeof a)throw new TypeError("resolver must be a function");this.state=s,this.queue=[],this.outcome=void 0,a!==d&&i(this,a)}function f(a,b,c){this.promise=a,"function"==typeof b&&(this.onFulfilled=b,this.callFulfilled=this.otherCallFulfilled),"function"==typeof c&&(this.onRejected=c,this.callRejected=this.otherCallRejected)}function g(a,b,c){o(function(){var d;try{d=b(c)}catch(e){return p.reject(a,e)}d===a?p.reject(a,new TypeError("Cannot resolve promise with itself")):p.resolve(a,d)})}function h(a){var b=a&&a.then;if(a&&"object"==typeof a&&"function"==typeof b)return function(){b.apply(a,arguments)}}function i(a,b){function c(b){f||(f=!0,p.reject(a,b))}function d(b){f||(f=!0,p.resolve(a,b))}function e(){b(d,c)}var f=!1,g=j(e);"error"===g.status&&c(g.value)}function j(a,b){var c={};try{c.value=a(b),c.status="success"}catch(d){c.status="error",c.value=d}return c}function k(a){return a instanceof this?a:p.resolve(new this(d),a)}function l(a){var b=new this(d);return p.reject(b,a)}function m(a){function b(a,b){function d(a){g[b]=a,++h!==e||f||(f=!0,p.resolve(j,g))}c.resolve(a).then(d,function(a){f||(f=!0,p.reject(j,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=new Array(e),h=0,i=-1,j=new this(d);++i<e;)b(a[i],i);return j}function n(a){function b(a){c.resolve(a).then(function(a){f||(f=!0,p.resolve(h,a))},function(a){f||(f=!0,p.reject(h,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=-1,h=new this(d);++g<e;)b(a[g]);return h}var o=a("immediate"),p={},q=["REJECTED"],r=["FULFILLED"],s=["PENDING"];b.exports=e,e.prototype["catch"]=function(a){return this.then(null,a)},e.prototype.then=function(a,b){if("function"!=typeof a&&this.state===r||"function"!=typeof b&&this.state===q)return this;var c=new this.constructor(d);if(this.state!==s){var e=this.state===r?a:b;g(c,e,this.outcome)}else this.queue.push(new f(c,a,b));return c},f.prototype.callFulfilled=function(a){p.resolve(this.promise,a)},f.prototype.otherCallFulfilled=function(a){g(this.promise,this.onFulfilled,a)},f.prototype.callRejected=function(a){p.reject(this.promise,a)},f.prototype.otherCallRejected=function(a){g(this.promise,this.onRejected,a)},p.resolve=function(a,b){var c=j(h,b);if("error"===c.status)return p.reject(a,c.value);var d=c.value;if(d)i(a,d);else{a.state=r,a.outcome=b;for(var e=-1,f=a.queue.length;++e<f;)a.queue[e].callFulfilled(b)}return a},p.reject=function(a,b){a.state=q,a.outcome=b;for(var c=-1,d=a.queue.length;++c<d;)a.queue[c].callRejected(b);return a},e.resolve=k,e.reject=l,e.all=m,e.race=n},{immediate:57}],59:[function(a,b,c){"use strict";var d=a("./lib/utils/common").assign,e=a("./lib/deflate"),f=a("./lib/inflate"),g=a("./lib/zlib/constants"),h={};d(h,e,f,g),b.exports=h},{"./lib/deflate":60,"./lib/inflate":61,"./lib/utils/common":62,"./lib/zlib/constants":65}],60:[function(a,b,c){"use strict";function d(a){if(!(this instanceof d))return new d(a);this.options=i.assign({level:s,method:u,chunkSize:16384,windowBits:15,memLevel:8,strategy:t,to:""},a||{});var b=this.options;b.raw&&b.windowBits>0?b.windowBits=-b.windowBits:b.gzip&&b.windowBits>0&&b.windowBits<16&&(b.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new l,this.strm.avail_out=0;var c=h.deflateInit2(this.strm,b.level,b.method,b.windowBits,b.memLevel,b.strategy);if(c!==p)throw new Error(k[c]);if(b.header&&h.deflateSetHeader(this.strm,b.header),b.dictionary){var e;if(e="string"==typeof b.dictionary?j.string2buf(b.dictionary):"[object ArrayBuffer]"===m.call(b.dictionary)?new Uint8Array(b.dictionary):b.dictionary,c=h.deflateSetDictionary(this.strm,e),c!==p)throw new Error(k[c]);this._dict_set=!0}}function e(a,b){var c=new d(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function f(a,b){return b=b||{},b.raw=!0,e(a,b)}function g(a,b){return b=b||{},b.gzip=!0,e(a,b)}var h=a("./zlib/deflate"),i=a("./utils/common"),j=a("./utils/strings"),k=a("./zlib/messages"),l=a("./zlib/zstream"),m=Object.prototype.toString,n=0,o=4,p=0,q=1,r=2,s=-1,t=0,u=8;d.prototype.push=function(a,b){var c,d,e=this.strm,f=this.options.chunkSize;if(this.ended)return!1;d=b===~~b?b:b===!0?o:n,"string"==typeof a?e.input=j.string2buf(a):"[object ArrayBuffer]"===m.call(a)?e.input=new Uint8Array(a):e.input=a,e.next_in=0,e.avail_in=e.input.length;do{if(0===e.avail_out&&(e.output=new i.Buf8(f),e.next_out=0,e.avail_out=f),c=h.deflate(e,d),c!==q&&c!==p)return this.onEnd(c),this.ended=!0,!1;0!==e.avail_out&&(0!==e.avail_in||d!==o&&d!==r)||("string"===this.options.to?this.onData(j.buf2binstring(i.shrinkBuf(e.output,e.next_out))):this.onData(i.shrinkBuf(e.output,e.next_out)))}while((e.avail_in>0||0===e.avail_out)&&c!==q);return d===o?(c=h.deflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===p):d!==r||(this.onEnd(p),e.avail_out=0,!0)},d.prototype.onData=function(a){this.chunks.push(a)},d.prototype.onEnd=function(a){a===p&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=i.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Deflate=d,c.deflate=e,c.deflateRaw=f,c.gzip=g},{"./utils/common":62,"./utils/strings":63,"./zlib/deflate":67,"./zlib/messages":72,"./zlib/zstream":74}],61:[function(a,b,c){"use strict";function d(a){if(!(this instanceof d))return new d(a);this.options=h.assign({chunkSize:16384,windowBits:0,to:""},a||{});var b=this.options;b.raw&&b.windowBits>=0&&b.windowBits<16&&(b.windowBits=-b.windowBits,0===b.windowBits&&(b.windowBits=-15)),!(b.windowBits>=0&&b.windowBits<16)||a&&a.windowBits||(b.windowBits+=32),b.windowBits>15&&b.windowBits<48&&0===(15&b.windowBits)&&(b.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new l,this.strm.avail_out=0;var c=g.inflateInit2(this.strm,b.windowBits);if(c!==j.Z_OK)throw new Error(k[c]);this.header=new m,g.inflateGetHeader(this.strm,this.header)}function e(a,b){var c=new d(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function f(a,b){return b=b||{},b.raw=!0,e(a,b)}var g=a("./zlib/inflate"),h=a("./utils/common"),i=a("./utils/strings"),j=a("./zlib/constants"),k=a("./zlib/messages"),l=a("./zlib/zstream"),m=a("./zlib/gzheader"),n=Object.prototype.toString;d.prototype.push=function(a,b){var c,d,e,f,k,l,m=this.strm,o=this.options.chunkSize,p=this.options.dictionary,q=!1;if(this.ended)return!1;d=b===~~b?b:b===!0?j.Z_FINISH:j.Z_NO_FLUSH,"string"==typeof a?m.input=i.binstring2buf(a):"[object ArrayBuffer]"===n.call(a)?m.input=new Uint8Array(a):m.input=a,m.next_in=0,m.avail_in=m.input.length;do{if(0===m.avail_out&&(m.output=new h.Buf8(o),m.next_out=0,m.avail_out=o),c=g.inflate(m,j.Z_NO_FLUSH),c===j.Z_NEED_DICT&&p&&(l="string"==typeof p?i.string2buf(p):"[object ArrayBuffer]"===n.call(p)?new Uint8Array(p):p,c=g.inflateSetDictionary(this.strm,l)),c===j.Z_BUF_ERROR&&q===!0&&(c=j.Z_OK,q=!1),c!==j.Z_STREAM_END&&c!==j.Z_OK)return this.onEnd(c),this.ended=!0,!1;m.next_out&&(0!==m.avail_out&&c!==j.Z_STREAM_END&&(0!==m.avail_in||d!==j.Z_FINISH&&d!==j.Z_SYNC_FLUSH)||("string"===this.options.to?(e=i.utf8border(m.output,m.next_out),f=m.next_out-e,k=i.buf2string(m.output,e),m.next_out=f,m.avail_out=o-f,f&&h.arraySet(m.output,m.output,e,f,0),this.onData(k)):this.onData(h.shrinkBuf(m.output,m.next_out)))),0===m.avail_in&&0===m.avail_out&&(q=!0)}while((m.avail_in>0||0===m.avail_out)&&c!==j.Z_STREAM_END);return c===j.Z_STREAM_END&&(d=j.Z_FINISH),d===j.Z_FINISH?(c=g.inflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===j.Z_OK):d!==j.Z_SYNC_FLUSH||(this.onEnd(j.Z_OK),m.avail_out=0,!0)},d.prototype.onData=function(a){this.chunks.push(a)},d.prototype.onEnd=function(a){a===j.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=h.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Inflate=d,c.inflate=e,c.inflateRaw=f,c.ungzip=e},{"./utils/common":62,"./utils/strings":63,"./zlib/constants":65,"./zlib/gzheader":68,"./zlib/inflate":70,"./zlib/messages":72,"./zlib/zstream":74}],62:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;c.assign=function(a){for(var b=Array.prototype.slice.call(arguments,1);b.length;){var c=b.shift();if(c){if("object"!=typeof c)throw new TypeError(c+"must be non-object");for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}}return a},c.shrinkBuf=function(a,b){return a.length===b?a:a.subarray?a.subarray(0,b):(a.length=b,a)};var e={arraySet:function(a,b,c,d,e){if(b.subarray&&a.subarray)return void a.set(b.subarray(c,c+d),e);for(var f=0;f<d;f++)a[e+f]=b[c+f]},flattenChunks:function(a){var b,c,d,e,f,g;for(d=0,b=0,c=a.length;b<c;b++)d+=a[b].length;for(g=new Uint8Array(d),e=0,b=0,c=a.length;b<c;b++)f=a[b],g.set(f,e),e+=f.length;return g}},f={arraySet:function(a,b,c,d,e){for(var f=0;f<d;f++)a[e+f]=b[c+f]},flattenChunks:function(a){return[].concat.apply([],a)}};c.setTyped=function(a){a?(c.Buf8=Uint8Array,c.Buf16=Uint16Array,c.Buf32=Int32Array,c.assign(c,e)):(c.Buf8=Array,c.Buf16=Array,c.Buf32=Array,c.assign(c,f))},c.setTyped(d)},{}],63:[function(a,b,c){"use strict";function d(a,b){if(b<65537&&(a.subarray&&g||!a.subarray&&f))return String.fromCharCode.apply(null,e.shrinkBuf(a,b));for(var c="",d=0;d<b;d++)c+=String.fromCharCode(a[d]);return c}var e=a("./common"),f=!0,g=!0;try{String.fromCharCode.apply(null,[0])}catch(h){f=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(h){g=!1}for(var i=new e.Buf8(256),j=0;j<256;j++)i[j]=j>=252?6:j>=248?5:j>=240?4:j>=224?3:j>=192?2:1;i[254]=i[254]=1,c.string2buf=function(a){var b,c,d,f,g,h=a.length,i=0;for(f=0;f<h;f++)c=a.charCodeAt(f),55296===(64512&c)&&f+1<h&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),i+=c<128?1:c<2048?2:c<65536?3:4;for(b=new e.Buf8(i),g=0,f=0;g<i;f++)c=a.charCodeAt(f),55296===(64512&c)&&f+1<h&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),c<128?b[g++]=c:c<2048?(b[g++]=192|c>>>6,b[g++]=128|63&c):c<65536?(b[g++]=224|c>>>12,b[g++]=128|c>>>6&63,b[g++]=128|63&c):(b[g++]=240|c>>>18,b[g++]=128|c>>>12&63,b[g++]=128|c>>>6&63,b[g++]=128|63&c);return b},c.buf2binstring=function(a){return d(a,a.length)},c.binstring2buf=function(a){for(var b=new e.Buf8(a.length),c=0,d=b.length;c<d;c++)b[c]=a.charCodeAt(c);return b},c.buf2string=function(a,b){var c,e,f,g,h=b||a.length,j=new Array(2*h);for(e=0,c=0;c<h;)if(f=a[c++],f<128)j[e++]=f;else if(g=i[f],g>4)j[e++]=65533,c+=g-1;else{for(f&=2===g?31:3===g?15:7;g>1&&c<h;)f=f<<6|63&a[c++],g--;g>1?j[e++]=65533:f<65536?j[e++]=f:(f-=65536,j[e++]=55296|f>>10&1023,j[e++]=56320|1023&f)}return d(j,e)},c.utf8border=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return c<0?b:0===c?b:c+i[a[c]]>b?c:b}},{"./common":62}],64:[function(a,b,c){"use strict";function d(a,b,c,d){for(var e=65535&a|0,f=a>>>16&65535|0,g=0;0!==c;){g=c>2e3?2e3:c,c-=g;do e=e+b[d++]|0,f=f+e|0;while(--g);e%=65521,f%=65521}return e|f<<16|0}b.exports=d},{}],65:[function(a,b,c){"use strict";b.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],66:[function(a,b,c){"use strict";function d(){
-for(var a,b=[],c=0;c<256;c++){a=c;for(var d=0;d<8;d++)a=1&a?3988292384^a>>>1:a>>>1;b[c]=a}return b}function e(a,b,c,d){var e=f,g=d+c;a^=-1;for(var h=d;h<g;h++)a=a>>>8^e[255&(a^b[h])];return a^-1}var f=d();b.exports=e},{}],67:[function(a,b,c){"use strict";function d(a,b){return a.msg=I[b],b}function e(a){return(a<<1)-(a>4?9:0)}function f(a){for(var b=a.length;--b>=0;)a[b]=0}function g(a){var b=a.state,c=b.pending;c>a.avail_out&&(c=a.avail_out),0!==c&&(E.arraySet(a.output,b.pending_buf,b.pending_out,c,a.next_out),a.next_out+=c,b.pending_out+=c,a.total_out+=c,a.avail_out-=c,b.pending-=c,0===b.pending&&(b.pending_out=0))}function h(a,b){F._tr_flush_block(a,a.block_start>=0?a.block_start:-1,a.strstart-a.block_start,b),a.block_start=a.strstart,g(a.strm)}function i(a,b){a.pending_buf[a.pending++]=b}function j(a,b){a.pending_buf[a.pending++]=b>>>8&255,a.pending_buf[a.pending++]=255&b}function k(a,b,c,d){var e=a.avail_in;return e>d&&(e=d),0===e?0:(a.avail_in-=e,E.arraySet(b,a.input,a.next_in,e,c),1===a.state.wrap?a.adler=G(a.adler,b,e,c):2===a.state.wrap&&(a.adler=H(a.adler,b,e,c)),a.next_in+=e,a.total_in+=e,e)}function l(a,b){var c,d,e=a.max_chain_length,f=a.strstart,g=a.prev_length,h=a.nice_match,i=a.strstart>a.w_size-la?a.strstart-(a.w_size-la):0,j=a.window,k=a.w_mask,l=a.prev,m=a.strstart+ka,n=j[f+g-1],o=j[f+g];a.prev_length>=a.good_match&&(e>>=2),h>a.lookahead&&(h=a.lookahead);do if(c=b,j[c+g]===o&&j[c+g-1]===n&&j[c]===j[f]&&j[++c]===j[f+1]){f+=2,c++;do;while(j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&f<m);if(d=ka-(m-f),f=m-ka,d>g){if(a.match_start=b,g=d,d>=h)break;n=j[f+g-1],o=j[f+g]}}while((b=l[b&k])>i&&0!==--e);return g<=a.lookahead?g:a.lookahead}function m(a){var b,c,d,e,f,g=a.w_size;do{if(e=a.window_size-a.lookahead-a.strstart,a.strstart>=g+(g-la)){E.arraySet(a.window,a.window,g,g,0),a.match_start-=g,a.strstart-=g,a.block_start-=g,c=a.hash_size,b=c;do d=a.head[--b],a.head[b]=d>=g?d-g:0;while(--c);c=g,b=c;do d=a.prev[--b],a.prev[b]=d>=g?d-g:0;while(--c);e+=g}if(0===a.strm.avail_in)break;if(c=k(a.strm,a.window,a.strstart+a.lookahead,e),a.lookahead+=c,a.lookahead+a.insert>=ja)for(f=a.strstart-a.insert,a.ins_h=a.window[f],a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+1])&a.hash_mask;a.insert&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+ja-1])&a.hash_mask,a.prev[f&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=f,f++,a.insert--,!(a.lookahead+a.insert<ja)););}while(a.lookahead<la&&0!==a.strm.avail_in)}function n(a,b){var c=65535;for(c>a.pending_buf_size-5&&(c=a.pending_buf_size-5);;){if(a.lookahead<=1){if(m(a),0===a.lookahead&&b===J)return ua;if(0===a.lookahead)break}a.strstart+=a.lookahead,a.lookahead=0;var d=a.block_start+c;if((0===a.strstart||a.strstart>=d)&&(a.lookahead=a.strstart-d,a.strstart=d,h(a,!1),0===a.strm.avail_out))return ua;if(a.strstart-a.block_start>=a.w_size-la&&(h(a,!1),0===a.strm.avail_out))return ua}return a.insert=0,b===M?(h(a,!0),0===a.strm.avail_out?wa:xa):a.strstart>a.block_start&&(h(a,!1),0===a.strm.avail_out)?ua:ua}function o(a,b){for(var c,d;;){if(a.lookahead<la){if(m(a),a.lookahead<la&&b===J)return ua;if(0===a.lookahead)break}if(c=0,a.lookahead>=ja&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ja-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),0!==c&&a.strstart-c<=a.w_size-la&&(a.match_length=l(a,c)),a.match_length>=ja)if(d=F._tr_tally(a,a.strstart-a.match_start,a.match_length-ja),a.lookahead-=a.match_length,a.match_length<=a.max_lazy_match&&a.lookahead>=ja){a.match_length--;do a.strstart++,a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ja-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart;while(0!==--a.match_length);a.strstart++}else a.strstart+=a.match_length,a.match_length=0,a.ins_h=a.window[a.strstart],a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+1])&a.hash_mask;else d=F._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++;if(d&&(h(a,!1),0===a.strm.avail_out))return ua}return a.insert=a.strstart<ja-1?a.strstart:ja-1,b===M?(h(a,!0),0===a.strm.avail_out?wa:xa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ua:va}function p(a,b){for(var c,d,e;;){if(a.lookahead<la){if(m(a),a.lookahead<la&&b===J)return ua;if(0===a.lookahead)break}if(c=0,a.lookahead>=ja&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ja-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),a.prev_length=a.match_length,a.prev_match=a.match_start,a.match_length=ja-1,0!==c&&a.prev_length<a.max_lazy_match&&a.strstart-c<=a.w_size-la&&(a.match_length=l(a,c),a.match_length<=5&&(a.strategy===U||a.match_length===ja&&a.strstart-a.match_start>4096)&&(a.match_length=ja-1)),a.prev_length>=ja&&a.match_length<=a.prev_length){e=a.strstart+a.lookahead-ja,d=F._tr_tally(a,a.strstart-1-a.prev_match,a.prev_length-ja),a.lookahead-=a.prev_length-1,a.prev_length-=2;do++a.strstart<=e&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ja-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart);while(0!==--a.prev_length);if(a.match_available=0,a.match_length=ja-1,a.strstart++,d&&(h(a,!1),0===a.strm.avail_out))return ua}else if(a.match_available){if(d=F._tr_tally(a,0,a.window[a.strstart-1]),d&&h(a,!1),a.strstart++,a.lookahead--,0===a.strm.avail_out)return ua}else a.match_available=1,a.strstart++,a.lookahead--}return a.match_available&&(d=F._tr_tally(a,0,a.window[a.strstart-1]),a.match_available=0),a.insert=a.strstart<ja-1?a.strstart:ja-1,b===M?(h(a,!0),0===a.strm.avail_out?wa:xa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ua:va}function q(a,b){for(var c,d,e,f,g=a.window;;){if(a.lookahead<=ka){if(m(a),a.lookahead<=ka&&b===J)return ua;if(0===a.lookahead)break}if(a.match_length=0,a.lookahead>=ja&&a.strstart>0&&(e=a.strstart-1,d=g[e],d===g[++e]&&d===g[++e]&&d===g[++e])){f=a.strstart+ka;do;while(d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&e<f);a.match_length=ka-(f-e),a.match_length>a.lookahead&&(a.match_length=a.lookahead)}if(a.match_length>=ja?(c=F._tr_tally(a,1,a.match_length-ja),a.lookahead-=a.match_length,a.strstart+=a.match_length,a.match_length=0):(c=F._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++),c&&(h(a,!1),0===a.strm.avail_out))return ua}return a.insert=0,b===M?(h(a,!0),0===a.strm.avail_out?wa:xa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ua:va}function r(a,b){for(var c;;){if(0===a.lookahead&&(m(a),0===a.lookahead)){if(b===J)return ua;break}if(a.match_length=0,c=F._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++,c&&(h(a,!1),0===a.strm.avail_out))return ua}return a.insert=0,b===M?(h(a,!0),0===a.strm.avail_out?wa:xa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ua:va}function s(a,b,c,d,e){this.good_length=a,this.max_lazy=b,this.nice_length=c,this.max_chain=d,this.func=e}function t(a){a.window_size=2*a.w_size,f(a.head),a.max_lazy_match=D[a.level].max_lazy,a.good_match=D[a.level].good_length,a.nice_match=D[a.level].nice_length,a.max_chain_length=D[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=ja-1,a.match_available=0,a.ins_h=0}function u(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=$,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new E.Buf16(2*ha),this.dyn_dtree=new E.Buf16(2*(2*fa+1)),this.bl_tree=new E.Buf16(2*(2*ga+1)),f(this.dyn_ltree),f(this.dyn_dtree),f(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new E.Buf16(ia+1),this.heap=new E.Buf16(2*ea+1),f(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new E.Buf16(2*ea+1),f(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function v(a){var b;return a&&a.state?(a.total_in=a.total_out=0,a.data_type=Z,b=a.state,b.pending=0,b.pending_out=0,b.wrap<0&&(b.wrap=-b.wrap),b.status=b.wrap?na:sa,a.adler=2===b.wrap?0:1,b.last_flush=J,F._tr_init(b),O):d(a,Q)}function w(a){var b=v(a);return b===O&&t(a.state),b}function x(a,b){return a&&a.state?2!==a.state.wrap?Q:(a.state.gzhead=b,O):Q}function y(a,b,c,e,f,g){if(!a)return Q;var h=1;if(b===T&&(b=6),e<0?(h=0,e=-e):e>15&&(h=2,e-=16),f<1||f>_||c!==$||e<8||e>15||b<0||b>9||g<0||g>X)return d(a,Q);8===e&&(e=9);var i=new u;return a.state=i,i.strm=a,i.wrap=h,i.gzhead=null,i.w_bits=e,i.w_size=1<<i.w_bits,i.w_mask=i.w_size-1,i.hash_bits=f+7,i.hash_size=1<<i.hash_bits,i.hash_mask=i.hash_size-1,i.hash_shift=~~((i.hash_bits+ja-1)/ja),i.window=new E.Buf8(2*i.w_size),i.head=new E.Buf16(i.hash_size),i.prev=new E.Buf16(i.w_size),i.lit_bufsize=1<<f+6,i.pending_buf_size=4*i.lit_bufsize,i.pending_buf=new E.Buf8(i.pending_buf_size),i.d_buf=1*i.lit_bufsize,i.l_buf=3*i.lit_bufsize,i.level=b,i.strategy=g,i.method=c,w(a)}function z(a,b){return y(a,b,$,aa,ba,Y)}function A(a,b){var c,h,k,l;if(!a||!a.state||b>N||b<0)return a?d(a,Q):Q;if(h=a.state,!a.output||!a.input&&0!==a.avail_in||h.status===ta&&b!==M)return d(a,0===a.avail_out?S:Q);if(h.strm=a,c=h.last_flush,h.last_flush=b,h.status===na)if(2===h.wrap)a.adler=0,i(h,31),i(h,139),i(h,8),h.gzhead?(i(h,(h.gzhead.text?1:0)+(h.gzhead.hcrc?2:0)+(h.gzhead.extra?4:0)+(h.gzhead.name?8:0)+(h.gzhead.comment?16:0)),i(h,255&h.gzhead.time),i(h,h.gzhead.time>>8&255),i(h,h.gzhead.time>>16&255),i(h,h.gzhead.time>>24&255),i(h,9===h.level?2:h.strategy>=V||h.level<2?4:0),i(h,255&h.gzhead.os),h.gzhead.extra&&h.gzhead.extra.length&&(i(h,255&h.gzhead.extra.length),i(h,h.gzhead.extra.length>>8&255)),h.gzhead.hcrc&&(a.adler=H(a.adler,h.pending_buf,h.pending,0)),h.gzindex=0,h.status=oa):(i(h,0),i(h,0),i(h,0),i(h,0),i(h,0),i(h,9===h.level?2:h.strategy>=V||h.level<2?4:0),i(h,ya),h.status=sa);else{var m=$+(h.w_bits-8<<4)<<8,n=-1;n=h.strategy>=V||h.level<2?0:h.level<6?1:6===h.level?2:3,m|=n<<6,0!==h.strstart&&(m|=ma),m+=31-m%31,h.status=sa,j(h,m),0!==h.strstart&&(j(h,a.adler>>>16),j(h,65535&a.adler)),a.adler=1}if(h.status===oa)if(h.gzhead.extra){for(k=h.pending;h.gzindex<(65535&h.gzhead.extra.length)&&(h.pending!==h.pending_buf_size||(h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending!==h.pending_buf_size));)i(h,255&h.gzhead.extra[h.gzindex]),h.gzindex++;h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),h.gzindex===h.gzhead.extra.length&&(h.gzindex=0,h.status=pa)}else h.status=pa;if(h.status===pa)if(h.gzhead.name){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.name.length?255&h.gzhead.name.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.gzindex=0,h.status=qa)}else h.status=qa;if(h.status===qa)if(h.gzhead.comment){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.comment.length?255&h.gzhead.comment.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=H(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.status=ra)}else h.status=ra;if(h.status===ra&&(h.gzhead.hcrc?(h.pending+2>h.pending_buf_size&&g(a),h.pending+2<=h.pending_buf_size&&(i(h,255&a.adler),i(h,a.adler>>8&255),a.adler=0,h.status=sa)):h.status=sa),0!==h.pending){if(g(a),0===a.avail_out)return h.last_flush=-1,O}else if(0===a.avail_in&&e(b)<=e(c)&&b!==M)return d(a,S);if(h.status===ta&&0!==a.avail_in)return d(a,S);if(0!==a.avail_in||0!==h.lookahead||b!==J&&h.status!==ta){var o=h.strategy===V?r(h,b):h.strategy===W?q(h,b):D[h.level].func(h,b);if(o!==wa&&o!==xa||(h.status=ta),o===ua||o===wa)return 0===a.avail_out&&(h.last_flush=-1),O;if(o===va&&(b===K?F._tr_align(h):b!==N&&(F._tr_stored_block(h,0,0,!1),b===L&&(f(h.head),0===h.lookahead&&(h.strstart=0,h.block_start=0,h.insert=0))),g(a),0===a.avail_out))return h.last_flush=-1,O}return b!==M?O:h.wrap<=0?P:(2===h.wrap?(i(h,255&a.adler),i(h,a.adler>>8&255),i(h,a.adler>>16&255),i(h,a.adler>>24&255),i(h,255&a.total_in),i(h,a.total_in>>8&255),i(h,a.total_in>>16&255),i(h,a.total_in>>24&255)):(j(h,a.adler>>>16),j(h,65535&a.adler)),g(a),h.wrap>0&&(h.wrap=-h.wrap),0!==h.pending?O:P)}function B(a){var b;return a&&a.state?(b=a.state.status,b!==na&&b!==oa&&b!==pa&&b!==qa&&b!==ra&&b!==sa&&b!==ta?d(a,Q):(a.state=null,b===sa?d(a,R):O)):Q}function C(a,b){var c,d,e,g,h,i,j,k,l=b.length;if(!a||!a.state)return Q;if(c=a.state,g=c.wrap,2===g||1===g&&c.status!==na||c.lookahead)return Q;for(1===g&&(a.adler=G(a.adler,b,l,0)),c.wrap=0,l>=c.w_size&&(0===g&&(f(c.head),c.strstart=0,c.block_start=0,c.insert=0),k=new E.Buf8(c.w_size),E.arraySet(k,b,l-c.w_size,c.w_size,0),b=k,l=c.w_size),h=a.avail_in,i=a.next_in,j=a.input,a.avail_in=l,a.next_in=0,a.input=b,m(c);c.lookahead>=ja;){d=c.strstart,e=c.lookahead-(ja-1);do c.ins_h=(c.ins_h<<c.hash_shift^c.window[d+ja-1])&c.hash_mask,c.prev[d&c.w_mask]=c.head[c.ins_h],c.head[c.ins_h]=d,d++;while(--e);c.strstart=d,c.lookahead=ja-1,m(c)}return c.strstart+=c.lookahead,c.block_start=c.strstart,c.insert=c.lookahead,c.lookahead=0,c.match_length=c.prev_length=ja-1,c.match_available=0,a.next_in=i,a.input=j,a.avail_in=h,c.wrap=g,O}var D,E=a("../utils/common"),F=a("./trees"),G=a("./adler32"),H=a("./crc32"),I=a("./messages"),J=0,K=1,L=3,M=4,N=5,O=0,P=1,Q=-2,R=-3,S=-5,T=-1,U=1,V=2,W=3,X=4,Y=0,Z=2,$=8,_=9,aa=15,ba=8,ca=29,da=256,ea=da+1+ca,fa=30,ga=19,ha=2*ea+1,ia=15,ja=3,ka=258,la=ka+ja+1,ma=32,na=42,oa=69,pa=73,qa=91,ra=103,sa=113,ta=666,ua=1,va=2,wa=3,xa=4,ya=3;D=[new s(0,0,0,0,n),new s(4,4,8,4,o),new s(4,5,16,8,o),new s(4,6,32,32,o),new s(4,4,16,16,p),new s(8,16,32,32,p),new s(8,16,128,128,p),new s(8,32,128,256,p),new s(32,128,258,1024,p),new s(32,258,258,4096,p)],c.deflateInit=z,c.deflateInit2=y,c.deflateReset=w,c.deflateResetKeep=v,c.deflateSetHeader=x,c.deflate=A,c.deflateEnd=B,c.deflateSetDictionary=C,c.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":62,"./adler32":64,"./crc32":66,"./messages":72,"./trees":73}],68:[function(a,b,c){"use strict";function d(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}b.exports=d},{}],69:[function(a,b,c){"use strict";var d=30,e=12;b.exports=function(a,b){var c,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C;c=a.state,f=a.next_in,B=a.input,g=f+(a.avail_in-5),h=a.next_out,C=a.output,i=h-(b-a.avail_out),j=h+(a.avail_out-257),k=c.dmax,l=c.wsize,m=c.whave,n=c.wnext,o=c.window,p=c.hold,q=c.bits,r=c.lencode,s=c.distcode,t=(1<<c.lenbits)-1,u=(1<<c.distbits)-1;a:do{q<15&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=r[p&t];b:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,0===w)C[h++]=65535&v;else{if(!(16&w)){if(0===(64&w)){v=r[(65535&v)+(p&(1<<w)-1)];continue b}if(32&w){c.mode=e;break a}a.msg="invalid literal/length code",c.mode=d;break a}x=65535&v,w&=15,w&&(q<w&&(p+=B[f++]<<q,q+=8),x+=p&(1<<w)-1,p>>>=w,q-=w),q<15&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=s[p&u];c:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,!(16&w)){if(0===(64&w)){v=s[(65535&v)+(p&(1<<w)-1)];continue c}a.msg="invalid distance code",c.mode=d;break a}if(y=65535&v,w&=15,q<w&&(p+=B[f++]<<q,q+=8,q<w&&(p+=B[f++]<<q,q+=8)),y+=p&(1<<w)-1,y>k){a.msg="invalid distance too far back",c.mode=d;break a}if(p>>>=w,q-=w,w=h-i,y>w){if(w=y-w,w>m&&c.sane){a.msg="invalid distance too far back",c.mode=d;break a}if(z=0,A=o,0===n){if(z+=l-w,w<x){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}else if(n<w){if(z+=l+n-w,w-=n,w<x){x-=w;do C[h++]=o[z++];while(--w);if(z=0,n<x){w=n,x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}}else if(z+=n-w,w<x){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}for(;x>2;)C[h++]=A[z++],C[h++]=A[z++],C[h++]=A[z++],x-=3;x&&(C[h++]=A[z++],x>1&&(C[h++]=A[z++]))}else{z=h-y;do C[h++]=C[z++],C[h++]=C[z++],C[h++]=C[z++],x-=3;while(x>2);x&&(C[h++]=C[z++],x>1&&(C[h++]=C[z++]))}break}}break}}while(f<g&&h<j);x=q>>3,f-=x,q-=x<<3,p&=(1<<q)-1,a.next_in=f,a.next_out=h,a.avail_in=f<g?5+(g-f):5-(f-g),a.avail_out=h<j?257+(j-h):257-(h-j),c.hold=p,c.bits=q}},{}],70:[function(a,b,c){"use strict";function d(a){return(a>>>24&255)+(a>>>8&65280)+((65280&a)<<8)+((255&a)<<24)}function e(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new s.Buf16(320),this.work=new s.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function f(a){var b;return a&&a.state?(b=a.state,a.total_in=a.total_out=b.total=0,a.msg="",b.wrap&&(a.adler=1&b.wrap),b.mode=L,b.last=0,b.havedict=0,b.dmax=32768,b.head=null,b.hold=0,b.bits=0,b.lencode=b.lendyn=new s.Buf32(pa),b.distcode=b.distdyn=new s.Buf32(qa),b.sane=1,b.back=-1,D):G}function g(a){var b;return a&&a.state?(b=a.state,b.wsize=0,b.whave=0,b.wnext=0,f(a)):G}function h(a,b){var c,d;return a&&a.state?(d=a.state,b<0?(c=0,b=-b):(c=(b>>4)+1,b<48&&(b&=15)),b&&(b<8||b>15)?G:(null!==d.window&&d.wbits!==b&&(d.window=null),d.wrap=c,d.wbits=b,g(a))):G}function i(a,b){var c,d;return a?(d=new e,a.state=d,d.window=null,c=h(a,b),c!==D&&(a.state=null),c):G}function j(a){return i(a,sa)}function k(a){if(ta){var b;for(q=new s.Buf32(512),r=new s.Buf32(32),b=0;b<144;)a.lens[b++]=8;for(;b<256;)a.lens[b++]=9;for(;b<280;)a.lens[b++]=7;for(;b<288;)a.lens[b++]=8;for(w(y,a.lens,0,288,q,0,a.work,{bits:9}),b=0;b<32;)a.lens[b++]=5;w(z,a.lens,0,32,r,0,a.work,{bits:5}),ta=!1}a.lencode=q,a.lenbits=9,a.distcode=r,a.distbits=5}function l(a,b,c,d){var e,f=a.state;return null===f.window&&(f.wsize=1<<f.wbits,f.wnext=0,f.whave=0,f.window=new s.Buf8(f.wsize)),d>=f.wsize?(s.arraySet(f.window,b,c-f.wsize,f.wsize,0),f.wnext=0,f.whave=f.wsize):(e=f.wsize-f.wnext,e>d&&(e=d),s.arraySet(f.window,b,c-d,e,f.wnext),d-=e,d?(s.arraySet(f.window,b,c-d,d,0),f.wnext=d,f.whave=f.wsize):(f.wnext+=e,f.wnext===f.wsize&&(f.wnext=0),f.whave<f.wsize&&(f.whave+=e))),0}function m(a,b){var c,e,f,g,h,i,j,m,n,o,p,q,r,pa,qa,ra,sa,ta,ua,va,wa,xa,ya,za,Aa=0,Ba=new s.Buf8(4),Ca=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!a||!a.state||!a.output||!a.input&&0!==a.avail_in)return G;c=a.state,c.mode===W&&(c.mode=X),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,o=i,p=j,xa=D;a:for(;;)switch(c.mode){case L:if(0===c.wrap){c.mode=X;break}for(;n<16;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(2&c.wrap&&35615===m){c.check=0,Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0),m=0,n=0,c.mode=M;break}if(c.flags=0,c.head&&(c.head.done=!1),!(1&c.wrap)||(((255&m)<<8)+(m>>8))%31){a.msg="incorrect header check",c.mode=ma;break}if((15&m)!==K){a.msg="unknown compression method",c.mode=ma;break}if(m>>>=4,n-=4,wa=(15&m)+8,0===c.wbits)c.wbits=wa;else if(wa>c.wbits){a.msg="invalid window size",c.mode=ma;break}c.dmax=1<<wa,a.adler=c.check=1,c.mode=512&m?U:W,m=0,n=0;break;case M:for(;n<16;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.flags=m,(255&c.flags)!==K){a.msg="unknown compression method",c.mode=ma;break}if(57344&c.flags){a.msg="unknown header flags set",c.mode=ma;break}c.head&&(c.head.text=m>>8&1),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0,c.mode=N;case N:for(;n<32;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.time=m),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,Ba[2]=m>>>16&255,Ba[3]=m>>>24&255,c.check=u(c.check,Ba,4,0)),m=0,n=0,c.mode=O;case O:for(;n<16;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.xflags=255&m,c.head.os=m>>8),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0,c.mode=P;case P:if(1024&c.flags){for(;n<16;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length=m,c.head&&(c.head.extra_len=m),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0}else c.head&&(c.head.extra=null);c.mode=Q;case Q:if(1024&c.flags&&(q=c.length,q>i&&(q=i),q&&(c.head&&(wa=c.head.extra_len-c.length,c.head.extra||(c.head.extra=new Array(c.head.extra_len)),s.arraySet(c.head.extra,e,g,q,wa)),512&c.flags&&(c.check=u(c.check,e,q,g)),i-=q,g+=q,c.length-=q),c.length))break a;c.length=0,c.mode=R;case R:if(2048&c.flags){if(0===i)break a;q=0;do wa=e[g+q++],c.head&&wa&&c.length<65536&&(c.head.name+=String.fromCharCode(wa));while(wa&&q<i);if(512&c.flags&&(c.check=u(c.check,e,q,g)),i-=q,g+=q,wa)break a}else c.head&&(c.head.name=null);c.length=0,c.mode=S;case S:if(4096&c.flags){if(0===i)break a;q=0;do wa=e[g+q++],c.head&&wa&&c.length<65536&&(c.head.comment+=String.fromCharCode(wa));while(wa&&q<i);if(512&c.flags&&(c.check=u(c.check,e,q,g)),i-=q,g+=q,wa)break a}else c.head&&(c.head.comment=null);c.mode=T;case T:if(512&c.flags){for(;n<16;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(65535&c.check)){a.msg="header crc mismatch",c.mode=ma;break}m=0,n=0}c.head&&(c.head.hcrc=c.flags>>9&1,c.head.done=!0),a.adler=c.check=0,c.mode=W;break;case U:for(;n<32;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}a.adler=c.check=d(m),m=0,n=0,c.mode=V;case V:if(0===c.havedict)return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,F;a.adler=c.check=1,c.mode=W;case W:if(b===B||b===C)break a;case X:if(c.last){m>>>=7&n,n-=7&n,c.mode=ja;break}for(;n<3;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}switch(c.last=1&m,m>>>=1,n-=1,3&m){case 0:c.mode=Y;break;case 1:if(k(c),c.mode=ca,b===C){m>>>=2,n-=2;break a}break;case 2:c.mode=_;break;case 3:a.msg="invalid block type",c.mode=ma}m>>>=2,n-=2;break;case Y:for(m>>>=7&n,n-=7&n;n<32;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if((65535&m)!==(m>>>16^65535)){a.msg="invalid stored block lengths",c.mode=ma;break}if(c.length=65535&m,m=0,n=0,c.mode=Z,b===C)break a;case Z:c.mode=$;case $:if(q=c.length){if(q>i&&(q=i),q>j&&(q=j),0===q)break a;s.arraySet(f,e,g,q,h),i-=q,g+=q,j-=q,h+=q,c.length-=q;break}c.mode=W;break;case _:for(;n<14;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.nlen=(31&m)+257,m>>>=5,n-=5,c.ndist=(31&m)+1,m>>>=5,n-=5,c.ncode=(15&m)+4,m>>>=4,n-=4,c.nlen>286||c.ndist>30){a.msg="too many length or distance symbols",c.mode=ma;break}c.have=0,c.mode=aa;case aa:for(;c.have<c.ncode;){for(;n<3;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.lens[Ca[c.have++]]=7&m,m>>>=3,n-=3}for(;c.have<19;)c.lens[Ca[c.have++]]=0;if(c.lencode=c.lendyn,c.lenbits=7,ya={bits:c.lenbits},xa=w(x,c.lens,0,19,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid code lengths set",c.mode=ma;break}c.have=0,c.mode=ba;case ba:for(;c.have<c.nlen+c.ndist;){for(;Aa=c.lencode[m&(1<<c.lenbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(sa<16)m>>>=qa,n-=qa,c.lens[c.have++]=sa;else{if(16===sa){for(za=qa+2;n<za;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m>>>=qa,n-=qa,0===c.have){a.msg="invalid bit length repeat",c.mode=ma;break}wa=c.lens[c.have-1],q=3+(3&m),m>>>=2,n-=2}else if(17===sa){for(za=qa+3;n<za;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qa,n-=qa,wa=0,q=3+(7&m),m>>>=3,n-=3}else{for(za=qa+7;n<za;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qa,n-=qa,wa=0,q=11+(127&m),m>>>=7,n-=7}if(c.have+q>c.nlen+c.ndist){a.msg="invalid bit length repeat",c.mode=ma;break}for(;q--;)c.lens[c.have++]=wa}}if(c.mode===ma)break;if(0===c.lens[256]){a.msg="invalid code -- missing end-of-block",c.mode=ma;break}if(c.lenbits=9,ya={bits:c.lenbits},xa=w(y,c.lens,0,c.nlen,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid literal/lengths set",c.mode=ma;break}if(c.distbits=6,c.distcode=c.distdyn,ya={bits:c.distbits},xa=w(z,c.lens,c.nlen,c.ndist,c.distcode,0,c.work,ya),c.distbits=ya.bits,xa){a.msg="invalid distances set",c.mode=ma;break}if(c.mode=ca,b===C)break a;case ca:c.mode=da;case da:if(i>=6&&j>=258){a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,v(a,p),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,c.mode===W&&(c.back=-1);break}for(c.back=0;Aa=c.lencode[m&(1<<c.lenbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(ra&&0===(240&ra)){for(ta=qa,ua=ra,va=sa;Aa=c.lencode[va+((m&(1<<ta+ua)-1)>>ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(ta+qa<=n);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,c.length=sa,0===ra){c.mode=ia;break}if(32&ra){c.back=-1,c.mode=W;break}if(64&ra){a.msg="invalid literal/length code",c.mode=ma;break}c.extra=15&ra,c.mode=ea;case ea:if(c.extra){for(za=c.extra;n<za;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}c.was=c.length,c.mode=fa;case fa:for(;Aa=c.distcode[m&(1<<c.distbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(0===(240&ra)){for(ta=qa,ua=ra,va=sa;Aa=c.distcode[va+((m&(1<<ta+ua)-1)>>ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(ta+qa<=n);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,64&ra){a.msg="invalid distance code",c.mode=ma;break}c.offset=sa,c.extra=15&ra,c.mode=ga;case ga:if(c.extra){for(za=c.extra;n<za;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.offset+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}if(c.offset>c.dmax){a.msg="invalid distance too far back",c.mode=ma;break}c.mode=ha;case ha:if(0===j)break a;if(q=p-j,c.offset>q){if(q=c.offset-q,q>c.whave&&c.sane){a.msg="invalid distance too far back",c.mode=ma;break}q>c.wnext?(q-=c.wnext,r=c.wsize-q):r=c.wnext-q,q>c.length&&(q=c.length),pa=c.window}else pa=f,r=h-c.offset,q=c.length;q>j&&(q=j),j-=q,c.length-=q;do f[h++]=pa[r++];while(--q);0===c.length&&(c.mode=da);break;case ia:if(0===j)break a;f[h++]=c.length,j--,c.mode=da;break;case ja:if(c.wrap){for(;n<32;){if(0===i)break a;i--,m|=e[g++]<<n,n+=8}if(p-=j,a.total_out+=p,c.total+=p,p&&(a.adler=c.check=c.flags?u(c.check,f,p,h-p):t(c.check,f,p,h-p)),p=j,(c.flags?m:d(m))!==c.check){a.msg="incorrect data check",c.mode=ma;break}m=0,n=0}c.mode=ka;case ka:if(c.wrap&&c.flags){for(;n<32;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(4294967295&c.total)){a.msg="incorrect length check",c.mode=ma;break}m=0,n=0}c.mode=la;case la:xa=E;break a;case ma:xa=H;break a;case na:return I;case oa:default:return G}return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,(c.wsize||p!==a.avail_out&&c.mode<ma&&(c.mode<ja||b!==A))&&l(a,a.output,a.next_out,p-a.avail_out)?(c.mode=na,I):(o-=a.avail_in,p-=a.avail_out,a.total_in+=o,a.total_out+=p,c.total+=p,c.wrap&&p&&(a.adler=c.check=c.flags?u(c.check,f,p,a.next_out-p):t(c.check,f,p,a.next_out-p)),a.data_type=c.bits+(c.last?64:0)+(c.mode===W?128:0)+(c.mode===ca||c.mode===Z?256:0),(0===o&&0===p||b===A)&&xa===D&&(xa=J),xa)}function n(a){if(!a||!a.state)return G;var b=a.state;return b.window&&(b.window=null),a.state=null,D}function o(a,b){var c;return a&&a.state?(c=a.state,0===(2&c.wrap)?G:(c.head=b,b.done=!1,D)):G}function p(a,b){var c,d,e,f=b.length;return a&&a.state?(c=a.state,0!==c.wrap&&c.mode!==V?G:c.mode===V&&(d=1,d=t(d,b,f,0),d!==c.check)?H:(e=l(a,b,f,f))?(c.mode=na,I):(c.havedict=1,D)):G}var q,r,s=a("../utils/common"),t=a("./adler32"),u=a("./crc32"),v=a("./inffast"),w=a("./inftrees"),x=0,y=1,z=2,A=4,B=5,C=6,D=0,E=1,F=2,G=-2,H=-3,I=-4,J=-5,K=8,L=1,M=2,N=3,O=4,P=5,Q=6,R=7,S=8,T=9,U=10,V=11,W=12,X=13,Y=14,Z=15,$=16,_=17,aa=18,ba=19,ca=20,da=21,ea=22,fa=23,ga=24,ha=25,ia=26,ja=27,ka=28,la=29,ma=30,na=31,oa=32,pa=852,qa=592,ra=15,sa=ra,ta=!0;c.inflateReset=g,c.inflateReset2=h,c.inflateResetKeep=f,c.inflateInit=j,c.inflateInit2=i,c.inflate=m,c.inflateEnd=n,c.inflateGetHeader=o,c.inflateSetDictionary=p,c.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":62,"./adler32":64,"./crc32":66,"./inffast":69,"./inftrees":71}],71:[function(a,b,c){"use strict";var d=a("../utils/common"),e=15,f=852,g=592,h=0,i=1,j=2,k=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],l=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],m=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],n=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];b.exports=function(a,b,c,o,p,q,r,s){var t,u,v,w,x,y,z,A,B,C=s.bits,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=null,O=0,P=new d.Buf16(e+1),Q=new d.Buf16(e+1),R=null,S=0;for(D=0;D<=e;D++)P[D]=0;for(E=0;E<o;E++)P[b[c+E]]++;for(H=C,G=e;G>=1&&0===P[G];G--);if(H>G&&(H=G),0===G)return p[q++]=20971520,p[q++]=20971520,s.bits=1,0;for(F=1;F<G&&0===P[F];F++);for(H<F&&(H=F),K=1,D=1;D<=e;D++)if(K<<=1,K-=P[D],K<0)return-1;if(K>0&&(a===h||1!==G))return-1;for(Q[1]=0,D=1;D<e;D++)Q[D+1]=Q[D]+P[D];for(E=0;E<o;E++)0!==b[c+E]&&(r[Q[b[c+E]]++]=E);if(a===h?(N=R=r,y=19):a===i?(N=k,O-=257,R=l,S-=257,y=256):(N=m,R=n,y=-1),M=0,E=0,D=F,x=q,I=H,J=0,v=-1,L=1<<H,w=L-1,a===i&&L>f||a===j&&L>g)return 1;for(var T=0;;){T++,z=D-J,r[E]<y?(A=0,B=r[E]):r[E]>y?(A=R[S+r[E]],B=N[O+r[E]]):(A=96,B=0),t=1<<D-J,u=1<<I,F=u;do u-=t,p[x+(M>>J)+u]=z<<24|A<<16|B|0;while(0!==u);for(t=1<<D-1;M&t;)t>>=1;if(0!==t?(M&=t-1,M+=t):M=0,E++,0===--P[D]){if(D===G)break;D=b[c+r[E]]}if(D>H&&(M&w)!==v){for(0===J&&(J=H),x+=F,I=D-J,K=1<<I;I+J<G&&(K-=P[I+J],!(K<=0));)I++,K<<=1;if(L+=1<<I,a===i&&L>f||a===j&&L>g)return 1;v=M&w,p[v]=H<<24|I<<16|x-q|0}}return 0!==M&&(p[x+M]=D-J<<24|64<<16|0),s.bits=H,0}},{"../utils/common":62}],72:[function(a,b,c){"use strict";b.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],73:[function(a,b,c){"use strict";function d(a){for(var b=a.length;--b>=0;)a[b]=0}function e(a,b,c,d,e){this.static_tree=a,this.extra_bits=b,this.extra_base=c,this.elems=d,this.max_length=e,this.has_stree=a&&a.length}function f(a,b){this.dyn_tree=a,this.max_code=0,this.stat_desc=b}function g(a){return a<256?ia[a]:ia[256+(a>>>7)]}function h(a,b){a.pending_buf[a.pending++]=255&b,a.pending_buf[a.pending++]=b>>>8&255}function i(a,b,c){a.bi_valid>X-c?(a.bi_buf|=b<<a.bi_valid&65535,h(a,a.bi_buf),a.bi_buf=b>>X-a.bi_valid,a.bi_valid+=c-X):(a.bi_buf|=b<<a.bi_valid&65535,a.bi_valid+=c)}function j(a,b,c){i(a,c[2*b],c[2*b+1])}function k(a,b){var c=0;do c|=1&a,a>>>=1,c<<=1;while(--b>0);return c>>>1}function l(a){16===a.bi_valid?(h(a,a.bi_buf),a.bi_buf=0,a.bi_valid=0):a.bi_valid>=8&&(a.pending_buf[a.pending++]=255&a.bi_buf,a.bi_buf>>=8,a.bi_valid-=8)}function m(a,b){var c,d,e,f,g,h,i=b.dyn_tree,j=b.max_code,k=b.stat_desc.static_tree,l=b.stat_desc.has_stree,m=b.stat_desc.extra_bits,n=b.stat_desc.extra_base,o=b.stat_desc.max_length,p=0;for(f=0;f<=W;f++)a.bl_count[f]=0;for(i[2*a.heap[a.heap_max]+1]=0,c=a.heap_max+1;c<V;c++)d=a.heap[c],f=i[2*i[2*d+1]+1]+1,f>o&&(f=o,p++),i[2*d+1]=f,d>j||(a.bl_count[f]++,g=0,d>=n&&(g=m[d-n]),h=i[2*d],a.opt_len+=h*(f+g),l&&(a.static_len+=h*(k[2*d+1]+g)));if(0!==p){do{for(f=o-1;0===a.bl_count[f];)f--;a.bl_count[f]--,a.bl_count[f+1]+=2,a.bl_count[o]--,p-=2}while(p>0);for(f=o;0!==f;f--)for(d=a.bl_count[f];0!==d;)e=a.heap[--c],e>j||(i[2*e+1]!==f&&(a.opt_len+=(f-i[2*e+1])*i[2*e],i[2*e+1]=f),d--)}}function n(a,b,c){var d,e,f=new Array(W+1),g=0;
-for(d=1;d<=W;d++)f[d]=g=g+c[d-1]<<1;for(e=0;e<=b;e++){var h=a[2*e+1];0!==h&&(a[2*e]=k(f[h]++,h))}}function o(){var a,b,c,d,f,g=new Array(W+1);for(c=0,d=0;d<Q-1;d++)for(ka[d]=c,a=0;a<1<<ba[d];a++)ja[c++]=d;for(ja[c-1]=d,f=0,d=0;d<16;d++)for(la[d]=f,a=0;a<1<<ca[d];a++)ia[f++]=d;for(f>>=7;d<T;d++)for(la[d]=f<<7,a=0;a<1<<ca[d]-7;a++)ia[256+f++]=d;for(b=0;b<=W;b++)g[b]=0;for(a=0;a<=143;)ga[2*a+1]=8,a++,g[8]++;for(;a<=255;)ga[2*a+1]=9,a++,g[9]++;for(;a<=279;)ga[2*a+1]=7,a++,g[7]++;for(;a<=287;)ga[2*a+1]=8,a++,g[8]++;for(n(ga,S+1,g),a=0;a<T;a++)ha[2*a+1]=5,ha[2*a]=k(a,5);ma=new e(ga,ba,R+1,S,W),na=new e(ha,ca,0,T,W),oa=new e(new Array(0),da,0,U,Y)}function p(a){var b;for(b=0;b<S;b++)a.dyn_ltree[2*b]=0;for(b=0;b<T;b++)a.dyn_dtree[2*b]=0;for(b=0;b<U;b++)a.bl_tree[2*b]=0;a.dyn_ltree[2*Z]=1,a.opt_len=a.static_len=0,a.last_lit=a.matches=0}function q(a){a.bi_valid>8?h(a,a.bi_buf):a.bi_valid>0&&(a.pending_buf[a.pending++]=a.bi_buf),a.bi_buf=0,a.bi_valid=0}function r(a,b,c,d){q(a),d&&(h(a,c),h(a,~c)),G.arraySet(a.pending_buf,a.window,b,c,a.pending),a.pending+=c}function s(a,b,c,d){var e=2*b,f=2*c;return a[e]<a[f]||a[e]===a[f]&&d[b]<=d[c]}function t(a,b,c){for(var d=a.heap[c],e=c<<1;e<=a.heap_len&&(e<a.heap_len&&s(b,a.heap[e+1],a.heap[e],a.depth)&&e++,!s(b,d,a.heap[e],a.depth));)a.heap[c]=a.heap[e],c=e,e<<=1;a.heap[c]=d}function u(a,b,c){var d,e,f,h,k=0;if(0!==a.last_lit)do d=a.pending_buf[a.d_buf+2*k]<<8|a.pending_buf[a.d_buf+2*k+1],e=a.pending_buf[a.l_buf+k],k++,0===d?j(a,e,b):(f=ja[e],j(a,f+R+1,b),h=ba[f],0!==h&&(e-=ka[f],i(a,e,h)),d--,f=g(d),j(a,f,c),h=ca[f],0!==h&&(d-=la[f],i(a,d,h)));while(k<a.last_lit);j(a,Z,b)}function v(a,b){var c,d,e,f=b.dyn_tree,g=b.stat_desc.static_tree,h=b.stat_desc.has_stree,i=b.stat_desc.elems,j=-1;for(a.heap_len=0,a.heap_max=V,c=0;c<i;c++)0!==f[2*c]?(a.heap[++a.heap_len]=j=c,a.depth[c]=0):f[2*c+1]=0;for(;a.heap_len<2;)e=a.heap[++a.heap_len]=j<2?++j:0,f[2*e]=1,a.depth[e]=0,a.opt_len--,h&&(a.static_len-=g[2*e+1]);for(b.max_code=j,c=a.heap_len>>1;c>=1;c--)t(a,f,c);e=i;do c=a.heap[1],a.heap[1]=a.heap[a.heap_len--],t(a,f,1),d=a.heap[1],a.heap[--a.heap_max]=c,a.heap[--a.heap_max]=d,f[2*e]=f[2*c]+f[2*d],a.depth[e]=(a.depth[c]>=a.depth[d]?a.depth[c]:a.depth[d])+1,f[2*c+1]=f[2*d+1]=e,a.heap[1]=e++,t(a,f,1);while(a.heap_len>=2);a.heap[--a.heap_max]=a.heap[1],m(a,b),n(f,j,a.bl_count)}function w(a,b,c){var d,e,f=-1,g=b[1],h=0,i=7,j=4;for(0===g&&(i=138,j=3),b[2*(c+1)+1]=65535,d=0;d<=c;d++)e=g,g=b[2*(d+1)+1],++h<i&&e===g||(h<j?a.bl_tree[2*e]+=h:0!==e?(e!==f&&a.bl_tree[2*e]++,a.bl_tree[2*$]++):h<=10?a.bl_tree[2*_]++:a.bl_tree[2*aa]++,h=0,f=e,0===g?(i=138,j=3):e===g?(i=6,j=3):(i=7,j=4))}function x(a,b,c){var d,e,f=-1,g=b[1],h=0,k=7,l=4;for(0===g&&(k=138,l=3),d=0;d<=c;d++)if(e=g,g=b[2*(d+1)+1],!(++h<k&&e===g)){if(h<l){do j(a,e,a.bl_tree);while(0!==--h)}else 0!==e?(e!==f&&(j(a,e,a.bl_tree),h--),j(a,$,a.bl_tree),i(a,h-3,2)):h<=10?(j(a,_,a.bl_tree),i(a,h-3,3)):(j(a,aa,a.bl_tree),i(a,h-11,7));h=0,f=e,0===g?(k=138,l=3):e===g?(k=6,l=3):(k=7,l=4)}}function y(a){var b;for(w(a,a.dyn_ltree,a.l_desc.max_code),w(a,a.dyn_dtree,a.d_desc.max_code),v(a,a.bl_desc),b=U-1;b>=3&&0===a.bl_tree[2*ea[b]+1];b--);return a.opt_len+=3*(b+1)+5+5+4,b}function z(a,b,c,d){var e;for(i(a,b-257,5),i(a,c-1,5),i(a,d-4,4),e=0;e<d;e++)i(a,a.bl_tree[2*ea[e]+1],3);x(a,a.dyn_ltree,b-1),x(a,a.dyn_dtree,c-1)}function A(a){var b,c=4093624447;for(b=0;b<=31;b++,c>>>=1)if(1&c&&0!==a.dyn_ltree[2*b])return I;if(0!==a.dyn_ltree[18]||0!==a.dyn_ltree[20]||0!==a.dyn_ltree[26])return J;for(b=32;b<R;b++)if(0!==a.dyn_ltree[2*b])return J;return I}function B(a){pa||(o(),pa=!0),a.l_desc=new f(a.dyn_ltree,ma),a.d_desc=new f(a.dyn_dtree,na),a.bl_desc=new f(a.bl_tree,oa),a.bi_buf=0,a.bi_valid=0,p(a)}function C(a,b,c,d){i(a,(L<<1)+(d?1:0),3),r(a,b,c,!0)}function D(a){i(a,M<<1,3),j(a,Z,ga),l(a)}function E(a,b,c,d){var e,f,g=0;a.level>0?(a.strm.data_type===K&&(a.strm.data_type=A(a)),v(a,a.l_desc),v(a,a.d_desc),g=y(a),e=a.opt_len+3+7>>>3,f=a.static_len+3+7>>>3,f<=e&&(e=f)):e=f=c+5,c+4<=e&&b!==-1?C(a,b,c,d):a.strategy===H||f===e?(i(a,(M<<1)+(d?1:0),3),u(a,ga,ha)):(i(a,(N<<1)+(d?1:0),3),z(a,a.l_desc.max_code+1,a.d_desc.max_code+1,g+1),u(a,a.dyn_ltree,a.dyn_dtree)),p(a),d&&q(a)}function F(a,b,c){return a.pending_buf[a.d_buf+2*a.last_lit]=b>>>8&255,a.pending_buf[a.d_buf+2*a.last_lit+1]=255&b,a.pending_buf[a.l_buf+a.last_lit]=255&c,a.last_lit++,0===b?a.dyn_ltree[2*c]++:(a.matches++,b--,a.dyn_ltree[2*(ja[c]+R+1)]++,a.dyn_dtree[2*g(b)]++),a.last_lit===a.lit_bufsize-1}var G=a("../utils/common"),H=4,I=0,J=1,K=2,L=0,M=1,N=2,O=3,P=258,Q=29,R=256,S=R+1+Q,T=30,U=19,V=2*S+1,W=15,X=16,Y=7,Z=256,$=16,_=17,aa=18,ba=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],ca=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],da=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],ea=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],fa=512,ga=new Array(2*(S+2));d(ga);var ha=new Array(2*T);d(ha);var ia=new Array(fa);d(ia);var ja=new Array(P-O+1);d(ja);var ka=new Array(Q);d(ka);var la=new Array(T);d(la);var ma,na,oa,pa=!1;c._tr_init=B,c._tr_stored_block=C,c._tr_flush_block=E,c._tr_tally=F,c._tr_align=D},{"../utils/common":62}],74:[function(a,b,c){"use strict";function d(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}b.exports=d},{}]},{},[10])(10)});
\ No newline at end of file

From 65eece267028eb49e9bf74640f68e666c987e0a7 Mon Sep 17 00:00:00 2001
From: Lari Taskula <lari.taskula@hypernova.fi>
Date: Thu, 19 Sep 2024 17:37:09 +0000
Subject: [PATCH 299/429] Bug 37965: Restore Bug 12032 - TinyMCE rewrites urls
 in a very annoying and unexpected way

To test:
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=12032#c3

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7b38dd9a0840a888212c3126257175b85faade1c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/tools/additional-contents.tt   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/additional-contents.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/additional-contents.tt
index 2edc683570..7ae7c79658 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/additional-contents.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/additional-contents.tt
@@ -784,6 +784,7 @@
                     branding : false,
                     block_unsupported_drop : false,
                     content_css : "[% interface | html %]/[% theme | html %]/css/tinymce.css",
+                    convert_urls : false,
                     custom_elements:"style,link,~link",
                     extended_valid_elements:"style,link[href|rel]",
                     force_br_newlines : false,

From 51daa12f619a0505ad1af755d61f95775bac80db Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 29 Oct 2024 22:45:23 +0000
Subject: [PATCH 300/429] Bug 37965: (QA follow-up) fix WYSIWYG in sysprefs

Test plan:
1. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?
op=search&searchfield=UseWYSIWYGinSystemPreferences
2. Enable the system preference to show WYSIWYG in HTML system preferences
3. Go to http://localhost:8081/cgi-bin/koha/admin/preferences.pl?
op=search&searchfield=OPACSearchForTitleIn
4. Add a link for "http://localhost:8081/cool.pl"
5. Note in the source code that the URL gets mangled

6. Apply the patch

7. Repeat the above test plan
8. Note that the URL has NOT been mangled and remains
http://localhost:8081/cool.pl

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6a22ae84fb90125976eb0a574c3dde1516e8bdb2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/includes/wysiwyg-systempreferences.inc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/wysiwyg-systempreferences.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/wysiwyg-systempreferences.inc
index 97e9bdefe7..4f6d7497b6 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/wysiwyg-systempreferences.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/wysiwyg-systempreferences.inc
@@ -22,6 +22,7 @@
         branding : false,
         relative_urls : false,
         content_css : "[% interface | html %]/[% theme | html %]/css/tinymce.css",
+        convert_urls : false,
         editor_selector : "mce",
         menubar : "file edit view insert format tools table",
         mode : "specific_textareas",

From 84c254f1600b50cd21f2d6d165e241d0c8d33281 Mon Sep 17 00:00:00 2001
From: Jan Kissig <jkissig@th-wildau.de>
Date: Wed, 3 Jul 2024 14:48:17 +0200
Subject: [PATCH 301/429] Bug 37209: improve Record overlay rules validation
 and styling

This patch adds jquery form validation to the input named tag. It also changes the behavior for save and edit actions which now both can be triggered by pressing enter. Batch deletion will be only possible if no edit is taking place.

Test plan:
1)
 a) open http://localhost:8081/cgi-bin/koha/admin/marc-overlay-rules.pl
 b) just click + Add rule
 c) the input 'tag' gets highlighted and the form won't submit

2)
 a) edit an existing rule
 b) empty input value for tag
 c) notice the checkboxes for batch deletion on other rules if existing
 d) click Save and check the input 'tag' gets highlighted and the form won't submit

apply patch
1) redo steps and check that form does not get submitted and the input is marked as required via a label that says This field is required.

2) redo steps and check that form does not get submitted and the input is marked as required via a label that says This field is required. Also checkboxes for batchremove actions are gone.

Signed-off-by: Olivier Vezina <olivier.vezina@inLibro.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a7fb0c431e281ce0075ce051c144bf29a5fd043c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../en/modules/admin/marc-overlay-rules.tt    | 78 ++++++++-----------
 1 file changed, 34 insertions(+), 44 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
index b36b90cdc2..268c8c0bd9 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
@@ -16,8 +16,9 @@
 [% INCLUDE 'doc-head-close.inc' %]
 
 <style>
-    .required {
-        background-color: #C00;
+    #tag-error {
+        display: block;
+        margin-top: 5px;
     }
 </style>
 
@@ -97,7 +98,7 @@
                                     </select>
                                 </th>
                                 <th id="filter-container"></th>
-                                <th><input type="text" size="5" name="tag"/></th>
+                                <th><input type="text" size="5" name="tag" class="required" required="required"/></th>
                                 <th>
                                     <select name="preset">
                                         <option value="" selected>Custom</option>
@@ -132,8 +133,8 @@
                                         <option value="1">Delete</option>
                                     </select>
                                 </th>
-                                <th><button type="submit" class="btn btn-default btn-xs" title="Add" id="add" name="op" value="cud-add"><i class="fa fa-plus"></i> Add rule</button></th>
-                                <th><button type="submit" id="btn_batchremove" disabled="disabled" class="btn btn-default btn-xs" name="op" value="cud-remove" title="Batch remove"><i class="fa fa-trash-can"></i> Delete selected</button></th>
+                                <th><button type="submit" class="btn btn-default btn-xs" title="Add" id="add"><i class="fa fa-plus"></i> Add rule</button></th>
+                                <th><button type="button" class="btn btn-default btn-xs" id="btn_batchremove" disabled="disabled" title="Batch remove"><i class="fa fa-trash-can"></i> Delete selected</button></th>
                             </tr>
                         </tfoot>
                         [% END %]
@@ -162,8 +163,8 @@
                                         </select>
                                     </td>
                                     <td id="filter-container" data-filter="[% rule.filter | html %]"></td>
-                                    <td><input type="text" size="3" name="tag" value="[% rule.tag | html %]"/></td>
-                                    <th>
+                                    <td><input type="text" size="3" name="tag" value="[% rule.tag | html %]" class="required" required="required" /></td>
+                                    <td>
                                         <select name="preset">
                                             <option value="" selected>Custom</option>
                                             <option value="protect">Protect</option>
@@ -172,7 +173,7 @@
                                             <option value="addappend">Add and append</option>
                                             <option value="protectfromdeletion">Protect from deletion</option>
                                         </select>
-                                    </th>
+                                    </td>
                                     <td class="rule-operation-action-edit">
                                         <select name="add">
                                             [% IF rule.add %]
@@ -218,7 +219,7 @@
                                         </select>
                                     </td>
                                     <td class="actions">
-                                        <button class="btn btn-default btn-xs" title="Save" id="doedit" value="[% rule.id | html %]"><i class="fa fa-check"></i> Save</button>
+                                        <button class="btn btn-default btn-xs" title="Save" type="submit" id="doedit" value="[% rule.id | html %]"><i class="fa fa-check"></i> Save</button>
                                         <a href="/cgi-bin/koha/admin/marc-overlay-rules.pl" class="btn btn-default btn-xs" title="Cancel"><i class="fa fa-times"></i> Cancel</a>
                                     </td>
                                     <td></td>
@@ -260,11 +261,13 @@
                                     <td class="rule-operation-action" data-operation="remove" data-value="[% rule.remove | html %]">[% IF rule.remove %]Remove[% ELSE %]Skip[% END %]</td>
                                     <td class="rule-operation-action" data-operation="delete" data-value="[% rule.delete | html %]">[% IF rule.delete %]Delete[% ELSE %]Skip[% END %]</td>
                                     <td class="actions">
-                                        <a title="Delete" class="btn btn-default btn-xs btn_remove"><i class="fa fa-trash-can"></i> Delete</a>
+                                        <button type="button" title="Delete" class="btn btn-default btn-xs btn_remove" name="batchremove" value="[% rule.id | html %]"><i class="fa fa-trash-can"></i> Delete</button>
                                         <a href="?op=edit&id=[% rule.id | uri %]" title="Edit" class="btn btn-default btn-xs"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Edit</a>
                                     </td>
                                     <td>
+                                        [% UNLESS edit %]
                                         <input type="checkbox" name="batchremove" value="[% rule.id | html %]"/>
+                                        [% END %]
                                     </td>
                                 [% END %]
                                 </tr>
@@ -291,7 +294,13 @@
 
     <script>
     $(document).ready(function(){
-        function doSubmit(op, id) {
+
+        var validator = $( "#marc-overlay-rules-form" ).validate({
+            ignore: ".ignore"
+        });
+
+        function prepareSubmit(op, id) {
+
             $('#marc-overlay-rules-form > input[name="op"]').val(op);
 
             if(id) {
@@ -301,55 +310,36 @@
                 .appendTo('#marc-overlay-rules-form');
             }
 
-            var valid = true;
             if (op == 'cud-add' || op == 'cud-edit') {
-                var validate = [
-                    $('#marc-overlay-rules-form select[name="filter"]'),
-                    $('#marc-overlay-rules-form input[name="tag"]')
-                ];
-                for(var i = 0; i < validate.length; i++) {
-                    if (validate[i].length) {
-                        if(validate[i].val().length == 0) {
-                            validate[i].addClass('required');
-                            validate[i].focus();
-                            valid = false;
-                        } else {
-                            validate[i].removeClass('required');
-                        }
-                    }
-                }
+                $('#marc-overlay-rules-form input[name="tag"]').removeClass('ignore');
+            } else {
+                $('#marc-overlay-rules-form input[name="tag"]').addClass('ignore');
             }
 
-            if (valid) {
-                return $('#marc-overlay-rules-form').submit();
-            }
-
-            return valid;
         }
 
         $('#doedit').on('click', function(){
-            doSubmit('cud-edit', $("#doedit").attr('value'));
-            return false;
+            prepareSubmit('cud-edit', $("#doedit").attr('value'));
         });
         $('#add').on('click', function(){
-            doSubmit('cud-add');
-            return false;
+            prepareSubmit('cud-add');
         });
         $('.btn_remove').on('click', function(el){
-            // mark delete checkbox in row
-            $(el.target).closest('tr').find('[name="batchremove"]').prop( "checked", true );
-            $('#btn_batchremove').removeAttr('disabled');
-            confirm_remove();
-            return false;
+            // remove all earlier selections
+            $('#marc-overlay-rules-form input[name="batchremove"]').each(function() {
+                $(this).prop( "checked", false );
+            });
+
+            confirm_remove(el.currentTarget.value);
         });
         $('#btn_batchremove').on('click', function(){
             confirm_remove();
-            return false;
         });
 
-        function confirm_remove(){
+        function confirm_remove(id){
             if (confirm(_("Are you sure you want to remove the selected rule(s)?"))){
-                doSubmit('cud-remove');
+                prepareSubmit('cud-remove', id);
+                $('#marc-overlay-rules-form').submit()
             }
         }
 

From 856e522a26a25277ca5ea7ccf327eea9cdd04502 Mon Sep 17 00:00:00 2001
From: Jan Kissig <bibliothek@th-wildau.de>
Date: Tue, 29 Oct 2024 18:59:13 +0000
Subject: [PATCH 302/429] Bug 37209: (QA follow-up) Add default op value to
 form

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 10687f7c6e13932c808f5860f1518a7fd61278fc)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
index 268c8c0bd9..04ccbf2a4f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-overlay-rules.tt
@@ -71,7 +71,7 @@
                 <div class="page-section">
                 <form action="/cgi-bin/koha/admin/marc-overlay-rules.pl" method="POST" id="marc-overlay-rules-form">
                     [% INCLUDE 'csrf-token.inc' %]
-                    <input type="hidden" name="op">
+                    <input type="hidden" name="op" value="cud-add">
                     <table id="marc-overlay-rules">
                         <thead><tr>
                             <th>Rule</th>

From babd4f857665fec6d8792aae9add3ea6c083c51d Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Tue, 3 Sep 2024 17:35:16 +0200
Subject: [PATCH 303/429] Bug 37824: Replace webpack with rspack for fun and
 profit

I left the webpack.config.js as well as the devDependencies in place for now.
We can remove them in a follow-up patch on this bug after testing it out.

To test:
1) Run js:build and js:build:prod
2) Note the build time
3) Apply patch
4) Run yarn install
5) Run js:build and js:build:prod again
6) Note the much faster build time
7) Extra credit: take a look at the ERM or preservations module and make sure everything works as expected.
8) Extra credit: run the cypress tests.
9) Sign off or give your opinion

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Looks faster to me. Where webpack failed for me (Killed, error 137), this passed.
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 28b6294461c58ea515e0479b7a1f1346795064ed)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 package.json     |   9 +-
 rspack.config.js |  66 ++++
 yarn.lock        | 774 ++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 832 insertions(+), 17 deletions(-)
 create mode 100644 rspack.config.js

diff --git a/package.json b/package.json
index 0513fd0efb..0887ce97be 100644
--- a/package.json
+++ b/package.json
@@ -47,9 +47,10 @@
     "css:build": "gulp css && gulp css --view opac",
     "css:build:prod": "gulp build && gulp build --view opac",
     "css:watch": "gulp watch",
-    "js:build": "webpack --mode development",
-    "js:watch": "webpack --mode development --watch",
-    "js:build:prod": "webpack --mode production",
+    "js:build": "rspack build --mode development",
+    "js:build:analyze": "rspack build --mode development --analyze",
+    "js:watch": "rspack build --mode development --watch",
+    "js:build:prod": "rspack build --mode production",
     "build": "yarn css:build && yarn js:build && yarn api:bundle",
     "build:prod": "yarn css:build:prod && yarn js:build:prod && yarn api:bundle",
     "cypress": "cypress",
@@ -69,6 +70,8 @@
   "devDependencies": {
     "@babel/core": "^7.17.5",
     "@babel/preset-env": "^7.16.11",
+    "@rspack/cli": "^1.0.1",
+    "@rspack/core": "^1.0.1",
     "@vue/compiler-sfc": "^3.2.31",
     "babel-loader": "^8.2.3",
     "babelify": "^10.0.0",
diff --git a/rspack.config.js b/rspack.config.js
new file mode 100644
index 0000000000..54eefc216f
--- /dev/null
+++ b/rspack.config.js
@@ -0,0 +1,66 @@
+const { VueLoaderPlugin } = require("vue-loader");
+//const autoprefixer = require("autoprefixer");
+const path = require("path");
+const rspack = require("@rspack/core");
+
+module.exports = {
+    experiments: {
+        css: true,
+    },
+    entry: {
+        erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
+        preservation:
+            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
+        "admin/record_sources":
+            "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts",
+    },
+    output: {
+        filename: "[name].js",
+        path: path.resolve(
+            __dirname,
+            "koha-tmpl/intranet-tmpl/prog/js/vue/dist/"
+        ),
+        chunkFilename: "[name].js",
+    },
+    module: {
+        rules: [
+            {
+                test: /\.vue$/,
+                loader: "vue-loader",
+                options: {
+                    experimentalInlineMatchResource: true,
+                },
+                exclude: [path.resolve(__dirname, "t/cypress/")],
+            },
+            {
+                test: /\.ts$/,
+                loader: "builtin:swc-loader",
+                options: {
+                    jsc: {
+                        parser: {
+                            syntax: "typescript",
+                        },
+                    },
+                    appendTsSuffixTo: [/\.vue$/],
+                },
+                exclude: [
+                    /node_modules/,
+                    path.resolve(__dirname, "t/cypress/"),
+                ],
+                type: "javascript/auto",
+            },
+            {
+                test: /\.css$/i,
+                type: "javascript/auto",
+                use: ["style-loader", "css-loader"],
+            },
+        ],
+    },
+    plugins: [
+        new VueLoaderPlugin(),
+        new rspack.DefinePlugin({
+            __VUE_OPTIONS_API__: true,
+            __VUE_PROD_DEVTOOLS__: false,
+        }),
+    ],
+};
diff --git a/yarn.lock b/yarn.lock
index 5584e39ee3..2d43c06886 100755
--- a/yarn.lock
+++ b/yarn.lock
@@ -1038,7 +1038,7 @@
     debug "^3.1.0"
     lodash.once "^4.1.1"
 
-"@discoveryjs/json-ext@^0.5.0":
+"@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.7":
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
   integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
@@ -1120,6 +1120,18 @@
   dependencies:
     "@hapi/hoek" "^9.0.0"
 
+"@isaacs/cliui@^8.0.2":
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+  integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+  dependencies:
+    string-width "^5.1.2"
+    string-width-cjs "npm:string-width@^4.2.0"
+    strip-ansi "^7.0.1"
+    strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+    wrap-ansi "^8.1.0"
+    wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
 "@jridgewell/gen-mapping@^0.1.0":
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
@@ -1173,11 +1185,59 @@
   resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
   integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
 
+"@jsonjoy.com/base64@^1.1.1":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578"
+  integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==
+
+"@jsonjoy.com/json-pack@^1.0.3":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz#33ca57ee29d12feef540f2139225597469dec894"
+  integrity sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==
+  dependencies:
+    "@jsonjoy.com/base64" "^1.1.1"
+    "@jsonjoy.com/util" "^1.1.2"
+    hyperdyperid "^1.2.0"
+    thingies "^1.20.0"
+
+"@jsonjoy.com/util@^1.1.2", "@jsonjoy.com/util@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.3.0.tgz#e5623885bb5e0c48c1151e4dae422fb03a5887a1"
+  integrity sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==
+
 "@leichtgewicht/ip-codec@^2.0.1":
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
   integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
 
+"@module-federation/runtime-tools@0.5.1":
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.5.1.tgz#1b1f93837159a6bf0c0ba78730d589a5a8f74aa3"
+  integrity sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==
+  dependencies:
+    "@module-federation/runtime" "0.5.1"
+    "@module-federation/webpack-bundler-runtime" "0.5.1"
+
+"@module-federation/runtime@0.5.1":
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.5.1.tgz#b548a75e2068952ff66ad717cbf73fc921edd5d7"
+  integrity sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==
+  dependencies:
+    "@module-federation/sdk" "0.5.1"
+
+"@module-federation/sdk@0.5.1":
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.5.1.tgz#6c0a4053c23fa84db7aae7e4736496c541de7191"
+  integrity sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==
+
+"@module-federation/webpack-bundler-runtime@0.5.1":
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.5.1.tgz#ef626af0d57e3568c474d66d7d3797366e09cafd"
+  integrity sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==
+  dependencies:
+    "@module-federation/runtime" "0.5.1"
+    "@module-federation/sdk" "0.5.1"
+
 "@node-ipc/js-queue@2.0.3":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@node-ipc/js-queue/-/js-queue-2.0.3.tgz#ac7fe33d766fa53e233ef8fedaf3443a01c5a4cd"
@@ -1206,6 +1266,11 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
+"@pkgjs/parseargs@^0.11.0":
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+  integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
 "@pkgr/core@^0.1.0":
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
@@ -1276,6 +1341,110 @@
     pluralize "^8.0.0"
     yaml-ast-parser "0.0.43"
 
+"@rspack/binding-darwin-arm64@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz#c09eedca2e0de205d6192f78f76645f95ee115fa"
+  integrity sha512-oJ4ex0USLoOR5nFt0uQFinG6bDCSbCqwobepjA0qk33kuXdJyJw5LAwDW0Mfw3up+/ngEhySOZtQIv6PDeSFqQ==
+
+"@rspack/binding-darwin-x64@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz#0a5c97b63c2c77f13e772c731ef9bf9e26f32274"
+  integrity sha512-v5rfOUVGIQ25WAOSbzBHv42Qsz/DaC7bqjc7mEb1jO8a4Y9y0fO7Nw2/bgLRg1di2y+Q2M9CBgPf9TDt8T1jOg==
+
+"@rspack/binding-linux-arm64-gnu@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz#c7ed28d070f02bcd2c166e73e0054dfa8e25d6b9"
+  integrity sha512-kCh94gOTms2N9A9oAaVpxqOn6l9Lr/oWGSQS9Zc4f0cyHmThYYMyPX76kLdL1WR/UpzrihQ3L9RfMU1FHMfjow==
+
+"@rspack/binding-linux-arm64-musl@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz#2a233830a2694e6b1e396abaf7e7182f2d17dc29"
+  integrity sha512-GCp5TxYSESzstqoZKBR0dFm8bh1F9aWsVnypcKndE3PCEPsptGwWvGZKl2QLZm87d/FL21eysAjFX4KQ1NNy+Q==
+
+"@rspack/binding-linux-x64-gnu@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz#f52196769e1e31653a0feef1e11a3686e310d857"
+  integrity sha512-kvwDRsXHp9HRWRqSqsFn+tEk8Uc2wPuGPzHo6pHaEKUu8S5czQmPuF5W0UtK7OluR+eec/6RfCy18WclGQx8Vg==
+
+"@rspack/binding-linux-x64-musl@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz#366004fb42e4ae108390faade29a95cffd051878"
+  integrity sha512-Di64N+wOxiWFH5xTg8NJpPGe2Syv/wkHYlA8mVo6nPvfTIPdyzybrjYl7TX9YHUnCXPKSIaEVuQM3FppuSvK1w==
+
+"@rspack/binding-win32-arm64-msvc@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz#b1e825bcfdb4a855f7d2752c031970b45f69e40d"
+  integrity sha512-UChXLKqND90LwBjoPZgD9P9ZYKmp9+y51n7kgNtDX2Hi/wsnFOZHUK2kpiSH9W69vE+c3pGFf0NIJ/scLdeUZw==
+
+"@rspack/binding-win32-ia32-msvc@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.1.tgz#7cedb99c0c02ca8651ae9c0b7bf1d0353f03124d"
+  integrity sha512-at+/S2t5yaADOV8s27S1nSsQc1Y5BxAb/y9lRvxKJJgshewsoOTNMQ1PFcy8pNDOF4Dvn/xiiCctqpZUjrT+jw==
+
+"@rspack/binding-win32-x64-msvc@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz#e43056670304b7ae002ae5c620c27ddd681591e6"
+  integrity sha512-PpKDYqFSUypbATkSx7N0j521T6shYjiOEl44VI+99AkZVt7gQnS/LWXWrRYuZrueO2klPm+LUBACBJUKPWNYNg==
+
+"@rspack/binding@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.0.1.tgz#fd571b4ed90a05c54b28b87faf51de2b142d7bee"
+  integrity sha512-bf5uTyen6Y1NYbHERA3oLX/IGx6T9C2PyKH+cia9Ik5A4hUwkOYamIAQIIC29VEPs06W1DccPLmGRZd3gjA7pA==
+  optionalDependencies:
+    "@rspack/binding-darwin-arm64" "1.0.1"
+    "@rspack/binding-darwin-x64" "1.0.1"
+    "@rspack/binding-linux-arm64-gnu" "1.0.1"
+    "@rspack/binding-linux-arm64-musl" "1.0.1"
+    "@rspack/binding-linux-x64-gnu" "1.0.1"
+    "@rspack/binding-linux-x64-musl" "1.0.1"
+    "@rspack/binding-win32-arm64-msvc" "1.0.1"
+    "@rspack/binding-win32-ia32-msvc" "1.0.1"
+    "@rspack/binding-win32-x64-msvc" "1.0.1"
+
+"@rspack/cli@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/cli/-/cli-1.0.1.tgz#9b8bed9fdea7790c441c7ea878298d2aac9ca545"
+  integrity sha512-yW+U2W6kV0BYFSvXxjxRD+gbHVn+pR/zmJjWw78d3JtKcfroVkNNB+EGoPXhZuaoe+tG68WC55xN171xmzuP/A==
+  dependencies:
+    "@discoveryjs/json-ext" "^0.5.7"
+    "@rspack/dev-server" "1.0.1"
+    colorette "2.0.19"
+    exit-hook "^3.2.0"
+    interpret "^3.1.1"
+    rechoir "^0.8.0"
+    semver "^7.6.2"
+    webpack-bundle-analyzer "4.6.1"
+    yargs "17.6.2"
+
+"@rspack/core@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.0.1.tgz#b3709a1ef13365c4ac9fe8e346932cdd270c07da"
+  integrity sha512-z09J/6oJA5y7iclPe+4INg13J5OEWgBw6PdGAOExdcj0DvCicbTGR7QVx1vSrMRbi8oxWk10J3nosOs0zf1bRQ==
+  dependencies:
+    "@module-federation/runtime-tools" "0.5.1"
+    "@rspack/binding" "1.0.1"
+    "@rspack/lite-tapable" "1.0.0"
+    caniuse-lite "^1.0.30001616"
+
+"@rspack/dev-server@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@rspack/dev-server/-/dev-server-1.0.1.tgz#7f6ae24f624d6eb2bc9c25da6c4deb04346c15db"
+  integrity sha512-cRjTlp2/2c4/BM+B8wN4lhSrvXpVsHFIGe0ZqFfWmtyX/7jNG7A11M9GTJWWr6P0AERqpkFN4MTJRqbqIGZdJA==
+  dependencies:
+    chokidar "^3.6.0"
+    connect-history-api-fallback "^2.0.0"
+    express "^4.19.2"
+    http-proxy-middleware "^2.0.6"
+    mime-types "^2.1.35"
+    webpack-dev-middleware "^7.4.2"
+    webpack-dev-server "^5.0.4"
+    ws "^8.16.0"
+
+"@rspack/lite-tapable@1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@rspack/lite-tapable/-/lite-tapable-1.0.0.tgz#160883693462f164d0e6ede22f32614673ef6429"
+  integrity sha512-7MZf4lburSUZoEenwazwUDKHhqyfnLCGnQ/tKcUtztfmVzfjZfRn/EaiT0AKkYGnL2U8AGsw89oUeVyvaOLVCw==
+
 "@sideway/address@^4.1.3":
   version "4.1.4"
   resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
@@ -1321,6 +1490,13 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/bonjour@^3.5.13":
+  version "3.5.13"
+  resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956"
+  integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/bonjour@^3.5.9":
   version "3.5.10"
   resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275"
@@ -1336,6 +1512,14 @@
     "@types/express-serve-static-core" "*"
     "@types/node" "*"
 
+"@types/connect-history-api-fallback@^1.5.4":
+  version "1.5.4"
+  resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3"
+  integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==
+  dependencies:
+    "@types/express-serve-static-core" "*"
+    "@types/node" "*"
+
 "@types/connect@*":
   version "3.4.35"
   resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@@ -1378,6 +1562,16 @@
     "@types/qs" "*"
     "@types/range-parser" "*"
 
+"@types/express-serve-static-core@^4.17.33":
+  version "4.19.5"
+  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6"
+  integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==
+  dependencies:
+    "@types/node" "*"
+    "@types/qs" "*"
+    "@types/range-parser" "*"
+    "@types/send" "*"
+
 "@types/express@*", "@types/express@^4.17.13":
   version "4.17.14"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c"
@@ -1388,6 +1582,16 @@
     "@types/qs" "*"
     "@types/serve-static" "*"
 
+"@types/express@^4.17.21":
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
+  integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
+  dependencies:
+    "@types/body-parser" "*"
+    "@types/express-serve-static-core" "^4.17.33"
+    "@types/qs" "*"
+    "@types/serve-static" "*"
+
 "@types/glob@^7.1.1":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
@@ -1401,6 +1605,11 @@
   resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
   integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
 
+"@types/http-errors@*":
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
+  integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
+
 "@types/http-proxy@^1.17.8":
   version "1.17.9"
   resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
@@ -1423,6 +1632,11 @@
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
   integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
 
+"@types/mime@^1":
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
+  integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
+
 "@types/minimatch@*":
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
@@ -1433,6 +1647,13 @@
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
   integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
 
+"@types/node-forge@^1.3.0":
+  version "1.3.11"
+  resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
+  integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/node@*":
   version "18.11.2"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.2.tgz#c59b7641832531264fda3f1ba610362dc9a7dfc8"
@@ -1468,6 +1689,19 @@
   resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
   integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
 
+"@types/retry@0.12.2":
+  version "0.12.2"
+  resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a"
+  integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==
+
+"@types/send@*":
+  version "0.17.4"
+  resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
+  integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
+  dependencies:
+    "@types/mime" "^1"
+    "@types/node" "*"
+
 "@types/serve-index@^1.9.1":
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278"
@@ -1475,6 +1709,13 @@
   dependencies:
     "@types/express" "*"
 
+"@types/serve-index@^1.9.4":
+  version "1.9.4"
+  resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898"
+  integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==
+  dependencies:
+    "@types/express" "*"
+
 "@types/serve-static@*", "@types/serve-static@^1.13.10":
   version "1.15.0"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155"
@@ -1483,6 +1724,15 @@
     "@types/mime" "*"
     "@types/node" "*"
 
+"@types/serve-static@^1.15.5":
+  version "1.15.7"
+  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
+  integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
+  dependencies:
+    "@types/http-errors" "*"
+    "@types/node" "*"
+    "@types/send" "*"
+
 "@types/sinonjs__fake-timers@8.1.1":
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3"
@@ -1500,6 +1750,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/sockjs@^0.3.36":
+  version "0.3.36"
+  resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535"
+  integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==
+  dependencies:
+    "@types/node" "*"
+
 "@types/stylis@4.2.0":
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b"
@@ -1512,6 +1769,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/ws@^8.5.10":
+  version "8.5.12"
+  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
+  integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/yauzl@^2.9.1":
   version "2.10.0"
   resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
@@ -1987,7 +2251,7 @@ ajv-keywords@^3.5.2:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
-ajv-keywords@^5.0.0:
+ajv-keywords@^5.0.0, ajv-keywords@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
   integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
@@ -2014,7 +2278,7 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0:
     require-from-string "^2.0.2"
     uri-js "^4.2.2"
 
-ajv@^8.6.3:
+ajv@^8.6.3, ajv@^8.9.0:
   version "8.17.1"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
   integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
@@ -2089,6 +2353,11 @@ ansi-regex@^5.0.1:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
   integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
 
+ansi-regex@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
+  integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+
 ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -2103,6 +2372,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
   dependencies:
     color-convert "^2.0.1"
 
+ansi-styles@^6.1.0:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+  integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
 ansi-styles@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
@@ -2589,6 +2863,24 @@ body-parser@1.20.1:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+body-parser@1.20.2:
+  version "1.20.2"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
+  integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+  dependencies:
+    bytes "3.1.2"
+    content-type "~1.0.5"
+    debug "2.6.9"
+    depd "2.0.0"
+    destroy "1.2.0"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    on-finished "2.4.1"
+    qs "6.11.0"
+    raw-body "2.5.2"
+    type-is "~1.6.18"
+    unpipe "1.0.0"
+
 bonjour-service@^1.0.11:
   version "1.0.14"
   resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7"
@@ -2599,6 +2891,14 @@ bonjour-service@^1.0.11:
     fast-deep-equal "^3.1.3"
     multicast-dns "^7.2.5"
 
+bonjour-service@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02"
+  integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    multicast-dns "^7.2.5"
+
 boolbase@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@@ -2838,6 +3138,13 @@ builtin-status-codes@^3.0.0:
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
   integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==
 
+bundle-name@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889"
+  integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==
+  dependencies:
+    run-applescript "^7.0.0"
+
 bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -2943,6 +3250,11 @@ caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805:
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001199.tgz#062afccaad21023e2e647d767bac4274b8b8fd7f"
   integrity sha512-ifbK2eChUCFUwGhlEzIoVwzFt1+iriSjyKKFYNfv6hN34483wyWpLLavYQXhnR036LhkdUYaSDpHg1El++VgHQ==
 
+caniuse-lite@^1.0.30001616:
+  version "1.0.30001655"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz#0ce881f5a19a2dcfda2ecd927df4d5c1684b982f"
+  integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==
+
 case-sensitive-paths-webpack-plugin@^2.3.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
@@ -3026,7 +3338,7 @@ chokidar@^2.0.0:
   optionalDependencies:
     fsevents "^1.2.7"
 
-chokidar@^3.5.1:
+chokidar@^3.5.1, chokidar@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
   integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -3285,16 +3597,16 @@ colord@^2.9.1, colord@^2.9.3:
   resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
   integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
 
+colorette@2.0.19, colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16:
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
+  integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
+
 colorette@^1.2.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
   integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
 
-colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16:
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
-  integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
-
 combine-source-map@^0.8.0, combine-source-map@~0.8.0:
   version "0.8.0"
   resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b"
@@ -3416,6 +3728,11 @@ content-type@~1.0.4:
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
+content-type@~1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+  integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
 convert-source-map@1.X, convert-source-map@^1.5.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -3443,6 +3760,11 @@ cookie@0.5.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
   integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
 
+cookie@0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+  integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+
 copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@@ -3881,6 +4203,19 @@ deepmerge@^1.5.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
+default-browser-id@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26"
+  integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==
+
+default-browser@^5.2.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf"
+  integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==
+  dependencies:
+    bundle-name "^4.1.0"
+    default-browser-id "^5.0.0"
+
 default-compare@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
@@ -3912,6 +4247,11 @@ define-lazy-prop@^2.0.0:
   resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
   integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
 
+define-lazy-prop@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f"
+  integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==
+
 define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -4157,6 +4497,11 @@ each-props@^1.3.0:
     is-plain-object "^2.0.1"
     object.defaults "^1.1.0"
 
+eastasianwidth@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+  integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
 easy-stack@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066"
@@ -4203,6 +4548,11 @@ emoji-regex@^8.0.0:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
+emoji-regex@^9.2.2:
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+  integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
 emojis-list@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@@ -4515,6 +4865,11 @@ executable@^4.1.1:
   dependencies:
     pify "^2.2.0"
 
+exit-hook@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-3.2.0.tgz#7d86bc361a4d79278001b72a0509318a6f468f20"
+  integrity sha512-aIQN7Q04HGAV/I5BszisuHTZHXNoC23WtLkxdCLuYZMdWviRD0TMIt2bnUBi9MrHaF/hH8b3gwG9iaAUHKnJGA==
+
 expand-brackets@^2.1.4:
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -4572,6 +4927,43 @@ express@^4.17.3:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+express@^4.19.2:
+  version "4.19.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
+  integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
+  dependencies:
+    accepts "~1.3.8"
+    array-flatten "1.1.1"
+    body-parser "1.20.2"
+    content-disposition "0.5.4"
+    content-type "~1.0.4"
+    cookie "0.6.0"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "2.0.0"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.2.0"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "2.4.1"
+    parseurl "~1.3.3"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.7"
+    qs "6.11.0"
+    range-parser "~1.2.1"
+    safe-buffer "5.2.1"
+    send "0.18.0"
+    serve-static "1.15.0"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    type-is "~1.6.18"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
 ext@^1.1.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
@@ -4901,6 +5293,14 @@ foreach@^2.0.4:
   resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.6.tgz#87bcc8a1a0e74000ff2bf9802110708cfb02eb6e"
   integrity sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==
 
+foreground-child@^3.1.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
+  integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+  dependencies:
+    cross-spawn "^7.0.0"
+    signal-exit "^4.0.1"
+
 forever-agent@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -4992,6 +5392,11 @@ function-bind@^1.1.1:
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
+function-bind@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+  integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
 function.prototype.name@^1.1.5:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
@@ -5166,6 +5571,18 @@ glob-watcher@^5.0.3:
     normalize-path "^3.0.0"
     object.defaults "^1.1.0"
 
+glob@^10.3.7:
+  version "10.4.5"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
+  integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+  dependencies:
+    foreground-child "^3.1.0"
+    jackspeak "^3.1.2"
+    minimatch "^9.0.4"
+    minipass "^7.1.2"
+    package-json-from-dist "^1.0.0"
+    path-scurry "^1.11.1"
+
 glob@^7.0.3, glob@^7.1.1, glob@^7.1.3:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -5547,6 +5964,13 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
+hasown@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+  integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+  dependencies:
+    function-bind "^1.1.2"
+
 he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -5600,6 +6024,11 @@ html-entities@^2.3.2:
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
   integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
 
+html-entities@^2.4.0:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
+  integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
+
 html-minifier-terser@^6.0.2:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
@@ -5675,7 +6104,7 @@ http-parser-js@>=0.5.1:
   resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
   integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
 
-http-proxy-middleware@^2.0.3:
+http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
   integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
@@ -5724,6 +6153,11 @@ human-signals@^2.1.0:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
+hyperdyperid@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b"
+  integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==
+
 iconv-lite@0.4.24:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -5864,6 +6298,11 @@ interpret@^2.2.0:
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
   integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
 
+interpret@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
+  integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==
+
 invert-kv@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@@ -5879,6 +6318,11 @@ ipaddr.js@^2.0.1:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
   integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
 
+ipaddr.js@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
+  integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+
 is-absolute@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"
@@ -5960,6 +6404,13 @@ is-ci@^3.0.0:
   dependencies:
     ci-info "^3.2.0"
 
+is-core-module@^2.13.0:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
+  integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
+  dependencies:
+    hasown "^2.0.2"
+
 is-core-module@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
@@ -6025,6 +6476,11 @@ is-docker@^2.0.0, is-docker@^2.1.1:
   resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
   integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
 
+is-docker@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200"
+  integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
+
 is-extendable@^0.1.0, is-extendable@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -6094,6 +6550,13 @@ is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-inside-container@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
+  integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==
+  dependencies:
+    is-docker "^3.0.0"
+
 is-installed-globally@~0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
@@ -6117,6 +6580,11 @@ is-negative-zero@^2.0.2:
   resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
   integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
 
+is-network-error@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997"
+  integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==
+
 is-number-object@^1.0.4:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
@@ -6300,6 +6768,13 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
   dependencies:
     is-docker "^2.0.0"
 
+is-wsl@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2"
+  integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==
+  dependencies:
+    is-inside-container "^1.0.0"
+
 isarray@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -6332,6 +6807,15 @@ isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
+jackspeak@^3.1.2:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
+  integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+  dependencies:
+    "@isaacs/cliui" "^8.0.2"
+  optionalDependencies:
+    "@pkgjs/parseargs" "^0.11.0"
+
 javascript-stringify@^2.0.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
@@ -6560,6 +7044,14 @@ launch-editor@^2.2.1, launch-editor@^2.6.0:
     picocolors "^1.0.0"
     shell-quote "^1.7.3"
 
+launch-editor@^2.6.1:
+  version "2.8.2"
+  resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.2.tgz#939e1b3469f9d5471e4eaacedd51b3b7c45352cd"
+  integrity sha512-eF5slEUZXmi6WvFzI3dYcv+hA24/iKnROf24HztcURJpSz9RBmBgz5cNCVOeguouf1llrwy6Yctl4C4HM+xI8g==
+  dependencies:
+    picocolors "^1.0.0"
+    shell-quote "^1.8.1"
+
 lazy-ass@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
@@ -6796,6 +7288,11 @@ lower-case@^2.0.2:
   dependencies:
     tslib "^2.0.3"
 
+lru-cache@^10.2.0:
+  version "10.4.3"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
+  integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
 lru-cache@^4.1.2:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -6922,6 +7419,16 @@ memfs@^3.4.3:
   dependencies:
     fs-monkey "^1.0.3"
 
+memfs@^4.6.0:
+  version "4.11.1"
+  resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.11.1.tgz#9c9c8e65bf8ac72c0db8d0fbbbe29248cf51d56a"
+  integrity sha512-LZcMTBAgqUUKNXZagcZxvXXfgF1bHX7Y7nQ0QyEiNbRJgE29GhgPd8Yna1VQcLlPiHt/5RFJMWYN9Uv/VPNvjQ==
+  dependencies:
+    "@jsonjoy.com/json-pack" "^1.0.3"
+    "@jsonjoy.com/util" "^1.3.0"
+    tree-dump "^1.0.1"
+    tslib "^2.0.0"
+
 memoizee@0.4.X:
   version "0.4.15"
   resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72"
@@ -7033,7 +7540,7 @@ mime-types@^2.1.12, mime-types@~2.1.19:
   dependencies:
     mime-db "1.46.0"
 
-mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
+mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -7077,7 +7584,7 @@ minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
 
-minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^5.0.1:
+minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^5.0.1, minimatch@^9.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -7115,6 +7622,11 @@ minipass@^3.1.1:
   dependencies:
     yallist "^4.0.0"
 
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
+  integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
 mixin-deep@^1.2.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -7584,7 +8096,7 @@ obuf@^1.0.0, obuf@^1.1.2:
   resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
   integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
-on-finished@2.4.1:
+on-finished@2.4.1, on-finished@^2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
   integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
@@ -7617,6 +8129,16 @@ onetime@^5.1.0, onetime@^5.1.2:
   dependencies:
     mimic-fn "^2.1.0"
 
+open@^10.0.3:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1"
+  integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==
+  dependencies:
+    default-browser "^5.2.1"
+    define-lazy-prop "^3.0.0"
+    is-inside-container "^1.0.0"
+    is-wsl "^3.1.0"
+
 open@^8.0.2, open@^8.0.9:
   version "8.4.0"
   resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
@@ -7724,11 +8246,25 @@ p-retry@^4.5.0:
     "@types/retry" "0.12.0"
     retry "^0.13.1"
 
+p-retry@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd"
+  integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==
+  dependencies:
+    "@types/retry" "0.12.2"
+    is-network-error "^1.0.0"
+    retry "^0.13.1"
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
+package-json-from-dist@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
+  integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
+
 pako@~1.0.5:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -7907,6 +8443,14 @@ path-root@^0.1.1:
   dependencies:
     path-root-regex "^0.1.0"
 
+path-scurry@^1.11.1:
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
+  integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+  dependencies:
+    lru-cache "^10.2.0"
+    minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -8592,6 +9136,16 @@ raw-body@2.5.1:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
+raw-body@2.5.2:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+  integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+  dependencies:
+    bytes "3.1.2"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
 "react-dom@^17.0.0 || ^18.2.0":
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -8725,6 +9279,13 @@ rechoir@^0.7.0:
   dependencies:
     resolve "^1.9.0"
 
+rechoir@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
+  integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==
+  dependencies:
+    resolve "^1.20.0"
+
 redent@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
@@ -8988,6 +9549,15 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0:
     is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
+resolve@^1.20.0:
+  version "1.22.8"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
+  integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
+  dependencies:
+    is-core-module "^2.13.0"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
 restore-cursor@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -9038,6 +9608,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
+rimraf@^5.0.5:
+  version "5.0.10"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
+  integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
+  dependencies:
+    glob "^10.3.7"
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -9057,6 +9634,11 @@ rtlcss@^2.4.0:
     postcss "^6.0.23"
     strip-json-comments "^2.0.0"
 
+run-applescript@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb"
+  integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==
+
 run-parallel@^1.1.9:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -9146,6 +9728,16 @@ schema-utils@^4.0.0:
     ajv-formats "^2.1.1"
     ajv-keywords "^5.0.0"
 
+schema-utils@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"
+  integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==
+  dependencies:
+    "@types/json-schema" "^7.0.9"
+    ajv "^8.9.0"
+    ajv-formats "^2.1.1"
+    ajv-keywords "^5.1.0"
+
 select-hose@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -9158,6 +9750,14 @@ selfsigned@^2.1.1:
   dependencies:
     node-forge "^1"
 
+selfsigned@^2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
+  integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
+  dependencies:
+    "@types/node-forge" "^1.3.0"
+    node-forge "^1"
+
 semver-greatest-satisfied-range@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
@@ -9203,6 +9803,11 @@ semver@^7.5.3:
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.6.2:
+  version "7.6.3"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+  integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
 send@0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
@@ -9338,6 +9943,11 @@ shell-quote@^1.4.2, shell-quote@^1.6.1, shell-quote@^1.7.3:
   resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8"
   integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==
 
+shell-quote@^1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
+  integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
+
 should-equal@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
@@ -9401,6 +10011,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
   integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
 
+signal-exit@^4.0.1:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+  integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
 simple-concat@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
@@ -9717,6 +10332,15 @@ stream-splicer@^2.0.0:
     inherits "^2.0.1"
     readable-stream "^2.0.2"
 
+"string-width-cjs@npm:string-width@^4.2.0":
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
 string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -9743,6 +10367,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
+string-width@^5.0.1, string-width@^5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+  integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+  dependencies:
+    eastasianwidth "^0.2.0"
+    emoji-regex "^9.2.2"
+    strip-ansi "^7.0.1"
+
 string.prototype.trimend@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0"
@@ -9780,6 +10413,13 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -9801,6 +10441,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   dependencies:
     ansi-regex "^5.0.1"
 
+strip-ansi@^7.0.1:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+  integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+  dependencies:
+    ansi-regex "^6.0.1"
+
 strip-ansi@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
@@ -10127,6 +10774,11 @@ thenify-all@^1.0.0:
   dependencies:
     any-promise "^1.0.0"
 
+thingies@^1.20.0:
+  version "1.21.0"
+  resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1"
+  integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==
+
 thread-loader@^3.0.0:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/thread-loader/-/thread-loader-3.0.4.tgz#c392e4c0241fbc80430eb680e4886819b504a31b"
@@ -10296,6 +10948,11 @@ tr46@~0.0.3:
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
   integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
 
+tree-dump@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac"
+  integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==
+
 trim-newlines@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
@@ -10316,6 +10973,11 @@ tslib@2.5.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
   integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
 
+tslib@^2.0.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
+  integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
+
 tslib@^2.0.3, tslib@^2.1.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
@@ -10820,7 +11482,7 @@ webidl-conversions@^3.0.0:
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
   integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
 
-webpack-bundle-analyzer@^4.4.0:
+webpack-bundle-analyzer@4.6.1, webpack-bundle-analyzer@^4.4.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.1.tgz#bee2ee05f4ba4ed430e4831a319126bb4ed9f5a6"
   integrity sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw==
@@ -10872,6 +11534,18 @@ webpack-dev-middleware@^5.3.1:
     range-parser "^1.2.1"
     schema-utils "^4.0.0"
 
+webpack-dev-middleware@^7.1.0, webpack-dev-middleware@^7.4.2:
+  version "7.4.2"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877"
+  integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==
+  dependencies:
+    colorette "^2.0.10"
+    memfs "^4.6.0"
+    mime-types "^2.1.31"
+    on-finished "^2.4.1"
+    range-parser "^1.2.1"
+    schema-utils "^4.0.0"
+
 webpack-dev-server@^4.7.3, webpack-dev-server@^4.7.4:
   version "4.11.1"
   resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5"
@@ -10907,6 +11581,42 @@ webpack-dev-server@^4.7.3, webpack-dev-server@^4.7.4:
     webpack-dev-middleware "^5.3.1"
     ws "^8.4.2"
 
+webpack-dev-server@^5.0.4:
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8"
+  integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==
+  dependencies:
+    "@types/bonjour" "^3.5.13"
+    "@types/connect-history-api-fallback" "^1.5.4"
+    "@types/express" "^4.17.21"
+    "@types/serve-index" "^1.9.4"
+    "@types/serve-static" "^1.15.5"
+    "@types/sockjs" "^0.3.36"
+    "@types/ws" "^8.5.10"
+    ansi-html-community "^0.0.8"
+    bonjour-service "^1.2.1"
+    chokidar "^3.6.0"
+    colorette "^2.0.10"
+    compression "^1.7.4"
+    connect-history-api-fallback "^2.0.0"
+    default-gateway "^6.0.3"
+    express "^4.17.3"
+    graceful-fs "^4.2.6"
+    html-entities "^2.4.0"
+    http-proxy-middleware "^2.0.3"
+    ipaddr.js "^2.1.0"
+    launch-editor "^2.6.1"
+    open "^10.0.3"
+    p-retry "^6.2.0"
+    rimraf "^5.0.5"
+    schema-utils "^4.2.0"
+    selfsigned "^2.4.1"
+    serve-index "^1.9.1"
+    sockjs "^0.3.24"
+    spdy "^4.0.2"
+    webpack-dev-middleware "^7.1.0"
+    ws "^8.16.0"
+
 webpack-merge@^5.4.0, webpack-merge@^5.7.3:
   version "5.8.0"
   resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61"
@@ -11039,6 +11749,15 @@ wordwrap@^1.0.0:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
 
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -11073,6 +11792,15 @@ wrap-ansi@^7.0.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+  integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+  dependencies:
+    ansi-styles "^6.1.0"
+    string-width "^5.0.1"
+    strip-ansi "^7.0.1"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -11091,6 +11819,11 @@ ws@^7.3.1, ws@^7.4.2:
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
   integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
 
+ws@^8.16.0:
+  version "8.18.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
+  integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
+
 ws@^8.4.2:
   version "8.9.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e"
@@ -11175,6 +11908,19 @@ yargs@17.0.1:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
+yargs@17.6.2:
+  version "17.6.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
+  integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==
+  dependencies:
+    cliui "^8.0.1"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.3"
+    y18n "^5.0.5"
+    yargs-parser "^21.1.1"
+
 yargs@^15.4.1:
   version "15.4.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"

From ace4e71eb8cb8378cdad0c86151ace715f787c10 Mon Sep 17 00:00:00 2001
From: Katrin Fischer <katrin.fischer@bsz-bw.de>
Date: Mon, 16 Sep 2024 14:39:29 +0000
Subject: [PATCH 304/429] Bug 37824: (follow-up) Add rspack.config.js to
 Makefile.PL

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 562a240933d3e5b28ed64f10c5f498afabaaa19e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Makefile.PL | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Makefile.PL b/Makefile.PL
index 62380aae13..fd82d43c5e 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -404,6 +404,7 @@ my $target_map = {
     'cypress.config.ts' => 'NONE',
     'tsconfig.json'     => 'NONE',
     'webpack.config.js' => 'NONE',
+    'rspack.config.js'  => 'NONE',
 };
 
 =head1 CONFIGURATION OPTIONS

From d0de116c8f21637d9c9e08d39563c45809288fd2 Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Mon, 16 Sep 2024 16:25:36 +0000
Subject: [PATCH 305/429] Bug 37824: (QA follow-up) Fix improper handling of
 jQuery and DataTables libraries by marking them as externals

This commit addresses an issue where jQuery and DataTables libraries (datatables.net and related extensions) were not functioning correctly when bundled with rspack. These libraries expect to be loaded in the global scope rather than as bundled modules, leading to initialization issues.

To resolve this, jQuery and all related DataTables libraries (datatables.net, datatables.net-buttons, datatables.net-buttons/js/buttons.html5, etc.) are marked as externals in the rspack configuration. This ensures they are treated as global dependencies, preventing conflicts and allowing proper initialization.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit db4f151be5546e3b3319e248e3d71a8c0ad03558)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 rspack.config.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/rspack.config.js b/rspack.config.js
index 54eefc216f..6a8e40c4f6 100644
--- a/rspack.config.js
+++ b/rspack.config.js
@@ -21,6 +21,7 @@ module.exports = {
             "koha-tmpl/intranet-tmpl/prog/js/vue/dist/"
         ),
         chunkFilename: "[name].js",
+        globalObject: "window",
     },
     module: {
         rules: [
@@ -63,4 +64,12 @@ module.exports = {
             __VUE_PROD_DEVTOOLS__: false,
         }),
     ],
+    externals: {
+        jquery: "jQuery",
+        "datatables.net": "DataTable",
+        "datatables.net-buttons": "DataTable",
+        "datatables.net-buttons/js/buttons.html5": "DataTable",
+        "datatables.net-buttons/js/buttons.print": "DataTable",
+        "datatables.net-buttons/js/buttons.colVis": "DataTable",
+    },
 };

From 7d17200a385a5b44201e57a89170f4792d6e7e7e Mon Sep 17 00:00:00 2001
From: Mason James <mtj@kohaaloha.com>
Date: Tue, 24 Sep 2024 12:47:26 +1200
Subject: [PATCH 306/429] Bug 37824: remove references to non-existent files in
 Makefile.PL

to test...

0/ install debhelper tool

1/ run 'DEB_BUILD_OPTIONS=nocheck dh build', observe failure
    make: *** No rule to make target 'koha-tmpl/intranet-tmpl/prog/js/vue/dist/erm.js.LICENSE.txt', needed by 'pm_to_blib'.  Stop.
    dh_auto_build: error: make -j1 returned exit code 2

2/ apply patch

3/ run 'DEB_BUILD_OPTIONS=nocheck dh build', observe success

Signed-off-by: Blou <blou@inlibro.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit c8869aea1c4b3cbfa043a3c5b466cb3b5a3a5c98)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Makefile.PL | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Makefile.PL b/Makefile.PL
index fd82d43c5e..62062d96b2 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -674,9 +674,7 @@ $file_map->{'koha-tmpl/intranet-tmpl/prog/css/installer.css'} = 'blib/INTRANET_T
 $file_map->{'koha-tmpl/intranet-tmpl/prog/css/mainpage.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/mainpage.css';
 $file_map->{'koha-tmpl/intranet-tmpl/prog/css/staff-global.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/staff-global.css';
 $file_map->{'koha-tmpl/intranet-tmpl/prog/js/vue/dist/erm.js'} = 'blib/INTRANET_TMPL_DIR/prog/js/vue/dist/erm.js';
-$file_map->{'koha-tmpl/intranet-tmpl/prog/js/vue/dist/erm.js.LICENSE.txt'} = 'blib/INTRANET_TMPL_DIR/prog/js/vue/dist/erm.js.LICENSE.txt';
 $file_map->{'koha-tmpl/intranet-tmpl/prog/js/vue/dist/preservation.js'} = 'blib/INTRANET_TMPL_DIR/prog/js/vue/dist/preservation.js';
-$file_map->{'koha-tmpl/intranet-tmpl/prog/js/vue/dist/preservation.js.LICENSE.txt'} = 'blib/INTRANET_TMPL_DIR/prog/js/vue/dist/preservation.js.LICENSE.txt';
 #OPAC_TMPL_DIR
 $file_map->{'koha-tmpl/opac-tmpl/bootstrap/css/opac.css'} = 'blib/OPAC_TMPL_DIR/bootstrap/css/opac.css';
 $file_map->{'koha-tmpl/opac-tmpl/bootstrap/css/opac-rtl.css'} = 'blib/OPAC_TMPL_DIR/bootstrap/css/opac-rtl.css';

From 407661f5d32986412552e95a70508dc5ceb4caa8 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 13:45:59 +0000
Subject: [PATCH 307/429] Bug 37963: Case 1 - Add tests for invalid api key use
 case

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 05381ba6b18d50fdc0329dd13a364cdb4e0eca11)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../Koha/BackgroundJob/ErmSushiHarvester.t    | 59 ++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
index 150a642f66..614b5f8b0f 100755
--- a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
+++ b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
@@ -17,22 +17,28 @@
 
 use Modern::Perl;
 
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 use Koha::Database;
 use Koha::BackgroundJobs;
 use Koha::BackgroundJob::ErmSushiHarvester;
 
 use File::Basename qw( dirname );
+use JSON qw( decode_json );
 use File::Slurp;
 
 use t::lib::Mocks;
 use t::lib::TestBuilder;
 use Test::MockModule;
+use Test::MockObject;
 
 my $schema  = Koha::Database->new->schema;
 my $builder = t::lib::TestBuilder->new;
 
+my $sushi_response_errors = {
+    'invalid_api_key'        => '{"Code": 2020, "Severity": "Error", "Message": "API Key Invalid"}',
+};
+
 subtest 'enqueue() tests' => sub {
 
     plan tests => 3;
@@ -60,3 +66,54 @@ subtest 'enqueue() tests' => sub {
 
     $schema->storage->txn_rollback;
 };
+
+subtest 'invalid_api_key() tests' => sub {
+
+    plan tests => 3;
+
+    $schema->storage->txn_begin;
+
+    my $ua = Test::MockModule->new('LWP::UserAgent');
+    $ua->mock('simple_request', sub {
+            return mock_sushi_response({'error'=>'invalid_api_key', 'code'=>401});
+    });
+
+    my $usage_data_provider = $builder->build_object(
+        { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
+
+    my $job_args = {
+            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+            report_type      => 'TR_J1',
+            begin_date       => '2023-08-01',
+            end_date         => '2023-09-30',
+            ud_provider_name => $usage_data_provider->name,
+        };
+
+    my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
+    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process( $job_args );
+
+    is( $job->{messages}[0]->{message}, decode_json($sushi_response_errors->{invalid_api_key})->{Severity} . ' - ' . decode_json($sushi_response_errors->{invalid_api_key})->{Message},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is( $job->{messages}[0]->{type},'error','SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is( $job->{messages}[0]->{code},decode_json($sushi_response_errors->{invalid_api_key})->{Code},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+
+    $schema->storage->txn_rollback;
+};
+
+sub mock_sushi_response {
+    my ($args) = @_;
+    my $response = Test::MockObject->new();
+
+    $response->mock('code', sub {
+        return $args->{code} || 200;
+    });
+    $response->mock('is_error', sub {
+        return 0;
+    });
+    $response->mock('is_redirect', sub {
+        return 0;
+    });
+    $response->mock('decoded_content', sub {
+        return $sushi_response_errors->{$args->{error}};
+    });
+}
\ No newline at end of file

From 6987bf779f6d2f5ccc0d5b76c5e6a5a6eff7ee1a Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 13:39:07 +0000
Subject: [PATCH 308/429] Bug 37963: Case 1 - Return if sushi_errors even if
 response code >= 400

This ensures that SUSHI errors, if present, are shown on the UI even if
response->code >= 400.
This means that if, for example, SUSHI provider returns 401 with SUSHI
error like so:
{"Code": 2020, "Severity": "Error", "Message": "API Key Invalid"}

Then, the 'API Key Invalid' will be shown to the user, without having to
check logs.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5d53bb4bffa2cc1ee2bcc0a3d84fb74cc672fa97)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index e85ee58ce8..716610052d 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -180,8 +180,11 @@ sub harvest_sushi {
     $ua->agent( 'Koha/' . Koha::version() );
     my $response = $ua->simple_request($request);
 
+    my $result   = decode_json( $response->decoded_content );
+
     if ( $response->code >= 400 ) {
-        my $result = decode_json( $response->decoded_content );
+
+        return if $self->_sushi_errors($result);
 
         my $message;
         if ( ref($result) eq 'ARRAY' ) {
@@ -199,7 +202,6 @@ sub harvest_sushi {
             }
         }
 
-        #TODO: May want to add a job error message here?
         warn sprintf "ERROR - SUSHI service %s returned %s - %s\n", $url,
             $response->code, $message;
         if ( $response->code == 404 ) {
@@ -208,7 +210,6 @@ sub harvest_sushi {
             Koha::Exceptions::Authorization::Unauthorized->throw($message);
         } else {
 
-            #TODO: May want to add a job error message here?
             die sprintf "ERROR requesting SUSHI service\n%s\ncode %s: %s\n",
                 $url, $response->code,
                 $message;
@@ -217,12 +218,10 @@ sub harvest_sushi {
         return;
     }
 
-    my $decoded_response = decode_json( $response->decoded_content );
-
-    return if $self->_sushi_errors($decoded_response);
+    return if $self->_sushi_errors($result);
 
     # Parse the SUSHI response
-    my $sushi_counter = Koha::ERM::EUsage::SushiCounter->new( { response => $decoded_response } );
+    my $sushi_counter = Koha::ERM::EUsage::SushiCounter->new( { response => $result } );
     my $counter_file  = $sushi_counter->get_COUNTER_from_SUSHI;
 
     return if $self->_counter_file_size_too_large($counter_file);

From d4255bf632724ffce05095172040a8818c998d6b Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 13:49:58 +0000
Subject: [PATCH 309/429] Bug 37963: Case 2 - Add tests for different case
 error message

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4c76d2b565610737d9a0ec5a76d44c4cc2e347e0)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../Koha/BackgroundJob/ErmSushiHarvester.t    | 36 ++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
index 614b5f8b0f..f0e736006c 100755
--- a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
+++ b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
@@ -17,7 +17,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 2;
+use Test::More tests => 3;
 
 use Koha::Database;
 use Koha::BackgroundJobs;
@@ -36,6 +36,7 @@ my $schema  = Koha::Database->new->schema;
 my $builder = t::lib::TestBuilder->new;
 
 my $sushi_response_errors = {
+    'invalid_date_arguments' => '{"message":"Invalid Date Arguments","code":3020,"severity":"Error"}',
     'invalid_api_key'        => '{"Code": 2020, "Severity": "Error", "Message": "API Key Invalid"}',
 };
 
@@ -67,6 +68,39 @@ subtest 'enqueue() tests' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'invalid_date_arguments() tests' => sub {
+
+    plan tests => 3;
+
+    $schema->storage->txn_begin;
+
+    my $ua = Test::MockModule->new('LWP::UserAgent');
+    $ua->mock('simple_request', sub {
+            return mock_sushi_response({'error'=>'invalid_date_arguments'});
+    });
+
+    my $usage_data_provider = $builder->build_object(
+        { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
+
+    my $job_args = {
+            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+            report_type      => 'TR_J1',
+            begin_date       => '2023-08-01',
+            end_date         => '2023-09-30',
+            ud_provider_name => $usage_data_provider->name,
+        };
+
+    my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
+    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process( $job_args );
+
+    is( $job->{messages}[0]->{message}, decode_json($sushi_response_errors->{invalid_date_arguments})->{severity} . ' - ' . decode_json($sushi_response_errors->{invalid_date_arguments})->{message},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is( $job->{messages}[0]->{type},'error','SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is( $job->{messages}[0]->{code},decode_json($sushi_response_errors->{invalid_date_arguments})->{code},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+
+    $schema->storage->txn_rollback;
+};
+
 subtest 'invalid_api_key() tests' => sub {
 
     plan tests => 3;

From 7baba70b69e12dfa3436db02404a8cdf12b12a71 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 13:49:10 +0000
Subject: [PATCH 310/429] Bug 37963: Case 2 - Consider providers returning
 error messages lower cased

Test plan:
1) Apply only test patch for case 1. Run tests:
prove t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
2) Verify tests fail. Apply the other patch for case 1.
3) Run tests again. Verify they now pass.
4) Repeat the test plan but using patches for case 2

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 23ab84c9b6c1a2206d9495c6671d833b14bf5a0f)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index 716610052d..b13a760925 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -458,12 +458,16 @@ Additionally, adds background job report message(s) if that is the case
 sub _sushi_errors {
     my ( $self, $decoded_response ) = @_;
 
-    if ( $decoded_response->{Severity} ) {
+    my $severity = $decoded_response->{Severity} // $decoded_response->{severity};
+    my $message = $decoded_response->{Message} // $decoded_response->{message};
+    my $code = $decoded_response->{Code} // $decoded_response->{code};
+
+    if ( $severity ) {
         $self->{job_callbacks}->{add_message_callback}->(
             {
                 type    => 'error',
-                code    => $decoded_response->{Code},
-                message => $decoded_response->{Severity} . ' - ' . $decoded_response->{Message},
+                code    => $code,
+                message => $severity . ' - ' . $message,
             }
         ) if $self->{job_callbacks};
         return 1;

From 209b685871b51aa6d62ced3e906d520cef9d3a6d Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 26 Sep 2024 18:34:35 +0000
Subject: [PATCH 311/429] Bug 37963: (QA follow-up) Tidy

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5b51ef78d4da3703c575bad32a112f0d99cd01c5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm          |   8 +-
 .../Koha/BackgroundJob/ErmSushiHarvester.t    | 124 ++++++++++++------
 2 files changed, 86 insertions(+), 46 deletions(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index b13a760925..f6d9ef7b92 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -180,7 +180,7 @@ sub harvest_sushi {
     $ua->agent( 'Koha/' . Koha::version() );
     my $response = $ua->simple_request($request);
 
-    my $result   = decode_json( $response->decoded_content );
+    my $result = decode_json( $response->decoded_content );
 
     if ( $response->code >= 400 ) {
 
@@ -459,10 +459,10 @@ sub _sushi_errors {
     my ( $self, $decoded_response ) = @_;
 
     my $severity = $decoded_response->{Severity} // $decoded_response->{severity};
-    my $message = $decoded_response->{Message} // $decoded_response->{message};
-    my $code = $decoded_response->{Code} // $decoded_response->{code};
+    my $message  = $decoded_response->{Message}  // $decoded_response->{message};
+    my $code     = $decoded_response->{Code}     // $decoded_response->{code};
 
-    if ( $severity ) {
+    if ($severity) {
         $self->{job_callbacks}->{add_message_callback}->(
             {
                 type    => 'error',
diff --git a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
index f0e736006c..d6a7440eae 100755
--- a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
+++ b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
@@ -24,7 +24,7 @@ use Koha::BackgroundJobs;
 use Koha::BackgroundJob::ErmSushiHarvester;
 
 use File::Basename qw( dirname );
-use JSON qw( decode_json );
+use JSON           qw( decode_json );
 use File::Slurp;
 
 use t::lib::Mocks;
@@ -75,28 +75,42 @@ subtest 'invalid_date_arguments() tests' => sub {
     $schema->storage->txn_begin;
 
     my $ua = Test::MockModule->new('LWP::UserAgent');
-    $ua->mock('simple_request', sub {
-            return mock_sushi_response({'error'=>'invalid_date_arguments'});
-    });
+    $ua->mock(
+        'simple_request',
+        sub {
+            return mock_sushi_response( { 'error' => 'invalid_date_arguments' } );
+        }
+    );
 
     my $usage_data_provider = $builder->build_object(
         { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
 
     my $job_args = {
-            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
-            report_type      => 'TR_J1',
-            begin_date       => '2023-08-01',
-            end_date         => '2023-09-30',
-            ud_provider_name => $usage_data_provider->name,
-        };
+        ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+        report_type      => 'TR_J1',
+        begin_date       => '2023-08-01',
+        end_date         => '2023-09-30',
+        ud_provider_name => $usage_data_provider->name,
+    };
 
     my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
-    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
-    $job->process( $job_args );
+    my $job    = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process($job_args);
 
-    is( $job->{messages}[0]->{message}, decode_json($sushi_response_errors->{invalid_date_arguments})->{severity} . ' - ' . decode_json($sushi_response_errors->{invalid_date_arguments})->{message},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
-    is( $job->{messages}[0]->{type},'error','SUSHI error invalid_date_arguments is stored on job messages correctly' );
-    is( $job->{messages}[0]->{code},decode_json($sushi_response_errors->{invalid_date_arguments})->{code},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is(
+        $job->{messages}[0]->{message},
+        decode_json( $sushi_response_errors->{invalid_date_arguments} )->{severity} . ' - '
+            . decode_json( $sushi_response_errors->{invalid_date_arguments} )->{message},
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
+    is(
+        $job->{messages}[0]->{type}, 'error',
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
+    is(
+        $job->{messages}[0]->{code}, decode_json( $sushi_response_errors->{invalid_date_arguments} )->{code},
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
 
     $schema->storage->txn_rollback;
 };
@@ -108,28 +122,42 @@ subtest 'invalid_api_key() tests' => sub {
     $schema->storage->txn_begin;
 
     my $ua = Test::MockModule->new('LWP::UserAgent');
-    $ua->mock('simple_request', sub {
-            return mock_sushi_response({'error'=>'invalid_api_key', 'code'=>401});
-    });
+    $ua->mock(
+        'simple_request',
+        sub {
+            return mock_sushi_response( { 'error' => 'invalid_api_key', 'code' => 401 } );
+        }
+    );
 
     my $usage_data_provider = $builder->build_object(
         { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
 
     my $job_args = {
-            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
-            report_type      => 'TR_J1',
-            begin_date       => '2023-08-01',
-            end_date         => '2023-09-30',
-            ud_provider_name => $usage_data_provider->name,
-        };
+        ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+        report_type      => 'TR_J1',
+        begin_date       => '2023-08-01',
+        end_date         => '2023-09-30',
+        ud_provider_name => $usage_data_provider->name,
+    };
 
     my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
-    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
-    $job->process( $job_args );
+    my $job    = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process($job_args);
 
-    is( $job->{messages}[0]->{message}, decode_json($sushi_response_errors->{invalid_api_key})->{Severity} . ' - ' . decode_json($sushi_response_errors->{invalid_api_key})->{Message},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
-    is( $job->{messages}[0]->{type},'error','SUSHI error invalid_date_arguments is stored on job messages correctly' );
-    is( $job->{messages}[0]->{code},decode_json($sushi_response_errors->{invalid_api_key})->{Code},'SUSHI error invalid_date_arguments is stored on job messages correctly' );
+    is(
+        $job->{messages}[0]->{message},
+        decode_json( $sushi_response_errors->{invalid_api_key} )->{Severity} . ' - '
+            . decode_json( $sushi_response_errors->{invalid_api_key} )->{Message},
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
+    is(
+        $job->{messages}[0]->{type}, 'error',
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
+    is(
+        $job->{messages}[0]->{code}, decode_json( $sushi_response_errors->{invalid_api_key} )->{Code},
+        'SUSHI error invalid_date_arguments is stored on job messages correctly'
+    );
 
     $schema->storage->txn_rollback;
 };
@@ -138,16 +166,28 @@ sub mock_sushi_response {
     my ($args) = @_;
     my $response = Test::MockObject->new();
 
-    $response->mock('code', sub {
-        return $args->{code} || 200;
-    });
-    $response->mock('is_error', sub {
-        return 0;
-    });
-    $response->mock('is_redirect', sub {
-        return 0;
-    });
-    $response->mock('decoded_content', sub {
-        return $sushi_response_errors->{$args->{error}};
-    });
-}
\ No newline at end of file
+    $response->mock(
+        'code',
+        sub {
+            return $args->{code} || 200;
+        }
+    );
+    $response->mock(
+        'is_error',
+        sub {
+            return 0;
+        }
+    );
+    $response->mock(
+        'is_redirect',
+        sub {
+            return 0;
+        }
+    );
+    $response->mock(
+        'decoded_content',
+        sub {
+            return $sushi_response_errors->{ $args->{error} };
+        }
+    );
+}

From d4a4d38974435f4ed2825cf3b29dc152018ae336 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Wed, 18 Sep 2024 15:55:51 +0000
Subject: [PATCH 312/429] Bug 37856: Append platform parameter to SUSHI url if
 defined

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit dc9a5684f438d7b676ab52ffd2e4a73179d422eb)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index f6d9ef7b92..da4171571f 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -398,6 +398,7 @@ sub _build_url_query {
     $url .= '&api_key=' . $self->api_key           if $self->api_key;
     $url .= '&begin_date=' . substr $self->{begin_date}, 0, 7 if $self->{begin_date};
     $url .= '&end_date=' . substr $self->{end_date},     0, 7 if $self->{end_date};
+    $url .= '&platform=' . $self->service_platform if $self->service_platform;
 
     return $url;
 }

From 8e4ed8d701d2d7cecf96bf3356cb9eb8285ad392 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Wed, 18 Sep 2024 15:57:43 +0000
Subject: [PATCH 313/429] Bug 37856: atomicupdate

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 2e4b1ca42f7a940e8329ed860dda102deb629db0)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../data/mysql/atomicupdate/bug_37856.pl      | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100755 installer/data/mysql/atomicupdate/bug_37856.pl

diff --git a/installer/data/mysql/atomicupdate/bug_37856.pl b/installer/data/mysql/atomicupdate/bug_37856.pl
new file mode 100755
index 0000000000..0d62d17143
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_37856.pl
@@ -0,0 +1,19 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
+
+return {
+    bug_number  => "37856",
+    description => "Add service_platform column to erm_usage_data_providers",
+    up          => sub {
+        my ($args) = @_;
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+        if( !column_exists( 'erm_usage_data_providers', 'service_platform' ) ) {
+          $dbh->do(q{
+              ALTER TABLE erm_usage_data_providers ADD COLUMN `service_platform` varchar(80) DEFAULT NULL COMMENT 'platform if provider requires it' AFTER `report_types`
+          });
+          say_info( $out, "Bug 37856 - Added service_platform column to table 'erm_usage_data_providers'" );
+        }
+        say_success( $out, "Bug 37856 - Done" );
+    },
+};

From c87bb2b3a7d1d988595c38ea8a8cc9dbc49f7f32 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Wed, 18 Sep 2024 15:56:19 +0000
Subject: [PATCH 314/429] Bug 37856: Add service_platform field

The change from this patch itself should be self-explanatory.
Testing for this requires credentials to a specific provider.
But the parameter is only added to the URL if defined.

Test plan, ktd, apply patch, run:
$ yarn js:build
$ yarn api:bundle
$ koha-plack --restart kohadev

1) Enable ERMModule
2) Create a new provider, visit:
<staff_url>/cgi-bin/koha/erm/eusage/usage_data_providers/add
3) Ensure 'Service platform' now shows. Create the provider and put something in this field.
4) View the provider, ensure the field is shown, visit:
<staff_url>/cgi-bin/koha/erm/eusage/usage_data_providers/1

Ensure all tests are still passing:
$ cypress run --spec t/cypress/integration/ERM/DataProviders_spec.ts
$ prove t/db_dependent/api/v1/erm_usage_*

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b852c042feff417c745e2166c26f84b22456b14a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 api/v1/swagger/definitions/erm_usage_data_provider.yaml  | 5 +++++
 installer/data/mysql/kohastructure.sql                   | 1 +
 .../ERM/UsageStatisticsDataProviderDetails.vue           | 6 ++++++
 .../ERM/UsageStatisticsDataProvidersFormAdd.vue          | 9 +++++++++
 4 files changed, 21 insertions(+)

diff --git a/api/v1/swagger/definitions/erm_usage_data_provider.yaml b/api/v1/swagger/definitions/erm_usage_data_provider.yaml
index baa6c5d092..ed6bdfe570 100644
--- a/api/v1/swagger/definitions/erm_usage_data_provider.yaml
+++ b/api/v1/swagger/definitions/erm_usage_data_provider.yaml
@@ -73,6 +73,11 @@ properties:
     type: 
     - string
     - "null"
+  service_platform:
+    description: service platform of the harvester
+    type:
+    - string
+    - "null"
   metric_type:
     description: metric type of the harvester when reporting
     type: 
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 6e3f8dfc39..f55cdc5ccf 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -3211,6 +3211,7 @@ CREATE TABLE `erm_usage_data_providers` (
   `requestor_name` varchar(80) DEFAULT NULL COMMENT 'requestor name',
   `requestor_email` varchar(80) DEFAULT NULL COMMENT 'requestor email',
   `report_types` varchar(255) DEFAULT NULL COMMENT 'report types provided by the harvester',
+  `service_platform` varchar(80) DEFAULT NULL COMMENT 'platform if provider requires it',
   PRIMARY KEY (`erm_usage_data_provider_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProviderDetails.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProviderDetails.vue
index 9190f5834a..3032b8d668 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProviderDetails.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProviderDetails.vue
@@ -32,6 +32,12 @@
                     {{ usage_data_provider.report_types }}
                 </span>
             </li>
+            <li>
+                <label>{{ $__("Service platform") }}:</label>
+                <span id="service_platform">
+                    {{ usage_data_provider.service_platform }}
+                </span>
+            </li>
         </ol>
     </fieldset>
     <legend>{{ $__("SUSHI credentials") }}</legend>
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProvidersFormAdd.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProvidersFormAdd.vue
index 7d4e30afb9..e9681dd471 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProvidersFormAdd.vue
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsDataProvidersFormAdd.vue
@@ -117,6 +117,15 @@
                                 :disabled="!selected_provider && !manual_form"
                             />
                         </li>
+                        <li>
+                            <label for="usage_data_provider_service_platform"
+                                >{{ $__("Service platform") }}:
+                            </label>
+                            <input
+                                id="usage_data_provider_service_platform"
+                                v-model="usage_data_provider.service_platform"
+                            />
+                        </li>
                         <li>
                             <label
                                 class="required"

From c3681fd9ff52d77b3b4e9fc2832561c31f709f9e Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 13:55:48 +0000
Subject: [PATCH 315/429] Bug 37856: Consider 'Exceptions' not being part of
 'Report_Header'

When fixing the issue around the missing platform parameter, we
noticed this particular provider also returns 'Exceptions' as
its own thing, not part of 'Report_Header', despite documentation:
https://countermetrics.stoplight.io/docs/counter-sushi-api/7cp0h7n1hm2en-tr-j1-journal-requests-controlled

From the link above, search for 'Exceptions'. Notice it should be under 'Report_Header'.
Either way, this patch accounts for this possibility.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7c175c2160e7ed59c3c67b5a7cd890a4ff0b1306)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index da4171571f..b9ead5ca86 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -474,8 +474,9 @@ sub _sushi_errors {
         return 1;
     }
 
-    if ( $decoded_response->{Report_Header}->{Exceptions} ) {
-        foreach my $exception ( @{ $decoded_response->{Report_Header}->{Exceptions} } ) {
+    my $exceptions = $decoded_response->{Report_Header}->{Exceptions} // $decoded_response->{Exceptions};
+    if ( $exceptions ) {
+        foreach my $exception ( @{ $exceptions } ) {
             $self->{job_callbacks}->{add_message_callback}->(
                 {
                     type    => 'error',

From 730ca49e063c7a6ff22bbb13f5761b74310f9322 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 14:00:41 +0000
Subject: [PATCH 316/429] Bug 37856: Consider absence of 'Report_Items'

The SUSHI provider in question for this patchset also does not return any
Report_Items entry. This patch accounts for that.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 72b38a1dbfe12685124b9814dc97807e0d301d3f)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index b9ead5ca86..1f094e15d3 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -488,7 +488,7 @@ sub _sushi_errors {
         return 1;
     }
 
-    if ( scalar @{ $decoded_response->{Report_Items} } == 0 ) {
+    if ( $decoded_response->{Report_Items} && scalar @{ $decoded_response->{Report_Items} } == 0 ) {
         $self->{job_callbacks}->{add_message_callback}->(
             {
                 type => 'error',

From 89de703030db785c9f7c6bc4054a9f4f9b128263 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Thu, 19 Sep 2024 14:03:40 +0000
Subject: [PATCH 317/429] Bug 37856: Add tests

This is using the groundwork from bug 37963.
This is testing the specific error this bug is addressing:
Required parameter Platform is missing

It is also testing that the error message is correctly shown as a background
job report message to the user

Test plan:
prove t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit edb6056a741f6c326a1745648ebadb1dbea7fc1c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../Koha/BackgroundJob/ErmSushiHarvester.t    | 47 ++++++++++++++++++-
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
index d6a7440eae..a3b26367f1 100755
--- a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
+++ b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
@@ -17,7 +17,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 3;
+use Test::More tests => 4;
 
 use Koha::Database;
 use Koha::BackgroundJobs;
@@ -40,6 +40,10 @@ my $sushi_response_errors = {
     'invalid_api_key'        => '{"Code": 2020, "Severity": "Error", "Message": "API Key Invalid"}',
 };
 
+my $sushi_response_exceptions = {
+    'multiple_exceptions'    => '{"Created":"2024-09-06T10:41:02Z","Created_By":"Test Services","Customer_ID":"Test_customer_id","Report_ID":"TR_J1","Release":"5","Report_Name":"Journal Requests (Excluding OA_Gold)","Institution_Name":"Test Institution","Report_Filters":[{"Name":"Begin_Date","Value":"2024-06-01"},{"Name":"End_Date","Value":"2024-08-31"}],"Exceptions":[{"Code":3050,"Severity":"Warning","Message":"Parameter Not Recognized in this Context","Data":"Parameter api_key is not recognized"},{"Code":3070,"Severity":"Error","Message":"Required ReportFilter Missing","Data":"Required parameter Platform is missing and should be one of:sd|sc|ev|em|ck"}]}',
+};
+
 subtest 'enqueue() tests' => sub {
 
     plan tests => 3;
@@ -162,6 +166,43 @@ subtest 'invalid_api_key() tests' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'multiple_exceptions() tests' => sub {
+
+    plan tests => 6;
+
+    $schema->storage->txn_begin;
+
+    my $ua = Test::MockModule->new('LWP::UserAgent');
+    $ua->mock('simple_request', sub {
+            return mock_sushi_response({'exception'=>'multiple_exceptions'});
+    });
+
+    my $usage_data_provider = $builder->build_object(
+        { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
+
+    my $job_args = {
+            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+            report_type      => 'TR_J1',
+            begin_date       => '2023-08-01',
+            end_date         => '2023-09-30',
+            ud_provider_name => $usage_data_provider->name,
+        };
+
+    my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
+    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process( $job_args );
+
+    is( $job->{messages}[0]->{message}, decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Message} . ' - ' . decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Data},'SUSHI exceptions multiple_exceptions are stored on job messages correctly' );
+    is( $job->{messages}[0]->{type},'error','SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is( $job->{messages}[0]->{code},decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Code},'SUSHI error multiple_exceptions are stored on job messages correctly' );
+
+    is( $job->{messages}[1]->{message}, decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Message} . ' - ' . decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Data},'SUSHI exceptions multiple_exceptions are stored on job messages correctly' );
+    is( $job->{messages}[1]->{type},'error','SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is( $job->{messages}[1]->{code},decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Code},'SUSHI error multiple_exceptions are stored on job messages correctly' );
+
+    $schema->storage->txn_rollback;
+};
+
 sub mock_sushi_response {
     my ($args) = @_;
     my $response = Test::MockObject->new();
@@ -187,7 +228,9 @@ sub mock_sushi_response {
     $response->mock(
         'decoded_content',
         sub {
-            return $sushi_response_errors->{ $args->{error} };
+            return $args->{error}
+                ? $sushi_response_errors->{ $args->{error} }
+                : $sushi_response_exceptions->{ $args->{exception} };
         }
     );
 }

From 2e9a6b16f9032ed386bab068949302cf8eb9c996 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 26 Sep 2024 19:20:15 +0000
Subject: [PATCH 318/429] Bug 37856: (QA follow-up) Tidy

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b7600e480151cc8d8762f6826d1b7da3e697c3f7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ERM/EUsage/UsageDataProvider.pm          |  4 +-
 .../data/mysql/atomicupdate/bug_37856.pl      | 10 ++--
 .../Koha/BackgroundJob/ErmSushiHarvester.t    | 58 +++++++++++++------
 3 files changed, 48 insertions(+), 24 deletions(-)

diff --git a/Koha/ERM/EUsage/UsageDataProvider.pm b/Koha/ERM/EUsage/UsageDataProvider.pm
index 1f094e15d3..ab9f314c80 100644
--- a/Koha/ERM/EUsage/UsageDataProvider.pm
+++ b/Koha/ERM/EUsage/UsageDataProvider.pm
@@ -475,8 +475,8 @@ sub _sushi_errors {
     }
 
     my $exceptions = $decoded_response->{Report_Header}->{Exceptions} // $decoded_response->{Exceptions};
-    if ( $exceptions ) {
-        foreach my $exception ( @{ $exceptions } ) {
+    if ($exceptions) {
+        foreach my $exception ( @{$exceptions} ) {
             $self->{job_callbacks}->{add_message_callback}->(
                 {
                     type    => 'error',
diff --git a/installer/data/mysql/atomicupdate/bug_37856.pl b/installer/data/mysql/atomicupdate/bug_37856.pl
index 0d62d17143..8544c30b5f 100755
--- a/installer/data/mysql/atomicupdate/bug_37856.pl
+++ b/installer/data/mysql/atomicupdate/bug_37856.pl
@@ -8,11 +8,13 @@ return {
         my ($args) = @_;
         my ( $dbh, $out ) = @$args{qw(dbh out)};
 
-        if( !column_exists( 'erm_usage_data_providers', 'service_platform' ) ) {
-          $dbh->do(q{
+        if ( !column_exists( 'erm_usage_data_providers', 'service_platform' ) ) {
+            $dbh->do(
+                q{
               ALTER TABLE erm_usage_data_providers ADD COLUMN `service_platform` varchar(80) DEFAULT NULL COMMENT 'platform if provider requires it' AFTER `report_types`
-          });
-          say_info( $out, "Bug 37856 - Added service_platform column to table 'erm_usage_data_providers'" );
+          }
+            );
+            say_info( $out, "Bug 37856 - Added service_platform column to table 'erm_usage_data_providers'" );
         }
         say_success( $out, "Bug 37856 - Done" );
     },
diff --git a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
index a3b26367f1..874a5e99d8 100755
--- a/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
+++ b/t/db_dependent/Koha/BackgroundJob/ErmSushiHarvester.t
@@ -41,7 +41,8 @@ my $sushi_response_errors = {
 };
 
 my $sushi_response_exceptions = {
-    'multiple_exceptions'    => '{"Created":"2024-09-06T10:41:02Z","Created_By":"Test Services","Customer_ID":"Test_customer_id","Report_ID":"TR_J1","Release":"5","Report_Name":"Journal Requests (Excluding OA_Gold)","Institution_Name":"Test Institution","Report_Filters":[{"Name":"Begin_Date","Value":"2024-06-01"},{"Name":"End_Date","Value":"2024-08-31"}],"Exceptions":[{"Code":3050,"Severity":"Warning","Message":"Parameter Not Recognized in this Context","Data":"Parameter api_key is not recognized"},{"Code":3070,"Severity":"Error","Message":"Required ReportFilter Missing","Data":"Required parameter Platform is missing and should be one of:sd|sc|ev|em|ck"}]}',
+    'multiple_exceptions' =>
+        '{"Created":"2024-09-06T10:41:02Z","Created_By":"Test Services","Customer_ID":"Test_customer_id","Report_ID":"TR_J1","Release":"5","Report_Name":"Journal Requests (Excluding OA_Gold)","Institution_Name":"Test Institution","Report_Filters":[{"Name":"Begin_Date","Value":"2024-06-01"},{"Name":"End_Date","Value":"2024-08-31"}],"Exceptions":[{"Code":3050,"Severity":"Warning","Message":"Parameter Not Recognized in this Context","Data":"Parameter api_key is not recognized"},{"Code":3070,"Severity":"Error","Message":"Required ReportFilter Missing","Data":"Required parameter Platform is missing and should be one of:sd|sc|ev|em|ck"}]}',
 };
 
 subtest 'enqueue() tests' => sub {
@@ -173,32 +174,53 @@ subtest 'multiple_exceptions() tests' => sub {
     $schema->storage->txn_begin;
 
     my $ua = Test::MockModule->new('LWP::UserAgent');
-    $ua->mock('simple_request', sub {
-            return mock_sushi_response({'exception'=>'multiple_exceptions'});
-    });
+    $ua->mock(
+        'simple_request',
+        sub {
+            return mock_sushi_response( { 'exception' => 'multiple_exceptions' } );
+        }
+    );
 
     my $usage_data_provider = $builder->build_object(
         { class => 'Koha::ERM::EUsage::UsageDataProviders', value => { name => 'TestProvider' } } );
 
     my $job_args = {
-            ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
-            report_type      => 'TR_J1',
-            begin_date       => '2023-08-01',
-            end_date         => '2023-09-30',
-            ud_provider_name => $usage_data_provider->name,
-        };
+        ud_provider_id   => $usage_data_provider->erm_usage_data_provider_id,
+        report_type      => 'TR_J1',
+        begin_date       => '2023-08-01',
+        end_date         => '2023-09-30',
+        ud_provider_name => $usage_data_provider->name,
+    };
 
     my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue($job_args);
-    my $job = Koha::BackgroundJobs->find($job_id)->_derived_class;
-    $job->process( $job_args );
+    my $job    = Koha::BackgroundJobs->find($job_id)->_derived_class;
+    $job->process($job_args);
 
-    is( $job->{messages}[0]->{message}, decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Message} . ' - ' . decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Data},'SUSHI exceptions multiple_exceptions are stored on job messages correctly' );
-    is( $job->{messages}[0]->{type},'error','SUSHI error multiple_exceptions are stored on job messages correctly' );
-    is( $job->{messages}[0]->{code},decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[0]->{Code},'SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is(
+        $job->{messages}[0]->{message},
+        decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[0]->{Message} . ' - '
+            . decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[0]->{Data},
+        'SUSHI exceptions multiple_exceptions are stored on job messages correctly'
+    );
+    is( $job->{messages}[0]->{type}, 'error', 'SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is(
+        $job->{messages}[0]->{code},
+        decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[0]->{Code},
+        'SUSHI error multiple_exceptions are stored on job messages correctly'
+    );
 
-    is( $job->{messages}[1]->{message}, decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Message} . ' - ' . decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Data},'SUSHI exceptions multiple_exceptions are stored on job messages correctly' );
-    is( $job->{messages}[1]->{type},'error','SUSHI error multiple_exceptions are stored on job messages correctly' );
-    is( $job->{messages}[1]->{code},decode_json($sushi_response_exceptions->{multiple_exceptions})->{Exceptions}[1]->{Code},'SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is(
+        $job->{messages}[1]->{message},
+        decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[1]->{Message} . ' - '
+            . decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[1]->{Data},
+        'SUSHI exceptions multiple_exceptions are stored on job messages correctly'
+    );
+    is( $job->{messages}[1]->{type}, 'error', 'SUSHI error multiple_exceptions are stored on job messages correctly' );
+    is(
+        $job->{messages}[1]->{code},
+        decode_json( $sushi_response_exceptions->{multiple_exceptions} )->{Exceptions}[1]->{Code},
+        'SUSHI error multiple_exceptions are stored on job messages correctly'
+    );
 
     $schema->storage->txn_rollback;
 };

From 6b8e7d7152aa1c4a7ad4d1e006a784b650c4baf9 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Fri, 27 Sep 2024 18:42:29 +0000
Subject: [PATCH 319/429] Bug 37856: Fix existing test

A service_platform value was being randomly to 'service_platform' as is a new attribute.
Assign undef to that to fix existing test

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b3fb2e85fe5a1e8fd8941da3cfee8a04879cebd3)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t b/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
index cf06ae0b56..bde75ecbb7 100755
--- a/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
+++ b/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
@@ -44,7 +44,7 @@ subtest '_build_url_query' => sub {
             class => 'Koha::ERM::EUsage::UsageDataProviders',
             value => {
                 service_url => $service_url, api_key => $api_key, requestor_id => $requestor_id,
-                customer_id => $customer_id, name    => $name
+                customer_id => $customer_id, name    => $name, service_platform => undef
             }
         }
     );

From aae9a4466c4e53f38e090d8f739eb1ead74ba2a0 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Fri, 27 Sep 2024 18:42:58 +0000
Subject: [PATCH 320/429] Bug 37856: Add new test to test platform is correctly
 added to build_url

prove t/db\_dependent/Koha/ERM/EUsage/UsageDataProvider.t

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e7dbd2a8b7683e78f9420d0e86e9dc651f067c2e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../Koha/ERM/EUsage/UsageDataProvider.t       | 37 ++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t b/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
index bde75ecbb7..2513b6f247 100755
--- a/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
+++ b/t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t
@@ -29,7 +29,7 @@ my $builder = t::lib::TestBuilder->new;
 
 subtest '_build_url_query' => sub {
 
-    plan tests => 1;
+    plan tests => 2;
 
     $schema->storage->txn_begin;
 
@@ -70,6 +70,41 @@ subtest '_build_url_query' => sub {
             . substr( $usage_data_provider->{end_date}, 0, 7 )
     );
 
+    my $test_platform = 'www.whatever.com';
+
+    my $usage_data_provider_with_platform = $builder->build_object(
+        {
+            class => 'Koha::ERM::EUsage::UsageDataProviders',
+            value => {
+                service_url => $service_url, api_key => $api_key, requestor_id => $requestor_id,
+                customer_id => $customer_id, name    => $name, service_platform => $test_platform
+            }
+        }
+    );
+
+    $usage_data_provider_with_platform->{report_type} = 'TR_J1';
+    $usage_data_provider_with_platform->{begin_date}  = '2023-08-01';
+    $usage_data_provider_with_platform->{end_date}    = '2023-09-30';
+
+    is(
+        $usage_data_provider_with_platform->_build_url_query,
+        $service_url
+            . '/reports/'
+            . lc( $usage_data_provider->{report_type} )
+            . '?customer_id='
+            . $customer_id
+            . '&requestor_id='
+            . $requestor_id
+            . '&api_key='
+            . $api_key
+            . '&begin_date='
+            . substr( $usage_data_provider->{begin_date}, 0, 7 )
+            . '&end_date='
+            . substr( $usage_data_provider->{end_date}, 0, 7 )
+            . '&platform='
+            . $test_platform
+    );
+
     $schema->storage->txn_rollback;
 
 };

From 1fb5bc4d8374ed149134bb8f945bf0c56c12ea5a Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 19:05:46 +0000
Subject: [PATCH 321/429] Bug 37856: DBRev 24.05.05.007

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha.pm                                                         | 2 +-
 .../mysql/{atomicupdate/bug_37856.pl => db_revs/240505007.pl}   | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_37856.pl => db_revs/240505007.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index b5e488ace4..735048d364 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.006";
+$VERSION = "24.05.05.007";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_37856.pl b/installer/data/mysql/db_revs/240505007.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_37856.pl
rename to installer/data/mysql/db_revs/240505007.pl

From 5eed8269b24edebe75a6c182c9f55e16eb9bae44 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 19:07:20 +0000
Subject: [PATCH 322/429] Bug 37526: DBIC Schema updates

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Schema/Result/ErmUsageDataProvider.pm | 14 ++++++++++++--
 Koha/Schema/Result/OldReserve.pm           |  6 +++---
 Koha/Schema/Result/Reserve.pm              |  6 +++---
 3 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/Koha/Schema/Result/ErmUsageDataProvider.pm b/Koha/Schema/Result/ErmUsageDataProvider.pm
index a65fa28789..0a5251cf74 100644
--- a/Koha/Schema/Result/ErmUsageDataProvider.pm
+++ b/Koha/Schema/Result/ErmUsageDataProvider.pm
@@ -142,6 +142,14 @@ requestor email
 
 report types provided by the harvester
 
+=head2 service_platform
+
+  data_type: 'varchar'
+  is_nullable: 1
+  size: 80
+
+platform if provider requires it
+
 =cut
 
 __PACKAGE__->add_columns(
@@ -175,6 +183,8 @@ __PACKAGE__->add_columns(
   { data_type => "varchar", is_nullable => 1, size => 80 },
   "report_types",
   { data_type => "varchar", is_nullable => 1, size => 255 },
+  "service_platform",
+  { data_type => "varchar", is_nullable => 1, size => 80 },
 );
 
 =head1 PRIMARY KEY
@@ -328,8 +338,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07049 @ 2023-10-11 10:09:06
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:MSvyv/Abt3S3fqZF21v/7w
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 19:06:48
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:VzC6luzfNA1QBZ7slRs6OQ
 
 # __PACKAGE__->add_columns(
 #     '+active' => { is_boolean => 1 }
diff --git a/Koha/Schema/Result/OldReserve.pm b/Koha/Schema/Result/OldReserve.pm
index 4f4354c80d..422b60aeac 100644
--- a/Koha/Schema/Result/OldReserve.pm
+++ b/Koha/Schema/Result/OldReserve.pm
@@ -59,7 +59,7 @@ foreign key from the biblio table defining which bib record this hold is on
   data_type: 'integer'
   is_nullable: 1
 
-links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)
+links the hold to the deleted bibliographic record (deletedbiblio.biblionumber)
 
 =head2 item_group_id
 
@@ -437,8 +437,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 16:46:06
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:droS+czhyVcRgVqOF06sQg
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 19:06:48
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7mO1W8tdYtEk7pijJuxsMA
 
 __PACKAGE__->belongs_to(
   "item",
diff --git a/Koha/Schema/Result/Reserve.pm b/Koha/Schema/Result/Reserve.pm
index 803864d5be..da25403fcf 100644
--- a/Koha/Schema/Result/Reserve.pm
+++ b/Koha/Schema/Result/Reserve.pm
@@ -62,7 +62,7 @@ foreign key from the biblio table defining which bib record this hold is on
   data_type: 'integer'
   is_nullable: 1
 
-links the reserve to the deleted bibliographic record (deletedbiblio.biblionumber)
+links the hold to the deleted bibliographic record (deletedbiblio.biblionumber)
 
 =head2 item_group_id
 
@@ -465,8 +465,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 16:46:06
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ljjbd9tYWR6AXmEyrg/sog
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-05 19:06:48
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OU6N7F+5LfbGfkhJMv53GA
 
 __PACKAGE__->belongs_to(
   "item",

From 9007d54b02ee1cc83c816cafd53e95333aa6c70e Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Tue, 22 Oct 2024 17:16:07 +0000
Subject: [PATCH 323/429] Bug 38231: Adjust CSS for search result controls in
 the OPAC

This patch makes some markup and CSS changes to the OPAC so that
controls are aligned correctly in the "mobile" view's catalog search
results. The patch also removes the border from the controls... I think
the borders looks out of place.

To test apply the patch and rebuild the OPAC CSS.

- Perform a catalog search.
- Check the styling of the controls shown for each search result, e.g.
  Place hold, Add tg, Save to lists, etc.
  - Make sure the controls work well when the browser window is various
    widths, from ~600px and up.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Yanjun Li <yanjun.li@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b8e69361bd8a0021f272636c61b2be51ab10ba01)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../bootstrap/css/src/_responsive.scss         | 16 +++++-----------
 .../opac-tmpl/bootstrap/css/src/opac.scss      |  6 ------
 .../en/includes/title-actions-menu.inc         | 18 +++++++++---------
 3 files changed, 14 insertions(+), 26 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
index 3339e0c919..082a9cba4c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss
@@ -110,17 +110,11 @@
     .actions {
         display: block;
 
-        &:first-child {
-            .btn-link {
-                padding: .375rem .75rem;
-            }
+        .btn-link {
+            padding-left: 0;
         }
 
         a {
-            @include border-radius-all( 4px );
-
-            background-color: #F2F2EF;
-            border: 1px solid #DDD;
             display: block;
             font-size: 120%;
             font-weight: bold;
@@ -210,9 +204,9 @@
     .cart-actions {
         display: flex;
 
-        .btn {
-            flex-grow: 1;
-            padding: .375rem .75rem;
+        .btn + .btn {
+            line-height: 2;
+            margin-left: .5em;
         }
     }
 
diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
index c852515e27..ba62d7a779 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss
@@ -280,12 +280,6 @@ th {
 
 .actions {
     white-space: nowrap;
-
-    &:first-child {
-        .btn-link {
-            padding-left: 0;
-        }
-    }
 }
 
 .advsearch_limit {
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
index 79db98d6e4..99fb97ee1c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/title-actions-menu.inc
@@ -16,18 +16,18 @@
     [% END # / OPACHoldRequests %]
 
     [% IF ( RecallsEnabled ) %]
-        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
+        <span class="actions"><a class="btn btn-link btn-sm recall" href="/cgi-bin/koha/opac-recall.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa-fw fa-solid fa-book-bookmark" aria-hidden="true"></i> Place recall</a></span>
     [% END %]
 
     [% IF ( LoginEnabled ) %]
         [% IF items.artreqpossible %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-request-article.pl?biblionumber=[% items.biblionumber | uri %]" class="btn btn-link btn-sm"><i class="fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> Request article</a></span>
         [% END %]
     [% END %]
 
     [% IF ( TagsInputEnabled ) %]
         [% IF ( loggedinusername ) %]
-            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-tag" aria-hidden="true"></i> Add tag</a></span>
+            <span class="actions"><a id="tag_add[% items.biblionumber | html %]" href="#" class="tag_add btn btn-link btn-sm"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Add tag</a></span>
             <div id="tagform[% items.biblionumber | html %]" class="tag_results_input form-inline" style="display:none;">
                 <label for="newtag[% items.biblionumber | html %]">New tag(s), separated by a comma:</label>
                 <input type="text" name="newtag[% items.biblionumber | html %]" id="newtag[% items.biblionumber | html %]" class="form-control form-control-sm newtag" maxlength="100" />
@@ -40,7 +40,7 @@
         [% ELSE %]
             <span class="actions">
                 <span class="login4tags">
-                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-tag" aria-hidden="true"></i> Log in to add tags</a>
+                    <a class="btn btn-link btn-sm loginModal-trigger" href="/cgi-bin/koha/opac-user.pl"><i class="fa fa-fw fa-tag" aria-hidden="true"></i> Log in to add tags</a>
                 </span>
             </span>
         [% END # IF loggedinusername %]
@@ -48,20 +48,20 @@
 
     [% IF ( ListsEnabled ) %]
         [% IF ( shelf AND op == 'view' ) %]
-            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-list" aria-hidden="true"></i> Save to another list</a></span>
+            <span class="actions"><a href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | uri %]" class="addtoshelf btn btn-link btn-sm"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to another list</a></span>
             [% IF can_remove_biblios %]
-                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
+                <span class="actions"><a class="btn btn-link btn-sm removeitems" data-biblionumber="[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-remove" aria-hidden="true"></i> Remove from this list</a></span>
             [% END %]
         [% ELSE %]
-            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-list" aria-hidden="true"></i> Save to lists</a></span>
+            <span class="actions"><a class="addtoshelf btn btn-link btn-sm" href="/cgi-bin/koha/opac-addbybiblionumber.pl?biblionumber=[% items.biblionumber | html %]"><i class="fa fa-fw fa-list" aria-hidden="true"></i> Save to lists</a></span>
         [% END # /IF ( shelf AND op == 'view' ) %]
     [% END # /IF Koha.Preference( 'opacuserlogin' ) %]
 
     [% IF ( CartEnabled ) %]
         [% IF ( items.incart ) %]
-            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> In your cart</a> <a data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% ELSE %]
-            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
+            <span class="actions cart-actions"><a aria-label="Add [% items.title | html %] to the cart" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm addtocart cart cart[% items.biblionumber | html %]" href="#"><i class="fa fa-fw fa-shopping-cart" aria-hidden="true"></i> Add to cart</a> <a style="display:none;" data-biblionumber="[% items.biblionumber | html %]" class="btn btn-link btn-sm remove cartRemove cartR[% items.biblionumber | html %]" href="#">(remove)</a></span>
         [% END %]
     [% END # / IF opacbookbag %]
 </div>

From 5b320189f0c0ac442a95f00653533fd6b9e74d28 Mon Sep 17 00:00:00 2001
From: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Date: Fri, 11 Oct 2024 15:56:29 -0400
Subject: [PATCH 324/429] Bug 38158: Typo in inventory 'Items has no "not for
 loan" status'

This patch corrects a typo in the inventory tool when an inventory
is done against not for loan items and an item has no not for loan
status.

To test:
1. Go to Cataloging > Inventory
2. Enter a barcode for an item that has items.notforloan = 0 in the "Or scan items one by one" text box
3. Check all the boxes EXCEPT "For loan" under "Optional filters for inventory list or comparing barcodes" > "items.notforloan"
4. Click "Submit"
   --> In the "Problems" column, it says "Items has no "not for loan" status"
5. Apply patch
6. Reload the page
   --> In the "Problems" column, "Item" should now be singular

Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit baf884600d2d3d1666a13455d2db584f314fd414)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
index 96684a0e90..456c906633 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt
@@ -358,7 +358,7 @@
                                                     [% IF result.notforloan %]
                                                         <span>Item has unselected "Not for loan" status "[% result.notforloan | html %]"</span><br/>
                                                     [% ELSE %]
-                                                        <span>Items has no "not for loan" status</span><br/>
+                                                        <span>Item has no "not for loan" status</span><br/>
                                                     [% END %]
                                                 [% ELSIF problem.key == 'not_scanned' %]
                                                     <span>Missing (not scanned)</span><br/>

From 5178687b67cefb9ca0c9847c6aeb123366d0a59c Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 24 Oct 2024 18:17:54 +0000
Subject: [PATCH 325/429] Bug 38266: (bug 21083 follow-up) Increment after
 comparison

To test:
 1 - Define a patron attribute as repeatable
 2 - Add multiple values to a patron (I used borrowernumber 5)
 3 - Tools->Batch patron modification
 4 - Modify the patron from step 1, selecting the attribute you created and checking the box to clear
 5 - View the patron, they look correct
 6 - Check the DB:
    SELECT * FROM borrower_attributes WHERE borrowernumber=5;
 7 - There is one blank value
 8 - Apply patch
 9 - Repeat batch modification
10 - Check the DB, no attribute!
11 - Test with multiple attributes in the batch modification and ensure only the correct one is removed
12 - Sign off

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a62953aa9ee8c5ac80e6e5cc6dd5300725b89e5e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 tools/modborrowers.pl | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tools/modborrowers.pl b/tools/modborrowers.pl
index fabf4ae091..b0b364c301 100755
--- a/tools/modborrowers.pl
+++ b/tools/modborrowers.pl
@@ -408,9 +408,9 @@ if ( $op eq 'cud-do' ) {
         my @attr_values = $input->multi_param('patron_attributes_value');
         my $attributes;
         my $i = 0;
-        for my $code ( @attributes ) {
-            push @{ $attributes->{$code}->{values} }, shift @attr_values; # Handling repeatables
-            $attributes->{$code}->{disabled} = grep { $_ eq sprintf("attr%s_value", ++$i) } @disabled;
+        for my $code (@attributes) {
+            push @{ $attributes->{$code}->{values} }, shift @attr_values;    # Handling repeatables
+            $attributes->{$code}->{disabled} = grep { $_ eq sprintf( "attr%s_value", $i++ ) } @disabled;
         }
 
         for my $code ( keys %$attributes ) {

From 81ad98eeed04c7b62b1ba6ee0e72e87fb686e363 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 29 Oct 2024 13:52:48 +0100
Subject: [PATCH 326/429] Bug 38286: Do not fetch the items if
 OpacHiddenItemsHidesRecord is set

Koha::Biblio:hidden_in_opac does not need to fetch the items if OpacHiddenItemsHidesRecord is set

Test plan:
  prove t/db_dependent/Koha/Biblio.t t/db_dependent/Koha/Item.t t/db_dependent/api/v1/biblios.t
Should return green

Confirm the assumption above is correct.

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit df429e4aba6b94bee7bc07691334475a9817c420)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Biblio.pm | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm
index 1fdcad53d1..4c7867b6ee 100644
--- a/Koha/Biblio.pm
+++ b/Koha/Biblio.pm
@@ -463,15 +463,14 @@ the I<OpacHiddenItems> system preference.
 sub hidden_in_opac {
     my ( $self, $params ) = @_;
 
+    return 0 unless C4::Context->preference('OpacHiddenItemsHidesRecord');
+
     my $rules = $params->{rules} // {};
 
     my @items = $self->items->as_list;
 
     return 0 unless @items; # Do not hide if there is no item
 
-    # Ok, there are items, don't even try the rules unless OpacHiddenItemsHidesRecord
-    return 0 unless C4::Context->preference('OpacHiddenItemsHidesRecord');
-
     return !(any { !$_->hidden_in_opac({ rules => $rules }) } @items);
 }
 

From 1b27067e9adc695662c570b411bc546c6c7c41c0 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 29 Oct 2024 14:41:19 +0100
Subject: [PATCH 327/429] Bug 33188: Remove warning from
 Koha::Item->hidden_in_opac

"Use of uninitialized value in string eq"

Test plan:
  prove t/db_dependent/Koha/Item.t
should return green

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Edit: tidied the tests (tcohen)
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f575d4cd73567d5a0e0edc777f83bdf99959a526)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Item.pm               |  2 +-
 t/db_dependent/Koha/Item.t | 10 ++++++++--
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/Koha/Item.pm b/Koha/Item.pm
index 729f4ba761..7590dbd34d 100644
--- a/Koha/Item.pm
+++ b/Koha/Item.pm
@@ -920,7 +920,7 @@ sub hidden_in_opac {
 
     foreach my $field ( keys %{$rules} ) {
 
-        if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
+        if ( any { defined $self->$field && $self->$field eq $_ } @{ $rules->{$field} } ) {
             $hidden_in_opac = 1;
             last;
         }
diff --git a/t/db_dependent/Koha/Item.t b/t/db_dependent/Koha/Item.t
index d1d85dac72..c99db7a8b8 100755
--- a/t/db_dependent/Koha/Item.t
+++ b/t/db_dependent/Koha/Item.t
@@ -23,6 +23,7 @@ use utf8;
 use Test::More tests => 35;
 use Test::Exception;
 use Test::MockModule;
+use Test::Warn;
 
 use C4::Biblio qw( GetMarcSubfieldStructure );
 use C4::Circulation qw( AddIssue AddReturn );
@@ -326,7 +327,7 @@ subtest 'remove_from_bundle tests' => sub {
 
 subtest 'hidden_in_opac() tests' => sub {
 
-    plan tests => 4;
+    plan tests => 5;
 
     $schema->storage->txn_begin;
 
@@ -351,7 +352,12 @@ subtest 'hidden_in_opac() tests' => sub {
 
     ok( $item->hidden_in_opac({ rules => $rules }), 'Rule matching itype passed, should hide' );
 
-
+    $rules = { ccode => ['FOO'] };
+    $item  = $item->ccode(undef)->store->get_from_storage;
+    warning_is {
+        $item->hidden_in_opac( { rules => $rules } )
+    }
+    undef, 'No warning from hidden_in_opac';
 
     $schema->storage->txn_rollback;
 };

From bf0420b14079c409380af61ec95f6cf7ba0de9c1 Mon Sep 17 00:00:00 2001
From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Date: Wed, 9 Oct 2024 15:00:33 +0000
Subject: [PATCH 328/429] Bug 38132: Add data-isbn to shelfbrowser

This patch adds a data-isbn attribute to the shelfbrowser_cover container for use in plugins

Test plan
1) View the patch diff and confirm that the attribute has been correctly added

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 2cca46e611c4324afbbb06fdc1bfc1e431cb478b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/opac-tmpl/bootstrap/en/includes/shelfbrowser.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/shelfbrowser.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/shelfbrowser.inc
index 78c6139142..d382a193c8 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/shelfbrowser.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/shelfbrowser.inc
@@ -29,7 +29,7 @@
                             [% END %]
 
                             [% IF ( OPACLocalCoverImages || OPACAmazonCoverImages || ( SyndeticsEnabled && SyndeticsCoverImages ) || GoogleJackets || BakerTaylorEnabled || ( Koha.Preference('OpacCoce') && Koha.Preference('CoceProviders') ) || ( Koha.Preference('OPACCustomCoverImages') AND Koha.Preference('CustomCoverImagesURL') ) ) %]
-                                <a class="shelfbrowser_cover" href="/cgi-bin/koha/opac-detail.pl?biblionumber=[% item.biblionumber | uri %]&shelfbrowse_itemnumber=[% item.itemnumber | uri %]#shelfbrowser">
+                                <a class="shelfbrowser_cover" href="/cgi-bin/koha/opac-detail.pl?biblionumber=[% item.biblionumber | uri %]&shelfbrowse_itemnumber=[% item.itemnumber | uri %]#shelfbrowser" data-isbn="[% item.browser_normalized_isbn | html%]">
                                     [% IF ( OPACLocalCoverImages ) %]
                                         <div title="[% img_title | html %]" class="[% item.biblionumber | html %] thumbnail-shelfbrowser" id="local-thumbnail-shelf-[% item.biblionumber | html %]"></div>
                                     [% END %]

From 345d1b943ad09867ef5e32da9710dc4debfb6030 Mon Sep 17 00:00:00 2001
From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Date: Wed, 30 Oct 2024 11:46:05 +0000
Subject: [PATCH 329/429] Bug 38297: Add a permissions check to the New vendor
 button

Test plan:
1) Choose a staff patron and make sure they have permissions to log
   into the staff interface
2) Ensure that the patron does not have the vendors_manage permission
   but give them one other acquisitions permission so that they can see
   the link on the homepage (e.g. budget_manage)
3) In an incognito browser, log in with that patron and go to the
   acquisitions homepage
4) The New vendor button will be visible
5) Click on it and fill in the required fields. Submit the form and it
   will error for lack of permissions
6) Apply patch
7) Return to the acquisitions page and the New vendor button should no
   longer be visible
8) Back in your main browser, give the patron the vendors_manage
   permission and then refresh the incognito window - the button should
   be visible

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit b11b2b213e611ba759a79f58940d6d8fb8581e71)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/includes/acquisitions-toolbar.inc     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/acquisitions-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/acquisitions-toolbar.inc
index 67488b141c..d4f28c3a2c 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/acquisitions-toolbar.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/acquisitions-toolbar.inc
@@ -31,7 +31,7 @@
             [% IF CAN_user_acquisition_order_receive && basketcount && active %]
                 <div class="btn-group"><a class="btn btn-default" id="receive" href="/cgi-bin/koha/acqui/parcels.pl?booksellerid=[% booksellerid | html %]"><i class="fa fa-inbox"></i> Receive shipments</a></div>
             [% END %]
-        [% ELSE %]
+        [% ELSIF ( CAN_user_acquisition_vendors_manage ) %]
             <div class="btn-group"><a class="btn btn-default" href="/cgi-bin/koha/acqui/supplier.pl?op=enter"><i class="fa fa-plus"></i> New vendor</a></div>
         [% END %]
 </div>

From 848d6e07ef74111501d5e71d3e4336dd493c1d53 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 30 Oct 2024 16:37:36 +0100
Subject: [PATCH 330/429] Bug 38303: Set item's replacement price to
 defaultreplacecost if 0.00

No idea if this is a real bug. This code is fixing the following test
failure:
t/db_dependent/Circulation/CalcFine.t .. 1/7
    #   Failed test 'Amount is calculated correctly'
    #   at t/db_dependent/Circulation/CalcFine.t line 271.
    #          got: '0'
    #     expected: '6'
    # Looks like you failed 1 test of 2.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5776f28efa094e2cdd0dd441ae58be09068cb49a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Overdues.pm | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/C4/Overdues.pm b/C4/Overdues.pm
index a2494f7f1e..7faa2e9f21 100644
--- a/C4/Overdues.pm
+++ b/C4/Overdues.pm
@@ -288,10 +288,12 @@ sub CalcFine {
         && $amount > $issuing_rule->{overduefinescap};
 
     # This must be moved to Koha::Item (see also similar code in C4::Accounts::chargelostitem
-    $item->{replacementprice} ||= $itemtype->defaultreplacecost
-      if $itemtype
-      && ( ! defined $item->{replacementprice} || $item->{replacementprice} == 0 )
-      && C4::Context->preference("useDefaultReplacementCost");
+    if (   $itemtype
+        && ( !defined $item->{replacementprice} || $item->{replacementprice} + .0 == 0 )
+        && C4::Context->preference("useDefaultReplacementCost") )
+    {
+        $item->{replacementprice} = $itemtype->defaultreplacecost;
+    }
 
     $amount = $item->{replacementprice} if ( $issuing_rule->{cap_fine_to_replacement_price} && $item->{replacementprice} && $amount > $item->{replacementprice} );
 

From 9e83ee3ca4546dd7771059ba3aeb5a2e20d0e2f8 Mon Sep 17 00:00:00 2001
From: Yanjun Li <yanjun.li@bywatersolutions.com>
Date: Thu, 24 Oct 2024 20:51:12 +0000
Subject: [PATCH 331/429] Bug 38020: Change form to submit link

To test:

1. Create a list in the staff interface
2. Go to lists
3. View the list
4. Click the Edit dropdown
5. Notice the two dropdown options for Edit list and Delete list have different formatting and look different
6. Apply patch
7. Try steps 1 through 4 again
8. The delete list button form looks right, and delete function works

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d3488291f63195ec0291009b7cb0919a5679c279)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/includes/virtualshelves-toolbar.inc        | 10 +++-------
 .../prog/en/modules/virtualshelves/shelves.tt          |  1 +
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
index 2ce1ccd503..0791f3fd41 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
@@ -16,13 +16,9 @@
             <ul class="dropdown-menu">
                 <li><a href="/cgi-bin/koha/virtualshelves/shelves.pl?op=edit_form&shelfnumber=[% shelf.shelfnumber | uri %]&referer=[% op | uri %]">Edit list</a></li>
                 <li>
-                    <form id="deleteshelf_toolbar_form" action="shelves.pl" method="post">
-                        [% INCLUDE 'csrf-token.inc' %]
-                        <input type="hidden" name="op" value="cud-delete" />
-                        <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" />
-                        <button type="submit" class="btn btn-default delete-list">Delete list</button>
-                    </form>
-                </li>
+                    [% INCLUDE 'csrf-token.inc' %]
+                    <a class="submit-form-link dropdown-item" href="#" data-shelfnumber="[% shelf.shelfnumber | html %]" data-method="post" data-action="shelves.pl" data-op="cud-delete" data-confirmation-msg="[% t('Are you sure you want to delete this list?') | html %]">Delete List</a>
+               </li>
             </ul>
         </div>
         [% END %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
index e82cf2f618..e0624410fa 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
@@ -599,6 +599,7 @@
 [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
     [% Asset.js("lib/jquery/plugins/jquery.dataTables.columnFilter.js") | $raw %]
+    [% Asset.js("js/form-submit.js") | $raw %]
     [% IF print %]
         <script>
             $( document ).ready(function() {

From 36c8be5dcd90d4115d5b64bac9ce5190ecc29e42 Mon Sep 17 00:00:00 2001
From: Yanjun Li <yanjun.li@bywatersolutions.com>
Date: Wed, 30 Oct 2024 16:21:06 +0000
Subject: [PATCH 332/429] Bug 38020: (follow-up) Remove unused delete
 confirmation code

This follow-up patch removes the unused delete list confirmation dialog in virtualshelves/shelves.tt

Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 2e8b1e2b5b8dfa922ae95c13ab5127b2d45218ae)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt    | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
index e0624410fa..d0ac9ca1c2 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/virtualshelves/shelves.tt
@@ -939,9 +939,6 @@
                     sendList();
                     return false;
                 });
-                $(".delete-list").click(function(e){
-                    return confirm(_("Are you sure you want to delete this list?"));
-                });
              });
 
         [% END %]

From 57a450319273f0e1d369ab40f7f6ca4df7c83da7 Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Thu, 31 Oct 2024 05:01:42 +0000
Subject: [PATCH 333/429] Bug 38020: (QA follow-up) Decapitilise 'list'

Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a11a866871d14e120ae9f3db3b39effdefb2a362)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
index 0791f3fd41..944476c5bc 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/virtualshelves-toolbar.inc
@@ -17,7 +17,7 @@
                 <li><a href="/cgi-bin/koha/virtualshelves/shelves.pl?op=edit_form&shelfnumber=[% shelf.shelfnumber | uri %]&referer=[% op | uri %]">Edit list</a></li>
                 <li>
                     [% INCLUDE 'csrf-token.inc' %]
-                    <a class="submit-form-link dropdown-item" href="#" data-shelfnumber="[% shelf.shelfnumber | html %]" data-method="post" data-action="shelves.pl" data-op="cud-delete" data-confirmation-msg="[% t('Are you sure you want to delete this list?') | html %]">Delete List</a>
+                    <a class="submit-form-link dropdown-item" href="#" data-shelfnumber="[% shelf.shelfnumber | html %]" data-method="post" data-action="shelves.pl" data-op="cud-delete" data-confirmation-msg="[% t('Are you sure you want to delete this list?') | html %]">Delete list</a>
                </li>
             </ul>
         </div>

From 264c2ad2f0d4d18de1d90e6030d70b7645ddcde1 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Tue, 29 Oct 2024 11:12:25 +0000
Subject: [PATCH 334/429] Bug 37872: Add tests

$ prove t/db_dependent/Koha/ILL/Request.t
$ prove t/db_dependent/Koha/ILL/Request/Config.t

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 6f3a55d2a36a37510950b1c4c179cabad79af301)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Koha/ILL/Request.t        | 20 +++++++++++++++++++-
 t/db_dependent/Koha/ILL/Request/Config.t | 17 ++++++++++++++++-
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/t/db_dependent/Koha/ILL/Request.t b/t/db_dependent/Koha/ILL/Request.t
index 5f4de8a993..4f558189e4 100755
--- a/t/db_dependent/Koha/ILL/Request.t
+++ b/t/db_dependent/Koha/ILL/Request.t
@@ -19,10 +19,12 @@
 
 use Modern::Perl;
 
-use Test::More tests => 3;
+use Test::More tests => 4;
+use Test::MockModule;
 
 use Koha::ILL::Requests;
 
+use t::lib::Mocks;
 use t::lib::TestBuilder;
 
 my $builder = t::lib::TestBuilder->new;
@@ -112,3 +114,19 @@ subtest 'get_type_disclaimer_date() tests' => sub {
 
     $schema->storage->txn_rollback;
 };
+
+subtest 'get_backend_plugin() tests' => sub {
+
+    plan tests => 1;
+
+    $schema->storage->txn_begin;
+
+    my $request = $builder->build_object( { class => 'Koha::ILL::Requests' } );
+    t::lib::Mocks::mock_config( 'enable_plugins', 0 );
+    is(
+        $request->get_backend_plugin, undef,
+        'get_backend_plugin returns undef if plugins are disabled'
+    );
+
+    $schema->storage->txn_rollback;
+};
diff --git a/t/db_dependent/Koha/ILL/Request/Config.t b/t/db_dependent/Koha/ILL/Request/Config.t
index f511726c30..2d4b60f260 100755
--- a/t/db_dependent/Koha/ILL/Request/Config.t
+++ b/t/db_dependent/Koha/ILL/Request/Config.t
@@ -23,7 +23,7 @@ use t::lib::TestBuilder;
 use Test::MockObject;
 use Test::Exception;
 
-use Test::More tests => 5;
+use Test::More tests => 6;
 
 my $schema = Koha::Database->new->schema;
 my $builder = t::lib::TestBuilder->new;
@@ -487,5 +487,20 @@ subtest 'Final tests' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'get_backend_plugin_names() tests' => sub {
+
+    plan tests => 1;
+
+    $schema->storage->txn_begin;
+
+    my $config = Koha::ILL::Request::Config->new;
+    t::lib::Mocks::mock_config( 'enable_plugins', 0 );
+    is(
+        $config->get_backend_plugin_names(), 0,
+        'get_backend_plugin_names returns empty list if plugins are disabled'
+    );
+
+    $schema->storage->txn_rollback;
+};
 
 1;

From 4dfd705f9d4a4f756c9b90823462a3c8db2be36c Mon Sep 17 00:00:00 2001
From: Andreas Jonsson <andreas.jonsson@kreablo.se>
Date: Mon, 9 Sep 2024 16:51:10 +0000
Subject: [PATCH 335/429] Bug 37872: Add guards for enable_plugins

There are ILL-backends that are not implemented as
Koha plugins and does not require plugins to be enabled.

The 'new' method in Koha::Plugins returns undefined if
plugins are disabled.  Therefore, calls to this method
must be guarded by a check that plugins actually are enabled.

Testplan for koha-testing-docker:

- Edit /etc/koha/sites/kohadev/koha-conf.xml and disable plugins by
  setting <enable_plugins>0</enable_plugins>
- sudo koha-mysql kohadev -e "UPDATE systempreferences SET value='1' WHERE variable='ILLModule';"
- Run restart_all.
- In staff interface, go to About koha -> System information. The page
  should load normally (as opposed to returning 500 internal server
  error).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5ebffcff284d95830f9f054e916aa39a05963baa)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ILL/Request/Config.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Koha/ILL/Request/Config.pm b/Koha/ILL/Request/Config.pm
index 111317804c..70f73f0ff5 100644
--- a/Koha/ILL/Request/Config.pm
+++ b/Koha/ILL/Request/Config.pm
@@ -115,6 +115,7 @@ Returns a list of names for all the installed ILL backend plugins.
 sub get_backend_plugin_names {
     my ( $self ) = @_;
 
+    return () unless C4::Context->config("enable_plugins");
     my @backend_plugins = Koha::Plugins->new()->GetPlugins(
         {
             method => 'ill_backend',

From fde180efbf1fd79e9af8f866ad230560f40d608a Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Tue, 29 Oct 2024 11:12:04 +0000
Subject: [PATCH 336/429] Bug 37872: Consider enable_plugins = 0 when fetching
 plugin backends

This patch builds on top of Andreas patch checking for Koha::Plugins->new() instead as the enable_plugins is already done there.
It also adds a check in reply to Joubu's observation, which is indeed an additional bug.

Test plan:
1) Apply tests patch. Run the tests. Notice it fails
2) Apply the remaining patches. Run the tests. Notice they pass.

Additional (and better) tests can be written, but for now I think this is good enough.
Additional tests should be written on top of bug 36197 (which adds a dummy ILL backend plugin for testing), at:
https://bugs.koha-community.org/bugzilla3/page.cgi?id=splinter.html&bug=36197&attachment=171371

Signed-off-by: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7e336006c27e6747b7c0b92b23eea95be4946e82)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/ILL/Request.pm        |  9 ++++++---
 Koha/ILL/Request/Config.pm | 10 ++++++----
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/Koha/ILL/Request.pm b/Koha/ILL/Request.pm
index beb185e078..6290d5be20 100644
--- a/Koha/ILL/Request.pm
+++ b/Koha/ILL/Request.pm
@@ -401,20 +401,23 @@ sub status {
 
     my $backend_plugin = $self->get_backend_plugin($backend_name);
 
-Returns the installed I<Koha::Plugin> corresponding to the given backend_id
+Returns the installed I<Koha::Plugin> corresponding to the given backend_id or undef if no plugin is found
 
 =cut
 
 sub get_backend_plugin {
     my ( $self, $backend_id ) = @_;
 
-    my @backend_plugins = Koha::Plugins->new()->GetPlugins(
+    my $koha_plugins    = Koha::Plugins->new();
+    my @backend_plugins = $koha_plugins
+        ? Koha::Plugins->new()->GetPlugins(
         {
             method   => 'ill_backend',
             metadata => { name => $backend_id },
             all      => 1,
         }
-    );
+        )
+        : ();
 
     return $backend_plugins[0];
 }
diff --git a/Koha/ILL/Request/Config.pm b/Koha/ILL/Request/Config.pm
index 70f73f0ff5..815f63309f 100644
--- a/Koha/ILL/Request/Config.pm
+++ b/Koha/ILL/Request/Config.pm
@@ -113,15 +113,17 @@ Returns a list of names for all the installed ILL backend plugins.
 =cut
 
 sub get_backend_plugin_names {
-    my ( $self ) = @_;
+    my ($self) = @_;
 
-    return () unless C4::Context->config("enable_plugins");
-    my @backend_plugins = Koha::Plugins->new()->GetPlugins(
+    my $koha_plugins    = Koha::Plugins->new();
+    my @backend_plugins = $koha_plugins
+        ? $koha_plugins->GetPlugins(
         {
             method => 'ill_backend',
             all    => 1
         }
-    );
+        )
+        : ();
 
     return map { $_->{metadata}->{name} } @backend_plugins;
 }

From 629d2a6f6ca168b9d434017cd785e10dc281f3e7 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Tue, 20 Aug 2024 00:12:21 +0000
Subject: [PATCH 337/429] Bug 22223: Add filter to make item URLs safe in
 template output

This change adds a "safe_url" filter which takes a text input and
returns a Perl URL object which stringifies to a safe URL.

This change is only needed in the OPAC as the staff interface
handles the item URL display using Javascript not Template Toolkit.

0. Apply patch and koha-plack --restart kohadev
1. Create an item for a record using the following URL
https://koha-community.org?url=https%3A%2F%2Fkoha-community.org
2. Go to the OPAC for that record and verify that the URL is
not double-escaped
3. Create a malicious payload (talk to QA/security team for this if necessary)
4. Note that the malicious payload is escaped
5. prove t/Koha/Plugins/SafeURL.t
6. Celebrate!

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e8e00cfeb30391009391849c0194a0ff0603d78c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/Template/Plugin/SafeURL.pm               | 66 +++++++++++++++++++
 .../bootstrap/en/modules/opac-detail.tt       |  5 +-
 t/Koha/Plugins/SafeURL.t                      | 57 ++++++++++++++++
 3 files changed, 126 insertions(+), 2 deletions(-)
 create mode 100644 Koha/Template/Plugin/SafeURL.pm
 create mode 100755 t/Koha/Plugins/SafeURL.t

diff --git a/Koha/Template/Plugin/SafeURL.pm b/Koha/Template/Plugin/SafeURL.pm
new file mode 100644
index 0000000000..0a8847dd64
--- /dev/null
+++ b/Koha/Template/Plugin/SafeURL.pm
@@ -0,0 +1,66 @@
+package Koha::Template::Plugin::SafeURL;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use URI;
+use base 'Template::Plugin::Filter';
+
+sub init {
+    my $self = shift;
+    my $name = 'safe_url';
+    $self->{_DYNAMIC} = 1;
+    $self->install_filter($name);
+    return $self;
+}
+
+sub filter {
+    my ( $self, $text, $args, $config ) = @_;
+    my $uri = URI->new($text);
+    if ($uri) {
+        return $uri;
+    }
+
+    #NOTE: If it isn't a real URL, just return a safe fragment
+    return '#';
+}
+
+1;
+
+=head1 NAME
+
+Koha::Template::Plugin::SafeURL - TT Plugin for filtering whole URLs
+
+=head1 SYNOPSIS
+
+[% USE SafeURL %]
+
+[% $url | safe_url %]
+
+This filter parses the text as a URL and returns the object which
+is stringified in a safe format.
+
+=head1 METHODS
+
+=head2 init
+
+This method installs the filter name and declares it as a dynamic filter
+
+=head2 filter
+
+Returns a stringified version of a Perl URL object
+
+=cut
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
index 7dd44416f5..f88db51c2f 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
@@ -7,6 +7,7 @@
 [% USE TablesSettings %]
 [% USE AuthorisedValues %]
 [% USE KohaPlugins %]
+[% USE SafeURL %]
 [% SET TagsShowEnabled = ( ( Koha.Preference( 'TagsEnabled' ) == 1 ) && TagsShowOnDetail ) %]
 [% SET TagsInputEnabled = ( ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( Koha.Preference( 'TagsEnabled' ) == 1 ) && TagsInputOnDetail ) %]
 [% SET CoverImagePlugins = KohaPlugins.get_plugins_opac_cover_images %]
@@ -1362,14 +1363,14 @@
                                 [% IF trackclicks == 'track' || trackclicks == 'anonymous' %]
                                     <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="/cgi-bin/koha/tracklinks.pl?uri=[% uri | uri %]&biblionumber=[% biblio.biblionumber | html %]&itemnumber=[% ITEM_RESULT.itemnumber | html %]" property="url">[% uri | html %]</a>
                                 [% ELSE %]
-                                    <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% uri | url %]" property="url">[% uri | html %]</a>
+                                    <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% uri | safe_url %]" property="url">[% uri | html %]</a>
                                 [% END %]
                             [% END %]
                         [% ELSE %]
                             [% IF trackclicks == 'track' || trackclicks == 'anonymous' %]
                                 <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="/cgi-bin/koha/tracklinks.pl?uri=[% ITEM_RESULT.uri | uri %]&biblionumber=[% biblio.biblionumber | html %]&itemnumber=[% ITEM_RESULT.itemnumber | html %]" property="url">
                             [% ELSE %]
-                                <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% ITEM_RESULT.uri | url %]" property="url">
+                                <a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% ITEM_RESULT.uri | safe_url %]" property="url">
                             [% END %]
                                 [% IF Koha.Preference('URLLinkText') %][% Koha.Preference('URLLinkText') | html %][% ELSE %]Link to resource[% END %]
                             </a>
diff --git a/t/Koha/Plugins/SafeURL.t b/t/Koha/Plugins/SafeURL.t
new file mode 100755
index 0000000000..ae0cf40d05
--- /dev/null
+++ b/t/Koha/Plugins/SafeURL.t
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use Test::More tests => 2;
+use Template;
+
+subtest 'test double-encoding prevention' => sub {
+    plan tests => 1;
+    my $template = Template->new(
+        {
+            PLUGIN_BASE => 'Koha::Template::Plugin',
+        }
+    );
+
+    my $tt = <<EOF;
+[%- USE SafeURL %]
+[%- 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org' | safe_url -%]
+EOF
+
+    my $output;
+    $template->process( \$tt, {}, \$output );
+    is( $output, 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org', 'URL is not double-encoded' );
+};
+
+subtest 'double quotes are escaped' => sub {
+    plan tests => 1;
+    my $template = Template->new(
+        {
+            PLUGIN_BASE => 'Koha::Template::Plugin',
+        }
+    );
+
+    my $tt = <<EOF;
+[%- USE SafeURL %]
+[%- 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org"' | safe_url -%]
+EOF
+
+    my $output;
+    $template->process( \$tt, {}, \$output );
+    is( $output, 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org%22', 'double quotes are escaped' );
+};

From 4023df1789db4e31a5bd1d03658d9d10a077399b Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Thu, 7 Nov 2024 01:37:14 +0000
Subject: [PATCH 338/429] Bug 22223: t::lib::QA::TemplateFilters missing
 built-in TT filter "safe_url" causes false warnings

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit fe495d19dfc138fc99f7c77e41a33c77521cb738)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/lib/QA/TemplateFilters.pm | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/t/lib/QA/TemplateFilters.pm b/t/lib/QA/TemplateFilters.pm
index cc8c07bd79..66ed70f92f 100644
--- a/t/lib/QA/TemplateFilters.pm
+++ b/t/lib/QA/TemplateFilters.pm
@@ -160,6 +160,9 @@ sub process_tt_block {
         # Already has trim filter
         or $tt_block =~ m{\|\s?trim}
 
+        # Already has safe_url filter
+        or $tt_block =~ m{\|\s?safe_url}
+
         # Specific for [% foo UNLESS bar %]
         or $tt_block =~ m{^(?<before>\S+)\s+UNLESS\s+(?<after>\S+)}
     ;

From eada390b354e8b5767daf82471a95fb165720659 Mon Sep 17 00:00:00 2001
From: Lari Strand <lari.strand@koha-suomi.fi>
Date: Fri, 13 Sep 2024 13:49:30 +0300
Subject: [PATCH 339/429] Bug 35987: Fix broken See highlighted items below
 link

Signed-off-by: Magnus Enger <magnus@libriotech.no>
Test plan with KTD:
- Issue a couple of items to a patron, and make sure one of them is
  overdue.
- Make sure "Always show checkouts automatically" is not checked
- Reload the page
- Click on the link in "Overdues: Patron has ITEMS OVERDUE See
  highlighted items below"
- Nothing happens
- Apply the patch, restart_all, reload the "Check out" page for the
  patron
- Click on the link again, and the active loans should be shown

Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4716b6a43d2eaa82f966e79ab937badac7da3ec3)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc
index ea611abdce..9d373d1c26 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc
@@ -101,7 +101,7 @@
 
         [% IF ( odues ) %]
             <li class="odues blocker">
-                <span class="circ-hlt">Overdues:</span>  Patron has ITEMS OVERDUE <a href="#checkouts">See highlighted items below</a>
+                <span class="circ-hlt">Overdues:</span>  Patron has ITEMS OVERDUE <a href="#checkouts_panel" onclick="$('#issues-table-load-immediately').click(); $('#issues-table-load-immediately').click();">See highlighted items below </a>
             </li>
         [% END %]
 

From 5c45839b5b4cd51a2f865d95abbaff37c10eacb2 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Thu, 31 Oct 2024 14:41:19 +0100
Subject: [PATCH 340/429] Bug 30397: Remove duplicate entry from length menu of
 patrons search

If you have 20 in PatronsPerPage, there will be two 20 entries in the
length menu of the patrons search.
jQuery.unique is stupid it seems, it requires the array to be sorted (?)

let x = [1, 2, 1, 2]
jQuery.unique(x);
Array(4) [ 1, 2, 1, 2 ]

x = [1, 1, 2, 2]
jQuery.unique(x);
Array [ 1, 2 ]

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 60249a979cdf14f78c4d0a777a8f9f536272aa29)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
index 5f73e30f40..74ec8d5a4f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
@@ -316,9 +316,8 @@
 
             // Build the aLengthMenu
             let aLengthMenu = [
-                [% Koha.Preference('PatronsPerPage') | html %], 10, 20, 50, 100, -1
+                ...new Set([[% Koha.Preference('PatronsPerPage') | html %], 10, 20, 50, 100, -1])
             ];
-            jQuery.unique(aLengthMenu);
             aLengthMenu.sort(function( a, b ){
                 // Put "All" at the end
                 if ( a == -1 ) {

From dd45949443ecc8c04f78f91b2f90e90ab8a12c12 Mon Sep 17 00:00:00 2001
From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Date: Fri, 4 Oct 2024 09:04:18 +0000
Subject: [PATCH 341/429] Bug 37365: (QA follow-up) Add borrowernumber to
 referrer url

Since we post to add_message, we need to ensure that the
borrowernumber is added to the referrer url. This 'tric' is
already done there for another form.

Test plan:
See original plan.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit fbc2e2e9333fbdd61a744297e36a6752106c1977)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/add_message.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/circ/add_message.pl b/circ/add_message.pl
index 08cf1e4d9f..aa59914556 100755
--- a/circ/add_message.pl
+++ b/circ/add_message.pl
@@ -97,7 +97,7 @@ if ( $op eq 'cud-edit_message' && $message_id ) {
 
 my $url = $input->referer;
 if ($url) {
-    if ( $url =~ m|circulation\.pl$| ) {
+    if ( $url =~ m/(circulation\.pl|members\/files\.pl)$/ ) {
 
         # Trick for POST form from batch checkouts
         $url .= "?borrowernumber=$borrowernumber";

From c8cab299195bea2039068258018bd2685013be64 Mon Sep 17 00:00:00 2001
From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Date: Fri, 4 Oct 2024 09:11:47 +0000
Subject: [PATCH 342/429] Bug 37365: (QA follow-up) Add error checking around
 AddFile call

Test plan:
Add a return; in AddFile to simulate a failing db insert.
Verify that an alert pops up.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 35d90969f14bd55fb77acbbafbf0f9a23a2b2d18)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/members/files.tt | 1 +
 members/files.pl                                         | 7 +++----
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/files.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/files.tt
index d3a887fa38..5f0d582387 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/files.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/files.tt
@@ -47,6 +47,7 @@
                     <div class="dialog alert">
                         [% IF errors.empty_upload %]The file you are attempting to upload has no contents.[% END %]
                         [% IF errors.no_file %]You did not select a file to upload.[% END %]
+                        [% IF errors.upload_failed %]The upload failed. More information may be in the system logs.[% END %]
                     </div>
                 [% END %]
 
diff --git a/members/files.pl b/members/files.pl
index c09658a2d2..e60a9a7d2a 100755
--- a/members/files.pl
+++ b/members/files.pl
@@ -87,12 +87,13 @@ else {
                     $file_content .= $_;
                 }
 
-                $bf->AddFile(
+                my $rv = $bf->AddFile(
                     name    => $filename,
                     type    => $mimetype,
                     content => $file_content,
                     description => scalar $cgi->param('description'),
                 );
+                $errors{upload_failed} = 1 unless $rv;
             }
         }
         else {
@@ -103,9 +104,7 @@ else {
     }
 
     $template->param(
-        files => Koha::Patron::Files->new( borrowernumber => $borrowernumber )
-          ->GetFilesInfo(),
-
+        files => Koha::Patron::Files->new( borrowernumber => $borrowernumber )->GetFilesInfo(),
         errors => \%errors,
     );
     output_html_with_http_headers $cgi, $cookie, $template->output;

From f7402371c0cf25786c3cb5345ce2093eacc3a0bd Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 1 Aug 2024 17:37:35 +0000
Subject: [PATCH 343/429] Bug 37550: Move item check after error handling

To test:
1 - Grab the sample file on this bug report
2 - perl misc/migration_tools/bulkmarcimport.pl -b --file=spaceditems.pl -v
3 - An exception for 'Duplicate ID' is thrown and script dies
4 - Apply patch
5 - run script again
6 - The script finishes, no items are added, but record is

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5e15ede505b413b46f36fe428c8e87e5c8217ae4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index efcac9589f..4a8cb041b9 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -613,7 +613,6 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                         AddItemBatchFromMarc( $record, $record_id, $biblioitemnumber, $framework );
                 };
                 my $error_adding = $@;
-                $record_has_added_items = @{$itemnumbers_ref};
 
                 if ($error_adding) {
                     warn "ERROR: Adding items to bib $record_id failed: $error_adding";
@@ -623,6 +622,9 @@ RECORD: foreach my $record ( @{$marc_records} ) {
                     # the MARC columns in biblioitems were not set.
                     next RECORD;
                 }
+
+                $record_has_added_items = @{$itemnumbers_ref};
+
                 if ( $dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' }
                     @$errors_ref )
                 {

From ad83fffffd21a1e9a0d17373408d3d4f9eda6e40 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 1 Aug 2024 18:00:57 +0000
Subject: [PATCH 344/429] Bug 37550: Unit test for CheckItemPreSave

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5cab218d91cae9088cf043917cdd00c22574a089)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Items.t | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/t/db_dependent/Items.t b/t/db_dependent/Items.t
index f6451c6f4e..48315d6288 100755
--- a/t/db_dependent/Items.t
+++ b/t/db_dependent/Items.t
@@ -20,7 +20,7 @@ use Data::Dumper;
 
 use MARC::Record;
 use C4::Items
-    qw( ModItemTransfer SearchItems AddItemFromMarc ModItemFromMarc get_hostitemnumbers_of Item2Marc ModDateLastSeen CartToShelf );
+    qw( ModItemTransfer SearchItems AddItemFromMarc ModItemFromMarc get_hostitemnumbers_of Item2Marc ModDateLastSeen CartToShelf CheckItemPreSave );
 use C4::Biblio qw( GetMarcFromKohaField AddBiblio );
 use C4::Circulation qw( AddIssue );
 use Koha::BackgroundJobs;
@@ -36,7 +36,7 @@ use Koha::AuthorisedValues;
 use t::lib::Mocks;
 use t::lib::TestBuilder;
 
-use Test::More tests => 13;
+use Test::More tests => 14;
 
 use Test::Warn;
 
@@ -129,6 +129,28 @@ subtest 'General Add, Get and Del tests' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'CheckItemPreSave tests' => sub {
+    plan tests => 1;
+
+    $schema->storage->txn_begin;
+
+    my $builder = t::lib::TestBuilder->new;
+    my $item    = Koha::Items->find( { barcode => 'SPAGHETTI' } );
+    $item = $builder->build_sample_item( { barcode => "SPAGHETTI" } ) unless $item;
+
+    my $check_item = $item->unblessed;
+    delete $check_item->{itemnumber};
+    $check_item->{barcode} = " SPAGHETTI";
+
+    my %errors = CheckItemPreSave($check_item);
+    is_deeply(
+        \%errors, { duplicate_barcode => " SPAGHETTI" },
+        "We get a duplicate item error for barcode even if whitespace"
+    );
+
+    $schema->storage->txn_rollback;
+};
+
 subtest 'ModItemTransfer tests' => sub {
     plan tests => 8;
 

From 7d4399a99fc735c9a045105e81c164abfe456a37 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 1 Aug 2024 18:01:25 +0000
Subject: [PATCH 345/429] Bug 37550: CheckItemPreSave should run barcodes
 through barcodedecode

In Koha::Item we run a barcode through barcodedecode before any save.
We should do the same when checking barcodes to avoid a duplicate error
when the barcode is cleaned before it is written to the DB

To test:
1 - Follow previous test plan
2 - Note that after this patch is applied there is no exception
    All items are skipped as duplicate barcodes

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a262ffbdfa2adf54f36774547fd95c30b83ed8c6)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/Items.pm | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/C4/Items.pm b/C4/Items.pm
index 3041d8f13e..a91279b665 100644
--- a/C4/Items.pm
+++ b/C4/Items.pm
@@ -45,6 +45,7 @@ BEGIN {
 }
 
 use Carp qw( croak );
+use C4::Circulation qw( barcodedecode );
 use C4::Context;
 use C4::Koha;
 use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
@@ -459,11 +460,15 @@ sub CheckItemPreSave {
     my %errors = ();
 
     # check for duplicate barcode
-    if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
-        my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
+    if ( exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'} ) {
+        my $barcode       = C4::Circulation::barcodedecode( $item_ref->{'barcode'} );
+        my $existing_item = Koha::Items->find( { barcode => $barcode } );
         if ($existing_item) {
-            if (!exists $item_ref->{'itemnumber'}                       # new item
-                or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
+            if (
+                !exists $item_ref->{'itemnumber'}    # new item
+                or $item_ref->{'itemnumber'} != $existing_item->itemnumber
+                )
+            {                                        # existing item
                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
             }
         }

From 1be8a9eedd80ebeecaf89698fe3405ecd5d517b4 Mon Sep 17 00:00:00 2001
From: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Date: Tue, 20 Aug 2024 12:38:38 +0200
Subject: [PATCH 346/429] Bug 37684: Direct link to expired news are broken

When using a direct link to a news (koha.url/cgi-bin/koha/opac-main.pl?news_id=XXX), the link is broken if the news is expired.

Formerly, a using AdditionalContent->get( id => "my_news_id") on an
expired news was returning a news, and calling get without id was
returning all news but the expired ones.

This patch adds tests to check this behaviour by adding one expired news and performing following new tests:
1 - It may not be returned by AdditionalContent.get()
2 - It must be returned by AdditionalContent.get() using its id

This patch fixes the behaviour by addind the new behaviour or
AdditionalContent.get:
1 - Any news must be returned by AdditionalContent.get() using its id;

TEST PLAN:
1 - Apply patch
2 - Remove the changes made to Koha/AdditionalContents.pm
3 - Run tests -> one test must fail
4 - Create a news with a expired expiration date, notice the id of the
  news in the url of the modification panel
5 - Go to "opac.url/cgi-bin/koha/opac-main.pl?news_id=MY_ID" -> notice
  it does not work
6 - Reapply the whole patch
7 - Run test -> all test must pass
8 - Go to "opac.url/cgi-bin/koha/opac-main.pl?news_id=MY_ID" -> notice
  it does work now

Signed-off-by: Olivier V <olivier.vezina@inLibro.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4ad5cbdb9708774e91a198e5e9a04d548a4503a7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/AdditionalContents.pm                    | 10 +++---
 .../Template/Plugin/AdditionalContents.t      | 31 ++++++++++++++++++-
 2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/Koha/AdditionalContents.pm b/Koha/AdditionalContents.pm
index 215e6247fc..f434fba1a0 100644
--- a/Koha/AdditionalContents.pm
+++ b/Koha/AdditionalContents.pm
@@ -84,11 +84,11 @@ sub search_for_display {
     $search_params->{'additional_content.id'} = $params->{id} if $params->{id};
     $search_params->{location}                = $params->{location};
     $search_params->{branchcode}              = $params->{library_id} ? [ $params->{library_id}, undef ] : undef;
-    $search_params->{published_on}            = { '<=' => \'CAST(NOW() AS DATE)' };
-    $search_params->{expirationdate}          = [ '-or', { '>=' => \'CAST(NOW() AS DATE)' }, undef ];
-    $search_params->{category}                = $params->{category} if $params->{category};
-    $search_params->{lang}                    = 'default' if !$lang || $lang eq 'default';
-    $search_params->{-or}                     = [ { 'lang' => $lang }, '-and' => [ 'lang', 'default', \$subquery ] ]
+    $search_params->{published_on}   = { '<=' => \'CAST(NOW() AS DATE)' }                   unless $params->{id};
+    $search_params->{expirationdate} = [ '-or', { '>=' => \'CAST(NOW() AS DATE)' }, undef ] unless $params->{id};
+    $search_params->{category}       = $params->{category} if $params->{category};
+    $search_params->{lang}           = 'default'           if !$lang || $lang eq 'default';
+    $search_params->{-or}            = [ { 'lang' => $lang }, '-and' => [ 'lang', 'default', \$subquery ] ]
         if !$search_params->{lang};
 
     my $attribs = { prefetch => 'additional_content', order_by => 'additional_content.number' };
diff --git a/t/db_dependent/Template/Plugin/AdditionalContents.t b/t/db_dependent/Template/Plugin/AdditionalContents.t
index 0a3226dc86..95c519f0f0 100755
--- a/t/db_dependent/Template/Plugin/AdditionalContents.t
+++ b/t/db_dependent/Template/Plugin/AdditionalContents.t
@@ -33,7 +33,7 @@ my $builder = t::lib::TestBuilder->new;
 $schema->storage->txn_begin;
 
 subtest 'get' => sub {
-    plan tests => 4;
+    plan tests => 6;
 
     my $additional_contents = Koha::Template::Plugin::AdditionalContents->get(
         { category => 'news', location => [ 'opac_only', 'staff_and_opac' ], lang => 'default', library => '%' } );
@@ -61,6 +61,35 @@ subtest 'get' => sub {
         { category => 'news', location => [ 'opac_only', 'staff_and_opac' ], lang => 'default' } );
     is( $additional_contents->{content}->count, $before_count + 1, "We get the additional one we added" );
 
+    my $expired_additional_content = $builder->build_object(
+        {
+            class => 'Koha::AdditionalContents',
+            value => {
+                category       => 'news',
+                location       => 'opac_only',
+                branchcode     => undef,
+                expirationdate => '0001-01-01',
+            }
+        }
+    );
+    $builder->build_object(
+        {
+            class => 'Koha::AdditionalContentsLocalizations',
+            value => {
+                additional_content_id => $expired_additional_content->id,
+                lang                  => 'default',
+            }
+        }
+    );
+    $additional_contents = Koha::Template::Plugin::AdditionalContents->get(
+        { category => 'news', location => [ 'opac_only', 'staff_and_opac' ], lang => 'default' } );
+
+    is( $additional_contents->{content}->count, $before_count + 1, "We do not get the expired content" );
+
+    $additional_contents = Koha::Template::Plugin::AdditionalContents->get(
+        { category => 'news', location => [ 'opac_only', 'staff_and_opac' ], id => $expired_additional_content->id } );
+    is( $additional_contents->{content}->count, 1, "We still get expired news through direct link" );
+
     $additional_contents = Koha::Template::Plugin::AdditionalContents->get(
         {
             category   => 'news', location => [ 'opac_only', 'staff_and_opac' ], lang => 'default',

From 71900a95087091f06f191a30118e49245bad1fd9 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Tue, 29 Oct 2024 15:16:42 +0000
Subject: [PATCH 347/429] Bug 37912: Restore detail-trigger handling

This patch converts our jQuery based click to native JS and restores the
bootstrap modal button click trigger handling.

Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 93be319372e13890a4cb426904aed682c8eedba6)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/js/modals/display_ticket.js          | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js b/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
index 023b1a0b3a..bd9f07a4f9 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
@@ -1,11 +1,20 @@
 /* keep tidy */
 $(document).ready(function () {
-    $("#table_concerns").on("click", ".detail-trigger", function () {
-        // Find the main trigger anchor within the same table row
-        var $mainTrigger = $(this).closest("tr").find(".main-trigger");
+    document.addEventListener("click", function (event) {
+        const detailTrigger = event.target.closest(".detail-trigger");
+        if (detailTrigger) {
+            event.preventDefault();
 
-        // Trigger the click event of the main trigger anchor
-        $mainTrigger.trigger("click");
+            // Find the main trigger button in the same row
+            const mainTrigger = detailTrigger
+                .closest("tr")
+                .querySelector(".main-trigger");
+
+            // Trigger a click on the main trigger if it exists
+            if (mainTrigger) {
+                mainTrigger.click();
+            }
+        }
     });
 
     $("#ticketDetailsModal").on("show.bs.modal", function (event) {

From 291b4c074bded0533c6c70e0b74bc133fa572b60 Mon Sep 17 00:00:00 2001
From: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Date: Thu, 24 Oct 2024 14:57:46 +0300
Subject: [PATCH 348/429] Bug 38257: Fix cart pop up functionalities

Almost all functionalities in cart pop up were broken
due changes made in bug 37033. Also following errors
were displayed in browsers console:

> Uncaught TypeError: antiClickjack is null
> Uncaught TypeError: $(...).dataTable is not a function

This happens because when 'intranet-bottom.inc' is loaded
after other .inc files in page.

To test:
1. Search for items and add one or more search results to your cart.
2. Open cart.
3. Attempt to use one or more functionalities (download, remove item etc.).
=> Either nothing happens or table is loaded again with no items in
table. However items are still displayed in cart when it's opened again.
4. Apply this patch, restart services if needed.
5. Again attempt to download, remove items, print etc. from cart
=> Everything should now work as expected.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 9cae91c7da4c429350584dd394282be9d01e79da)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
index efd48a74d9..07cf2a9c5a 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
@@ -355,6 +355,7 @@
         </div> [% # /.row %]
     </div> [% # /.main.container-fluid %]
 
+    [% INCLUDE 'intranet-bottom.inc' popup_window=1 %]
     [% INCLUDE js_includes.inc %]
     [% INCLUDE 'datatables.inc' %]
     [% Asset.js("js/cart.js") | $raw %]

From fda6397bc631dbf78ad6686a09dd48cb2ecbf28b Mon Sep 17 00:00:00 2001
From: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Date: Mon, 28 Oct 2024 12:03:26 +0200
Subject: [PATCH 349/429] Bug 38257: Do not use js_includes.inc in two
 different files

Downloading cart didn't work because js_includes.inc file
was included both in basket.tt and intranet-bottom.inc.
This patch removes related codeline from basket.tt.

To test:
1. Search for items and add one or more search results to your cart.
2. Open cart.
3. Attempt to download items from your cart.
=> Either nothing happens.
4. Apply this patch, restart services if needed.
5. Again attempt to download.
=> Download menu should now work as expected.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f8e20489134d5b865b931c0db481dbbcb0382cf1)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
index 07cf2a9c5a..2c0a4e02fd 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt
@@ -356,7 +356,6 @@
     </div> [% # /.main.container-fluid %]
 
     [% INCLUDE 'intranet-bottom.inc' popup_window=1 %]
-    [% INCLUDE js_includes.inc %]
     [% INCLUDE 'datatables.inc' %]
     [% Asset.js("js/cart.js") | $raw %]
 

From 783047fb08123d877b5fc5195a751f8f65bdf73a Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Thu, 31 Oct 2024 19:44:35 -0700
Subject: [PATCH 350/429] Bug 38309: Cannot delete additional fields

Deleting an additional field tries to do a GET of ?op=delete... which no
longer works with CSRF protection. It needs to be a POST with op=cud-delete.

Test plan:
 1. Administration - Additional fields, click any table name
 2. New field, fill in a name and Save
 3. In the row for your field, click Delete
 4. You have a blank page
 5. Apply patch, click the browser Back button, refresh the page
 6. In the row for your undeleted field, click Delete
 7. Your field is deleted

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d5fbb1e87c6964b27b7d9829ca41e40f49869009)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/admin/additional-fields.tt            | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
index 3a9e5fdfe3..0cb319a4fe 100755
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
@@ -1,5 +1,6 @@
 [% USE AuthorisedValues %]
 [% USE raw %]
+[% USE Asset %]
 [% PROCESS 'i18n.inc' %]
 [% SET footerjs = 1 %]
 [% INCLUDE 'doc-head-open.inc' %]
@@ -180,7 +181,7 @@
                                 [% END %]
                                 <td class="actions">
                                     <a class="btn btn-default btn-xs" href="?op=add_form&field_id=[% field.id | html %]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Edit</a>
-                                    <a class="confirmdelete btn btn-default btn-xs" href="?op=delete&field_id=[% field.id | uri %]&tablename=[% tablename | uri %]"><i class="fa fa-trash-can"></i> Delete</a>
+                                    <a class="submit-form-link btn btn-default btn-xs" href="#" data-action="additional-fields.pl" data-method="post" data-op="cud-delete" data-field_id="[% field.id | html %]" data-tablename="[% tablename | html %]" data-confirmation-msg="[% t('Are you sure you want to delete this field?') | html %]"><i class="fa fa-trash-can"></i> Delete</a>
                                 </td>
                             </tr>
                         [% END %]
@@ -272,6 +273,7 @@
 
 [% MACRO jsinclude BLOCK %]
     [% INCLUDE "datatables.inc" %]
+    [% Asset.js("js/form-submit.js") | $raw %]
     <script>
         $(document).ready(function(){
             $('#selecttable').find(":submit").hide();
@@ -288,10 +290,6 @@
                 "order": [[ 0, "asc" ]],
             }));
 
-            $(".confirmdelete").click(function(){
-                return confirm(_("Are you sure you want to delete this field?"));
-            });
-
             $("#add_field").on('submit', function() {
                 if ( $("#marcfield").length && $("select[name='authorised_value_category']").length ) {
                     if ( $("#marcfield").val().length > 0

From a60bcd9065b0f1cad76f1065c11899c872a76d72 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 1 Nov 2024 09:13:50 -0700
Subject: [PATCH 351/429] Bug 38325: Cannot delete invoice while viewing it

The link to delete while viewing an individual invoice needs to be updated to
use submit-form-link so it can POST with op='cud-delete'.

Test plan:
 1. Home - Acquisitions, click Search with an empty Vendor: input
 2. Click Receive shipments, enter a number in the Vendor invoice input,
    click Next
 3. In the left sidebar, click Invoices, click Search without any parameters
 4. In the row for your invoice, click the invoice number link
 5. Next to the Save button, click the Delete link, click OK in the
    confirmation alert
 6. You'll see the page reload, and the URL change to include ?op=delete,
    but your invoice remains undeleted.
 7. Apply patch, reload the page
 8. Click Delete, click OK in the confirmation alert
 9. Click Search with no parameters, just to make sure the message about
    "Sorry, but there are no results for your search" really truly means
    that your invoice was deleted and now you don't have any invoices

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f9bfb4d0ca279909398dc1501469ca2625f6c6ac)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt
index e4e2dd561c..886562e386 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt
@@ -141,7 +141,7 @@
                             <input type="hidden" name="invoiceid" value="[% invoiceid | html %]" />
                             <input type="submit" class="btn btn-primary" value="Save" />
                             [% IF CAN_user_acquisition_delete_invoices AND NOT orders_loop.size %]
-                                <a href="invoice.pl?op=delete&invoiceid=[% invoiceid | uri %]" id="delete">Delete</a>
+                                <a class="submit-form-link" href="#" data-action="invoice.pl" data-method="post" data-op="cud-delete" data-invoiceid="[% invoiceid | html %]" data-confirmation-msg="[% t('Are you sure you want to delete this invoice?') | html %]" id="delete">Delete</a>
                             [% END %]
                         </fieldset>
                     [% END %]
@@ -558,9 +558,6 @@
         }
 
         $(document).ready(function() {
-            $("#delete").click(function(){
-                return confirmDelete(_("Are you sure you want to delete this invoice?"));
-            });
             $("#orderst").dataTable($.extend(true, {}, dataTablesDefaults, {
                 "info": false,
                 "paging": false,

From c2606618c03edcac7e3356d757fd74f320df348d Mon Sep 17 00:00:00 2001
From: Lari Taskula <lari.taskula@hypernova.fi>
Date: Wed, 2 Oct 2024 18:44:27 +0000
Subject: [PATCH 352/429] Bug 35126: Remove onload from
 opac-addbybiblionumber.pl

To test:

Case a) Adding to a new list

1. Enable virtualshelves system preference
2. Search for any record and click it
3. On the right hand side of record view, click "Save to your lists"
4. A new pop up opens.
5. Under "Add to a new list", give any name to the list
6. Under "Add to a new list", click "Save"
7. Observe popup closing
8. Observe record view page reloading

Case b) Adding to an existing list

1. Enable virtualshelves system preference
2. Search for any record and click it
3. On the right hand side of record view, click "Save to your lists"
4. A new pop up opens.
5. Under "Add to a list", select an existing list
(it can be the same as the one you created in case a, it does not matter here)
6. Under "Add to a list", click "Save"
7. Observe popup closing
8. Unlike case a, observe record view page NOT reloading

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8cfce49d14ec38ff0fb053b0051391d55a9a341a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../en/modules/opac-addbybiblionumber.tt        | 13 ++++++++++++-
 opac/opac-addbybiblionumber.pl                  | 17 +++++++----------
 2 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-addbybiblionumber.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-addbybiblionumber.tt
index 7786a1097f..f562dfa0b4 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-addbybiblionumber.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-addbybiblionumber.tt
@@ -117,4 +117,15 @@
     </div> <!-- / .main -->
 
 [% INCLUDE 'opac-bottom.inc' is_popup=1 %]
-[% BLOCK jsinclude %][% END %]
+[% BLOCK jsinclude %]
+<script>
+         $(document).ready(function() {
+            [% IF WINDOW_PARENT_RELOAD %]
+                opener.location.reload();
+            [% END %]
+            [% IF WINDOW_CLOSE %]
+                window.close();
+            [% END %]
+        });
+</script>
+[% END %]
diff --git a/opac/opac-addbybiblionumber.pl b/opac/opac-addbybiblionumber.pl
index 1916c26e9e..3ea34220fd 100755
--- a/opac/opac-addbybiblionumber.pl
+++ b/opac/opac-addbybiblionumber.pl
@@ -70,12 +70,11 @@ if( $op && $op !~ /^cud-/ ) {
             for my $biblionumber (@biblionumbers) {
                 $shelf->add_biblio( $biblionumber, $loggedinuser );
             }
-
-            #Reload the page where you came from
-            print $query->header;
-            print "<html><meta http-equiv=\"refresh\" content=\"0\" /><body onload=\"window.opener.location.reload(true);self.close();\"></body></html>";
-            exit;
         }
+        $template->param(
+            WINDOW_CLOSE         => 1,
+            WINDOW_PARENT_RELOAD => 1,
+        );
     }
 } elsif ($shelfnumber) {
     my $shelfnumber = $query->param('shelfnumber');
@@ -84,11 +83,9 @@ if( $op && $op !~ /^cud-/ ) {
         for my $biblionumber (@biblionumbers) {
             $shelf->add_biblio( $biblionumber, $loggedinuser );
         }
-
-        #Close this page and return
-        print $query->header;
-        print "<html><meta http-equiv=\"refresh\" content=\"0\" /><body onload=\"self.close();\"></body></html>";
-        exit;
+        $template->param(
+            WINDOW_CLOSE         => 1,
+        );
     } else {
         $authorized = 0;
     }

From 0e2778159d14e438c8f6c60c5a43f97a74f9b79e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adolfo=20Rodr=C3=ADguez?= <adolfo.rodriguez@xercode.es>
Date: Mon, 9 Sep 2024 09:20:08 +0200
Subject: [PATCH 353/429] Bug 37863: Fix checking if a patron is already in the
 list adding users to a patron card batch

If the patron is already in the card batch it won't add it and it will show an error message

Test plan:
1 Create or edit an patron card batch
2 Try adding twice or more the same patron. It will show a success message everytime and you will end up with the same patron several times in the card batch
3 Apply patch, restart services
4 Repeat step 2. The first time you click on add user, it will do it and show the success message (Patron 'Patron name' added.), but after that it won't add the user and it will display an error message (Patron 'Patron name' is already in the list.)

Signed-off-by: Sukhmandeep Benipal <sukhmandeep.benipal@inLibro.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 293afd3de6a7f88d6da4311026fd294e49eb6b96)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/patroncards/edit-batch.tt              | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/edit-batch.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/edit-batch.tt
index 905db3fe7c..581cd9c1d0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/edit-batch.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/patroncards/edit-batch.tt
@@ -231,7 +231,12 @@
         };
 
         function add_user(borrowernumber) {
-            $("#bor_num_list").val($("#bor_num_list").val()+borrowernumber+"\r\n");
+            var list = $("#bor_num_list").val();
+            if (list.indexOf(borrowernumber) == -1) {
+                $("#bor_num_list").val(list+borrowernumber+"\r\n");
+            } else {
+                return -1;
+            }
         }
 
         function DeDuplicate() {

From 0b8b8cb782786fdc3210a0961e94402c42831814 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 1 Nov 2024 12:33:32 -0700
Subject: [PATCH 354/429] Bug 38329: Remove orphan confirm_deletion() in
 supplier.tt

The JavaScript function confirm_deletion() in supplier.tt has either
been an unused orphan since bug 28082 two years ago, or since the switch
from YUI to Bootstrap 12 years ago. Doesn't matter which, nothing calls
it, and if it was called, it would fail by trying to GET with
op="delete" rather than POST with op="cud-delete".

Test plan:
 1. Acquisitions - New vendor, enter a name and Save
 2. Click the name of your new vendor, so your URL is at supplier.pl
 3. Click Delete vendor to verify that deleting still works without this
    unused code which would fail

Sponsored-by: Chetco Community Public Library
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit c70946a0bef61297a0d31893d47a0fe7c98c39ce)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt
index d672bbef71..c965660775 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt
@@ -531,12 +531,6 @@
     [% Asset.js("js/acq.js") | $raw %]
     [% INCLUDE 'datatables.inc' %]
     <script>
-        function confirm_deletion() {
-            if (confirm(_("Confirm deletion of this vendor ?"))) {
-                window.location="/cgi-bin/koha/acqui/supplier.pl?booksellerid=[% booksellerid | html %]&op=delete";
-            }
-        }
-
         function add_contact() {
             var new_contact = $('#contact-template').clone();
             var timestamp = new Date().getTime();

From 49fef9bee9c3c2249ea11333bf66d18cd9279418 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 25 Jul 2024 16:37:17 +0000
Subject: [PATCH 355/429] Bug 37478: Add strict mode to bulkmarcimport

This patch adds a conversion from MARC -> XML -> MARC to catch any parsing errors.
If errors are found, we then lint the record to catch any problems, output the
warnings, and skip the record.

To test:
1 - Download the sample records from this bug report
2 - perl misc/migration_tools/bulkmarcimport.pl -b --file=520_nosubfield.mrc -v
    fails!
3 - perl misc/migration_tools/bulkmarcimport.pl -b --file=003_subfielda.mrc -v
    fails!
4 - Apply patch
5 - Repeat 2 & 3 - no change
6 - Add -st switch to the commands:
    perl misc/migration_tools/bulkmarcimport.pl -b --file=520_nosubfield.mrc -v -st
    perl misc/migration_tools/bulkmarcimport.pl -b --file=003_subfielda.mrc -v -st
7 - The records are now skipped, and the script completes
8 - Confirm the warnings generated are useful
9 - Sign off!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0306281e417ffe8f3f7e3ff033f58a0872ce980d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 4a8cb041b9..9172bc10df 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -9,6 +9,7 @@ use Modern::Perl;
 use MARC::File::USMARC;
 use MARC::File::XML;
 use MARC::Batch;
+use MARC::Lint;
 use Encode;
 
 use Koha::Script;
@@ -76,6 +77,7 @@ my $localcust;
 my $marc_mod_template    = '';
 my $marc_mod_template_id = -1;
 my $skip_indexing        = 0;
+my $strict_mode;
 $| = 1;
 
 GetOptions(
@@ -113,6 +115,7 @@ GetOptions(
     'custom:s'          => \$localcust,
     'marcmodtemplate:s' => \$marc_mod_template,
     'si|skip_indexing'  => \$skip_indexing,
+    'st|strict'         => \$strict_mode,
 );
 
 $biblios ||= !$authorities;
@@ -320,6 +323,7 @@ my $record_number = 0;
 my $logger        = Koha::Logger->get;
 my $schema        = Koha::Database->schema;
 my $marc_records  = [];
+my $lint          = MARC::Lint->new;
 RECORD: while () {
     my $record;
     $record_number++;
@@ -339,6 +343,19 @@ RECORD: while () {
     }
     if ($record) {
 
+        if ($strict_mode) {
+            my $xml = $record->as_xml_record();
+            eval { MARC::Record::new_from_xml( $xml, 'UTF-8', "MARC21" ); };
+            if ($@) {
+                print "Record $record_number generated invalid xml:\n";
+                $lint->check_record($record);
+                foreach my $warning ( $lint->warnings ) {
+                    print "    " . $warning . "\n";
+                }
+                print "    Record skipped!";
+                next;
+            }
+        }
         # transcode the record to UTF8 if needed & applicable.
         if ( $record->encoding() eq 'MARC-8' and not $skip_marc8_conversion ) {
             my ( $guessed_charset, $charset_errors );

From 2e252974f81d1d65ea42dfc7577a73807fc3570c Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 22 Aug 2024 11:26:52 +0000
Subject: [PATCH 356/429] Bug 37478: (follow-up) Add MARC::Lint to cpanfile

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 43fd84508d8017979d203363c7f3b6231562666d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 cpanfile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/cpanfile b/cpanfile
index 22d2d194ab..6b33fb1e22 100644
--- a/cpanfile
+++ b/cpanfile
@@ -74,6 +74,7 @@ requires 'Locale::PO', '0.24';
 requires 'Log::Log4perl', '1.29';
 requires 'MARC::Charset', '0.98';
 requires 'MARC::File::XML', 'v1.0.1';
+requires 'MARC::Lint', 'v1.52';
 requires 'MARC::Record', 'v2.0.6';
 requires 'MARC::Record::MiJ', '0.04';
 requires 'MIME::Base64', '3.07';

From b3dd152c5ebb22c8755838ce2d523dd35c9583d6 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 31 Oct 2024 19:23:08 +0000
Subject: [PATCH 357/429] Bug 37478: Rename strict to skip_bad_records and add
 POD

Option name changed, POD updated.
The reason not to do this by default is that it is an extra record conversion
to check the validity, so may slow down the import.

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit cfb72ca7fac22e5ee6a92ac03fdad4fc9dca559c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/migration_tools/bulkmarcimport.pl | 80 ++++++++++++++------------
 1 file changed, 43 insertions(+), 37 deletions(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 9172bc10df..0cac3d191e 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -77,45 +77,45 @@ my $localcust;
 my $marc_mod_template    = '';
 my $marc_mod_template_id = -1;
 my $skip_indexing        = 0;
-my $strict_mode;
+my $skip_bad_records;
 $| = 1;
 
 GetOptions(
-    'commit:f'          => \$commit,
-    'file:s'            => \$input_marc_file,
-    'n:f'               => \$number,
-    'o|offset:f'        => \$offset,
-    'h|help'            => \$version,
-    'd|delete'          => \$delete,
-    't|test'            => \$test_parameter,
-    's'                 => \$skip_marc8_conversion,
-    'c:s'               => \$char_encoding,
-    'v|verbose:+'       => \$verbose,
-    'fk'                => \$fk_off,
-    'm:s'               => \$format,
-    'l:s'               => \$logfile,
-    'append'            => \$append,
-    'k|keepids:s'       => \$keepids,
-    'b|biblios'         => \$biblios,
-    'a|authorities'     => \$authorities,
-    'authtypes:s'       => \$authtypes,
-    'filter=s@'         => \$filters,
-    'insert'            => \$insert,
-    'update'            => \$update,
-    'all'               => \$all,
-    'match=s@'          => \$match,
-    'i|isbn'            => \$isbn_check,
-    'x:s'               => \$sourcetag,
-    'y:s'               => \$sourcesubfield,
-    'idmap:s'           => \$idmapfl,
-    'cleanisbn!'        => \$cleanisbn,
-    'yaml:s'            => \$yamlfile,
-    'dedupbarcode'      => \$dedup_barcode,
-    'framework=s'       => \$framework,
-    'custom:s'          => \$localcust,
-    'marcmodtemplate:s' => \$marc_mod_template,
-    'si|skip_indexing'  => \$skip_indexing,
-    'st|strict'         => \$strict_mode,
+    'commit:f'            => \$commit,
+    'file:s'              => \$input_marc_file,
+    'n:f'                 => \$number,
+    'o|offset:f'          => \$offset,
+    'h|help'              => \$version,
+    'd|delete'            => \$delete,
+    't|test'              => \$test_parameter,
+    's'                   => \$skip_marc8_conversion,
+    'c:s'                 => \$char_encoding,
+    'v|verbose:+'         => \$verbose,
+    'fk'                  => \$fk_off,
+    'm:s'                 => \$format,
+    'l:s'                 => \$logfile,
+    'append'              => \$append,
+    'k|keepids:s'         => \$keepids,
+    'b|biblios'           => \$biblios,
+    'a|authorities'       => \$authorities,
+    'authtypes:s'         => \$authtypes,
+    'filter=s@'           => \$filters,
+    'insert'              => \$insert,
+    'update'              => \$update,
+    'all'                 => \$all,
+    'match=s@'            => \$match,
+    'i|isbn'              => \$isbn_check,
+    'x:s'                 => \$sourcetag,
+    'y:s'                 => \$sourcesubfield,
+    'idmap:s'             => \$idmapfl,
+    'cleanisbn!'          => \$cleanisbn,
+    'yaml:s'              => \$yamlfile,
+    'dedupbarcode'        => \$dedup_barcode,
+    'framework=s'         => \$framework,
+    'custom:s'            => \$localcust,
+    'marcmodtemplate:s'   => \$marc_mod_template,
+    'si|skip_indexing'    => \$skip_indexing,
+    'sk|skip_bad_records' => \$skip_bad_records,
 );
 
 $biblios ||= !$authorities;
@@ -343,7 +343,7 @@ RECORD: while () {
     }
     if ($record) {
 
-        if ($strict_mode) {
+        if ($skip_bad_records) {
             my $xml = $record->as_xml_record();
             eval { MARC::Record::new_from_xml( $xml, 'UTF-8', "MARC21" ); };
             if ($@) {
@@ -1046,6 +1046,12 @@ If set, do not index the imported records with Zebra or Elasticsearch.
 Use this when you plan to do a complete reindex of your data after running
 bulkmarciport. This can increase performance and avoid unnecessary load.
 
+=item B<-sk, --skip_bad_records>
+
+If set, check the validity of records before adding. If they are invalid we will
+print the output of MARC::Lint->check_record and skip them during the import. Without
+this option bad records may kill the job.
+
 =back
 
 =cut

From 76f82167f1803a08ccdf71c650a4a95028d03b24 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Tue, 24 Sep 2024 21:05:32 +0000
Subject: [PATCH 358/429] Bug 37998: Tabs and backslashes in the data break
 item search display

If the item data (e.g. callnumber) contains a tab or backslash OR the
title part contains a backslash, the DataTable displaying the item
search results stops throwing an error modal "Something went wrong when
loading the table. 200: OK." and a JS console log: "DataTables warning:
table id=results - Invalid JSON response. For more information about
this error, please see http://datatables.net/tn/1".

Test plan:
==========

1. Make an item search w/o any parameters.
2. From the first result page choose a record and modify the title (245 $a)
   to contain " \ " string. Repeat the item search. You should see a modal
   "Something went wrong when loading the table. 200: OK.".
3. Remove the " \ " from the title.  Verify that the problem no longer occurs.
4. In the same record, insert the string " \ " or a tab (copied from a text
   editor) into item data (itemcallnumber, enumchron, barcode etc.).
5. Repeat the item search. You should see again a modal "Something went wrong
   when loading the table. 200: OK.".
6. Apply the patch; restart_all.
7. Repeat p. 2, 3, 4, and 5.  Verify, that the problem is solved.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d289ebf0b36dc919f67d9947d6eee862e5362ce5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/includes/biblio-title.inc  | 11 ++++++-----
 .../en/includes/catalogue/itemsearch_item.json.inc   | 12 ++++++------
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-title.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-title.inc
index 66ebc8527a..694f569f57 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-title.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-title.inc
@@ -1,3 +1,4 @@
+[%- USE To -%]
 [%- IF link && ! biblio %][% link = 0 %][% END %]
 [%- IF ( link ) -%]
     <a href="[%- PROCESS biblio_a_href biblionumber => biblio.biblionumber -%]" class="title">
@@ -8,23 +9,23 @@
 [%- ELSIF !biblio.title -%]
     <span class="biblio-no-title">No title</span>
 [%- ELSE -%]
-    <span class="biblio-title">[% biblio.title | html %]</span>
+    <span class="biblio-title">[% biblio.title | html | $To %]</span>
 [%- END -%]
 [%- IF ( biblio.medium ) -%]
-    <span class="biblio-medium">[%- biblio.medium | html -%]</span>
+    <span class="biblio-medium">[%- biblio.medium | html | $To -%]</span>
 [%- END -%]
 [%- FOREACH subtitle IN biblio.subtitle.split(' \\| ') -%][%- IF Koha.Preference('marcflavour')=='UNIMARC' -%],[%- END -%]
-    <span class="subtitle">[%- subtitle | html -%]</span>
+    <span class="subtitle">[%- subtitle | html | $To -%]</span>
 [%- END -%]
 [%- part_numbers = biblio.part_number.split(' \\| ') -%]
 [%- part_names = biblio.part_name.split(' \\| ') -%]
 [%- i = 0 -%]
 [%- WHILE ( part_numbers.$i.defined || part_names.$i.defined ) -%]
     [%- IF ( part_numbers.$i.defined ) -%]
-        <span class="part-number">[%- part_numbers.$i | html -%]</span>
+        <span class="part-number">[%- part_numbers.$i | html | $To -%]</span>
     [%- END -%]
     [%- IF ( part_names.$i.defined ) -%]
-        <span class="part-name">[%- part_names.$i | html -%]</span>
+        <span class="part-name">[%- part_names.$i | html | $To -%]</span>
     [%- END -%]
     [%- i = i + 1 -%]
 [%- END -%]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
index 08ca9f76d0..6da8095af9 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
@@ -13,20 +13,20 @@
   "[% FILTER escape_quotes ~%]
     <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblio.biblionumber | uri %]" title="Go to record detail page">[% INCLUDE 'biblio-title.inc' | trim | collapse %]</a>[% IF ( Koha.Preference( 'marcflavour' ) == 'UNIMARC' && biblio.author ) %] by[% END %] [% biblio.author | html %]
   [%~ END %]",
-  "[% (biblioitem.publicationyear || biblio.copyrightdate) | html %]",
+  "[% (biblioitem.publicationyear || biblio.copyrightdate) | html | $To %]",
   "[% biblioitem.publishercode | html | $To %]",
-  "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => item.ccode ) | html %]",
+  "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => item.ccode ) | html | $To %]",
   "[% FILTER escape_quotes ~%]
-    <a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% biblio.biblionumber | uri %]#item[% item.itemnumber | uri %]" title="Go to item details">[% item.barcode | html %]</a>
+    <a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% biblio.biblionumber | uri %]#item[% item.itemnumber | uri %]" title="Go to item details">[% item.barcode | html | $To %]</a>
   [%~ END %]",
   "[% item.itemnumber | html %]",
-  "[% item.enumchron | html %]",
-  "[% item.itemcallnumber | html %]",
+  "[% item.enumchron | html | $To %]",
+  "[% item.itemcallnumber | html | $To %]",
   "[% Branches.GetName(item.homebranch) | html %]",
   "[% Branches.GetName(item.holdingbranch) | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( frameworkcode => biblio.frameworkcode, kohafield => 'items.location', authorised_value => item.location) | html %]",
   "[% ItemTypes.GetDescription(item.itype) | html %]",
-  "[% item.stocknumber | html %]",
+  "[% item.stocknumber | html  | $To %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( frameworkcode => biblio.frameworkcode, kohafield => 'items.notforloan', authorised_value => item.notforloan) | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.itemlost', authorised_value => item.itemlost ) || "" | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.withdrawn', authorised_value => item.withdrawn ) || "" | html %]",

From 93e9d8dd169d911f6d9a16025f324cfb0c818645 Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Thu, 7 Nov 2024 11:51:21 +0000
Subject: [PATCH 359/429] Bug 37998: (QA follow-up) Remove excess whitespace in
 catalogue/itemsearch_item.json.inc

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 48e7aad1f17f3d51f627a0d7604172c8665ad42c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/includes/catalogue/itemsearch_item.json.inc         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
index 6da8095af9..2a584db8bd 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
@@ -26,7 +26,7 @@
   "[% Branches.GetName(item.holdingbranch) | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( frameworkcode => biblio.frameworkcode, kohafield => 'items.location', authorised_value => item.location) | html %]",
   "[% ItemTypes.GetDescription(item.itype) | html %]",
-  "[% item.stocknumber | html  | $To %]",
+  "[% item.stocknumber | html | $To %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( frameworkcode => biblio.frameworkcode, kohafield => 'items.notforloan', authorised_value => item.notforloan) | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.itemlost', authorised_value => item.itemlost ) || "" | html %]",
   "[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.withdrawn', authorised_value => item.withdrawn ) || "" | html %]",

From de82706184490a28e7e6a1599f3e01fe4c5b3425 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Fri, 4 Oct 2024 19:07:12 +0000
Subject: [PATCH 360/429] Bug 38100: Items with damaged status are shown in
 OPAC results as "Not available" even with AllowHoldsOnDamagedItems

Items with damaged status are shown on the OPAC results page as "Not
available" even with AllowHoldsOnDamagedItems set to 'Allow', which is
misleading for the users.  'other/Damaged' status should be assigned
only if AllowHoldsOnDamagedItems is set to 'Don't allow'.

Test plan:
==========

1. Check that AllowHoldsOnDamagedItems system preference is set to
   'Allow'.
2. In librarian interface, change the damaged status of an item by
   setting it to 'Damaged'.
3. In OPAC, make a search for the record with this item attached.  Use
   common words from title to get a list and not a single record.
4. Note that the item in question is labelled as 'Not available'
   (Damaged).
5. Apply the patch; restart_all.
6. Repeat p. 3.  Note that the item is now labelled as 'available for
   loan'.
7. Set AllowHoldsOnDamagedItems system preference is set to 'Don't
   allow'.
8. Repeat p. 3.  Note that the item is now labelled as 'Not available'.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit c3dc86286c29661357fb696ec6759b93df4bcc89)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/XSLT.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/C4/XSLT.pm b/C4/XSLT.pm
index 458d732d23..93ae47ec85 100644
--- a/C4/XSLT.pm
+++ b/C4/XSLT.pm
@@ -320,7 +320,7 @@ sub buildKohaItemsNamespace {
             $status = 'other';
             $substatus = 'In transit';
         }
-        elsif ($item->damaged) {
+        elsif ($item->damaged && ! C4::Context->preference('AllowHoldsOnDamagedItems')) {
             $status = 'other';
             $substatus = "Damaged";
         }

From 1fbd8ebf83f697b5486455e762f692c1a1cd7e11 Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Fri, 4 Oct 2024 19:18:16 +0000
Subject: [PATCH 361/429] Bug 38100: Unit tests

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit af270fc2397a49f15795ce9f3839a9fd2b0bf0df)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/XSLT.t | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/XSLT.t b/t/db_dependent/XSLT.t
index 5f79b35b13..4f7ef3c84b 100755
--- a/t/db_dependent/XSLT.t
+++ b/t/db_dependent/XSLT.t
@@ -82,12 +82,13 @@ subtest 'Tests moved from t' => sub {
 };
 
 subtest 'buildKohaItemsNamespace status tests' => sub {
-    plan tests => 18;
+    plan tests => 20;
     $schema->storage->txn_begin;
 
     t::lib::Mocks::mock_preference('Reference_NFL_Statuses', '1|2');
     t::lib::Mocks::mock_preference( 'OPACResultsLibrary', 'holdingbranch' );
     t::lib::Mocks::mock_preference( 'OPACResultsMaxItems', '2' );
+    t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', '0' );
 
     my $itype = $builder->build_object({ class => 'Koha::ItemTypes' });
     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
@@ -152,6 +153,17 @@ subtest 'buildKohaItemsNamespace status tests' => sub {
     $xml = C4::XSLT::buildKohaItemsNamespace( $item->biblionumber,[]);
     like($xml,qr{<substatus>Damaged</substatus>},"Damaged status takes precedence over Lost");
 
+    my $item2 = $builder->build_sample_item( { itype => $itype->itemtype } );
+    $item2->holdingbranch( $holdinglibrary->branchcode )->store;
+    $item2->biblioitem->itemtype( $itemtype->itemtype )->store;
+    $item2->damaged(1)->store;
+
+    $xml = C4::XSLT::buildKohaItemsNamespace( $item2->biblionumber, [] );
+    like( $xml, qr{<status>other</status>}, "Damaged status with AllowHoldsOnDamagedItems = 0 results in 'other'" );
+    t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', '1' );
+    $xml = C4::XSLT::buildKohaItemsNamespace( $item2->biblionumber, [] );
+    like( $xml, qr{<status>available</status>}, "Damaged status with AllowHoldsOnDamagedItems results in 'available'" );
+
     $builder->build({ source => "Branchtransfer", value => {
         itemnumber  => $item->itemnumber,
         datearrived => undef,

From 15e35296481d66141ec8fa979b00d564e6d09a86 Mon Sep 17 00:00:00 2001
From: Paul Derscheid <paul.derscheid@lmscloud.de>
Date: Thu, 7 Nov 2024 11:11:46 +0000
Subject: [PATCH 362/429] Bug 38100: (QA follow-up) Tidy changes in C4/XSLT.pm

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8ee845311810590a7cbb3d2de9ef3c046d9b39b2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/XSLT.pm | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/C4/XSLT.pm b/C4/XSLT.pm
index 93ae47ec85..a2134225d8 100644
--- a/C4/XSLT.pm
+++ b/C4/XSLT.pm
@@ -320,9 +320,9 @@ sub buildKohaItemsNamespace {
             $status = 'other';
             $substatus = 'In transit';
         }
-        elsif ($item->damaged && ! C4::Context->preference('AllowHoldsOnDamagedItems')) {
-            $status = 'other';
-            $substatus = "Damaged";
+        elsif ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') ) {
+            $status    = 'other';
+            $substatus = 'Damaged';
         }
         elsif ($item->itemlost) {
             $status = 'other';

From 8e8979cb4e1c86b943ad3a2d0f8cd843658003f2 Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Tue, 28 Mar 2023 02:10:59 +0000
Subject: [PATCH 363/429] Bug 33348: Show authority heading use with
 Elasticsearch

This patch ensures the ShowHeadingUse system preference and feature works as expected when using Elasticsearch as the searchengine.

To test, follow the test plan at Bug 29990 Comment 9. Ensure the SearchEngine system preference is set to Elasticsearch.

Sponsored-by: Education Services Australia SCIS
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0fc9f22a935e66ae42a3dc07c4e2bd4c6905f616)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/SearchEngine/Elasticsearch/Search.pm | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/Koha/SearchEngine/Elasticsearch/Search.pm b/Koha/SearchEngine/Elasticsearch/Search.pm
index 42326e3573..df46134bb7 100644
--- a/Koha/SearchEngine/Elasticsearch/Search.pm
+++ b/Koha/SearchEngine/Elasticsearch/Search.pm
@@ -248,6 +248,18 @@ sub search_auth_compat {
             $result{authtype}     = $authtype ? $authtype->authtypetext : $authtypecode;
             $result{reported_tag} = $reported_tag;
 
+            if ( C4::Context->preference('ShowHeadingUse') ) {
+                # checking valid heading use
+                my $f008 = $marc->field('008');
+                my $pos14to16 = substr( $f008->data, 14, 3 );
+                my $main = substr( $pos14to16, 0, 1 );
+                $result{main} = 1 if $main eq 'a';
+                my $subject = substr( $pos14to16, 1, 1);
+                $result{subject} = 1 if $subject eq 'a';
+                my $series = substr( $pos14to16, 2, 1 );
+                $result{series} = 1 if $series eq 'a';
+            }
+
             # Reimplementing BuildSummary is out of scope because it'll be hard
             $result{summary} =
             C4::AuthoritiesMarc::BuildSummary( $marc, $result{authid},

From 97477f92581b6f6b87ea63d88c2f21ebe24f4ead Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Mon, 1 May 2023 22:38:57 +0000
Subject: [PATCH 364/429] Bug 33348: (follow-up) Fix if no 008 defined

And copy fix across to Zebra search as well.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7c40544951701015e251120579082d5c60ddd7d2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/AuthoritiesMarc.pm                     | 20 ++++++++++----------
 Koha/SearchEngine/Elasticsearch/Search.pm | 16 +++++++++-------
 2 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/C4/AuthoritiesMarc.pm b/C4/AuthoritiesMarc.pm
index 1656a2ba9b..4fe55cd34b 100644
--- a/C4/AuthoritiesMarc.pm
+++ b/C4/AuthoritiesMarc.pm
@@ -323,17 +323,17 @@ sub SearchAuthorities {
             if ( C4::Context->preference('ShowHeadingUse') ) {
                 # checking valid heading use
                 my $f008 = $authrecord->field('008');
-                my $pos14to16 = substr( $f008->data, 14, 3 );
-                my $main = substr( $pos14to16, 0, 1 );
-                $newline{main} = 1 if $main eq 'a';
-                my $subject = substr( $pos14to16, 1, 1);
-                $newline{subject} = 1 if $subject eq 'a';
-                my $series = substr( $pos14to16, 2, 1 );
-                $newline{series} = 1 if $series eq 'a';
+                if ($f008) {
+                    my $pos14to16 = substr( $f008->data, 14, 3 );
+                    my $main      = substr( $pos14to16,  0,  1 );
+                    $newline{main} = 1 if $main eq 'a';
+                    my $subject = substr( $pos14to16, 1, 1 );
+                    $newline{subject} = 1 if $subject eq 'a';
+                    my $series = substr( $pos14to16, 2, 1 );
+                    $newline{series} = 1 if $series eq 'a';
+                }
             }
-
-            $newline{authtype}     = defined($thisauthtype) ?
-                                        $thisauthtype->authtypetext : '';
+            $newline{authtype}     = defined($thisauthtype) ? $thisauthtype->authtypetext : '';
             $newline{summary}      = $summary;
             $newline{even}         = $counter % 2;
             $newline{reported_tag} = $reported_tag;
diff --git a/Koha/SearchEngine/Elasticsearch/Search.pm b/Koha/SearchEngine/Elasticsearch/Search.pm
index df46134bb7..f7fd99aaf5 100644
--- a/Koha/SearchEngine/Elasticsearch/Search.pm
+++ b/Koha/SearchEngine/Elasticsearch/Search.pm
@@ -251,13 +251,15 @@ sub search_auth_compat {
             if ( C4::Context->preference('ShowHeadingUse') ) {
                 # checking valid heading use
                 my $f008 = $marc->field('008');
-                my $pos14to16 = substr( $f008->data, 14, 3 );
-                my $main = substr( $pos14to16, 0, 1 );
-                $result{main} = 1 if $main eq 'a';
-                my $subject = substr( $pos14to16, 1, 1);
-                $result{subject} = 1 if $subject eq 'a';
-                my $series = substr( $pos14to16, 2, 1 );
-                $result{series} = 1 if $series eq 'a';
+                if ($f008) {
+                    my $pos14to16 = substr( $f008->data, 14, 3 );
+                    my $main      = substr( $pos14to16,  0,  1 );
+                    $result{main} = 1 if $main eq 'a';
+                    my $subject = substr( $pos14to16, 1, 1 );
+                    $result{subject} = 1 if $subject eq 'a';
+                    my $series = substr( $pos14to16, 2, 1 );
+                    $result{series} = 1 if $series eq 'a';
+                }
             }
 
             # Reimplementing BuildSummary is out of scope because it'll be hard

From d444b97d036474f4327dc57ae616b0c4d2606d37 Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Thu, 16 Nov 2023 21:38:35 +0000
Subject: [PATCH 365/429] Bug 33348: Tests

prove t/Koha/SearchEngine/Elasticsearch/Search.t

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8e73930408da6aad055016989438f75e718ae2f0)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/Koha/SearchEngine/Elasticsearch/Search.t | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/t/Koha/SearchEngine/Elasticsearch/Search.t b/t/Koha/SearchEngine/Elasticsearch/Search.t
index cae1b2a96e..1ca6bd359e 100755
--- a/t/Koha/SearchEngine/Elasticsearch/Search.t
+++ b/t/Koha/SearchEngine/Elasticsearch/Search.t
@@ -20,11 +20,13 @@ use Modern::Perl;
 use Test::More tests => 2;
 use Test::MockModule;
 use t::lib::Mocks;
+use Encode       qw( encode );
+use MIME::Base64 qw( encode_base64 );
 
 use_ok('Koha::SearchEngine::Elasticsearch::Search');
 
 subtest 'search_auth_compat' => sub {
-    plan tests => 4;
+    plan tests => 7;
 
     t::lib::Mocks::mock_preference( 'QueryRegexEscapeOptions', 'dont_escape' );
     t::lib::Mocks::mock_preference( 'SearchEngine', 'Elasticsearch' );
@@ -65,6 +67,10 @@ subtest 'search_auth_compat' => sub {
             $marc_record->append_fields(
                 MARC::Field->new( '001', 'Wrong001Number' ),
             );
+            $marc_record->append_fields(
+                MARC::Field->new( '008', '100803n||a| nnaban          |a aaa    |d' ),
+            );
+            my $marc_data = encode_base64( encode( 'UTF-8', $marc_record->as_usmarc() ) );
             return {
                 hits => {
                     hits => [
@@ -72,7 +78,7 @@ subtest 'search_auth_compat' => sub {
                             '_id'     => 8675309,
                             '_source' => {
                                 'local-number' => ['Wrong001Number'],
-                                'marc_data'    => $marc_record,
+                                'marc_data'    => $marc_data,
                                 'marc_format'  => 'base64ISO2709',
                             },
                         }
@@ -82,10 +88,17 @@ subtest 'search_auth_compat' => sub {
         }
     );
 
+    t::lib::Mocks::mock_preference( 'ShowHeadingUse', 1 );
     my ( $results, undef ) = $search->search_auth_compat('faked');
 
     is( @$results[0]->{authid}, '8675309', 'We get the expected record _id and not the 001' );
 
+    is( @$results[0]->{main}, 1, 'Valid main heading with ShowHeadingUse' );
+    is(
+        @$results[0]->{subject},
+        undef, 'Valid main heading with ShowHeadingUse'
+    );
+    is( @$results[0]->{series}, 1, 'Valid main heading with ShowHeadingUse' );
 };
 
 1;

From 41e74dd1a1900241a747578e9bd979019f6b34fc Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Tue, 29 Oct 2024 09:23:31 +0000
Subject: [PATCH 366/429] Bug 38284: Add tests

Sponsored-by: PTFS Europe <https://ptfs-europe.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 5acf88d91d33ce0f5901656e63c263e12bdb823e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/SIP/Message.t | 39 +++++++++++++++++++++++++++++++++++-
 1 file changed, 38 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t
index 98f92998c2..0ea1b4ed3a 100755
--- a/t/db_dependent/SIP/Message.t
+++ b/t/db_dependent/SIP/Message.t
@@ -21,7 +21,7 @@
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 20;
+use Test::More tests => 21;
 use Test::Exception;
 use Test::MockObject;
 use Test::MockModule;
@@ -408,6 +408,43 @@ subtest 'Lastseen response patron status' => sub {
 
 };
 
+subtest 'Invalid cardnumber test response patron status' => sub {
+
+    plan tests => 1;
+
+    my $schema = Koha::Database->new->schema;
+    $schema->storage->txn_begin;
+
+    my $builder    = t::lib::TestBuilder->new();
+    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
+    my ( $response, $findpatron );
+    my $mocks      = create_mocks( \$response, \$findpatron, \$branchcode );
+    my $cardnum    = 'nonexistentcardnumber';
+    my $sip_patron = C4::SIP::ILS::Patron->new($cardnum);
+    $findpatron = $sip_patron;
+
+    my $siprequest =
+          PATRON_STATUS_REQ
+        . 'engYYYYMMDDZZZZHHMMSS'
+        . FID_INST_ID
+        . $branchcode . '|'
+        . FID_PATRON_ID
+        . $cardnum . '|'
+        . FID_PATRON_PWD
+        . PATRON_PW . '|';
+    my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
+
+    my $server = { ils => $mocks->{ils} };
+    undef $response;
+
+    t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', 'login' );
+    $msg->handle_patron_status($server);
+    isnt( $response, undef, 'At least we got a response.' );
+
+    $schema->storage->txn_rollback;
+
+};
+
 subtest "Test build_additional_item_fields_string" => sub {
     my $schema = Koha::Database->new->schema;
     $schema->storage->txn_begin;

From 4a460068a89e77c5a47ab48b40a2e761052b1447 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Tue, 29 Oct 2024 09:24:36 +0000
Subject: [PATCH 367/429] Bug 38284: Add patron check for
 TrackLastPatronActivityTriggers

If an invalid or empty cardnumber is supplied to patron status request SIP message, SIP dies silently and no 'READ:' exists on the response.

Test plan:
1) Apply tests plan, run tests:
$ prove t/db_dependent/SIP/Message.t
2) Verify tests fail. Apply 2nd patch. Run tests again. Verify they pass.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 87256d7ae3d9400472a86dfe0cebf41345e844c1)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/Sip/MsgType.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/C4/SIP/Sip/MsgType.pm b/C4/SIP/Sip/MsgType.pm
index 406159c52a..6fb0e936dd 100644
--- a/C4/SIP/Sip/MsgType.pm
+++ b/C4/SIP/Sip/MsgType.pm
@@ -504,7 +504,7 @@ sub handle_patron_status {
 
     $ils->check_inst_id( $fields->{ (FID_INST_ID) }, "handle_patron_status" );
     $patron = $ils->find_patron( $fields->{ (FID_PATRON_ID) } );
-    if ( C4::Context->preference('TrackLastPatronActivityTriggers') ) {
+    if ( C4::Context->preference('TrackLastPatronActivityTriggers') && $patron ) {
         my $koha_patron = Koha::Patrons->find($patron->{borrowernumber});
         $koha_patron->update_lastseen('connection');
     }

From fbdb96e293925b3f150c4ab33485286e9b5b4d95 Mon Sep 17 00:00:00 2001
From: Andrii Nugged <nugged@gmail.com>
Date: Mon, 29 Jul 2024 16:13:34 +0300
Subject: [PATCH 368/429] Bug 37865: Fix uninitialized value $op warns

[WARN] Use of uninitialized value $op in string eq at /usr/share/koha/intranet/cgi-bin/circ/circulation.pl line 144.
[WARN] Use of uninitialized value $op in string eq at /usr/share/koha/intranet/cgi-bin/circ/returns.pl line 253.
These happen in cases when $op is undef and circulation.pl or returns.pl tries to directly compare it to a string.

To reproduce:
1. Go to "Check Out" and "Check In" circulation pages, or refresh already opened one.
2. Check your logs for "Use of uninitialized value $op" warning.
3. Apply patch.
4. Repeat step 1 and check that no new "Use of uninitialized value $op" error were logged.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8e2b493d67814ce3411a3882446ac66239e4cbbb)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/circulation.pl | 2 +-
 circ/returns.pl     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/circ/circulation.pl b/circ/circulation.pl
index f44005593d..a354c1257d 100755
--- a/circ/circulation.pl
+++ b/circ/circulation.pl
@@ -112,7 +112,7 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user (
     }
 );
 
-my $op                      = $query->param('op');
+my $op                      = $query->param('op') // '';
 my $override_high_holds     = $query->param('override_high_holds');
 my $override_high_holds_tmp = $query->param('override_high_holds_tmp');
 
diff --git a/circ/returns.pl b/circ/returns.pl
index 72e004a795..cb39bd4dfa 100755
--- a/circ/returns.pl
+++ b/circ/returns.pl
@@ -143,7 +143,7 @@ foreach ( $query->param ) {
     push( @inputloop, \%input );
 }
 
-my $op          = $query->param('op');
+my $op          = $query->param('op') // '';
 
 ############
 # Deal with the requests....

From 3d0f11a60cd3213955f0525b033a43028ccec5f4 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Thu, 7 Nov 2024 12:59:40 +0000
Subject: [PATCH 369/429] Bug 38390: Add subscriptions+count to vendors embed

This patch adds the subscriptions+count option to the embeddable enum
for the /vendors endpoint.

Test plan
1) Inspect the change to t/db_dependant/api/v1/acquisitions_vendors.t
   and confirm it's testing for the addition of subscriptions_count.
2) Run the above tests and confirm it passes

Note: For the above tests to pass, you will need to ensure your api
bundle is rebuilt after the patches are applied and restart plack.
`yarn build && restart_all` in ktd

Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ce5c7695e24738d23dd658c514517a19ee660f5b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 api/v1/swagger/definitions/vendor.yaml         | 5 +++++
 api/v1/swagger/paths/acquisitions_vendors.yaml | 1 +
 t/db_dependent/api/v1/acquisitions_vendors.t   | 9 ++++++++-
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/api/v1/swagger/definitions/vendor.yaml b/api/v1/swagger/definitions/vendor.yaml
index 3bf8a17977..262db4fb10 100644
--- a/api/v1/swagger/definitions/vendor.yaml
+++ b/api/v1/swagger/definitions/vendor.yaml
@@ -119,6 +119,11 @@ properties:
     description: List of aliases
     items:
       $ref: "vendor_alias.yaml"
+  subscriptions_count:
+    type:
+      - integer
+      - "null"
+    description: Number of linked subscriptions
 additionalProperties: false
 required:
   - name
diff --git a/api/v1/swagger/paths/acquisitions_vendors.yaml b/api/v1/swagger/paths/acquisitions_vendors.yaml
index ed817cc2a1..991419e541 100644
--- a/api/v1/swagger/paths/acquisitions_vendors.yaml
+++ b/api/v1/swagger/paths/acquisitions_vendors.yaml
@@ -35,6 +35,7 @@
           type: string
           enum:
             - aliases
+            - subscriptions+count
         collectionFormat: csv
     responses:
       "200":
diff --git a/t/db_dependent/api/v1/acquisitions_vendors.t b/t/db_dependent/api/v1/acquisitions_vendors.t
index f9520d886e..9fd1e6d9dc 100755
--- a/t/db_dependent/api/v1/acquisitions_vendors.t
+++ b/t/db_dependent/api/v1/acquisitions_vendors.t
@@ -36,7 +36,7 @@ my $t = Test::Mojo->new('Koha::REST::V1');
 
 subtest 'list() and delete() tests | authorized user' => sub {
 
-    plan tests => 40;
+    plan tests => 44;
 
     $schema->storage->txn_begin;
 
@@ -100,6 +100,13 @@ subtest 'list() and delete() tests | authorized user' => sub {
       ->json_is('/0/aliases/0/alias' => 'alias 1', 'alias 1 is embeded')
       ->json_is('/0/aliases/1/alias' => 'alias 2', 'alias 2 is embeded');
 
+    for ( 0 .. 1 ) {
+        $builder->build_object( { class => 'Koha::Subscriptions', value => { aqbooksellerid => $vendor->id } } );
+    }
+    $t->get_ok( "//$userid:$password@/api/v1/acquisitions/vendors" => { 'x-koha-embed' => 'subscriptions+count' } )
+        ->status_is(200)->json_has( '/0/subscriptions_count', 'subscriptions_count is embeded' )
+        ->json_is( '/0/subscriptions_count' => '2', 'subscription count is 2' );
+
     $t->delete_ok( "//$userid:$password@/api/v1/acquisitions/vendors/" . $vendor->id )
       ->status_is(204, 'SWAGGER3.2.4')
       ->content_is('', 'SWAGGER3.3.4');

From fabcb433d78fb1cd54dd386427a0232cc27fff24 Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Mon, 1 Jul 2024 10:30:08 +0000
Subject: [PATCH 370/429] Bug 37178: Disable sorting on comments column

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0ed10ee3da7132388cc422b82ddc284ff6871fbc)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js b/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js
index d1aefcc5e8..ded2cca3b7 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js
@@ -390,7 +390,7 @@ $(document).ready(function() {
             },
             {
                 "data": "comments_count",
-                "orderable": true,
+                "orderable": false,
                 "searchable": false,
                 "render": function(data, type, row, meta) {
                     return escape_str(data);

From 650ef902d4fc6760765a33dbf14ec42c293c296d Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Thu, 7 Nov 2024 11:33:03 +0100
Subject: [PATCH 371/429] Bug 38385: Improve DB update output on error (UI)

When an error occurred during the DB update the UI is not correct.

1. say_success, say_info, etc. show the span tags and are not colored
2. "Everything went okay" shown even if atomic updates failed
3. The same error can be displayed several time (see https://snipboard.io/IGiKgM.jpg)

Test plan:
Create a new db rev and/or atomic update
Have some say_* statement to render messages, and raise an error

For instance:
    say_info( $out, "Use blue for further information" );

    try {
        say_warning( $out, "Use yellow for warning/a call to action" );
        $dbh->do(q{});
    } catch {
        say_failure( $out, "Use red for danger/failure" );
        $_->rethrow;
    };

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0ab32306eee1572e259567f855a3a7fe8c4976e4)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/installer/step3.tt        | 36 +++++++++----------
 1 file changed, 17 insertions(+), 19 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt
index 05659bf91e..35152e50aa 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt
@@ -419,16 +419,15 @@
                             <ul>
                                 [% FOR e IN error %]
                                     [% FOR o IN e.output %]
-                                        <li class="update_error">
-                                            [% o | html %]
-                                            <br/>
-                                            ERROR: [% e.error | $raw %]
-
-                                            [% IF e.output.size > 1 %]
-                                                [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
-                                            [% END %]
-                                        </li>
+                                        [% o | $raw %]
+                                        <br/>
+                                        [% IF e.output.size > 1 %]
+                                            [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
+                                        [% END %]
                                     [% END %]
+                                    <li class="update_error">
+                                        ERROR: [% e.error | $raw %]
+                                    </li>
                                 [% END %]
                             </ul>
                         [% END %]
@@ -439,21 +438,20 @@
                         <ul>
                             [% FOR e IN atomic_updates.error %]
                                 [% FOR o IN e.output %]
-                                    <li class="update_error">
-                                        [% o | html %]
-                                        <br/>
-                                        ERROR: [% e.error | $raw %]
-
-                                        [% IF e.output.size > 1 %]
-                                            [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
-                                        [% END %]
-                                    </li>
+                                    [% o | $raw %]
+                                    <br/>
+                                    [% IF e.output.size > 1 %]
+                                        [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
+                                    [% END %]
                                 [% END %]
+                                <li class="update_error">
+                                    ERROR: [% e.error | $raw %]
+                                </li>
                             [% END %]
                         </ul>
                     [% END %]
 
-                    [% UNLESS error OR has_update_errors %]
+                    [% UNLESS error OR atomic_updates.error OR has_update_errors %]
                         <p>Everything went okay. Update done.</p>
                         <p><a href="install.pl?step=3&op=finished" class="btn btn-primary">Continue to log in to Koha</a></p>
                     [% ELSE %]

From 1c2f7e2708df1c7bfdd5c96e2e5093d1b42fbecf Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 1 Nov 2024 12:54:09 +0000
Subject: [PATCH 372/429] Bug 38322: Adjust tests

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0e899b8882273c9b80fae267398c90bb314ad4dc)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/api/v1/erm_users.t | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/t/db_dependent/api/v1/erm_users.t b/t/db_dependent/api/v1/erm_users.t
index f038105384..ad60b1b2aa 100755
--- a/t/db_dependent/api/v1/erm_users.t
+++ b/t/db_dependent/api/v1/erm_users.t
@@ -35,7 +35,7 @@ t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
 
 subtest 'list() tests' => sub {
 
-    plan tests => 11;
+    plan tests => 14;
 
     $schema->storage->txn_begin;
 
@@ -70,10 +70,21 @@ subtest 'list() tests' => sub {
         }
     );
 
-    # Two erm_users created, they should both be returned
+    # Two erm_users created, onyl self is returned without permission to view_any_borrower
     $t->get_ok("//$userid:$password@/api/v1/erm/users")->status_is(200)
         ->json_is( [ $librarian->to_api( { user => $librarian } ) ] );
 
+    my $dbh = C4::Context->dbh;
+    $dbh->do(
+        q{INSERT INTO user_permissions( borrowernumber, module_bit, code ) VALUES (?, ?, ?)}, undef,
+        ( $librarian->borrowernumber, 4, 'view_borrower_infos_from_any_libraries' )
+    );
+
+    # Two erm_users created, they should both be returned
+    $t->get_ok("//$userid:$password@/api/v1/erm/users")->status_is(200)
+        ->json_is(
+        [ $librarian->to_api( { user => $librarian } ), $another_erm_user->to_api( { user => $another_erm_user } ) ] );
+
     # Warn on unsupported query parameter
     $t->get_ok("//$userid:$password@/api/v1/erm/users?blah=blah")
       ->status_is(400)

From 087ceae831c3f3a38af9e37924d48abaf5f8f33b Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Wed, 13 Nov 2024 09:35:50 +0000
Subject: [PATCH 373/429] Bug 38322: (QA follow-up) Fix spelling

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a9e7e8f14d68be2642394fcafad15c4d7f01ca7d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/api/v1/erm_users.t | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/db_dependent/api/v1/erm_users.t b/t/db_dependent/api/v1/erm_users.t
index ad60b1b2aa..04268c1db8 100755
--- a/t/db_dependent/api/v1/erm_users.t
+++ b/t/db_dependent/api/v1/erm_users.t
@@ -70,7 +70,7 @@ subtest 'list() tests' => sub {
         }
     );
 
-    # Two erm_users created, onyl self is returned without permission to view_any_borrower
+    # Two erm_users created, only self is returned without permission to view_any_borrower
     $t->get_ok("//$userid:$password@/api/v1/erm/users")->status_is(200)
         ->json_is( [ $librarian->to_api( { user => $librarian } ) ] );
 

From d6bc0b103eacd170a54ce82d5b77842ca6915a0b Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 1 Nov 2024 11:22:59 -0700
Subject: [PATCH 374/429] Bug 38328: Cannot delete ILL batch statuses

You are permitted to delete ILL batch statuses that you (rather than the
system) add, but the UI didn't get the CSRF memo about delete ops needing to
be POSTs with op="cud-delete", so it still uses a GET of ?op="delete" and
fails.

Test plan:
 1. Set the preference ILLModule to 'Enable'
 2. Administration - Interlibrary loan batch statuses - New batch status
 3. Give it a name, an uppercase code, and Save
 4. Click the Delete button to the right of your new status
 5. The page is blank, and if you navigate back to Interlibrary loan batch
    statuses, you'll see that yours was not deleted
 6. Apply patch, reload page
 7. Click the Delete button to the right of your new status, verify it is
    deleted

Sponsored-by: Chetco Community Public Library
Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 479df408fc42d6a226ea023c71dead6d7e37caba)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/ill_batch_statuses.tt  | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/ill_batch_statuses.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/ill_batch_statuses.tt
index 290cdb884e..a17ca6c4a4 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/ill_batch_statuses.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/ill_batch_statuses.tt
@@ -134,7 +134,7 @@
                                         <td class="actions">
                                             <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/ill_batch_statuses.pl?op=add_form&code=[% status.code | uri %]"><i class="fa fa-pencil"></i> Edit</a>
                                             [% IF !status.is_system %]
-                                            <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/ill_batch_statuses.pl?op=delete&code=[% status.code | uri %]"><i class="fa fa-delete"></i> Delete</a>
+                                            <a class="btn btn-default btn-xs submit-form-link" href="#" data-action="/cgi-bin/koha/admin/ill_batch_statuses.pl" data-method="post" data-op="cud-delete" data-code="[% status.code | html %]"><i class="fa fa-delete"></i> Delete</a>
                                             [% END %]
                                         </td>
                                     </tr>
@@ -160,6 +160,7 @@
 
 [% MACRO jsinclude BLOCK %]
     [% Asset.js("js/admin-menu.js") | $raw %]
+    [% Asset.js("js/form-submit.js") | $raw %]
     [% INCLUDE 'datatables.inc' %]
 [% END %]
 

From 8e4413881cd3b0fd6bc35615f5bdf3aac10be41c Mon Sep 17 00:00:00 2001
From: David Nind <david@davidnind.com>
Date: Sun, 10 Nov 2024 19:17:02 +0000
Subject: [PATCH 375/429] Bug 38344: Fix typo - space before exclamation mark
 in "Thank you !"

In English, there should not be a space before an exclamation mark
in a sentence. For example, "Thank you !" is not correct.

Test plan:
1. Search the codebase for "Thank you !": grep -rn "Thank you !" *
2. Result - one occurance:
   C4/SIP/ILS.pm:317:    return (1, 'Thank you !', '');
3. Apply the patch.
4. Repeat step 1.
5. There should now be no occurances.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1c6dfb5f3558e297626e2f4a4ba361790044d550)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/ILS.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/C4/SIP/ILS.pm b/C4/SIP/ILS.pm
index 3e77abf602..7b0addd61b 100644
--- a/C4/SIP/ILS.pm
+++ b/C4/SIP/ILS.pm
@@ -311,7 +311,7 @@ sub end_patron_session {
     my ($self, $patron_id) = @_;
 
     # success?, screen_msg, print_line
-    return (1, 'Thank you !', '');
+    return (1, 'Thank you!', '');
 }
 
 sub pay_fee {

From c64114eef68316600298a4787754de7ae946d53e Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 15 Nov 2024 14:13:11 -0800
Subject: [PATCH 376/429] Bug 38463: Unnecessary CSRF token in OPAC authority
 search

Bug 37069 correctly changed OPAC authority searches from a POST to a GET, but
forgot to remove the CSRF token that is only needed for a POST, so now it
clutters up the URL by making the first 107 characters of the query string
meaningless.

Test plan:
 1. Without the patch, in the OPAC, go to Authority search
 2. Change the dropdowns to non-default values so you have meaningful search
    conditions, and search for something that will return results, like
    Topical Term/starts with/a/in any heading/Heading descendant
 3. Copy the URL of your search results, paste it in an email compose window,
    look at what you just pasted and wonder whether that big opaque string is
    actually safe to send to a coworker. Go to lunch. Come back and wonder
    what you searched for, and look at the URL in the browser to try to tell
 4. Apply patch, click the browser back button, reload the page, search again
 5. Copy and paste the URL, notice it looks fine to send, just a search. Look
    at the URL in the browser address bar, notice that within the limits of
    your window size, you can see what you searched for

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ddddaf2e3dc052660fd0e220e594407bde8131df)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../opac-tmpl/bootstrap/en/modules/opac-authorities-home.tt      | 1 -
 1 file changed, 1 deletion(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-authorities-home.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-authorities-home.tt
index dcaf28b387..847f9d4639 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-authorities-home.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-authorities-home.tt
@@ -33,7 +33,6 @@
                 [% END %]
                     <div id="userauthhome" class="maincontent">
                         <form name="f" action="/cgi-bin/koha/opac-authorities-home.pl" method="get">
-                            [% INCLUDE 'csrf-token.inc' %]
                             <input type="hidden" name="op" value="do_search" />
                             <input type="hidden" name="type" value="opac" />
                             <fieldset class="rows">

From 3f2af68a055f1677a197aec962e7b7313f5e8109 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Fri, 30 Aug 2024 11:05:59 +0000
Subject: [PATCH 377/429] Bug 37790: Add skip indexing and holds queue options
 and verbosity to update localuse script

This patch skips record indexing and real time holds queue updates when updating the localuse field
from statistics. A note is added to the script that the user should reindex if the localuse field is mapped.

Additionally a verbose option is added to the script, and doubled use of GetOptions is removed.

Lastly, a check is added to confirm the items value is being changed before the value is stored.

To test:
 1 - Enable the real time holds queue
 2 - Enable Elasticsearch
 3 - perl misc/maintenance/update_localuse_from_statistics.pl --confirm
 4 - Note all items are touched and reported
 5 - Check the background jobs table - there are many jobs generated
 6 - Apply patch
 7 - perl misc/maintenance/update_localuse_from_statistics.pl --confirm
 8 - Note 0 items are reported updated, no new background jobs
 9 - perl misc/maintenance/update_localuse_from_statistics.pl --confirm --verbose
10 - No items reported
11 - Update some items in the DB
     UPDATE items SET localuse = 99 WHERE itemnumber LIKE '%9';
12 - perl misc/maintenance/update_localuse_from_statistics.pl --confirm
13 - Only the number of items changed above reported
14 - UPDATE items SET localuse = 99 WHERE itemnumber LIKE '%9';
15 - perl misc/maintenance/update_localuse_from_statistics.pl --confirm --verbose
16 - Each item changed reported, and the amounts, and the total items updated.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1ba5e028cbedbb84ccf183f1e6888323c5964ea3)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../update_localuse_from_statistics.pl        | 43 ++++++++++++-------
 1 file changed, 28 insertions(+), 15 deletions(-)

diff --git a/misc/maintenance/update_localuse_from_statistics.pl b/misc/maintenance/update_localuse_from_statistics.pl
index 7d64c645d1..1782047276 100755
--- a/misc/maintenance/update_localuse_from_statistics.pl
+++ b/misc/maintenance/update_localuse_from_statistics.pl
@@ -29,40 +29,47 @@ use Pod::Usage   qw( pod2usage );
 
 my ($params);
 GetOptions(
-    'confirm' => \$params->{confirm}, 'help' => \$params->{help}, 'age:i' => \$params->{age},
+    'c|confirm' => \$params->{confirm},
+    'v|verbose' => \$params->{verbose},
+    'help'      => \$params->{help},
 );
 if ( $params->{help} ) {
     pod2usage( -verbose => 2 );
     exit;
 }
 
+my $updated;
+
+if ( $params->{confirm} ) {
+    $updated = update_localuse( { verbose => $params->{verbose} } );
+}
+
+print "Updated $updated items.\n";
+
 sub update_localuse {
+    my $params  = shift;
+    my $verbose = $params->{verbose};
 
     my $dbh = C4::Context->dbh;
 
-    my $items = Koha::Items->search();
+    my $items   = Koha::Items->search();
+    my $updated = 0;
 
     # Loop through each item and update it with statistics info
     while ( my $item = $items->next ) {
         my $itemnumber = $item->itemnumber;
 
         my $localuse_count = Koha::Statistics->search( { itemnumber => $itemnumber, type => 'localuse' } )->count;
+        my $item_localuse  = $item->localuse // 0;
+        next if $item_localuse == $localuse_count;
         $item->localuse($localuse_count);
-        $item->store;
+        $item->store( { skip_record_index => 1, skip_holds_queue => 1 } );
+        $updated++;
 
-        print "Updated item $itemnumber with localuse statistics info.\n";
+        print "Updated item $itemnumber with localuse statistics info. Was $item_localuse, now $localuse_count\n"
+            if $verbose;
     }
-}
-
-my ($help);
-my $result = GetOptions(
-    'help|h' => \$help,
-);
-
-usage() if $help;
-
-if ( $params->{confirm} ) {
-    update_localuse();
+    return $updated;
 }
 
 =head1 NAME
@@ -74,10 +81,12 @@ update_local_use_from_statistics.pl
   update_localuse_from_statistics.pl
   update_localuse_from_statistics.pl --help
   update_localuse_from_statistics.pl --confirm
+  update_localuse_from_statistics.pl --verbose
 
 =head1 DESCRIPTION
 
 This script updates the items.localuse column with data from the statistics table to make sure the two tables are congruent.
+NOTE: If you have mapped the items.localuse field in your search engine you must reindex your records after running this script.
 
 =head1 OPTIONS
 
@@ -91,6 +100,10 @@ Prints this help message
 
     Confirm to run the script.
 
+=item B<-v|--verbose>
+
+    Add verbosity to output.
+
 =back
 
 =cut

From bf57e1432217427cfa05a3e808c27714480dd013 Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Mon, 1 Jul 2024 12:28:56 +0000
Subject: [PATCH 378/429] Bug 24690: Make OPACPopupAuthorsSearch work with
 search terms containing parenthesis

This patch updates the OPACPopupAuthorsSearch feature so that it wraps
search terms with quotes. This is the behavior we have in place for
author searches outside the context of OPACPopupAuthorisSearch, e.g.
'au:"Criterion Collection (Firm)"'

To test, apply the patch and enable the OPACPopupAuthorsSearch system
preference.

- Search for a record in the OPAC which has author data containing
  parentheses (it should not be a field linked to an authority record).
- View the detail page for that record.
- Click on one of the problematic author links.
- This should trigger a modal window with a list of authors and
  checkboxes for each.
- With just the single checkbox checked, click "Search."
- The search should return the correct results.
- Test other author searches to confirm that they work too.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d7ae20f0cb4bc0850896617aae59ec22fedcc71d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
index f88db51c2f..583644195a 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-detail.tt
@@ -1959,7 +1959,7 @@
                                 authid = $(this).data("authid");
                                 search_string = "an:" + authid;
                             } else {
-                                search_string = terms[term]["keyword_search"] + encodeURIComponent( search_label );
+                                search_string = terms[term]["keyword_search"] + '"' + encodeURIComponent( search_label ) + '"';
                             }
                             var termLink = $("<li>")
                                 .append( $("<input>", { type: "checkbox", class: "select_term", value: search_string, id: terms[term]["label"] + index } ).prop("checked", preselected ) )

From 561b8cdacbb607cf27f2e20d3bc770d0eb66019a Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Fri, 8 Nov 2024 14:08:18 -0800
Subject: [PATCH 379/429] Bug 38378: Serial frequency deletion needs to be
 converted to POST from GET

Deleting a serial frequency is (or can be) a two step process. If you are
deleting a frequency which is in use, you get back a page warning you that it
is in use, which the CSRF changes converted to a form POSTing with a CSRF
token and the new op cud-del. However, to get there you have to go through
the step that's the only step if the frequency isn't in use, clicking a link
that still thinks the op is named del rather than cud-del. That link needs to
instead be a form with a CSRF token and a POST of cud-del.

Test plan:
 1. Without the patch, Serials - Manage frequencies
 2. For any frequency, click Delete, click OK in the confirmation popup
 3. Nothing happened except your URL changing, the frequency is still there
 4. Apply patch, reload Manage frequencies
 5. For any frequency, click Delete, click OK in the confirmation popup
 6. This time, your frequency was deleted

Sponsored-by: Chetco Community Public Library
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit be6e13b50cd9d4e4b638ddae259a9b5c8ba9c5a7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/serials/subscription-frequencies.tt    | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
index 8f44f015d5..dedc1fe938 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
@@ -184,7 +184,12 @@
                                 <td>[% frequency.displayorder | html %]</td>
                                 <td class="actions">
                                     <a class="btn btn-default btn-xs" href="/cgi-bin/koha/serials/subscription-frequencies.pl?op=modify&frequencyid=[% frequency.id | html %]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Edit</a>
-                                    <a class="delete_frequency btn btn-default btn-xs" href="/cgi-bin/koha/serials/subscription-frequencies.pl?op=del&frequencyid=[% frequency.id | html %]"><i class="fa fa-trash-can"></i> Delete</a>
+                                    <form action="/cgi-bin/koha/serials/subscription-frequencies.pl" method="post">
+                                        [% INCLUDE 'csrf-token.inc' %]
+                                        <input type="hidden" name="op" value="cud-del" />
+                                        <input type="hidden" name="frequencyid" value="[% frequency.id | html %]" />
+                                        <button class="delete_frequency btn btn-default btn-xs"><i class="fa fa-trash-can"></i> Delete</button>
+                                    </form>
                                 </td>
                             </tr>
                         [% END %]

From 5f5a0b225fa06313047f4c7d7fde4f1f9d904411 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 6 Nov 2024 09:37:02 +0100
Subject: [PATCH 380/429] Bug 38357: Prevent holidays to be wrongly added

This patch resets @holiday_list to an empty array, to prevent holidays
to be accumulated in worker's memory.

If you added single holidays to library A then others to library B,
holidays from A were also added to B.

Test plan:
Go to /cgi-bin/koha/tools/holidays.pl
Define the holidays for Centerville
 Select a date
 To: another date (pick only 2 days to ease testing)
 Set a title "cpl"
 Tick "Holidays on a range"
 Save
Define the holidays for Fairview
 Select *another* date
 To: another date (pick only 2 days to ease testing)
 Set a title "fpl"
 Tick "Holidays on a range"
 Save

=> Without this patch Fairview has 4 days of holidays instead of 2
=> With this patch applied the dates you selected are considered
holidays for Fairview

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 43e22821a99bc9fee50354039f1215f8ff662b5a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 tools/newHolidays.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/newHolidays.pl b/tools/newHolidays.pl
index c8215f1b4f..91d233b95f 100755
--- a/tools/newHolidays.pl
+++ b/tools/newHolidays.pl
@@ -61,7 +61,7 @@ if ($description) {
 }
 
 # We make an array with holiday's days
-our @holiday_list;
+our @holiday_list = ();
 if ($end_dt){
     for (my $dt = $first_dt->clone();
     $dt <= $end_dt;

From 388186c0ac6903dcb406d1c7c5cb1470918a1aab Mon Sep 17 00:00:00 2001
From: Hammat Wele <hammat.wele@inlibro.com>
Date: Mon, 12 Aug 2024 19:15:04 +0000
Subject: [PATCH 381/429] Bug 36132: Allow to delete multiple patron lists on
 any page

Plan test :
1. Apply the patch.
2. Create at least 22 patron lists (Navigate to Tools > Patron lists > New patron list).
3. Select the lists you want to delete on the 2nd page
4. Click the "Delete selected lists" button.
5. Confirm that the selected lists have been deleted.
6. Ensure that the button cannot be used if no list is selected.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 96cc191e104e780f0f2c7527b374311160ad3ff2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/lists.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/lists.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/lists.tt
index 29fd544762..1b23b3aecb 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/lists.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/lists.tt
@@ -177,7 +177,7 @@
                 }
             });
 
-            $(".select_patron").on("click", function() {
+            $(document).on("click", ".select_patron", function() {
                 if($(this).is(':checked')){
                     $("#delete_selected_lists").attr("class","btn btn-default btn-sm");
                     selectedPatronLists.push($(this).data("patron-list-id"));

From d140000c989ee9ff09f7ba05003c7becd036c66d Mon Sep 17 00:00:00 2001
From: Jan Kissig <jkissig@th-wildau.de>
Date: Thu, 25 Apr 2024 11:13:55 +0200
Subject: [PATCH 382/429] Bug 23426: Add fine items to patron information
 response in SIP2

This patch adds fine items (AV) to patron information response in SIP2
In addition the active currency we be part of the response (BH)
This also fixes the number of items in the response which are specified in BP and BQ in the request

to test:
a) create a manual invoice for patron 23529000035676 : http://localhost:8081/cgi-bin/koha/members/maninvoice.pl?borrowernumber=19
b) in ktd call: perl /usr/share/koha/bin/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1   -l CPL --patron 23529000035676 -m patron_information -s "      Y   "
c) verify that no |AV field is in response
d) apply patch
e) in ktd call: perl /usr/share/koha/bin/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1   -l CPL --patron 23529000035676 -m patron_information -s "      Y   "
f) verify that response includes fields like '|AVManual fee '

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
[EDIT] Tidied inline
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1d80470105e709e729a41ff52512dbcfd2992c69)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/ILS/Patron.pm  | 48 +++++++++++++++----------------------------
 C4/SIP/Sip/MsgType.pm | 17 +++++++++++----
 2 files changed, 29 insertions(+), 36 deletions(-)

diff --git a/C4/SIP/ILS/Patron.pm b/C4/SIP/ILS/Patron.pm
index f3dfea74bd..f61c855ed2 100644
--- a/C4/SIP/ILS/Patron.pm
+++ b/C4/SIP/ILS/Patron.pm
@@ -121,6 +121,9 @@ sub new {
         }
     }
 
+    # Get currency 3 chars max
+    my $currency = substr Koha::Acquisition::Currencies->get_active->currency, 0, 3;
+
     my $circ_blocked =( C4::Context->preference('OverduesBlockCirc') ne "noblock" &&  defined $flags->{ODUES}->{itemlist} ) ? 1 : 0;
     {
     no warnings;    # any of these $kp->{fields} being concat'd could be undef
@@ -164,6 +167,7 @@ sub new {
         fine_blocked    => $fine_blocked,
         fee_limit       => $fee_limit,
         userid          => $kp->{userid},
+        currency        => $currency,
     );
     }
 
@@ -205,9 +209,17 @@ sub new {
         }
     }
 
-    # FIXME: populate fine_items recall_items
+    # FIXME: populate recall_items
     $ilspatron{unavail_holds} = _get_outstanding_holds($kp->{borrowernumber});
 
+    # fine items
+    my $debits = $patron->account->outstanding_debits;
+    my @fine_items;
+    while ( my $debit = $debits->next ) {
+        push @fine_items, { account_line => sprintf '%s %.02f', $debit->description, $debit->amountoutstanding };
+    }
+    $ilspatron{fine_items} =\@fine_items;
+
     my $pending_checkouts = $patron->pending_checkouts;
     my @barcodes;
     while ( my $c = $pending_checkouts->next ) {
@@ -390,6 +402,7 @@ sub x_items {
 
     my $item_list = [];
     if ($self->{$array_var}) {
+
         if ($start && $start > 1) {
             --$start;
         }
@@ -397,6 +410,7 @@ sub x_items {
             $start = 0;
         }
         if ( $end && $end < @{$self->{$array_var}} ) {
+          --$end;
         }
         else {
             $end = @{$self->{$array_var}};
@@ -430,38 +444,8 @@ sub charged_items {
     return $self->x_items('items', @_);
 }
 sub fine_items {
-    require Koha::Database;
-    require Template;
-
     my $self = shift;
-    my $start = shift;
-    my $end = shift;
-    my $server = shift;
-
-    my @fees = Koha::Database->new()->schema()->resultset('Accountline')->search(
-        {
-            borrowernumber    => $self->{borrowernumber},
-            amountoutstanding => { '>' => '0' },
-        }
-    );
-
-    $start = $start ? $start - 1 : 0;
-    $end   = $end   ? $end - 1   : scalar @fees - 1;
-
-    my $av_field_template = $server ? $server->{account}->{av_field_template} : undef;
-    $av_field_template ||= "[% accountline.description %] [% accountline.amountoutstanding | format('%.2f') %]";
-
-    my @return_values;
-    for ( my $i = $start; $i <= $end; $i++ ) {
-        my $fee = $fees[$i];
-
-        next unless $fee;
-
-        my $output = process_tt( $av_field_template, { accountline => $fee } );
-        push( @return_values, { barcode => $output } );
-    }
-
-    return \@return_values;
+    return $self->x_items('fine_items', @_);
 }
 sub recall_items {
     my $self = shift;
diff --git a/C4/SIP/Sip/MsgType.pm b/C4/SIP/Sip/MsgType.pm
index 6fb0e936dd..7978f9593d 100644
--- a/C4/SIP/Sip/MsgType.pm
+++ b/C4/SIP/Sip/MsgType.pm
@@ -958,11 +958,12 @@ sub summary_info {
         { func => $patron->can("fine_items"),    fid => FID_FINE_ITEMS },
         { func => $patron->can("recall_items"),  fid => FID_RECALL_ITEMS },
         { func => $patron->can("unavail_holds"), fid => FID_UNAVAILABLE_HOLD_ITEMS },
+        { func => $patron->can("fine_items"),    fid => FID_FINE_ITEMS },
     );
 
     my $summary_type = index( $summary, 'Y' );
     return q{} if $summary_type == -1;    # No detailed information required.
-    return q{} if $summary_type > 5;      # Positions 6-9 are not defined in the sip spec,
+    return q{} if $summary_type > 6;      # Positions 7-9 are not defined in the sip spec,
                                           # and we have no extensions to handle them.
 
     siplog( "LOG_DEBUG", "Summary_info: index == '%d', field '%s'", $summary_type, $summary_map[$summary_type]->{fid} );
@@ -971,9 +972,17 @@ sub summary_info {
     my $fid      = $summary_map[$summary_type]->{fid};
     my $itemlist = &$func( $patron, $start, $end, $server );
 
-    siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map{ $_->{barcode} } @{$itemlist} ) );
-    foreach my $i ( @{$itemlist} ) {
-        $resp .= add_field( $fid, $i->{barcode}, $server );
+    # fine items use account_line as key
+    if ( $summary_type == 6 ) {
+        siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map { $_->{account_line} } @{$itemlist} ) );
+        foreach my $i ( @{$itemlist} ) {
+            $resp .= add_field( $fid, $i->{account_line}, $server );
+        }
+    } else {
+        siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map { $_->{barcode} } @{$itemlist} ) );
+        foreach my $i ( @{$itemlist} ) {
+            $resp .= add_field( $fid, $i->{barcode}, $server );
+        }
     }
 
     return $resp;

From 3dec3a9cb8ca723c2eb5738cdb785ffe89a80d0f Mon Sep 17 00:00:00 2001
From: Jan Kissig <jkissig@th-wildau.de>
Date: Mon, 22 Jul 2024 12:41:40 +0200
Subject: [PATCH 383/429] Bug 23426: (follow-up) Add tests

Test plan:
[1] Prove t/db_dependent/SIP/Message.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
EDIT: Tidied inline.
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit efe0b8c627d8fc7396c980b458e6ac7469fe0867)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/SIP/Message.t | 86 +++++++++++++++++++++++++++++++++++-
 1 file changed, 85 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t
index 0ea1b4ed3a..c2e76ca458 100755
--- a/t/db_dependent/SIP/Message.t
+++ b/t/db_dependent/SIP/Message.t
@@ -21,7 +21,7 @@
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 21;
+use Test::More tests => 20;
 use Test::Exception;
 use Test::MockObject;
 use Test::MockModule;
@@ -249,6 +249,90 @@ subtest 'Lastseen response patron info' => sub {
 
 };
 
+subtest 'Fine items, currency, start and end item response in patron info' => sub {
+
+    plan tests => 6;
+
+    my $schema = Koha::Database->new->schema;
+    $schema->storage->txn_begin;
+
+    my $builder    = t::lib::TestBuilder->new();
+    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
+    my ( $response, $findpatron );
+    my $mocks             = create_mocks( \$response, \$findpatron, \$branchcode );
+    my $patron_with_fines = $builder->build(
+        {
+            source => 'Borrower',
+            value  => {
+                password   => hash_password(PATRON_PW),
+                branchcode => $branchcode,
+            },
+        }
+    );
+
+    # SIP2 currency field is limited to 3 chars
+    my $currency = substr Koha::Acquisition::Currencies->get_active->currency,
+        0, 3;
+
+    my $account = Koha::Account->new( { patron_id => $patron_with_fines->{borrowernumber} } );
+
+    my $amount1 = '1.00';
+    my $amount2 = '2.00';
+    my $amount3 = '3.00';
+
+    my $debt1 = $account->add_debit( { type => 'ACCOUNT', amount => $amount1, interface => 'commandline' } );
+    my $debt2 = $account->add_debit( { type => 'ACCOUNT', amount => $amount2, interface => 'commandline' } );
+    my $debt3 = $account->add_debit( { type => 'ACCOUNT', amount => $amount3, interface => 'commandline' } );
+
+    my $cardnum    = $patron_with_fines->{cardnumber};
+    my $sip_patron = C4::SIP::ILS::Patron->new($cardnum);
+    $findpatron = $sip_patron;
+
+    my $siprequest =
+          PATRON_INFO
+        . 'engYYYYMMDDZZZZHHMMSS'
+        . '      Y   '
+        . FID_INST_ID
+        . $branchcode . '|'
+        . FID_PATRON_ID
+        . $cardnum . '|'
+        . FID_PATRON_PWD
+        . PATRON_PW . '|'
+        . FID_START_ITEM . '2|'
+        . FID_END_ITEM . '3|';
+    my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
+
+    my $server = { ils => $mocks->{ils} };
+    undef $response;
+    $msg->handle_patron_info($server);
+
+    isnt( $response, undef, 'At least we got a response.' );
+    my $respcode = substr( $response, 0, 2 );
+    is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
+
+    check_field(
+        $respcode, $response, FID_CURRENCY, $currency,
+        'Verified active currency'
+    );
+
+    # the following checks apply to fields which can occur several times
+    isnt(
+        $response =~ /\|${\FID_FINE_ITEMS} $amount1/,
+        1, 'Verified fine item 1 is not in response'
+    );
+    is(
+        $response =~ /\|${\FID_FINE_ITEMS}\s$amount2/,
+        1, 'Verified fine item 2'
+    );
+    is(
+        $response =~ /\|${\FID_FINE_ITEMS}\s$amount3/,
+        1, 'Verified fine item 3'
+    );
+
+    $schema->storage->txn_rollback;
+};
+
+
 subtest "Test patron_status_string" => sub {
     my $schema = Koha::Database->new->schema;
     $schema->storage->txn_begin;

From 2685372f2337710eae8a8b44fe8a26899197bc4e Mon Sep 17 00:00:00 2001
From: Jan Kissig <bibliothek@th-wildau.de>
Date: Thu, 7 Nov 2024 12:42:59 +0000
Subject: [PATCH 384/429] Bug 23426: (follow-up) This patch reintroduces the
 former implementation of fine items

The original implementation of fine items was accidently overwritten with this patch. This follow up reverts these changes but keeps additional improvements that were also part of this patch.
These are:
- Returning the active currency as part of the response (BH)
- Fixing the number of items in the response which are specified in BP and BQ when other items as fine items are requested.

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e5bd8aec41cb8f7ade5f7a228bbb34ff40ecea5b)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 C4/SIP/ILS/Patron.pm  | 48 ++++++++++++++++++++++++++++++++++---------
 C4/SIP/Sip/MsgType.pm | 17 ++++-----------
 2 files changed, 42 insertions(+), 23 deletions(-)

diff --git a/C4/SIP/ILS/Patron.pm b/C4/SIP/ILS/Patron.pm
index f61c855ed2..c62fb51b53 100644
--- a/C4/SIP/ILS/Patron.pm
+++ b/C4/SIP/ILS/Patron.pm
@@ -212,14 +212,6 @@ sub new {
     # FIXME: populate recall_items
     $ilspatron{unavail_holds} = _get_outstanding_holds($kp->{borrowernumber});
 
-    # fine items
-    my $debits = $patron->account->outstanding_debits;
-    my @fine_items;
-    while ( my $debit = $debits->next ) {
-        push @fine_items, { account_line => sprintf '%s %.02f', $debit->description, $debit->amountoutstanding };
-    }
-    $ilspatron{fine_items} =\@fine_items;
-
     my $pending_checkouts = $patron->pending_checkouts;
     my @barcodes;
     while ( my $c = $pending_checkouts->next ) {
@@ -444,9 +436,45 @@ sub charged_items {
     return $self->x_items('items', @_);
 }
 sub fine_items {
-    my $self = shift;
-    return $self->x_items('fine_items', @_);
+
+    require Koha::Database;
+    require Template;
+
+    my $self   = shift;
+    my $start  = shift;
+    my $end    = shift;
+    my $server = shift;
+
+    my @fees =
+      Koha::Database->new()->schema()->resultset('Accountline')->search(
+        {
+            borrowernumber    => $self->{borrowernumber},
+            amountoutstanding => { '>' => '0' },
+        }
+      );
+
+    $start = $start ? $start - 1 : 0;
+    $end   = $end   ? $end - 1   : scalar @fees - 1;
+
+    my $av_field_template =
+      $server ? $server->{account}->{av_field_template} : undef;
+    $av_field_template ||=
+"[% accountline.description %] [% accountline.amountoutstanding | format('%.2f') %]";
+
+    my @return_values;
+    for ( my $i = $start ; $i <= $end ; $i++ ) {
+        my $fee = $fees[$i];
+
+        next unless $fee;
+
+        my $output = process_tt( $av_field_template, { accountline => $fee } );
+        push( @return_values, { barcode => $output } );
+    }
+
+    return \@return_values;
+
 }
+
 sub recall_items {
     my $self = shift;
     return $self->x_items('recall_items', @_);
diff --git a/C4/SIP/Sip/MsgType.pm b/C4/SIP/Sip/MsgType.pm
index 7978f9593d..f8583ff8ac 100644
--- a/C4/SIP/Sip/MsgType.pm
+++ b/C4/SIP/Sip/MsgType.pm
@@ -958,12 +958,11 @@ sub summary_info {
         { func => $patron->can("fine_items"),    fid => FID_FINE_ITEMS },
         { func => $patron->can("recall_items"),  fid => FID_RECALL_ITEMS },
         { func => $patron->can("unavail_holds"), fid => FID_UNAVAILABLE_HOLD_ITEMS },
-        { func => $patron->can("fine_items"),    fid => FID_FINE_ITEMS },
     );
 
     my $summary_type = index( $summary, 'Y' );
     return q{} if $summary_type == -1;    # No detailed information required.
-    return q{} if $summary_type > 6;      # Positions 7-9 are not defined in the sip spec,
+    return q{} if $summary_type > 5;      # Positions 6-9 are not defined in the sip spec,
                                           # and we have no extensions to handle them.
 
     siplog( "LOG_DEBUG", "Summary_info: index == '%d', field '%s'", $summary_type, $summary_map[$summary_type]->{fid} );
@@ -972,17 +971,9 @@ sub summary_info {
     my $fid      = $summary_map[$summary_type]->{fid};
     my $itemlist = &$func( $patron, $start, $end, $server );
 
-    # fine items use account_line as key
-    if ( $summary_type == 6 ) {
-        siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map { $_->{account_line} } @{$itemlist} ) );
-        foreach my $i ( @{$itemlist} ) {
-            $resp .= add_field( $fid, $i->{account_line}, $server );
-        }
-    } else {
-        siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map { $_->{barcode} } @{$itemlist} ) );
-        foreach my $i ( @{$itemlist} ) {
-            $resp .= add_field( $fid, $i->{barcode}, $server );
-        }
+    siplog( "LOG_DEBUG", "summary_info: list = (%s)", join( ", ", map { $_->{barcode} } @{$itemlist} ) );
+    foreach my $i ( @{$itemlist} ) {
+        $resp .= add_field( $fid, $i->{barcode}, $server );
     }
 
     return $resp;

From 9d9027cb410e85a00f10196ccd3077b11cfa3003 Mon Sep 17 00:00:00 2001
From: Jan Kissig <bibliothek@th-wildau.de>
Date: Thu, 7 Nov 2024 13:32:05 +0000
Subject: [PATCH 385/429] Bug 23426: (follow-up) Enhance sip_cli_emulator.pl
 for test plan

This follow up enhances the sip_cli_emulator.pl to use start-item and/or end-item as params. With these new params the original test plan can be extended:

Test plan:
a) create several manual invoices for patron 23529000035676 : http://localhost:8081/cgi-bin/koha/members/maninvoice.pl?borrowernumber=19
b) run
     perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1   -l CPL --patron 23529000035676 -m patron_information -s "   Y      " --start-item=1 --end-item=2
   to get fine 1 and 2 or
     perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1   -l CPL --patron 23529000035676 -m patron_information -s "   Y      " --start-item=3 --end-item=3
   to retrieve fine 3

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit cfb841f9e6abc56735f8567ec40f46702cda9786)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 misc/sip_cli_emulator.pl | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/misc/sip_cli_emulator.pl b/misc/sip_cli_emulator.pl
index dbb56537fe..95ee8f5f56 100755
--- a/misc/sip_cli_emulator.pl
+++ b/misc/sip_cli_emulator.pl
@@ -55,6 +55,8 @@ my $transaction_id;
 my $pickup_location;
 my $hold_mode;
 my $no_block = 'N';
+my $start_item;
+my $end_item;
 
 my $terminator = q{};
 
@@ -85,6 +87,8 @@ GetOptions(
     "pickup-location=s" => \$pickup_location,
     "hold-mode=s"       => \$hold_mode,
     "n|no-block=s"      => \$no_block,
+    "start-item=s"      => \$start_item,
+    "end-item=s"        => \$end_item,
 
     "t|terminator=s" => \$terminator,
 
@@ -158,8 +162,10 @@ my $handlers = {
             terminal_password => $terminal_password,
             patron_password   => $patron_password,
             summary           => $summary,
+            start_item        => $start_item,
+            end_item          => $end_item,
         },
-        optional => [ 'patron_password', 'summary' ],
+        optional => [ 'patron_password', 'summary', 'start_item', 'end_item' ],
     },
     item_information => {
         name       => 'Item Information',
@@ -404,9 +410,13 @@ sub build_patron_information_command_message {
     my $patron_identifier = $params->{patron_identifier};
     my $terminal_password = $params->{terminal_password};
     my $patron_password   = $params->{patron_password};
+    my $start_item        = $params->{start_item};
+    my $end_item          = $params->{end_item};
     my $summary           = $params->{summary};
 
     $summary //= "          ";
+    $start_item //= "";
+    $end_item //= "";
 
     return
         PATRON_INFO
@@ -416,7 +426,9 @@ sub build_patron_information_command_message {
       . build_field( FID_INST_ID,      $institution_id )
       . build_field( FID_PATRON_ID,    $patron_identifier )
       . build_field( FID_TERMINAL_PWD, $terminal_password )
-      . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } );
+      . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } )
+      . build_field( FID_START_ITEM,   $start_item, { optional => 1 } )
+      . build_field( FID_END_ITEM,     $end_item, { optional => 1 } );
 }
 
 sub build_item_information_command_message {

From e849ae175b654cfa83c08b5f259da4de94243975 Mon Sep 17 00:00:00 2001
From: Jan Kissig <bibliothek@th-wildau.de>
Date: Wed, 13 Nov 2024 20:18:26 +0000
Subject: [PATCH 386/429] Bug 23426: (follow-up) Fix failing test to send
 correct summary flag

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7509681e10fc8f8df71aa98627d1feaee64602e6)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/SIP/Message.t | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t
index c2e76ca458..5ac56d2544 100755
--- a/t/db_dependent/SIP/Message.t
+++ b/t/db_dependent/SIP/Message.t
@@ -291,7 +291,7 @@ subtest 'Fine items, currency, start and end item response in patron info' => su
     my $siprequest =
           PATRON_INFO
         . 'engYYYYMMDDZZZZHHMMSS'
-        . '      Y   '
+        . '   Y      '
         . FID_INST_ID
         . $branchcode . '|'
         . FID_PATRON_ID

From a61cfb1a358c481ef87162840176e0a77c2f84c5 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Thu, 5 Dec 2024 22:52:28 +0000
Subject: [PATCH 387/429] Bug 23426: (Rmaint follow-up) Adjust tests

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/SIP/Message.t | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t
index 5ac56d2544..dea011311a 100755
--- a/t/db_dependent/SIP/Message.t
+++ b/t/db_dependent/SIP/Message.t
@@ -21,7 +21,7 @@
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 20;
+use Test::More tests => 22;
 use Test::Exception;
 use Test::MockObject;
 use Test::MockModule;

From 1793c5386333bdf699abb1b86f66e8357e5aa449 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Thu, 11 Jul 2024 12:03:40 +0000
Subject: [PATCH 388/429] Bug 37326: decode barcode file in inventory tool

This patch ensures that barcodes uploaded as a file into batchMod are run through
any transformations to match the behaviour of barcodes entered in a list

To test:
1 - Edit BarcodeSeparators system preference to remove \s
2 - Install barcode transformer plugin:
    https://github.com/bywatersolutions/koha-plugin-barcode-transformer/releases/tag/v1.2.0
3 - Configure the plguin:
item:
  -
    match: "^[A-Z]* \| "
    search: "^[A-Z]* \| "
    replace: ""
  -
    match: " \| .*$"
    search: " \| .*$"
    replace: ""
4 - Go to Cataloging->Batch item modification
5 - Enter a list of barcodes into the 'Scan one by one' box like:
ERR | 12345 | ERR
FOO | 23456 | FOO
BAR | 34567 | BAR
6 - Click 'Continue'
7 - Note the barcodes not found are:
12345
23456
34567
8 - Save the barcodes with extra text into a file
9 - Perform batch mod, supplying the barcodes via the file
10 - Note the barcodes not found are the original strings
11 - Apply patch, restart all
12 - Perform batch modification using file again
13 - Note the not found barcodes are the transformed version
14 - Sign off!

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d794891005e4a457371bd9ecaaf845e70fe85255)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 tools/batchMod.pl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/batchMod.pl b/tools/batchMod.pl
index 1b43231857..d6367b48a1 100755
--- a/tools/batchMod.pl
+++ b/tools/batchMod.pl
@@ -223,6 +223,7 @@ if ($op eq "cud-show" || $op eq "show"){
         if ($filecontent eq 'barcode_file') {
             @contentlist = grep /\S/, ( map { split /[$split_chars]/ } @contentlist );
             @contentlist = uniq @contentlist;
+            @contentlist = map { barcodedecode($_) } @contentlist;
             # Note: adding lc for case insensitivity
             my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => { -in => \@contentlist } }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed };
             @itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @contentlist;

From b6a5bf7674e7969933cd230e60cc1df1aa28af20 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Fri, 6 Sep 2024 01:45:27 +0000
Subject: [PATCH 389/429] Bug 37854: Add fieldset.rows so that Javascript works

This change adds a fieldset.rows that the Javascript produced
by C4/Barcodes/ValueBuilder.pm will work here too like it
does for /cgi-bin/koha/cataloguing/additem.pl and
/cgi-bin/koha/acqui/neworderempty.pl

The fieldset.rows element ruins the styling, so we add some context
specific styling to the styling is preserved.

Test plan:
0. Apply the patch
1. Set "autoBarcode" to "generated in the form <branchcode>yymm0001"
2. Create a vendor
3. Create a backet with "Create items when" set to "receiving an order"
4. Add an order (any order)
5. Close the basket
6. Receive the shipment
7. Click in the barcode field
8. Note that you get a barcode like CPL24090001 and not undefined24090001

Signed-off-by: Olivier V <olivier.vezina@inLibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 79b5228116ccfac3fc4aa9366493bfaeb858c1f7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/acqui/orderreceive.tt     | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
index bbd320328d..9ca203eec8 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
@@ -71,6 +71,10 @@
             content: none;
         }
 
+        #top_itemfieldset {
+            padding: 0px;
+        }
+
     </style>
 </head>
 
@@ -204,6 +208,7 @@
                                     </table>
                                 </div>
                             </div>
+                            <fieldset class="rows" id="top_itemfieldset">
                             <div id="items-panel" class="page-section">
                                 <div id="acq-create-receiving">
                                     <div id="items_list" style="display: none">
@@ -275,6 +280,7 @@
                                     </div>
                                 </div>
                             </div>
+                            </fieldset>
                         </div>
                         <div class="col-md-12 col-lg-6">
                             <div id="accounting-panel" class="page-section">

From fa90836eda1bf0fe3c0b22c0e969f15385f5784c Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Fri, 6 Sep 2024 01:49:35 +0000
Subject: [PATCH 390/429] Bug 37854: Re-indent HTML (whitespace-only)

This whitespace only change re-indents the HTML

Signed-off-by: Olivier V <olivier.vezina@inLibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit f067830305c98c42143a2d5c579e91a0dc9e7147)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/acqui/orderreceive.tt     | 96 +++++++++----------
 1 file changed, 48 insertions(+), 48 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
index 9ca203eec8..e6e4627031 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
@@ -209,18 +209,59 @@
                                 </div>
                             </div>
                             <fieldset class="rows" id="top_itemfieldset">
-                            <div id="items-panel" class="page-section">
-                                <div id="acq-create-receiving">
-                                    <div id="items_list" style="display: none">
-                                        <h4>Items list</h4>
-                                        <div style="width:100%;overflow:auto;">
+                                <div id="items-panel" class="page-section">
+                                    <div id="acq-create-receiving">
+                                        <div id="items_list" style="display: none">
+                                            <h4>Items list</h4>
+                                            <div style="width:100%;overflow:auto;">
+                                                <table>
+                                                    <thead>
+                                                        <tr>
+                                                            <th>Actions</th>
+                                                            <th>Barcode</th>
+                                                            <th>Home library</th>
+                                                            <th>Holding library</th>
+                                                            <th>Not for loan</th>
+                                                            <th>Restricted</th>
+                                                            <th>Location</th>
+                                                            <th>Call number</th>
+                                                            <th>Copy number</th>
+                                                            <th>Inventory number</th>
+                                                            <th>Collection</th>
+                                                            <th>Item type</th>
+                                                            <th>Materials</th>
+                                                            <th>Notes</th>
+                                                        </tr>
+                                                    </thead>
+                                                    <tbody>
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                        </div>
+
+                                        <div id="itemfieldset">
+                                            <h4>Item</h4>
+                                            [% IF ( NoACQframework ) %]
+                                                <p class="required">
+                                                    No ACQ framework, using default. You should create a
+                                                    framework with code ACQ, the items framework would be
+                                                    used
+                                                </p>
+                                            [% END %]
+                                            <div id="outeritemblock"></div>
+                                        </div>
+                                    </div>
+                                    <div id="acq-create-ordering">
+                                        <h4>Items</h4>
+                                        <div style="width:100%;overflow:auto">
                                             <table>
                                                 <thead>
                                                     <tr>
-                                                        <th>Actions</th>
+                                                        <th>Receive?</th>
+                                                        <th> </th>
                                                         <th>Barcode</th>
                                                         <th>Home library</th>
-                                                        <th>Holding library</th>
+                                                        <th>Current library</th>
                                                         <th>Not for loan</th>
                                                         <th>Restricted</th>
                                                         <th>Location</th>
@@ -238,48 +279,7 @@
                                             </table>
                                         </div>
                                     </div>
-
-                                    <div id="itemfieldset">
-                                        <h4>Item</h4>
-                                        [% IF ( NoACQframework ) %]
-                                            <p class="required">
-                                                No ACQ framework, using default. You should create a
-                                                framework with code ACQ, the items framework would be
-                                                used
-                                            </p>
-                                        [% END %]
-                                        <div id="outeritemblock"></div>
-                                    </div>
                                 </div>
-                                <div id="acq-create-ordering">
-                                    <h4>Items</h4>
-                                    <div style="width:100%;overflow:auto">
-                                        <table>
-                                            <thead>
-                                                <tr>
-                                                    <th>Receive?</th>
-                                                    <th> </th>
-                                                    <th>Barcode</th>
-                                                    <th>Home library</th>
-                                                    <th>Current library</th>
-                                                    <th>Not for loan</th>
-                                                    <th>Restricted</th>
-                                                    <th>Location</th>
-                                                    <th>Call number</th>
-                                                    <th>Copy number</th>
-                                                    <th>Inventory number</th>
-                                                    <th>Collection</th>
-                                                    <th>Item type</th>
-                                                    <th>Materials</th>
-                                                    <th>Notes</th>
-                                                </tr>
-                                            </thead>
-                                            <tbody>
-                                            </tbody>
-                                        </table>
-                                    </div>
-                                </div>
-                            </div>
                             </fieldset>
                         </div>
                         <div class="col-md-12 col-lg-6">

From cd7f17e968f5cd1dd4daff4206fce44dbcccc9c5 Mon Sep 17 00:00:00 2001
From: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Date: Wed, 16 Oct 2024 10:14:51 -0400
Subject: [PATCH 391/429] Bug 38186: Don't initiate transfer when cancelling
 hold on lost item

To test:
0. In the Circulation Rules, set the default return policy to "item
    returns home (default settings have this already)
1. Find an item belonging to a branch other than the logged-in branch
2. Place a hold on that biblio record for pickup at the logged-in branch
3. Check in the item to set the hold to waiting
4. Set the expiration date to a date in the past
   To do this in KTD:
   ktd --shell
   koha-mysql kohadev
   UPDATE reserves SET expirationdate = < yesterday's date >;
5. Set a lost status on the item
6. Go to Circulation > Holds awaiting pickup
--> The hold should appear on the "holds waiting past their expiration
     date" tab
7. Click the "Cancel and return to <homebranch>" button next to the hold
8. Open the biblio record for the item
--> Note that the lost status is gone and the item shows as in-transit
9. Apply patch
10. Repeat steps 2-8 on the same item
--> This time, the item is still lost and is not in-transit

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Chris Cormack <chris@bigballofwax.co.nz>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 1c3ed6e4dbd2bb01762aa3bbd350c8346b1815b5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/waitingreserves.pl | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/circ/waitingreserves.pl b/circ/waitingreserves.pl
index 0ffebe1b24..9322a0f779 100755
--- a/circ/waitingreserves.pl
+++ b/circ/waitingreserves.pl
@@ -145,23 +145,24 @@ if ($item && $tab eq 'holdsover' && !@cancel_result) {
 exit;
 
 sub cancel {
-    my ($item, $borrowernumber, $fbr, $tbr, $skip_transfers ) = @_;
+    my ( $itemnumber, $borrowernumber, $fbr, $tbr, $skip_transfers ) = @_;
 
     my $transfer = $fbr ne $tbr; # XXX && !$nextreservinfo;
+    my $item     = Koha::Items->find( $itemnumber );
 
     return if $transfer && $skip_transfers;
 
-    my ( $messages, $nextreservinfo ) = ModReserveCancelAll( $item, $borrowernumber );
+    my ( $messages, $nextreservinfo ) = ModReserveCancelAll( $itemnumber, $borrowernumber );
 
 # 	if the document is not in his homebranch location and there is not reservation after, we transfer it
-    if ($transfer && !$nextreservinfo) {
-        ModItemTransfer( $item, $fbr, $tbr, 'CancelReserve' );
+    if ( $transfer && !$nextreservinfo && !$item->itemlost ) {
+        ModItemTransfer( $itemnumber, $fbr, $tbr, 'CancelReserve' );
     }
     # if we have a result
     if ($nextreservinfo) {
         my %res;
         my $patron = Koha::Patrons->find( $nextreservinfo );
-        my $title = Koha::Items->find( $item )->biblio->title;
+        my $title  = $item->biblio->title;
         if ( $messages->{'transfert'} ) {
             $res{messagetransfert} = $messages->{'transfert'};
             $res{branchcode}       = $messages->{'transfert'};
@@ -171,7 +172,7 @@ sub cancel {
         $res{nextreservnumber}    = $nextreservinfo;
         $res{nextreservsurname}   = $patron->surname;
         $res{nextreservfirstname} = $patron->firstname;
-        $res{nextreservitem}      = $item;
+        $res{nextreservitem}      = $itemnumber;
         $res{nextreservtitle}     = $title;
         $res{waiting}             = $messages->{'waiting'} ? 1 : 0;
 

From 20c59c0171738f162f4ff68a3febdc46596342c1 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Tue, 12 Nov 2024 21:31:02 -0800
Subject: [PATCH 392/429] Bug 29818: Cannot save subscription frequency without
 display order

The schema says that subscription_frequencies.displayorder can be null, and
everything else deals with it being null just fine, but if you try to save
a new frequency without specifying display order with strict_sql_modes set,
you get an error.

Test plan:
 1. Without the patch, Serials - Manage frequencies - New frequency
 2. Description is mandatory, so fill it in, then click Save
 3. Boom! Apply patch, restart_all
 4. Repeat steps 1-2, and verify that no error is thrown and the new
    frequency shows up (at the top of the list since nothing comes before
    something)
 5. New frequency, fill in Description, try typing something other than a
    number in Display order and saving. You should be told to follow the
    directions that only numeric characters are allowed

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 77c003ed544d653345acf4debe968110ea94a1fd)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/serials/subscription-frequencies.tt        | 3 ++-
 serials/subscription-frequencies.pl                            | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
index dedc1fe938..e3c87890c1 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/serials/subscription-frequencies.tt
@@ -117,7 +117,8 @@
               </li>
               <li>
                 <label for="displayorder">Display order: </label>
-                <input type="text" id="displayorder" name="displayorder" value="[% displayorder | html %]" size="3" />
+                <input type="text" inputmode="numeric" pattern="\d*" id="displayorder" name="displayorder" value="[% displayorder | html %]" size="3" />
+                <span class="hint">Only numeric characters are allowed</span>
               </li>
             </ol>
           </fieldset>
diff --git a/serials/subscription-frequencies.pl b/serials/subscription-frequencies.pl
index 77fa4c268b..8378f3be81 100755
--- a/serials/subscription-frequencies.pl
+++ b/serials/subscription-frequencies.pl
@@ -75,7 +75,8 @@ if($op && ($op eq 'cud-savenew' || $op eq 'cud-savemod')) {
     foreach (qw/ description unit issuesperunit unitsperissue displayorder /) {
         $frequency->{$_} = $input->param($_);
     }
-    $frequency->{unit} = undef if $frequency->{unit} eq '';
+    $frequency->{unit}         = undef if $frequency->{unit} eq '';
+    $frequency->{displayorder} = undef if $frequency->{displayorder} eq '';
     foreach (qw/issuesperunit unitsperissue/) {
         $frequency->{$_} = 1 if $frequency->{$_} !~ /\d+/;
     }

From 714775e370759898683278f7f36b5c5546926f0a Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Mon, 11 Nov 2024 04:30:06 +0000
Subject: [PATCH 393/429] Bug 38416: Failover to MARCXML if cannot roundtrip
 USMARC during indexing

This change failsover to MARCXML from USMARC if there are any
warnings generated by MARC::File::USMARC::decode when trying to
roundtrip the record.

Test plan:
0. Apply the patch
1. Setup your koha-testing-docker to use Elasticsearch
2. Create a new record with 15,000 characters in the 500$a field
3. Index that record
(e.g. perl misc/search_tools/rebuild_elasticsearch.pl --biblios -v -v)
4. Note that a warning saying the following appears:
"Warnings encountered while roundtripping a MARC record to/from USMARC.
Failing over to MARCXML"
5. View the "Elasticsearch record" on the detail page and note that the
marc_format is MARCXML
6. Perform a search for the record (the keyword should be something that
brings up other results too)
7. Note that the record appears correctly in the search results

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0d862343ddce4a86a4932b80d747b9574c739e4d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/SearchEngine/Elasticsearch.pm | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/Koha/SearchEngine/Elasticsearch.pm b/Koha/SearchEngine/Elasticsearch.pm
index e27036bc1a..34f0ba432b 100644
--- a/Koha/SearchEngine/Elasticsearch.pm
+++ b/Koha/SearchEngine/Elasticsearch.pm
@@ -790,7 +790,19 @@ sub marc_records_to_documents {
                 local $SIG{__WARN__} = sub {
                     push @warnings, $_[0];
                 };
-                $record_document->{'marc_data'} = encode_base64(encode('UTF-8', $record->as_usmarc()));
+                my $usmarc_record = $record->as_usmarc();
+
+                #NOTE: Try to round-trip the record to prove it will work for retrieval after searching
+                my $decoded_usmarc_record = MARC::Record->new_from_usmarc($usmarc_record);
+                if ($decoded_usmarc_record->warnings()){
+                    #NOTE: We override the warnings since they're many and misleading
+                    @warnings = (
+                        "Warnings encountered while roundtripping a MARC record to/from USMARC. Failing over to MARCXML.",
+                    );
+                }
+
+                my $marc_data = encode_base64(encode('UTF-8', $usmarc_record));
+                $record_document->{'marc_data'} = $marc_data;
             }
             if (@warnings) {
                 # Suppress warnings if record length exceeded

From 464967abbedac48f4f8ba6d980149f71abd989c9 Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Mon, 11 Nov 2024 04:40:52 +0000
Subject: [PATCH 394/429] Bug 38416: Add unit tests

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a9eaefa3b89d77ff3a43bb560d5bc79b0c7615e5)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../Koha/SearchEngine/Elasticsearch.t         | 21 ++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/t/db_dependent/Koha/SearchEngine/Elasticsearch.t b/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
index 48cd2025d8..79f4281f96 100755
--- a/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
+++ b/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
@@ -186,7 +186,7 @@ subtest 'get_elasticsearch_mappings() tests' => sub {
 
 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests' => sub {
 
-    plan tests => 66;
+    plan tests => 69;
 
     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
     t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ISO2709');
@@ -752,6 +752,25 @@ subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests'
     is_deeply( $docs->[0]->{author},[],'No value placed into field if mapped marc field is blank');
     is_deeply( $docs->[0]->{author__suggestion},[],'No value placed into suggestion if mapped marc field is blank');
 
+    my $marc_record_with_large_field = MARC::Record->new();
+    $marc_record_with_large_field->leader('     cam  22      a 4500');
+
+    $marc_record_with_large_field->append_fields(
+        MARC::Field->new('100', '', '', a => 'Author 1'),
+        MARC::Field->new('245', '', '', a => 'Title:', b => 'record with large field'),
+        MARC::Field->new('500', '', '', a => 'X' x 15000),
+        MARC::Field->new('999', '', '', c => '1234567'),
+    );
+
+    $docs = $see->marc_records_to_documents([$marc_record_with_large_field]);
+
+    is($docs->[0]->{marc_format}, 'MARCXML', 'For record with large field marc_format should be set correctly');
+
+    $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
+
+    ok($decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result");
+    is($decoded_marc_record->as_xml_record(), $marc_record_with_large_field->as_xml_record(), "Decoded MARCXML record has same data as original record");
+
 };
 
 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents_array () tests' => sub {

From 42490c2918d367d346ddd8cf6d45a0b06323ab6f Mon Sep 17 00:00:00 2001
From: David Cook <dcook@prosentient.com.au>
Date: Mon, 11 Nov 2024 22:17:58 +0000
Subject: [PATCH 395/429] Bug 38416: Tidy

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8b952b1a343f03c86589f4e696a84e18000525b8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 Koha/SearchEngine/Elasticsearch.pm            |  5 +++--
 .../Koha/SearchEngine/Elasticsearch.t         | 21 +++++++++++--------
 2 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/Koha/SearchEngine/Elasticsearch.pm b/Koha/SearchEngine/Elasticsearch.pm
index 34f0ba432b..79522b4f27 100644
--- a/Koha/SearchEngine/Elasticsearch.pm
+++ b/Koha/SearchEngine/Elasticsearch.pm
@@ -794,14 +794,15 @@ sub marc_records_to_documents {
 
                 #NOTE: Try to round-trip the record to prove it will work for retrieval after searching
                 my $decoded_usmarc_record = MARC::Record->new_from_usmarc($usmarc_record);
-                if ($decoded_usmarc_record->warnings()){
+                if ( $decoded_usmarc_record->warnings() ) {
+
                     #NOTE: We override the warnings since they're many and misleading
                     @warnings = (
                         "Warnings encountered while roundtripping a MARC record to/from USMARC. Failing over to MARCXML.",
                     );
                 }
 
-                my $marc_data = encode_base64(encode('UTF-8', $usmarc_record));
+                my $marc_data = encode_base64( encode( 'UTF-8', $usmarc_record ) );
                 $record_document->{'marc_data'} = $marc_data;
             }
             if (@warnings) {
diff --git a/t/db_dependent/Koha/SearchEngine/Elasticsearch.t b/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
index 79f4281f96..24159799f8 100755
--- a/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
+++ b/t/db_dependent/Koha/SearchEngine/Elasticsearch.t
@@ -756,20 +756,23 @@ subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests'
     $marc_record_with_large_field->leader('     cam  22      a 4500');
 
     $marc_record_with_large_field->append_fields(
-        MARC::Field->new('100', '', '', a => 'Author 1'),
-        MARC::Field->new('245', '', '', a => 'Title:', b => 'record with large field'),
-        MARC::Field->new('500', '', '', a => 'X' x 15000),
-        MARC::Field->new('999', '', '', c => '1234567'),
+        MARC::Field->new( '100', '', '', a => 'Author 1' ),
+        MARC::Field->new( '245', '', '', a => 'Title:', b => 'record with large field' ),
+        MARC::Field->new( '500', '', '', a => 'X' x 15000 ),
+        MARC::Field->new( '999', '', '', c => '1234567' ),
     );
 
-    $docs = $see->marc_records_to_documents([$marc_record_with_large_field]);
+    $docs = $see->marc_records_to_documents( [$marc_record_with_large_field] );
 
-    is($docs->[0]->{marc_format}, 'MARCXML', 'For record with large field marc_format should be set correctly');
+    is( $docs->[0]->{marc_format}, 'MARCXML', 'For record with large field marc_format should be set correctly' );
 
-    $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
+    $decoded_marc_record = $see->decode_record_from_result( $docs->[0] );
 
-    ok($decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result");
-    is($decoded_marc_record->as_xml_record(), $marc_record_with_large_field->as_xml_record(), "Decoded MARCXML record has same data as original record");
+    ok( $decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result" );
+    is(
+        $decoded_marc_record->as_xml_record(), $marc_record_with_large_field->as_xml_record(),
+        "Decoded MARCXML record has same data as original record"
+    );
 
 };
 

From 4f7327c5c1d14b136d2d2700b60c23869aa3af81 Mon Sep 17 00:00:00 2001
From: Didier Gautheron <didier.gautheron@biblibre.com>
Date: Thu, 7 Nov 2024 14:32:35 +0100
Subject: [PATCH 396/429] Bug 28075: Add missing UNIMARC coded data 135a

This patch add more values to selection of 135a in UNIMARC

Test Plan:
    1) Add on default framework the field 135a and check the Editor option
    2) Set on plugin section the value of unimarc_field_135a.pl
    3) Open cataloguing editor and use 135 field tag editor to select a value
    4) Apply patch
    5) Open cataloguing editor and use 135 field tag editor to select a value,
       after patch you have more options to select

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 3fa03e2e660de9c0ab12e8281671963f089c3ef9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../value_builder/unimarc_field_135a.tt       | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
index f2cb637661..28677ac368 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
@@ -43,6 +43,36 @@
                             [% ELSE %]
                                 <option value="d">text</option>
                             [% END %]
+                            [% IF ( f1e ) %]
+                                <option value="e" selected="selected">données bibliographiques</option>
+                            [% ELSE %]
+                                <option value="e">données bibliographiques</option>
+                            [% END %]
+                            [% IF ( f1f ) %]
+                                <option value="f" selected="selected">polices de caractères</option>
+                            [% ELSE %]
+                                <option value="f">polices de caractères</option>
+                            [% END %]
+                            [% IF ( f1g ) %]
+                                <option value="g" selected="selected">jeu</option>
+                            [% ELSE %]
+                                <option value="g">jeu</option>
+                            [% END %]
+                            [% IF ( f1h ) %]
+                                <option value="h" selected="selected">son</option>
+                            [% ELSE %]
+                                <option value="h">son</option>
+                            [% END %]
+                            [% IF ( f1i ) %]
+                                <option value="i" selected="selected">multimédia interactif</option>
+                            [% ELSE %]
+                                <option value="i">multimédia interactif</option>
+                            [% END %]
+                            [% IF ( f1j ) %]
+                                <option value="j" selected="selected">système ou service en ligne</option>
+                            [% ELSE %]
+                                <option value="j">système ou service en ligne</option>
+                            [% END %]
                             [% IF ( f1u ) %]
                                 <option value="u" selected="selected">unknown</option>
                             [% ELSE %]

From 5a07b6a869bab9362ef983139933f0af6262f556 Mon Sep 17 00:00:00 2001
From: George Veranis <gveranis@dataly.gr>
Date: Thu, 7 Nov 2024 14:39:16 +0100
Subject: [PATCH 397/429] Bug 28075: (follow-up) adding all choices and values
 of 135a

Extend patch of 135a to cover all choices with all possible values as
described by IFLA for 135a.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 9a72d3c7576d2c884ea93ae7f065d6836358c3d9)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../value_builder/unimarc_field_135a.tt       | 356 +++++++++++++++++-
 1 file changed, 355 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
index 28677ac368..5076aeb149 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/unimarc_field_135a.tt
@@ -90,6 +90,360 @@
                             [% END %]
                         </select>
                     </td></tr>
+                         <tr>
+                            <td><label for="f2">Determinant of material specificity:</label></td>
+                            <td>
+                                <select name="f2" id="f2">
+                                [% IF ( f2a ) %]
+                                    <option value="a" selected="selected">a- magnetic tape</option>
+                                [% ELSE %]
+                                    <option value="a">a- magnetic tape</option>
+                                [% END %]
+                                [% IF ( f2b ) %]
+                                    <option value="b" selected="selected">b- computer chip in the cartidge</option>
+                                [% ELSE %]
+                                    <option value="b">b- computer chip in the cartidge</option>
+                                [% END %]
+                                [% IF ( f2c ) %]
+                                    <option value="c" selected="selected">c- computer optical disc in a cassete</option>
+                                [% ELSE %]
+                                    <option value="c">c- computer optical disc in a cassete</option>
+                                [% END %]
+                                [% IF ( f2d ) %]
+                                    <option value="d" selected="selected">d- computer disk, type not defined</option>
+                                [% ELSE %]
+                                    <option value="d">d- computer disk, type not defined</option>
+                                [% END %]
+                                [% IF ( f2e ) %]
+                                    <option value="e" selected="selected">e- cassette with computer disk, type not defined</option>
+                                [% ELSE %]
+                                    <option value="e">e- cassete with computer disk, type not defined</option>
+                                [% END %]
+                                [% IF ( f2f ) %]
+                                    <option value="f" selected="selected">f- computer magnetic tape in a cassette</option>
+                                [% ELSE %]
+                                    <option value="f">f- computer magnetic tape in a cassette</option>
+                                [% END %]
+                                [% IF ( f2j ) %]
+                                    <option value="j" selected="selected">j- computer floppy disk</option>
+                                [% ELSE %]
+                                    <option value="j">j- computer floppy disk</option>
+                                [% END %]
+                                [% IF ( f2k ) %]
+                                    <option value="k" selected="selected">k- computer card</option>
+                                [% ELSE %]
+                                    <option value="k">k- computer card</option>
+                                [% END %]
+                                [% IF ( f2m ) %]
+                                    <option value="m" selected="selected">m- computer magneto-optical disk</option>
+                                [% ELSE %]
+                                    <option value="m">m- computer magneto-optical disk</option>
+                                [% END %]
+                                [% IF ( f2o ) %]
+                                    <option value="o" selected="selected">o- computer optical disk</option>
+                                [% ELSE %]
+                                    <option value="o">o- computer optical disk</option>
+                                [% END %]
+                                [% IF ( f2r ) %]
+                                    <option value="r" selected="selected">r- online</option>
+                                [% ELSE %]
+                                    <option value="r">r- online</option>
+                                [% END %]
+                                [% IF ( f2s ) %]
+                                    <option value="s" selected="selected">s- standalone device</option>
+                                [% ELSE %]
+                                    <option value="s">s- standalone device</option>
+                                [% END %]
+                                [% IF ( f2u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                                [% IF ( f2z ) %]
+                                    <option value="z" selected="selected">z- others</option>
+                                [% ELSE %]
+                                    <option value="z">z- others</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                        <tr>
+                            <td><label for="f3">Color:</label></td>
+                            <td>
+                                <select name="f3" id="f3">
+                                [% IF ( f3a ) %]
+                                    <option value="a" selected="selected">a- monochrome</option>
+                                [% ELSE %]
+                                    <option value="a">a- monochrome</option>
+                                [% END %]
+                                [% IF ( f3b ) %]
+                                    <option value="b" selected="selected">b- black and white</option>
+                                [% ELSE %]
+                                    <option value="b">b- black and white</option>
+                                [% END %]
+                                [% IF ( f3c ) %]
+                                    <option value="c" selected="selected">c- multicolored</option>
+                                [% ELSE %]
+                                    <option value="c">c- multicolored</option>
+                                [% END %]
+                                [% IF ( f3g ) %]
+                                    <option value="g" selected="selected">g- gray scale</option>
+                                [% ELSE %]
+                                    <option value="g">g- gray scaled</option>
+                                [% END %]
+                                [% IF ( f3m ) %]
+                                    <option value="m" selected="selected">m- mixed</option>
+                                [% ELSE %]
+                                    <option value="m">m- mixed</option>
+                                [% END %]
+                                [% IF ( f3n ) %]
+                                    <option value="n" selected="selected">n- not applicable</option>
+                                [% ELSE %]
+                                    <option value="n">n- not applicable</option>
+                                [% END %]
+                                [% IF ( f3u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                                [% IF ( f3z ) %]
+                                    <option value="z" selected="selected">z- others</option>
+                                [% ELSE %]
+                                    <option value="z">z- others</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                        <tr>
+                            <td><label for="f4">Geometric dimensions:</label></td>
+                            <td>
+                                <select name="f4" id="f4">
+                                [% IF ( f4a ) %]
+                                    <option value="a" selected="selected">a- 3.5 inches</option>
+                                [% ELSE %]
+                                    <option value="a">a- 3.5 inches</option>
+                                [% END %]
+                                [% IF ( f4e ) %]
+                                    <option value="e" selected="selected">e- 12 inches</option>
+                                [% ELSE %]
+                                    <option value="e">e- 12 inches</option>
+                                [% END %]
+                                [% IF ( f4g ) %]
+                                    <option value="g" selected="selected">g- 4.7 inches or 12 cm</option>
+                                [% ELSE %]
+                                    <option value="g">g- 4.7 inches or 12 cm</option>
+                                [% END %]
+                                [% IF ( f4i ) %]
+                                    <option value="i" selected="selected">i- 1 1/8 x 2 3/8 inches</option>
+                                [% ELSE %]
+                                    <option value="i">i- 1 1/8 x 2 3/8 inches</option>
+                                [% END %]
+                                [% IF ( f4j ) %]
+                                    <option value="j" selected="selected">j- 3 7/8 x 2 1/2 inches</option>
+                                [% ELSE %]
+                                    <option value="j">j- 3 7/8 x 2 1/2 inches</option>
+                                [% END %]
+                                [% IF ( f4n ) %]
+                                    <option value="n" selected="selected">n- not applicable</option>
+                                [% ELSE %]
+                                    <option value="n">n- not applicable</option>
+                                [% END %]
+                                [% IF ( f4o ) %]
+                                    <option value="o" selected="selected">o- 5.25 inches</option>
+                                [% ELSE %]
+                                    <option value="o">o- 5.25 inches</option>
+                                [% END %]
+                                [% IF ( f4u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                                [% IF ( f4v ) %]
+                                    <option value="v" selected="selected">v- 8 inches</option>
+                                [% ELSE %]
+                                    <option value="v">v- 8 inches</option>
+                                [% END %]
+                                [% IF ( f4z ) %]
+                                    <option value="z" selected="selected">z- others</option>
+                                [% ELSE %]
+                                    <option value="z">z- others</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f5">Sound:</label></td>
+                            <td>
+                                <select name="f5" id="f5">
+                                [% IF ( f5space ) %]
+                                    <option value=" " selected="selected">no sound</option>
+                                [% ELSE %]
+                                    <option value=" ">no sound</option>
+                                [% END %]
+                                [% IF ( f5a ) %]
+                                    <option value="a" selected="selected">a- with sound</option>
+                                [% ELSE %]
+                                    <option value="a">a- with sound</option>
+                                [% END %]
+                                [% IF ( f5u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f6">Image depth (in bits):</label></td>
+                            <td>
+                              <input type="text" value="[% f6 | html %]" name="f6" id="f6" maxlength="3" size="4">
+                            </td></tr>
+                         <tr>
+                            <td><label for="f7">The number of file formats in the electronic resource:</label></td>
+                            <td>
+                                <select name="f7" id="f7">
+                                [% IF ( f7a ) %]
+                                    <option value="a" selected="selected">a- one file format</option>
+                                [% ELSE %]
+                                    <option value="a">a- one file format</option>
+                                [% END %]
+                                [% IF ( f7m ) %]
+                                    <option value="m" selected="selected">m- many file formats</option>
+                                [% ELSE %]
+                                    <option value="m">m- many file formats</option>
+                                [% END %]
+                                [% IF ( f7u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f8">Quality Assurance Indicators:</label></td>
+                            <td>
+                                <select name="f8" id="f8">
+                                [% IF ( f8a ) %]
+                                    <option value="a" selected="selected">a- missing</option>
+                                [% ELSE %]
+                                    <option value="a">a- missing</option>
+                                [% END %]
+                                [% IF ( f8n ) %]
+                                    <option value="n" selected="selected">n- not applicable</option>
+                                [% ELSE %]
+                                    <option value="n">n- not applicable</option>
+                                [% END %]
+                                [% IF ( f8p ) %]
+                                    <option value="p" selected="selected">p- available</option>
+                                [% ELSE %]
+                                    <option value="p">p- available</option>
+                                [% END %]
+                                [% IF ( f8u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f9">Antecedent / Source:</label></td>
+                            <td>
+                                <select name="f9" id="f9">
+                                [% IF ( f9a ) %]
+                                    <option value="a" selected="selected">a- the file is played from the original</option>
+                                [% ELSE %]
+                                    <option value="a">a- the file is played from the original</option>
+                                [% END %]
+                                [% IF ( f9b ) %]
+                                    <option value="b" selected="selected">b- the file is played from a microform</option>
+                                [% ELSE %]
+                                    <option value="b">b- the file is played from a microform</option>
+                                [% END %]
+                                [% IF ( f9c ) %]
+                                    <option value="c" selected="selected">c- the file is played from an electronic resource</option>
+                                [% ELSE %]
+                                    <option value="c">c- the file is played from an electronic resource</option>
+                                [% END %]
+                                [% IF ( f9d ) %]
+                                    <option value="d" selected="selected">d- the file is played from an intermediate source other than the microform</option>
+                                [% ELSE %]
+                                    <option value="d">d- the file is played from an intermediate source other than the microform</option>
+                                [% END %]
+                                [% IF ( f9m ) %]
+                                    <option value="m" selected="selected">m- mixed</option>
+                                [% ELSE %]
+                                    <option value="m">m- mixed</option>
+                                [% END %]
+                                [% IF ( f9n ) %]
+                                    <option value="n" selected="selected">n- not applicable</option>
+                                [% ELSE %]
+                                    <option value="n">n- not applicable</option>
+                                [% END %]
+                                [% IF ( f9u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f10">Compression level:</label></td>
+                            <td>
+                                <select name="f10" id="f10">
+                                [% IF ( f10a ) %]
+                                    <option value="a" selected="selected">a- not compressed</option>
+                                [% ELSE %]
+                                    <option value="a">a- not compressed</option>
+                                [% END %]
+                                [% IF ( f10b ) %]
+                                    <option value="b" selected="selected">b- compressed without losses</option>
+                                [% ELSE %]
+                                    <option value="b">b- compressed without losses</option>
+                                [% END %]
+                                [% IF ( f10d ) %]
+                                    <option value="d" selected="selected">d- compressed with losses</option>
+                                [% ELSE %]
+                                    <option value="d">d- compressed with losses</option>
+                                [% END %]
+                                [% IF ( f10m ) %]
+                                    <option value="m" selected="selected">m- mixed</option>
+                                [% ELSE %]
+                                    <option value="m">m- mixed</option>
+                                [% END %]
+                                [% IF ( f10u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
+                         <tr>
+                            <td><label for="f11">Quality and purpose of the electronic resource:</label></td>
+                            <td>
+                                <select name="f11" id="f11">
+                                [% IF ( f11a ) %]
+                                    <option value="a" selected="selected">a- access</option>
+                                [% ELSE %]
+                                    <option value="a">a- access</option>
+                                [% END %]
+                                [% IF ( f11n ) %]
+                                    <option value="n" selected="selected">n- not applicable</option>
+                                [% ELSE %]
+                                    <option value="n">n- not applicable</option>
+                                [% END %]
+                                [% IF ( f11p ) %]
+                                    <option value="p" selected="selected">p- preservation</option>
+                                [% ELSE %]
+                                    <option value="p">p- preservation</option>
+                                [% END %]
+                                [% IF ( f11r ) %]
+                                    <option value="r" selected="selected">r- substitute</option>
+                                [% ELSE %]
+                                    <option value="r">r- substitute</option>
+                                [% END %]
+                                [% IF ( f11u ) %]
+                                    <option value="u" selected="selected">u- unknown</option>
+                                [% ELSE %]
+                                    <option value="u">u- unknown</option>
+                                [% END %]
+                              </select>
+                            </td></tr>
             </table>
         </div> <!-- /.page-section -->
 
@@ -111,7 +465,7 @@
             var doc   = opener.document;
             var field = doc.getElementById("[% index | html %]");
 
-            field.value =  document.f_pop.f1.value;
+            field.value =  document.f_pop.f1.value+document.f_pop.f2.value+document.f_pop.f3.value+document.f_pop.f4.value+document.f_pop.f5.value+document.f_pop.f6.value+document.f_pop.f7.value+document.f_pop.f8.value+document.f_pop.f9.value+document.f_pop.f10.value+document.f_pop.f11.value;
             window.close();
             return false;
         }

From dd6240343a4b982ffcd5a295bd54c69637c34f0a Mon Sep 17 00:00:00 2001
From: Janusz Kaczmarek <januszop@gmail.com>
Date: Wed, 23 Oct 2024 13:27:52 +0000
Subject: [PATCH 398/429] Bug 38239: Incorrect number of items to pull (in
 Holds to pull) with partially filled holds

With a over-sufficient number of items, when more than one patron has
placed hold, and the holds have been partially filled (checked-in =
waiting for pick up), the number of items to pull in the Holds
to pull table shows the total number of holds, including those waiting.
This erroneously suggests to the librarian to pull an excessive number
of items from the shelves.

Test plan:
==========

1. For a bibliographic record with more than two items (in ktd, e.g.
   "Lanark a life in four books"), place hold for two patrons.
2. On the Holds to pull page control that there are two items to pull.
3. As a librarian from the library of one of the patrons, Check-in one
   item.
4. Note that in Holds to pull table you still see two items to pull,
   which is misleading.
5. Apply the patch; restart_all.
6. Now you should see only one item to be pulled.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit abfba936fb4ffcab5d1234c3fc577ac493865c5e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 circ/pendingreserves.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/circ/pendingreserves.pl b/circ/pendingreserves.pl
index 366d0009af..b85cf5c90e 100755
--- a/circ/pendingreserves.pl
+++ b/circ/pendingreserves.pl
@@ -194,7 +194,7 @@ if ( $holds->count ) {
 # patrons count per biblio
 my $patrons_count = {
     map { $_->{biblionumber} => $_->{patrons_count} } @{ Koha::Holds->search(
-            { 'suspend' => 0 },
+            { 'suspend' => 0, 'found' => undef },
             {
                 select   => [ 'biblionumber', { count => { distinct => 'borrowernumber' } } ],
                 as       => [qw( biblionumber patrons_count )],

From 3b31cf80c3c2e4a6cbc440cd93b5645d927d5661 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Mon, 25 Nov 2024 11:57:54 +0100
Subject: [PATCH 399/429] Bug 38526: (bug 36822 follow-up): Improve datetime
 comparison in tests

17:39:54 koha_1       |     #   Failed test 'updated_on correctly saved on newly created user'
17:39:54 koha_1       |     #   at t/db_dependent/Auth_with_shibboleth.t line 319.
17:39:54 koha_1       |     #     Structures begin differing at:
17:39:54 koha_1       |     #          $got->[0] = '2024-11-19 16:39:30'
17:39:54 koha_1       |     #     $expected->[0] = '2024-11-19 16:39:29'
17:39:54 koha_1       |     # Looks like you failed 1 test of 54.

We must use t::lib::Dates::compare to compare datetimes in tests.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ed25a48ccf566bf4a21040a36d25bda8d598c301)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Auth_with_ldap.t       |  7 ++++---
 t/db_dependent/Auth_with_shibboleth.t | 14 +++++++-------
 2 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/t/db_dependent/Auth_with_ldap.t b/t/db_dependent/Auth_with_ldap.t
index f6e47fb450..dc83969c9e 100755
--- a/t/db_dependent/Auth_with_ldap.t
+++ b/t/db_dependent/Auth_with_ldap.t
@@ -20,9 +20,11 @@ use Modern::Perl;
 use Test::More tests => 4;
 use Test::MockModule;
 use Test::MockObject;
+use Test::Warn;
+
 use t::lib::Mocks;
 use t::lib::TestBuilder;
-use Test::Warn;
+use t::lib::Dates;
 
 use C4::Context;
 use C4::Auth;
@@ -276,11 +278,10 @@ subtest 'checkpw_ldap tests' => sub {
         $welcome   = 0;
 
         # replicate testing with checkpw
-        my $time_now = dt_from_string()->ymd . ' ' . dt_from_string()->hms;
         C4::Auth::checkpw( 'hola', password => 'hey' );
         my $patron_replicated_from_auth = Koha::Patrons->search( { userid => 'hola' } )->next;
         is(
-            $patron_replicated_from_auth->updated_on, $time_now,
+            t::lib::Dates::compare( $patron_replicated_from_auth->updated_on, dt_from_string ), 0,
             "updated_on correctly saved on newly created user"
         );
 
diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t
index 5ed83fcbf1..693bedf4a3 100755
--- a/t/db_dependent/Auth_with_shibboleth.t
+++ b/t/db_dependent/Auth_with_shibboleth.t
@@ -29,6 +29,7 @@ use File::Temp qw(tempdir);
 use t::lib::Mocks;
 use t::lib::Mocks::Logger;
 use t::lib::TestBuilder;
+use t::lib::Dates;
 
 use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib );
 use Koha::Database;
@@ -288,15 +289,14 @@ subtest "checkpw_shib tests" => sub {
     $ENV{'emailpro'} = 'me@myemail.com';
     $ENV{branchcode} = $library->branchcode;      # needed since T::D::C does no longer hides the FK constraint
 
-    my $time_now = dt_from_string()->ymd . ' ' . dt_from_string()->hms;
     checkpw($shib_login);
-    ok my $new_user_autocreated = $schema->resultset('Borrower')->search( { 'userid' => 'test43210' }, { rows => 1 } ),
-        "new user found";
+    ok(
+        my $new_user_autocreated = Koha::Patrons->find( { userid => 'test43210' } ),
+        "new user found"
+    );
 
-    my $rec_autocreated = $new_user_autocreated->next;
-    is_deeply(
-        [ map { $rec_autocreated->$_ } qw/updated_on/ ],
-        [$time_now],
+    is(
+        t::lib::Dates::compare( $new_user_autocreated->updated_on, dt_from_string ), 0,
         'updated_on correctly saved on newly created user'
     );
 };

From e3f16499be3f3043dbb7c76477abfd7d448c1c24 Mon Sep 17 00:00:00 2001
From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Date: Fri, 22 Nov 2024 08:28:46 +0000
Subject: [PATCH 400/429] Bug 38513: Fix Biblio.t for Koha_Main_My8

This should do the tric: The test output shows that the second
mapping was not found (field 264); also the expected suspect btw.
So adding that here.

Test plan:
Run t/db_dependent/Biblio.t
See also comment28 on bug 19097.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Simulated the above by removing 264c from Koha to MARC mapping.
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 28d24aaa0874844ec8643a9ea19cc0b501013c6a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 t/db_dependent/Biblio.t | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/t/db_dependent/Biblio.t b/t/db_dependent/Biblio.t
index 76413091ae..9229603d04 100755
--- a/t/db_dependent/Biblio.t
+++ b/t/db_dependent/Biblio.t
@@ -124,6 +124,11 @@ subtest 'AddBiblio' => sub {
 subtest 'GetMarcSubfieldStructureFromKohaField' => sub {
     plan tests => 27;
 
+    # Add second mapping for copyrightdate
+    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '264', tagsubfield => 'c' })->delete;
+    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '264', tagsubfield => 'c', kohafield => "biblio.copyrightdate" })->store;
+    Koha::Caches->get_instance->clear_from_cache( "MarcSubfieldStructure-" );
+
     my @columns = qw(
         tagfield tagsubfield liblibrarian libopac repeatable mandatory kohafield tab
         authorised_value authtypecode value_builder isurl hidden frameworkcode

From b6e238909d0cccde59c40892c2a8956f222ab639 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Wed, 20 Nov 2024 13:24:12 +0000
Subject: [PATCH 401/429] Bug 38495: Use JS to submit form to cancel background
 jobs

This patch adjusts the link to cancel jobs to be submitted as a POST with cud-cancel operation

To test:
1 - Stop your long tasks bacground jobs worker
   sudo koha-worker --stop --queue long_tasks kohadev
2 - Stage a file for import
3 - Administration -> Manage jobs
4 - See your new job
5 - Click 'cancel'
6 - It didn't work
7 - Apply patch
8 - Browse to jobs again
9 - Click 'Cancel'
10 - Job is successfully cancelled

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d047776630152a9e2b16c7797aaac600c0dff6e2)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/background_jobs.tt     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
index b85793db50..37353b0665 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
@@ -158,6 +158,7 @@
 
 [% MACRO jsinclude BLOCK %]
     [% Asset.js("js/admin-menu.js") | $raw %]
+    [% Asset.js("js/form-submit.js") | $raw %]
     [% INCLUDE 'js-date-format.inc' %]
     [% INCLUDE 'datatables.inc' %]
     <script>
@@ -340,7 +341,7 @@
                         "data": function( row, type, val, meta ) {
                             var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id='+ encodeURIComponent(row.job_id) +'"><i class="fa-solid fa-eye aria-hidden="true"></i> '+_("View")+'</a>'+"\n";
                             if ( row.status == 'new' || row.status == 'started' ) {
-                                result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/background_jobs.pl?op=cancel&id='+ encodeURIComponent(row.job_id) +'"><i class="fa fa-trash-can" aria-hidden="true"></i> '+_("Cancel")+'</a>';
+                                result += '<a class="btn btn-default btn-xs submit-form-link" role="button" href="#" data-action="/cgi-bin/koha/admin/background_jobs.pl" data-method="post" data-op="cud-cancel" data-id="'+ encodeURIComponent(row.job_id) +'"><i class="fa fa-trash-can" aria-hidden="true"></i> '+_("Cancel")+'</a>';
                             }
                             return result;
                         },

From 93ed8bb072e160dd86336740a2f60934bc726544 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Wed, 20 Nov 2024 13:55:21 +0000
Subject: [PATCH 402/429] Bug 38495: (follow-up) Add a confirmation message

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
[EDIT} Added a html filter for qa tools..
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 0be0f03d5267bb13eb28a7cdbfbcdd43cbb830df)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../intranet-tmpl/prog/en/modules/admin/background_jobs.tt      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
index 37353b0665..450e4d2b30 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
@@ -341,7 +341,7 @@
                         "data": function( row, type, val, meta ) {
                             var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id='+ encodeURIComponent(row.job_id) +'"><i class="fa-solid fa-eye aria-hidden="true"></i> '+_("View")+'</a>'+"\n";
                             if ( row.status == 'new' || row.status == 'started' ) {
-                                result += '<a class="btn btn-default btn-xs submit-form-link" role="button" href="#" data-action="/cgi-bin/koha/admin/background_jobs.pl" data-method="post" data-op="cud-cancel" data-id="'+ encodeURIComponent(row.job_id) +'"><i class="fa fa-trash-can" aria-hidden="true"></i> '+_("Cancel")+'</a>';
+                                result += '<a class="btn btn-default btn-xs submit-form-link" role="button" href="#" data-action="/cgi-bin/koha/admin/background_jobs.pl" data-method="post" data-op="cud-cancel" data-confirmation-msg="[% t('Are you sure you want to cancel this job?') | html %]" data-id="'+ encodeURIComponent(row.job_id) +'"><i class="fa fa-trash-can" aria-hidden="true"></i> '+_("Cancel")+'</a>';
                             }
                             return result;
                         },

From 15c702da914622e510008ad0d2cedb0dfa65f0ae Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 20 Nov 2024 11:51:59 +0100
Subject: [PATCH 403/429] Bug 38437: Auto-show modal on single receive

Might have been broken by the bootstrap 5 upgrade, but not sure.
Not sure why the click does not trigger the modal.

1. Place an order in acquisitions
   1.1. Go to Acquisitions
   1.2. Click 'Search' next to 'Search vendors'
   1.3. Click 'Add to basket' next to 'My basket'
   1.4. Search for an existing record (e.g. search for Shakespeare)
   1.5. Click 'Add order' next to a result or in the detailed record
   1.6. In the item form, choose an item type
   1.7. Click 'Add item'
   1.8. Choose a fund
   1.9. Enter a price in 'Vendor price'
   1.10. Click 'Save'
   1.11. Click 'Close basket'
   1.12. Click 'Yes, close'
2. Receive the order
   2.1. Click 'Receive shipments'
   2.2. Enter a value in 'Vendor invoice'
   2.3. Click 'Next'
   2.4. Click 'Receive' next to your order
   => The modal is shown

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit ef455af3445b824bd97c0db3e60ebe37dc1bdf20)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
index e6e4627031..a466259067 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt
@@ -653,7 +653,7 @@
                 ],
                 [% IF only_one_order %]
                     "drawCallback": function( settings ) {
-                        $(this).find("tbody tr td:last-child a.order_edit_toggle").click();
+                        $("#order_edit").modal("show");
                     },
                 [% END %]
             };

From 37a5cca6cda4a43d6494f57ffd371f8eb6026b65 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Tue, 23 Jul 2024 16:16:57 +0000
Subject: [PATCH 404/429] Bug 37424: Display additional materials note in batch
 checkout

This patch adds a missing conditional for ADDITIONAL_MATERIALS to the batch checkout table

Test plan:
 1. Set a staff member with circ permissions, including FORCE_CHECKOUT
 2. Turn on: CircConfirmItemParts, BatchCheckouts, BatchCheckoutsValidCategories (all)
 3. Log in as staff member in step 1
 4. Attempt to checkout an item with a 952$3 from the batch checkout tab
 5. The item with a 952$3 displays, but the copy/text of the materials specified note does not.
 6. Do not confirm checkout
 7. Apply patch
 8. Try again, the materials note should show this time
 9. Confirm checkout
10. Success! Item is checked out

Signed-off-by: Catrina Berka <catrina@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 03538b59aaa897375e1839e4260ffadac03890ab)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
---
 .../prog/en/modules/circ/circulation_batch_checkouts.tt        | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation_batch_checkouts.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation_batch_checkouts.tt
index a30ae28e07..50851975d9 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation_batch_checkouts.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation_batch_checkouts.tt
@@ -174,6 +174,9 @@
                   [% IF checkout_info.RENEW_ISSUE %]
                       <li><i class="fa fa-li fa-warning"></i>This item is currently checked out to this patron. Renew?</li>
                   [% END %]
+                  [% IF checkout_info.ADDITIONAL_MATERIALS %]
+                      <li><i class="fa fa-li fa-warning"></i>Please confirm that the accompanying materials are present: [%  checkout_info.ADDITIONAL_MATERIALS | html %]</li>
+                  [% END %]
                   [% IF checkout_info.RESERVE_WAITING %]
                       <li><i class="fa fa-li fa-warning"></i>This item is waiting for another patron.</li>
                   [% END %]

From e2dfce5b5df91df1d16730558586791027fe248f Mon Sep 17 00:00:00 2001
From: Wainui Witika-Park <wainuiwitikapark@catalyst.net.nz>
Date: Mon, 16 Dec 2024 14:54:28 +1300
Subject: [PATCH 405/429] Revert "Bug 38272: Add permission check for erm
 permission to additional-fields.tt"

This reverts commit 8de5dd66e6cddf3b603e3c1a70cf9f63cd4a6d1b.
---
 .../intranet-tmpl/prog/en/modules/admin/additional-fields.tt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
index 0cb319a4fe..9cec8aad1c 100755
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
@@ -113,7 +113,7 @@
     [% END %]
 
     [% IF op == 'list_tables' %]
-        [% IF CAN_user_acquisition_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions || CAN_user_erm %]
+        [% IF CAN_user_acquisitions_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions %]
         <h1>Additional fields</h1>
 
         <div class="page-section">

From 9b0eedd47f5220406a2977a537306476b5186233 Mon Sep 17 00:00:00 2001
From: Wainui Witika-Park <wainuiwitikapark@catalyst.net.nz>
Date: Mon, 16 Dec 2024 14:59:41 +1300
Subject: [PATCH 406/429] Revert "Bug 38272: Additionally fix typo in condition
 for acquisition orders permission"

This reverts commit be478eeb66781c26fa6fc3d9b456a59e43301818.
---
 .../intranet-tmpl/prog/en/modules/admin/additional-fields.tt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
index 9cec8aad1c..0008226b6c 100755
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/additional-fields.tt
@@ -113,7 +113,7 @@
     [% END %]
 
     [% IF op == 'list_tables' %]
-        [% IF CAN_user_acquisitions_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions %]
+        [% IF CAN_user_acquisition_order_manage || CAN_user_serials_edit_subscription || CAN_user_updatecharges_remaining_permissions %]
         <h1>Additional fields</h1>
 
         <div class="page-section">

From f8ee288523f7827460dfde88fccfe6a20a584655 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Mon, 25 Nov 2024 15:27:37 +0000
Subject: [PATCH 407/429] Bug 38517: Add the 25.05 release team

This patch updates the teams.yaml to include the voted in 25.05
release team.

Test plan
1/ Check against https://wiki.koha-community.org/wiki/Release_Teams

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 docs/teams.yaml | 84 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/docs/teams.yaml b/docs/teams.yaml
index e33250984e..5233e291ad 100644
--- a/docs/teams.yaml
+++ b/docs/teams.yaml
@@ -1405,6 +1405,8 @@ team:
       - name: Pedro Amorim
       - name: Matt Blenkinsop
       - name: Thomas Klausner
+      - name: Paul Derscheid
+      - name: Chris Cormack
     te:
       - area: UI Design
         name: Owen Leonard
@@ -1432,6 +1434,8 @@ team:
       - name: Kelly McElligott
       - name: Rasa Šatinskienė
       - name: Heather Hernandez
+    translation:
+      name: Jonathan Druart
     meeting_facilitator:
       - name: Paul Derscheid
     packaging:
@@ -1447,3 +1451,83 @@ team:
       - name: Liz Rea
     newsletter:
       name: Michael Kuhn
+  25.05:
+    release_date: 1747868400
+    manager:
+      name: Katrin Fischer
+    manager_assistants:
+      - name: Tomás Cohen Arazi
+      - name: Martin Renvoize
+      - name: Jonathan Druart
+    maintainer:
+      - version: 24.11
+        name: Paul Derscheid
+      - version: 24.05
+        name: Wainui Witika-Park
+      - version: 23.11
+        name: Fridolin Somers
+      - version: 22.11
+      - name: Laura Escamilla
+    qa_manager:
+      name: Martin Renvoize
+    qa:
+      - name: Victor Grousset
+      - name: Lisette Scheer
+      - name: Emily Lamancusa
+      - name: David Cook
+      - name: Jonathan Druart
+      - name: Julian Maurice
+      - name: Baptiste Wojtowski
+      - name: Paul Derscheid
+      - name: Aleisha Amohia
+      - name: Laura Escamilla
+      - name: Tomás Cohen Arazi
+      - name: Kyle M Hall
+      - name: Nick Clemens
+      - name: Lucas Gass
+      - name: Marcel de Rooy
+      - name: Matt Blenkinsop
+      - name: Pedro Amorim
+      - name: Brendan Lawlor
+      - name: Thomas Klausner
+    security_manager:
+      - name: Tomás Cohen Arazi
+    te:
+      - area: UI Design
+        name: Owen Leonard
+      - area: REST API
+        name: Tomás Cohen Arazi
+      - area: Zebra
+        name: Fridolin Somers
+    bugwrangler:
+      - name: Michaela Sieber
+      - name: Jacob O'Mara
+      - name: Jake Deery
+    documentation:
+      name: Philip Orr
+    documentation_team:
+      - name: Aude Charillon
+      - name: David Nind
+      - name: Caroline Cyr La Rose
+    translation:
+      name: Jonathan Druart
+    meeting_facilitator:
+      - name: Paul Derscheid
+    packaging:
+      - name: Mason James
+    ktd:
+      - name: Tomás Cohen Arazi
+    ci:
+      - name: Jonathan Druart
+      - name: Tomás Cohen Arazi
+      - name: Jake Deery
+    wiki:
+      - name: George Williams
+      - name: Thomas Dukleth
+      - name: Jonathan Druart
+      - name: Martin Renvoize
+    website:
+      - name: David Nind
+      - name: Lauren Purton
+    newsletter:
+      name: Michael Kuhn

From 6b066382177e7be6d037a3a71ed94b25359e78a8 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Mon, 25 Nov 2024 15:34:57 +0000
Subject: [PATCH 408/429] Bug 38517: Expose security release manager

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 koha-tmpl/intranet-tmpl/prog/en/includes/about-team.inc | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/about-team.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/about-team.inc
index fbafd90ebe..ff1c6103da 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/about-team.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/about-team.inc
@@ -32,6 +32,7 @@
     [%- CASE 'meeting_facilitator'   -%]<span>Meeting facilitator</span>
     [%- CASE 'ktd'                   -%]<span>Developer tooling</span>
     [%- CASE 'website'               -%]<span>Website administrator</span>
+    [%- CASE 'security_manager'      -%]<span>Security release manager</span>
     [%- CASE -%]<span>MISSING MAP FOR ROLE: [% role.key | html %]</span>
   [%- END -%]
   [%- IF ex %] - [% ex | html %][% END -%]
@@ -78,6 +79,12 @@
             <p>This is the team who are responsible for the next release of Koha and the ongoing maintenance of your
             currently installed Koha version. They will be in these roles up until <strong>[% t.release_date | $KohaDates %]</strong>, when the next team will be elected.</p>
 
+            [% FOREACH s IN t.security_manager %]
+                <li><strong>Security release manager:</strong>
+                      [% INCLUDE person p=s %]
+                </li>
+            [% END %]
+
             [% FOREACH m IN t.maintainer %]
                 [% IF m.version == ( v ) %]
                 <li><strong>Release maintainer:</strong>

From a567f9b818f2e85a4e91228fa3c3e01aff92a30e Mon Sep 17 00:00:00 2001
From: Phil Ringnalda <phil@chetcolibrary.org>
Date: Thu, 5 Sep 2024 17:16:28 -0700
Subject: [PATCH 409/429] Bug 37293: MARC bibliographic framework text for
 librarians and OPAC limited to 100 characters

The database columns for liblibrarian and libopac in marc_tag_structure and
marc_subfield_structure are 255 characters, but the HTML maxlength in
/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marctagstructure.tt and
/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc_subfields_structure.tt
limit tags to 100 characters and subfields to 80 characters.

Test plan:
 1. Apply patch, restart_all
 2. Administration - MARC bibliographic framework - for Default, Actions -
    MARC Structure
 3. For the 000 tag, Actions - Edit tag
 4. In both the "Description in staff interface" and "Description in OPAC"
    paste the 255 character string

1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 12

    and click Save changes
 5. Verify that the display shows all 255 characters, then Actions - Edit tag
    and verify that the two inputs reloaded with all 255 characters, and that
    you cannot type a 256th character, then click Cancel
 6. For the 000 tag, Actions - Edit subfields
 7. Paste the same string in both the staff interface and OPAC inputs, Save
    changes, verify that all 255 characters show, click Edit subfields, verify
    that all 255 characters reloaded in the inputs, verify that you cannot
    type a 256th character

Sponsored-by: Chetco Community Public Library
Signed-off-by: Shi Yao Wang <shi-yao.wang@inlibro.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a9044f4b3487d7b3eea7aaec7653edff48463ea3)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
---
 .../prog/en/modules/admin/marc_subfields_structure.tt         | 4 ++--
 .../intranet-tmpl/prog/en/modules/admin/marctagstructure.tt   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc_subfields_structure.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc_subfields_structure.tt
index 39cbe8a3f1..699df96c25 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc_subfields_structure.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc_subfields_structure.tt
@@ -148,11 +148,11 @@
                                                         [% END %]
                                                         <li>
                                                             <label for="liblibrarian[% loo.row | html %]">Description in staff interface: </label>
-                                                            <input id="liblibrarian[% loo.row | html %]" type="text" name="liblibrarian_[% loo.row | html %]" value="[% loo.liblibrarian | html_entity %]" size="40" maxlength="80" />
+                                                            <input id="liblibrarian[% loo.row | html %]" type="text" name="liblibrarian_[% loo.row | html %]" value="[% loo.liblibrarian | html_entity %]" size="40" maxlength="255" />
                                                         </li>
                                                         <li>
                                                             <label for="libopac[% loo.row | html %]">Description in OPAC: </label>
-                                                            <input type="text" id="libopac[% loo.row | html %]" name="libopac_[% loo.row | html %]" value="[% loo.libopac | html_entity %]" size="40" maxlength="80" />
+                                                            <input type="text" id="libopac[% loo.row | html %]" name="libopac_[% loo.row | html %]" value="[% loo.libopac | html_entity %]" size="40" maxlength="255" />
                                                         </li>
                                                         <li>
                                                             <label for="repeatable[% loo.row | html %]">Repeatable: </label>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marctagstructure.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marctagstructure.tt
index ed8c5993f1..850a8988a7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marctagstructure.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marctagstructure.tt
@@ -133,8 +133,8 @@
                     <span class="required">Required</span>
                 [% END %]
             </li>
-    <li><label for="liblibrarian">Description in staff interface: </label><input type="text" id="liblibrarian" name="liblibrarian" value="[% liblibrarian | html %]" size="40" maxlength="100" /></li>
-    <li><label for="libopac">Description in OPAC: </label><input type="text" id="libopac" name="libopac" value="[% libopac | html %]" size="40" maxlength="100" /></li>
+    <li><label for="liblibrarian">Description in staff interface: </label><input type="text" id="liblibrarian" name="liblibrarian" value="[% liblibrarian | html %]" size="40" maxlength="255" /></li>
+    <li><label for="libopac">Description in OPAC: </label><input type="text" id="libopac" name="libopac" value="[% libopac | html %]" size="40" maxlength="255" /></li>
     <li><label for="repeatable">Repeatable: </label>
         [% IF ( repeatable ) %]
             <input type="checkbox" name="repeatable" id="repeatable" value="1" checked="checked" />

From ba49b71340fda3e37bb339ed81f68d88c8189960 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Kula?=
 <148193449+mkibp@users.noreply.github.com>
Date: Wed, 17 Jul 2024 13:05:02 +0000
Subject: [PATCH 410/429] Bug 37393: fix "In bundle:" link not showing for
 items in staff interface

The cause was erroneous check `[% IF bundlesEnabled %]` before the parent bundle information is requested+shown. The `bundlesEnabled` variable checks if the current biblio ITSELF is of collection type, so should only be used for whether to show the button to add new items to the bundle items, NOT for querying whether current item is part of a bundle, as the items that make up the bundle aren't of collection type themselves.

The second fixed problem was that `bundle_host` didn't contain `.biblio` subitem (which'd contain the actual host biblio title) as the JavaScript code assumed, and it wasn't possible to request it with the API either.

Test plan: please follow the reproduction instructions from bug and ensure that the described issue is gone.

If you run into an error 400 on the detail page, you need to refresh API definitions, in kts shell you'drun:

redocly bundle --ext json api/v1/swagger/swagger.yaml --output api/v1/swagger/swagger_bundle.json
koha-plack --restart kohadev

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 7df4eda9ecb478c5d6dc5672998d2616f14c83f2)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 api/v1/swagger/paths/biblios.yaml                    |  1 +
 .../html_helpers/tables/items/catalogue_detail.inc   | 12 ++++--------
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/api/v1/swagger/paths/biblios.yaml b/api/v1/swagger/paths/biblios.yaml
index 005d75ec23..75e9dad401 100644
--- a/api/v1/swagger/paths/biblios.yaml
+++ b/api/v1/swagger/paths/biblios.yaml
@@ -472,6 +472,7 @@
             - item_type
             - in_bundle
             - bundle_host
+            - bundle_host.biblio
             - bundle_items_lost+count
             - bundle_items_not_lost+count
             - course_item.course_reserves.course
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
index 626886ab59..714e60282d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc
@@ -241,9 +241,7 @@
     [% IF Koha.Preference('UseRecalls') %]
         embed.push('recall', 'recall+strings', 'recall.patron')
     [% END %]
-    [% IF bundlesEnabled %]
-        embed.push('in_bundle', 'bundle_host', 'bundle_items_lost+count', 'bundle_items_not_lost+count');
-    [% END %]
+    embed.push('in_bundle', 'bundle_host', 'bundle_host.biblio', 'bundle_items_lost+count', 'bundle_items_not_lost+count');
     [% IF Koha.Preference('UseCourseReserves') %]
         embed.push('course_item.course_reserves.course');
     [% END %]
@@ -559,11 +557,9 @@
                         nodes += '<span class="restricted">(%s)</span>'.format(escape_str(av_restricted.get(row.restricted_status.toString())));
                     }
 
-                    [% IF bundlesEnabled %]
-                        if ( row.in_bundle ) {
-                            nodes += '<span class="bundled">%s</span>'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true })));
-                        }
-                    [% END %]
+                    if ( row.in_bundle ) {
+                        nodes += '<span class="bundled">%s</span>'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true })));
+                    }
                     return nodes;
                 }
             },

From 29a6f938e4a59a79c5e55bccfbb32c1401e7f211 Mon Sep 17 00:00:00 2001
From: Lucas Gass <lucas@bywatersolutions.com>
Date: Tue, 5 Nov 2024 22:13:32 +0000
Subject: [PATCH 411/429] Bug 38362: Fix printing lists on opac/opac-shelves.pl

To test from the OPAC:
1 - create a list with more than 10 items
2 - print the list -> there is a pagination and that only 10 items
  are printed
3 - Apply patch
4 - print the list again -> every items are being printed

Signed-off-by: Sam Sowanick <sam.sowanick@corvallisoregon.gov>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
[EDIT] Tidied: added a few spaces.
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 8d0e7ff906ae4e6b1367307e7dfdead909676bcb)
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 opac/opac-shelves.pl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/opac/opac-shelves.pl b/opac/opac-shelves.pl
index 728dad57d1..24f4ee90e9 100755
--- a/opac/opac-shelves.pl
+++ b/opac/opac-shelves.pl
@@ -317,7 +317,9 @@ if ( $op eq 'view' ) {
             $sortfield = 'title' if !$sortfield or !grep { $_ eq $sortfield } qw( title author copyrightdate itemcallnumber dateadded );
 
             my $rows;
-            unless ( $query->param('print') or $query->param('rss') ) {
+            if ( $query->param('print') or $query->param('rss') ) {
+                $page = "";
+            } else {
                 $rows = C4::Context->preference('OPACnumSearchResults') || 20;
             }
             my $order_by = $sortfield eq 'itemcallnumber' ? 'items.cn_sort' : $sortfield;

From 63bb6bfd67bb45b5b8bd22c14cf64eb278e69739 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 19 Nov 2024 11:15:54 +0100
Subject: [PATCH 412/429] Bug 38476: Make DT 'Configure' button a link

So that we can open in a separate bug using right click.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit d53784de3a4adeab7b7c6c097883b6b72e52e9d4)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 koha-tmpl/intranet-tmpl/prog/js/datatables.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js
index d8cf30b90b..23959505ef 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js
@@ -758,14 +758,17 @@ function _dt_buttons(params){
     );
 
     if ( table_settings && CAN_user_parameters_manage_column_config ) {
+        let href = '/cgi-bin/koha/admin/columns_settings.pl?module=%s&page=%s&table=%s'.format(table_settings.module, table_settings.page, table_settings.table);
         buttons.push(
             {
+                tag: "a",
+                attr: { href },
                 className: "dt_button_configure_table",
                 fade: 100,
                 titleAttr: __("Configure table"),
                 text: '<i class="fa fa-lg fa-wrench"></i> <span class="dt-button-text">' + __("Configure") + '</span>',
                 action: function() {
-                    window.location = '/cgi-bin/koha/admin/columns_settings.pl?module=' + table_settings['module'] + '&page=' + table_settings['page'] + '&table=' + table_settings['table'];
+                    window.location = href;
                 },
             }
         );

From 48da1efed33b146141469192b6b1bce7d48c1bba Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Tue, 26 Nov 2024 15:57:12 +0000
Subject: [PATCH 413/429] Bug 38536: Patrons requesting modifications: Expand
 correct panel

This patch updates the JavaScript on the patrons requesting modification
page in order to make it work following the upgrade to Bootstrap 5. The
automatic panel expansion is broken.

To test, apply the patch and enable, if necessary, the OPACPatronDetails
system preference.

- Log in to the OPAC and submit changes to your personal details.
- Do this again as a different user so that there is more than one
  pending request.
- From the staff interface home page, follow the link for "Patrons
  requesting modifications"
- Upon loading, the "Update patron records" page should automatically
  expand the first panel.
- Locate the patron record for the second of the patrons you requested
  updates for.
- From that patron's detail page, follow the "Review pending
  modifications" link.
- The panel containing that patron's information should expand by
  default.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit a42686d8910594d674e7bbb8d163351306d3fbb5)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 .../intranet-tmpl/prog/en/modules/members/members-update.tt   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
index 67155008d0..d17fcc39dd 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
@@ -222,9 +222,9 @@
     <script>
         $(document).ready(function(){
             [%- IF ( active ) -%]
-                $("#pending_updates_group a[href='#panel-" + [% active | html %] + "_panel']").click();
+                $("#panel-" + [% active | html %] + "_panel").collapse("show");
             [%- ELSE -%]
-                $("#pending_updates_group a.collapsed:eq(0)").click();
+                $(".panel-collapse:eq(0)").collapse("show");
             [%- END -%]
         });
     </script>

From ce8bb9def8890eec4eb422e7c2c07e70e1b45812 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 27 Nov 2024 09:55:23 +0100
Subject: [PATCH 414/429] Bug 38543: Do not include rowGroup asset files

rowGroup plugin is now part of the datatables.min.js bundle. We no
longer need to include its css and js files, and they no longer exist
anyway!

[2024/11/27 08:52:37] [WARN] File not found : lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css at /kohadevbox/koha/Koha/Template/Plugin/Asset.pm line 107
[2024/11/27 08:52:37] [WARN] File not found : lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js at /kohadevbox/koha/Koha/Template/Plugin/Asset.pm line 84.

Test plan:
Confirm that the checkouts are grouped "today" and "previous" on the
checkout page.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit 4b82e00416c73a0e8a1b1e57ed453863d802bf6d)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: wainuiwitikapark <wainuiwitikapark@catalyst.net.nz>
---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt   | 2 --
 koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt | 2 --
 koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt       | 2 --
 3 files changed, 6 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
index b26efc17a4..1258b08ff8 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
@@ -12,7 +12,6 @@
 [% PROCESS 'i18n.inc' %]
 [% SET footerjs = 1 %]
 [% INCLUDE 'doc-head-open.inc' %]
-[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 [% SET destination = "circ" %]
 <title>[% FILTER collapse %]
     [% IF patron %]
@@ -992,7 +991,6 @@
     [% INCLUDE 'datatables.inc' %]
     [% INCLUDE 'columns_settings.inc' %]
     [% INCLUDE 'select2.inc' %]
-    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% PROCESS 'modal-claims.inc' %]
     [% PROCESS 'modal-claims-js' %]
     [% INCLUDE 'js-date-format.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
index 4debd1abda..5da8da1355 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
@@ -13,7 +13,6 @@
 [% PROCESS 'i18n.inc' %]
 [% SET footerjs = 1 %]
 [% INCLUDE 'doc-head-open.inc' %]
-[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 <title>[% FILTER collapse %]
     [% UNLESS blocking_error %]
         [% t("Patron details for") | html %] [% INCLUDE 'patron-title.inc' no_html = 1 %]
@@ -734,7 +733,6 @@
 
 [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
-    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% INCLUDE 'columns_settings.inc' %]
     [% INCLUDE 'select2.inc' %]
     [% PROCESS 'modal-claims-js' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
index 81de56160b..12283d70a7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
@@ -13,7 +13,6 @@
     [% t("Koha") | html %]
 [% END %]
 [% INCLUDE 'doc-head-close.inc' %]
-[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 
 
 
@@ -350,7 +349,6 @@
 
     [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
-    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% INCLUDE 'format_price.inc' %]
     [% INCLUDE 'js-date-format.inc' %]
     [% Asset.js("js/cashup_modal.js") | $raw %]

From 066b0a3355c371e24756c32f5cccc2e8741256ae Mon Sep 17 00:00:00 2001
From: Tomas Cohen Arazi 
Date: Fri, 29 Nov 2024 12:08:18 -0300
Subject: [PATCH 415/429] Bug 37292: Add an index on
 oauth_access_tokens.expires

This patch adds a needed index to the column.

To test:
1. On a fresh KTD, run:
   $ ktd --shell
  k$ koha-mysql kohadev
   > SHOW CREATE TABLE oauth_access_tokens;
=> FAIL: There's no 'KEY' entry for the `expires` column
2. Apply this patch
3. Run:
  k$ updatedatabase
=> SUCCESS: A message tells the index was added
4. Repeat 1
=> SUCCESS: The index was actually added to the DB
5. Run:
  k$ reset_all
6. Repeat 1
=> SUCCESS: The index is created at install time too!
7. Run:
  k$ updatedatabase
=> SUCCESS: Nothing explodes, no message about index being created
8. Sign off :-D

Signed-off-by: Tomas Cohen Arazi 
Signed-off-by: David Nind 
Signed-off-by: Matt Blenkinsop 
Signed-off-by: Katrin Fischer 
(cherry picked from commit 2e63ece6ae9d560302408a4303df882a47791c87)
Signed-off-by: Paul Derscheid 
Signed-off-by: wainuiwitikapark 
---
 .../data/mysql/atomicupdate/bug_37292.pl      | 20 +++++++++++++++++++
 installer/data/mysql/kohastructure.sql        |  3 ++-
 2 files changed, 22 insertions(+), 1 deletion(-)
 create mode 100755 installer/data/mysql/atomicupdate/bug_37292.pl

diff --git a/installer/data/mysql/atomicupdate/bug_37292.pl b/installer/data/mysql/atomicupdate/bug_37292.pl
new file mode 100755
index 0000000000..934c0c6713
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_37292.pl
@@ -0,0 +1,20 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_success say_info);
+
+return {
+    bug_number  => "37292",
+    description => "'oauth_access_tokens.expires' needs an index",
+    up          => sub {
+        my ($args) = @_;
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+        if ( !index_exists( 'oauth_access_tokens', 'expires' ) ) {
+            $dbh->do(
+                q{
+            ALTER TABLE `oauth_access_tokens` ADD INDEX `expires` (`expires`)
+            }
+            );
+            say_success( $out, "Added new index on 'oauth_access_tokens.expires'" );
+        }
+    },
+};
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index f55cdc5ccf..75c884df48 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -4863,7 +4863,8 @@ CREATE TABLE `oauth_access_tokens` (
   `access_token` varchar(191) NOT NULL COMMENT 'generarated access token',
   `client_id` varchar(191) NOT NULL COMMENT 'the client id the access token belongs to',
   `expires` int(11) NOT NULL COMMENT 'expiration time in seconds',
-  PRIMARY KEY (`access_token`)
+  PRIMARY KEY (`access_token`),
+  KEY `expires` (`expires`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 

From 42c9ef440f703aab0c81fec7473499eaf2e22b0e Mon Sep 17 00:00:00 2001
From: wainuiwitikapark 
Date: Tue, 17 Dec 2024 22:41:54 +0000
Subject: [PATCH 416/429] Bug 37292: DBRev 24.05.05.008

Signed-off-by: wainuiwitikapark 
---
 Koha.pm                                                         | 2 +-
 .../mysql/{atomicupdate/bug_37292.pl => db_revs/240505008.pl}   | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_37292.pl => db_revs/240505008.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index 735048d364..eb27598548 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.007";
+$VERSION = "24.05.05.008";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_37292.pl b/installer/data/mysql/db_revs/240505008.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_37292.pl
rename to installer/data/mysql/db_revs/240505008.pl

From b0693468ffcb488335da4b1c1368c769e5284b6c Mon Sep 17 00:00:00 2001
From: Andrew Fuerste Henry 
Date: Fri, 22 Nov 2024 20:08:36 +0000
Subject: [PATCH 417/429] Bug 38522: Increase erm_agreements.license_info
 length

to test:
1- try to save an agreement with 81+ characters in License Info
2- it does not save
3- apply patch, updatedatabase
4- repeat 1, it works!

Signed-off-by: Lucas Gass 
Signed-off-by: Katrin Fischer 
(cherry picked from commit 2512392b2a8fc5f6485bb64cc90376067446b56e)
Signed-off-by: Paul Derscheid 
Signed-off-by: wainuiwitikapark 
---
 .../bug_38522_increase_field_length.pl         | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
 create mode 100755 installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl

diff --git a/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl b/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
new file mode 100755
index 0000000000..28a0b6a201
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
@@ -0,0 +1,18 @@
+use Modern::Perl;
+use Koha::Installer::Output qw(say_warning say_success say_info);
+
+return {
+    bug_number  => "38522",
+    description => "increase erm_agreements.license_info length",
+    up          => sub {
+        my ($args) = @_;
+        my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+        # Do you stuffs here
+        $dbh->do(q{ALTER TABLE erm_agreements MODIFY COLUMN license_info mediumtext});
+
+        # Print useful stuff here
+        # tables
+        say $out "Set erm_agreements.license_info to type mediumtext.";
+    },
+};

From 789e57210b3ec5542244e71df04d4db036a7a124 Mon Sep 17 00:00:00 2001
From: Andrew Fuerste Henry 
Date: Fri, 22 Nov 2024 20:10:24 +0000
Subject: [PATCH 418/429] Bug 38522: update kohastructure.sql

Signed-off-by: Lucas Gass 
Signed-off-by: Katrin Fischer 
(cherry picked from commit 73c7acd33f005eebe9e1338b263cec95e1099d48)
Signed-off-by: Paul Derscheid 
Signed-off-by: wainuiwitikapark 
---
 installer/data/mysql/kohastructure.sql | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 75c884df48..dba807a5d8 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -2966,7 +2966,7 @@ CREATE TABLE `erm_agreements` (
   `closure_reason` varchar(80) DEFAULT NULL COMMENT 'reason of the closure',
   `is_perpetual` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is the agreement perpetual',
   `renewal_priority` varchar(80) DEFAULT NULL COMMENT 'priority of the renewal',
-  `license_info` varchar(80) DEFAULT NULL COMMENT 'info about the license',
+  `license_info` mediumtext DEFAULT NULL COMMENT 'info about the license',
   PRIMARY KEY (`agreement_id`),
   KEY `erm_agreements_ibfk_1` (`vendor_id`),
   CONSTRAINT `erm_agreements_ibfk_1` FOREIGN KEY (`vendor_id`) REFERENCES `aqbooksellers` (`id`) ON DELETE SET NULL ON UPDATE CASCADE

From 20c22b5488cc2eeee8d2cec752818487d1533394 Mon Sep 17 00:00:00 2001
From: Andrew Fuerste Henry 
Date: Mon, 9 Dec 2024 19:38:50 +0000
Subject: [PATCH 419/429] Bug 38522: (follow-up) Edit db_rev and preserve
 comment

Signed-off-by: Katrin Fischer 
(cherry picked from commit 1a4820322e528fdea4ac6dd212254ca3021d30a2)
Signed-off-by: Paul Derscheid 
Signed-off-by: wainuiwitikapark 
---
 .../mysql/atomicupdate/bug_38522_increase_field_length.pl | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl b/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
index 28a0b6a201..2502fdca5d 100755
--- a/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
+++ b/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
@@ -8,11 +8,7 @@ return {
         my ($args) = @_;
         my ( $dbh, $out ) = @$args{qw(dbh out)};
 
-        # Do you stuffs here
-        $dbh->do(q{ALTER TABLE erm_agreements MODIFY COLUMN license_info mediumtext});
-
-        # Print useful stuff here
-        # tables
-        say $out "Set erm_agreements.license_info to type mediumtext.";
+        $dbh->do(q{ALTER TABLE erm_agreements MODIFY COLUMN license_info mediumtext DEFAULT NULL COMMENT 'info about the license'});
+        say_success( $out, "Updated erm_agreements.license_info to mediumtext." );
     },
 };

From 279d05ceae36da5f474c0cd9dc69480cce899c1d Mon Sep 17 00:00:00 2001
From: wainuiwitikapark 
Date: Tue, 17 Dec 2024 23:36:21 +0000
Subject: [PATCH 420/429] Bug 38522: DBRev 24.05.05.009

Signed-off-by: wainuiwitikapark 
---
 Koha.pm                                                         | 2 +-
 .../bug_38522_increase_field_length.pl => db_revs/240505009.pl} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename installer/data/mysql/{atomicupdate/bug_38522_increase_field_length.pl => db_revs/240505009.pl} (100%)

diff --git a/Koha.pm b/Koha.pm
index eb27598548..08eb2a2481 100644
--- a/Koha.pm
+++ b/Koha.pm
@@ -29,7 +29,7 @@ use vars qw{ $VERSION };
 # - #4 : the developer version. The 4th number is the database subversion.
 #        used by developers when the database changes. updatedatabase take care of the changes itself
 #        and is automatically called by Auth.pm when needed.
-$VERSION = "24.05.05.008";
+$VERSION = "24.05.05.009";
 
 sub version {
     return $VERSION;
diff --git a/installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl b/installer/data/mysql/db_revs/240505009.pl
similarity index 100%
rename from installer/data/mysql/atomicupdate/bug_38522_increase_field_length.pl
rename to installer/data/mysql/db_revs/240505009.pl

From 6d11eec626077e1d2aed51764de789dff7e695fc Mon Sep 17 00:00:00 2001
From: wainuiwitikapark 
Date: Tue, 17 Dec 2024 23:37:59 +0000
Subject: [PATCH 421/429] Bug 38522: DBIC schema update

Signed-off-by: wainuiwitikapark 
---
 Koha/Schema/Result/ErmAgreement.pm | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/Koha/Schema/Result/ErmAgreement.pm b/Koha/Schema/Result/ErmAgreement.pm
index b2b97e4f3e..9f80140770 100644
--- a/Koha/Schema/Result/ErmAgreement.pm
+++ b/Koha/Schema/Result/ErmAgreement.pm
@@ -88,9 +88,8 @@ priority of the renewal
 
 =head2 license_info
 
-  data_type: 'varchar'
+  data_type: 'mediumtext'
   is_nullable: 1
-  size: 80
 
 info about the license
 
@@ -114,7 +113,7 @@ __PACKAGE__->add_columns(
   "renewal_priority",
   { data_type => "varchar", is_nullable => 1, size => 80 },
   "license_info",
-  { data_type => "varchar", is_nullable => 1, size => 80 },
+  { data_type => "mediumtext", is_nullable => 1 },
 );
 
 =head1 PRIMARY KEY
@@ -267,8 +266,8 @@ Composing rels: L -> package
 __PACKAGE__->many_to_many("packages", "erm_eholdings_packages_agreements", "package");
 
 
-# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-11-11 11:52:09
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:N93LnvdKirtuV6BSrTGzVg
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-17 23:37:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lc0DnoQ5ai9sVBjczw6FdQ
 
 __PACKAGE__->has_many(
   "user_roles",

From 4588f793f1976361d50e9e304d59e6af0b72e633 Mon Sep 17 00:00:00 2001
From: Lucas Gass 
Date: Wed, 11 Dec 2024 22:34:23 +0000
Subject: [PATCH 422/429] Bug 38680: Dont convert the copynumber

Follow the test plan from Bug 38326.

Signed-off-by: Brendan Lawlor 
Signed-off-by: Emily Lamancusa 
Signed-off-by: wainuiwitikapark 
---
 acqui/addorderiso2709.pl | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/acqui/addorderiso2709.pl b/acqui/addorderiso2709.pl
index 4948beaab0..84e3949527 100755
--- a/acqui/addorderiso2709.pl
+++ b/acqui/addorderiso2709.pl
@@ -532,10 +532,9 @@ sub import_biblios_list {
                 my $quantity = delete $iteminfos->{quantity} || 1;
 
                 # Handle incorrectly named original parameters for MarcItemFieldsToOrder
-                $iteminfos->{location}   = delete $iteminfos->{loc}    if $iteminfos->{loc};
-                $iteminfos->{copynumber} = delete $iteminfos->{copyno} if $iteminfos->{copyno};
+                $iteminfos->{location}  = delete $iteminfos->{loc}   if $iteminfos->{loc};
                 # Price is handled as "itemprice" in the UI form to distinguish from MarcFieldsToOrder
-                $iteminfos->{itemprice}  = delete $iteminfos->{price}  if $iteminfos->{itemprice};
+                $iteminfos->{itemprice} = delete $iteminfos->{price} if $iteminfos->{itemprice};
 
                 # Convert budge code to a budget id
                 my $item_budget_code = delete $iteminfos->{budget_code};

From 9fc5936df210760780dba939a226869e7dac1546 Mon Sep 17 00:00:00 2001
From: Wainui Witika-Park 
Date: Thu, 19 Dec 2024 14:02:22 +1300
Subject: [PATCH 423/429] Revert "Bug 37912: Restore detail-trigger handling"

This reverts commit 71900a95087091f06f191a30118e49245bad1fd9.
---
 .../prog/js/modals/display_ticket.js          | 19 +++++--------------
 1 file changed, 5 insertions(+), 14 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js b/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
index bd9f07a4f9..023b1a0b3a 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/modals/display_ticket.js
@@ -1,20 +1,11 @@
 /* keep tidy */
 $(document).ready(function () {
-    document.addEventListener("click", function (event) {
-        const detailTrigger = event.target.closest(".detail-trigger");
-        if (detailTrigger) {
-            event.preventDefault();
+    $("#table_concerns").on("click", ".detail-trigger", function () {
+        // Find the main trigger anchor within the same table row
+        var $mainTrigger = $(this).closest("tr").find(".main-trigger");
 
-            // Find the main trigger button in the same row
-            const mainTrigger = detailTrigger
-                .closest("tr")
-                .querySelector(".main-trigger");
-
-            // Trigger a click on the main trigger if it exists
-            if (mainTrigger) {
-                mainTrigger.click();
-            }
-        }
+        // Trigger the click event of the main trigger anchor
+        $mainTrigger.trigger("click");
     });
 
     $("#ticketDetailsModal").on("show.bs.modal", function (event) {

From b936aaf4428b6adbe8016c844c3c213a8cfd53bf Mon Sep 17 00:00:00 2001
From: Wainui Witika-Park 
Date: Thu, 19 Dec 2024 15:46:13 +1300
Subject: [PATCH 424/429] Revert "Bug 38536: Patrons requesting modifications:
 Expand correct panel"

This reverts commit 48da1efed33b146141469192b6b1bce7d48c1bba.
---
 .../intranet-tmpl/prog/en/modules/members/members-update.tt   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
index d17fcc39dd..67155008d0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
@@ -222,9 +222,9 @@
     

From e12fa4769c4460293b91abdd48f1c1f59b4c8680 Mon Sep 17 00:00:00 2001
From: Wainui Witika-Park 
Date: Thu, 19 Dec 2024 15:48:22 +1300
Subject: [PATCH 425/429] Revert "Bug 38543: Do not include rowGroup asset
 files"

This reverts commit ce8bb9def8890eec4eb422e7c2c07e70e1b45812.
---
 koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt   | 2 ++
 koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt | 2 ++
 koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt       | 2 ++
 3 files changed, 6 insertions(+)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
index 1258b08ff8..b26efc17a4 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
@@ -12,6 +12,7 @@
 [% PROCESS 'i18n.inc' %]
 [% SET footerjs = 1 %]
 [% INCLUDE 'doc-head-open.inc' %]
+[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 [% SET destination = "circ" %]
 [% FILTER collapse %]
     [% IF patron %]
@@ -991,6 +992,7 @@
     [% INCLUDE 'datatables.inc' %]
     [% INCLUDE 'columns_settings.inc' %]
     [% INCLUDE 'select2.inc' %]
+    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% PROCESS 'modal-claims.inc' %]
     [% PROCESS 'modal-claims-js' %]
     [% INCLUDE 'js-date-format.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
index 5da8da1355..4debd1abda 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
@@ -13,6 +13,7 @@
 [% PROCESS 'i18n.inc' %]
 [% SET footerjs = 1 %]
 [% INCLUDE 'doc-head-open.inc' %]
+[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 <title>[% FILTER collapse %]
     [% UNLESS blocking_error %]
         [% t("Patron details for") | html %] [% INCLUDE 'patron-title.inc' no_html = 1 %]
@@ -733,6 +734,7 @@
 
 [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
+    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% INCLUDE 'columns_settings.inc' %]
     [% INCLUDE 'select2.inc' %]
     [% PROCESS 'modal-claims-js' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
index 12283d70a7..81de56160b 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt
@@ -13,6 +13,7 @@
     [% t("Koha") | html %]
 [% END %]
 [% INCLUDE 'doc-head-close.inc' %]
+[% Asset.css("lib/jquery/plugins/rowGroup/stylesheets/rowGroup.dataTables.min.css") | $raw %]
 
 
 
@@ -349,6 +350,7 @@
 
     [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
+    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
     [% INCLUDE 'format_price.inc' %]
     [% INCLUDE 'js-date-format.inc' %]
     [% Asset.js("js/cashup_modal.js") | $raw %]

From 021257fe2ed6297dee804d3c9ec3d20681aa94be Mon Sep 17 00:00:00 2001
From: Alex Buckley 
Date: Fri, 27 Dec 2024 09:29:33 +1300
Subject: [PATCH 426/429] Revert "Bug 37478: Rename strict to skip_bad_records
 and add POD"

This reverts commit b3dd152c5ebb22c8755838ce2d523dd35c9583d6.
---
 misc/migration_tools/bulkmarcimport.pl | 80 ++++++++++++--------------
 1 file changed, 37 insertions(+), 43 deletions(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 0cac3d191e..9172bc10df 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -77,45 +77,45 @@ my $localcust;
 my $marc_mod_template    = '';
 my $marc_mod_template_id = -1;
 my $skip_indexing        = 0;
-my $skip_bad_records;
+my $strict_mode;
 $| = 1;
 
 GetOptions(
-    'commit:f'            => \$commit,
-    'file:s'              => \$input_marc_file,
-    'n:f'                 => \$number,
-    'o|offset:f'          => \$offset,
-    'h|help'              => \$version,
-    'd|delete'            => \$delete,
-    't|test'              => \$test_parameter,
-    's'                   => \$skip_marc8_conversion,
-    'c:s'                 => \$char_encoding,
-    'v|verbose:+'         => \$verbose,
-    'fk'                  => \$fk_off,
-    'm:s'                 => \$format,
-    'l:s'                 => \$logfile,
-    'append'              => \$append,
-    'k|keepids:s'         => \$keepids,
-    'b|biblios'           => \$biblios,
-    'a|authorities'       => \$authorities,
-    'authtypes:s'         => \$authtypes,
-    'filter=s@'           => \$filters,
-    'insert'              => \$insert,
-    'update'              => \$update,
-    'all'                 => \$all,
-    'match=s@'            => \$match,
-    'i|isbn'              => \$isbn_check,
-    'x:s'                 => \$sourcetag,
-    'y:s'                 => \$sourcesubfield,
-    'idmap:s'             => \$idmapfl,
-    'cleanisbn!'          => \$cleanisbn,
-    'yaml:s'              => \$yamlfile,
-    'dedupbarcode'        => \$dedup_barcode,
-    'framework=s'         => \$framework,
-    'custom:s'            => \$localcust,
-    'marcmodtemplate:s'   => \$marc_mod_template,
-    'si|skip_indexing'    => \$skip_indexing,
-    'sk|skip_bad_records' => \$skip_bad_records,
+    'commit:f'          => \$commit,
+    'file:s'            => \$input_marc_file,
+    'n:f'               => \$number,
+    'o|offset:f'        => \$offset,
+    'h|help'            => \$version,
+    'd|delete'          => \$delete,
+    't|test'            => \$test_parameter,
+    's'                 => \$skip_marc8_conversion,
+    'c:s'               => \$char_encoding,
+    'v|verbose:+'       => \$verbose,
+    'fk'                => \$fk_off,
+    'm:s'               => \$format,
+    'l:s'               => \$logfile,
+    'append'            => \$append,
+    'k|keepids:s'       => \$keepids,
+    'b|biblios'         => \$biblios,
+    'a|authorities'     => \$authorities,
+    'authtypes:s'       => \$authtypes,
+    'filter=s@'         => \$filters,
+    'insert'            => \$insert,
+    'update'            => \$update,
+    'all'               => \$all,
+    'match=s@'          => \$match,
+    'i|isbn'            => \$isbn_check,
+    'x:s'               => \$sourcetag,
+    'y:s'               => \$sourcesubfield,
+    'idmap:s'           => \$idmapfl,
+    'cleanisbn!'        => \$cleanisbn,
+    'yaml:s'            => \$yamlfile,
+    'dedupbarcode'      => \$dedup_barcode,
+    'framework=s'       => \$framework,
+    'custom:s'          => \$localcust,
+    'marcmodtemplate:s' => \$marc_mod_template,
+    'si|skip_indexing'  => \$skip_indexing,
+    'st|strict'         => \$strict_mode,
 );
 
 $biblios ||= !$authorities;
@@ -343,7 +343,7 @@ RECORD: while () {
     }
     if ($record) {
 
-        if ($skip_bad_records) {
+        if ($strict_mode) {
             my $xml = $record->as_xml_record();
             eval { MARC::Record::new_from_xml( $xml, 'UTF-8', "MARC21" ); };
             if ($@) {
@@ -1046,12 +1046,6 @@ If set, do not index the imported records with Zebra or Elasticsearch.
 Use this when you plan to do a complete reindex of your data after running
 bulkmarciport. This can increase performance and avoid unnecessary load.
 
-=item B<-sk, --skip_bad_records>
-
-If set, check the validity of records before adding. If they are invalid we will
-print the output of MARC::Lint->check_record and skip them during the import. Without
-this option bad records may kill the job.
-
 =back
 
 =cut

From 0d08c9d2e4da90d8ae8403fd93abf06673b631a0 Mon Sep 17 00:00:00 2001
From: Alex Buckley 
Date: Fri, 27 Dec 2024 09:29:45 +1300
Subject: [PATCH 427/429] Revert "Bug 37478: (follow-up) Add MARC::Lint to
 cpanfile"

This reverts commit 2e252974f81d1d65ea42dfc7577a73807fc3570c.
---
 cpanfile | 1 -
 1 file changed, 1 deletion(-)

diff --git a/cpanfile b/cpanfile
index 6b33fb1e22..22d2d194ab 100644
--- a/cpanfile
+++ b/cpanfile
@@ -74,7 +74,6 @@ requires 'Locale::PO', '0.24';
 requires 'Log::Log4perl', '1.29';
 requires 'MARC::Charset', '0.98';
 requires 'MARC::File::XML', 'v1.0.1';
-requires 'MARC::Lint', 'v1.52';
 requires 'MARC::Record', 'v2.0.6';
 requires 'MARC::Record::MiJ', '0.04';
 requires 'MIME::Base64', '3.07';

From 987c61364f2cdced6a0931c0871a106b67821143 Mon Sep 17 00:00:00 2001
From: Alex Buckley 
Date: Fri, 27 Dec 2024 09:29:55 +1300
Subject: [PATCH 428/429] Revert "Bug 37478: Add strict mode to bulkmarcimport"

This reverts commit 49fef9bee9c3c2249ea11333bf66d18cd9279418.
---
 misc/migration_tools/bulkmarcimport.pl | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/misc/migration_tools/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl
index 9172bc10df..4a8cb041b9 100755
--- a/misc/migration_tools/bulkmarcimport.pl
+++ b/misc/migration_tools/bulkmarcimport.pl
@@ -9,7 +9,6 @@ use Modern::Perl;
 use MARC::File::USMARC;
 use MARC::File::XML;
 use MARC::Batch;
-use MARC::Lint;
 use Encode;
 
 use Koha::Script;
@@ -77,7 +76,6 @@ my $localcust;
 my $marc_mod_template    = '';
 my $marc_mod_template_id = -1;
 my $skip_indexing        = 0;
-my $strict_mode;
 $| = 1;
 
 GetOptions(
@@ -115,7 +113,6 @@ GetOptions(
     'custom:s'          => \$localcust,
     'marcmodtemplate:s' => \$marc_mod_template,
     'si|skip_indexing'  => \$skip_indexing,
-    'st|strict'         => \$strict_mode,
 );
 
 $biblios ||= !$authorities;
@@ -323,7 +320,6 @@ my $record_number = 0;
 my $logger        = Koha::Logger->get;
 my $schema        = Koha::Database->schema;
 my $marc_records  = [];
-my $lint          = MARC::Lint->new;
 RECORD: while () {
     my $record;
     $record_number++;
@@ -343,19 +339,6 @@ RECORD: while () {
     }
     if ($record) {
 
-        if ($strict_mode) {
-            my $xml = $record->as_xml_record();
-            eval { MARC::Record::new_from_xml( $xml, 'UTF-8', "MARC21" ); };
-            if ($@) {
-                print "Record $record_number generated invalid xml:\n";
-                $lint->check_record($record);
-                foreach my $warning ( $lint->warnings ) {
-                    print "    " . $warning . "\n";
-                }
-                print "    Record skipped!";
-                next;
-            }
-        }
         # transcode the record to UTF8 if needed & applicable.
         if ( $record->encoding() eq 'MARC-8' and not $skip_marc8_conversion ) {
             my ( $guessed_charset, $charset_errors );

From fca32a307ca485b19f051c964fa275c28ec33ebf Mon Sep 17 00:00:00 2001
From: David Cook 
Date: Fri, 14 Jun 2024 04:34:47 +0000
Subject: [PATCH 429/429] Bug 37087: Add TCP keepalive support to SIP server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change adds the ability to enable and configure TCP keepalive
support for the SIP server using SIPconfig.xml.

For the sake of backwards compatibility, it defaults to disabled
and additional parameters default match typical kernel defaults.

Technical detail can be found in the perldoc for C4/SIP/SIPserver.pm

Test plan:
0. Apply the patch
1. koha-sip --restart kohadev
2. apt-get update && apt-get install tcpdump
3. In one window, run "tcpdump -A -n -v -i any 'port 6001'"
4. In another window, run the following:
echo -e "9300CNterm1|COterm1|CPCPL|\r" | nc 127.0.0.1 6001 -v
5. Note in tcpdump output that after the initial flood of packets,
nothing more is received

6. vi /etc/koha/sites/kohadev/SIPconfig.xml
7. In the "server-params" element, add attributes like the following:
custom_tcp_keepalive='1'
custom_tcp_keepalive_time='10'
custom_tcp_keepalive_intvl='5'
8. koha-sip --restart kohadev
9. In one window, run "tcpdump -A -n -v -i any 'port 6001'"
10. In another window, run the following:
echo -e "9300CNterm1|COterm1|CPCPL|\r" | nc 127.0.0.1 6001 -v
11. Note in tcpdump output that after the initial flood of packets,
ACK packets are sent out every 10+ seconds for the idle connection

Signed-off-by: Tadeusz „tadzik” Sośnierz 
Signed-off-by: Martin Renvoize 
Signed-off-by: Katrin Fischer 
Signed-off-by: Alex Buckley 
---
 C4/SIP/SIPServer.pm            | 122 ++++++++++++++++++++++++++++++++-
 debian/templates/SIPconfig.xml |   3 +
 etc/SIPconfig.xml              |   3 +
 3 files changed, 127 insertions(+), 1 deletion(-)

diff --git a/C4/SIP/SIPServer.pm b/C4/SIP/SIPServer.pm
index d26f5e25e6..96c1809fbe 100644
--- a/C4/SIP/SIPServer.pm
+++ b/C4/SIP/SIPServer.pm
@@ -8,7 +8,7 @@ use FindBin qw($Bin);
 use lib "$Bin";
 use Net::Server::PreFork;
 use IO::Socket::INET;
-use Socket qw(:DEFAULT :crlf);
+use Socket qw(:DEFAULT :crlf TCP_KEEPIDLE TCP_KEEPINTVL IPPROTO_TCP);
 use Scalar::Util qw(blessed);
 require UNIVERSAL::require;
 
@@ -90,6 +90,126 @@ push @parms, 'group=' . $>;
 # This is the main event.
 __PACKAGE__ ->run(@parms);
 
+#
+# Server
+#
+
+=head3 options
+
+As per Net::Server documentation, override "options" to provide your own
+custom options to the Net::Server* object. This allows us to use the Net::Server
+infrastructure for configuration rather than hacking our own configuration into the
+object.
+
+=cut
+
+sub options {
+    my $self     = shift;
+    my $prop     = $self->{'server'};
+    my $template = shift;
+
+    # setup options in the parent classes
+    $self->SUPER::options($template);
+
+    $prop->{'custom_tcp_keepalive'} ||= undef;
+    $template->{'custom_tcp_keepalive'} = \$prop->{'custom_tcp_keepalive'};
+
+    $prop->{'custom_tcp_keepalive_time'} ||= undef;
+    $template->{'custom_tcp_keepalive_time'} = \$prop->{'custom_tcp_keepalive_time'};
+
+    $prop->{'custom_tcp_keepalive_intvl'} ||= undef;
+    $template->{'custom_tcp_keepalive_intvl'} = \$prop->{'custom_tcp_keepalive_intvl'};
+}
+
+=head3 post_configure_hook
+
+As per Net::Server documentation, this method validates our custom configuration.
+
+=cut
+
+sub post_configure_hook {
+    my $self = shift;
+    my $prop = $self->{'server'};
+    if ( defined $prop->{'custom_tcp_keepalive'} && $prop->{'custom_tcp_keepalive'} ) {
+
+        #NOTE: Any true value defined is forced to 1 just for the sake of predictability
+        $prop->{'custom_tcp_keepalive'} = 1;
+    }
+
+    foreach my $key ( 'custom_tcp_keepalive_time', 'custom_tcp_keepalive_intvl' ) {
+        my $value = $prop->{$key};
+
+        #NOTE: A regex is used here to detect a positive integer, as int() returns integers but does not validate them
+        #NOTE: We do not allow zero as it can lead to problematic behaviour
+        if ( $value && $value =~ /^\d+$/ ) {
+
+            #NOTE: Strictly, you must convert into an integer as a string will cause setsockopt to fail
+            $prop->{$key} = int($value);
+        }
+    }
+}
+
+=head3 post_accept_hook
+
+This hook occurs after the client connection socket is created, which gives
+us an opportunity to enable support for TCP keepalives using the SO_KEEPALIVE
+socket option.
+
+By default, the kernel-level defaults (in seconds) are used. You can view these in the output of "sysctl -a":
+net.ipv4.tcp_keepalive_intvl = 75
+net.ipv4.tcp_keepalive_time = 7200
+
+Alternatively, you can use "custom_tcp_keepalive_time" and "custom_tcp_keepalive_intvl" to define
+your own custom values for the socket. Note that these parameters are defined at the top server-level
+and not on the listener-level.
+
+If you lower "custom_tcp_keepalive_time" below 75, you will also need to set "custom_tcp_keepalive_intvl".
+The "tcp_keepalive_time" is the initial time used for the keepalive timer, and the "tcp_keepalive_intvl"
+is the time used for subsequent keepalive timers. However, timers only send keepalive ACKs if the idle time
+elapsed is greater than "tcp_keepalive_time".
+
+Thus, if "tcp_keepalive_time = 10" and "tcp_keepalive_intvl = 5", a keepalive ACK will be sent every 10 seconds
+of idle time. If "tcp_keepalive_intvl = 10" and "tcp_keepalive_time = 5", a keepalive ACK will be sent after 5
+seconds of idle time, and the next keepalive ACK will be sent after 10 seconds of idle time. Generally speaking,
+it's best to set "tcp_keepalive_time" to be higher than "tcp_keepalive_intvl".
+
+Reminder: once these settings are set on the socket, they are handled by the operating system kernel, and not
+by the SIP server application. If you are having trouble with your settings, monitor your TCP traffic using
+a tool such as "tcpdump" to review and refine how they work.
+
+=cut
+
+sub post_accept_hook {
+    my $self   = shift;
+    my $prop   = $self->{'server'};
+    my $client = shift || $prop->{'client'};
+
+    my $tcp_keepalive       = $prop->{custom_tcp_keepalive};
+    my $tcp_keepalive_time  = $prop->{custom_tcp_keepalive_time};
+    my $tcp_keepalive_intvl = $prop->{custom_tcp_keepalive_intvl};
+
+    if ($tcp_keepalive) {
+
+        #NOTE: set the SO_KEEPALIVE option to enable TCP keepalives
+        setsockopt( $client, SOL_SOCKET, SO_KEEPALIVE, 1 )
+            or die "Unable to set SO_KEEPALIVE: $!";
+
+        if ($tcp_keepalive_time) {
+
+            #NOTE: override "net.ipv4.tcp_keepalive_time" kernel parameter for this socket
+            setsockopt( $client, IPPROTO_TCP, TCP_KEEPIDLE, $tcp_keepalive_time )
+                or die "Unable to set TCP_KEEPIDLE: $!";
+        }
+
+        if ($tcp_keepalive_intvl) {
+
+            #NOTE: override "net.ipv4.tcp_keepalive_intvl" kernel parameter for this socket
+            setsockopt( $client, IPPROTO_TCP, TCP_KEEPINTVL, $tcp_keepalive_intvl )
+                or die "Unable to set TCP_KEEPINTVL: $!";
+        }
+    }
+}
+
 #
 # Child
 #
diff --git a/debian/templates/SIPconfig.xml b/debian/templates/SIPconfig.xml
index e79a2950b6..f0a408c67b 100644
--- a/debian/templates/SIPconfig.xml
+++ b/debian/templates/SIPconfig.xml
@@ -10,6 +10,9 @@
   
 
   
diff --git a/etc/SIPconfig.xml b/etc/SIPconfig.xml
index c353cb6b28..1988477691 100644
--- a/etc/SIPconfig.xml
+++ b/etc/SIPconfig.xml
@@ -19,6 +19,9 @@
     user='koha'
     group='koha'
     pid_file='/var/run/sipserver.pid'
+    custom_tcp_keepalive='0'
+    custom_tcp_keepalive_time='7200'
+    custom_tcp_keepalive_intvl='75'
   />