diff --git a/C4/Letters.pm b/C4/Letters.pm index a4b6d7f07f..16d75015fa 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -1739,8 +1739,14 @@ sub _process_tt { my $use_template_cache = C4::Context->config('template_cache_dir') && defined $ENV{GATEWAY_INTERFACE}; my $template = Template->new( { - EVAL_PERL => 0, - ABSOLUTE => 0, + EVAL_PERL => 0, + ABSOLUTE => 0, + RELATIVE => 0, + PLUGINS => { + cgi => 'Koha::Template::Plugin::noop', + directory => 'Koha::Template::Plugin::noop', + file => 'Koha::Template::Plugin::noop', + }, PLUGIN_BASE => 'Koha::Template::Plugin', COMPILE_EXT => $use_template_cache ? '.ttc' : '', COMPILE_DIR => $use_template_cache ? C4::Context->config('template_cache_dir') : '', diff --git a/Koha.pm b/Koha.pm index 1cdfae13c3..2bc40a5125 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.11.14.000"; +$VERSION = "24.11.15.000"; sub version { return $VERSION; diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 705caa96ad..76c8ba489e 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -120,23 +120,46 @@ sub new { =head3 fixup_cardnumber -Autogenerate next cardnumber from highest value found in database +Autogenerate next cardnumber from highest value found in database. + +Uses the C system preference as a monotonic floor so a +cardnumber is never re-issued after the original holder's cardnumber has been +changed or their account deleted. The syspref row is selected C +inside the enclosing transaction so that concurrent C callers are +serialised. =cut sub fixup_cardnumber { my ( $self ) = @_; - my $max = $self->cardnumber; - Koha::Plugins->call( 'patron_barcode_transform', \$max ); + my $next = $self->cardnumber; + Koha::Plugins->call( 'patron_barcode_transform', \$next ); - $max ||= Koha::Patrons->search({ - cardnumber => {-regexp => '^-?[0-9]+$'} - }, { - select => \'CAST(cardnumber AS SIGNED)', - as => ['cast_cardnumber'] - })->_resultset->get_column('cast_cardnumber')->max; - $self->cardnumber(($max || 0) +1); + # Only auto-generate when no cardnumber has been supplied (directly or by a plugin). + unless ($next) { + my $schema = $self->_result->result_source->schema; + my $pref_rs = $schema->resultset('Systempreference')->search( + { variable => 'autoMemberNumValue' }, + { for => 'update' }, + ); + my $pref = $pref_rs->first; + my $counter = ( $pref && $pref->value ) || 0; + + my $db_max = Koha::Patrons->search( + { cardnumber => { -regexp => '^-?[0-9]+$' } }, + { + select => \'CAST(cardnumber AS SIGNED)', + as => ['cast_cardnumber'], + } + )->_resultset->get_column('cast_cardnumber')->max + || 0; + + $next = ( $counter > $db_max ? $counter : $db_max ) + 1; + $pref->update( { value => $next } ) if $pref; + } + + $self->cardnumber($next); } =head3 trim_whitespace diff --git a/Koha/Template/Plugin/noop.pm b/Koha/Template/Plugin/noop.pm new file mode 100644 index 0000000000..ab9f012bf8 --- /dev/null +++ b/Koha/Template/Plugin/noop.pm @@ -0,0 +1,7 @@ +package Koha::Template::Plugin::noop; + +use Modern::Perl; + +use base qw( Template::Plugin ); + +1; diff --git a/Koha/TemplateUtils.pm b/Koha/TemplateUtils.pm index 3d2b198f31..85b3764598 100644 --- a/Koha/TemplateUtils.pm +++ b/Koha/TemplateUtils.pm @@ -61,8 +61,14 @@ sub process_tt { my $tt = Template->new( { - EVAL_PERL => 0, - ABSOLUTE => 0, + EVAL_PERL => 0, + ABSOLUTE => 0, + RELATIVE => 0, + PLUGINS => { + cgi => 'Koha::Template::Plugin::noop', + directory => 'Koha::Template::Plugin::noop', + file => 'Koha::Template::Plugin::noop', + }, PLUGIN_BASE => 'Koha::Template::Plugin', COMPILE_EXT => $use_template_cache ? '.ttc' : '', COMPILE_DIR => $use_template_cache ? C4::Context->config('template_cache_dir') : '', diff --git a/installer/data/mysql/db_revs/241114001.pl b/installer/data/mysql/db_revs/241114001.pl new file mode 100755 index 0000000000..07eec95787 --- /dev/null +++ b/installer/data/mysql/db_revs/241114001.pl @@ -0,0 +1,48 @@ +use Modern::Perl; +use Koha::Installer::Output qw(say_warning say_success say_info); + +return { + bug_number => "34000", + description => "Prevent re-issuance of auto-generated cardnumbers", + up => sub { + my ($args) = @_; + my ( $dbh, $out ) = @$args{qw(dbh out)}; + + my $exists = $dbh->selectrow_array(q{SELECT 1 FROM systempreferences WHERE variable = 'autoMemberNumValue'}); + + if ($exists) { + say_warning( $out, "System preference 'autoMemberNumValue' already exists, skipping" ); + return; + } + + # Seed from the highest numeric cardnumber ever issued, considering both live + # and deleted patrons so pre-upgrade deletions don't free up their numbers. + my ($seed) = $dbh->selectrow_array( + q{ + SELECT COALESCE( + GREATEST( + COALESCE( + (SELECT MAX(CAST(cardnumber AS SIGNED)) FROM borrowers + WHERE cardnumber REGEXP '^-?[0-9]+$'), + 0 + ), + COALESCE( + (SELECT MAX(CAST(cardnumber AS SIGNED)) FROM deletedborrowers + WHERE cardnumber REGEXP '^-?[0-9]+$'), + 0 + ) + ), + 0 + ) + } + ); + $seed //= 0; + + $dbh->do( + q{INSERT IGNORE INTO systempreferences (variable, value) VALUES ('autoMemberNumValue', ?)}, + undef, $seed + ); + + say_success( $out, "Added new system preference 'autoMemberNumValue' seeded to $seed" ); + }, +}; diff --git a/installer/data/mysql/db_revs/241115000.pl b/installer/data/mysql/db_revs/241115000.pl new file mode 100755 index 0000000000..c784a62bbf --- /dev/null +++ b/installer/data/mysql/db_revs/241115000.pl @@ -0,0 +1,7 @@ +use Modern::Perl; + +return { + bug_number => undef, + description => 'Koha 24.11.15 release', + up => sub { }, +}; diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index de6f70ed6f..663f881061 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -97,7 +97,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('AutomaticCheckinAutoFill','0',NULL,'Automatically fill the next hold with an automatic check in.','YesNo'), ('AutomaticConfirmTransfer','0',NULL,'Defines whether transfers should be automatically confirmed at checkin if modal dismissed','YesNo'), ('AutomaticItemReturn','1',NULL,'If ON, Koha will automatically set up a transfer of this item to its homebranch','YesNo'), -('autoMemberNum','0','','If ON, patron number is auto-calculated','YesNo'), +('autoMemberNum','0',NULL,'If ON, patron number is auto-calculated','YesNo'), +('autoMemberNumValue','0',NULL,'Stores the last auto-generated cardnumber to prevent re-issuance','Integer'), ('AutoRemoveOverduesRestrictions','no','no|when_no_overdue|when_no_overdue_causing_debarment', 'Defines if and on what conditions OVERDUES debarments should automatically be lifted when overdue items are returned by the patron.','Choice'), ('AutoRenewalNotices','preferences','cron|preferences|never','How should Koha determine whether to end autorenewal notices','Choice'), ('AutoResumeSuspendedHolds','1',NULL,'Allow suspended holds to be automatically resumed by a set date.','YesNo'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc index 68aaa31c15..257fb26bba 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc @@ -773,7 +773,7 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr macroEditor.setValue( '' ); return; } - $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' ); + $( '#macro-list li[data-id="' + id + '"]' ).addClass( 'active' ); api_url = "/api/v1/advanced_editor/macros/"; if( shared ) { api_url += "shared/" } let options = { @@ -878,7 +878,8 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr $.ajax(options) .then(function(result) { $.each(result,function( undef, macro ){ - var $li = $( '
  • ' + macro.name + '
    1. ' ); + var $li = $( '
    2. ' + escape_str(macro.name) + '
      1. ' ); + $li.get(0).setAttribute('data-name',macro.name); if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' ); $li.click( function() { loadMacro(macro.name, macro.macro_id, macro.shared); diff --git a/koha-tmpl/intranet-tmpl/prog/js/pages/tags-review.js b/koha-tmpl/intranet-tmpl/prog/js/pages/tags-review.js index 6a3ec853af..49a86f17df 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/pages/tags-review.js +++ b/koha-tmpl/intranet-tmpl/prog/js/pages/tags-review.js @@ -1,6 +1,6 @@ review_ajax_params = { url: "/cgi-bin/koha/tags/review.pl", - dataType: "script" + dataType: "json", }; var ok_count = 0; @@ -14,7 +14,12 @@ function pull_counts () { rej_count = parseInt(document.getElementById("terms_summary_rejected_count" ).innerHTML); } -function count_approve () { +function count_approve(response) { + if (response.status == "success") { + //success_approve(response.tag); + } else { + failure_approve(response.tag); + } pull_counts(); if (nok_count > 0) { $("#terms_summary_unapproved_count").html(nok_count -1); @@ -22,7 +27,12 @@ function count_approve () { } } -function count_reject () { +function count_reject(response) { + if (response.status == "success") { + //success_reject(response.tag); + } else { + failure_reject(response.tag); + } pull_counts(); if (nok_count > 0) { $("#terms_summary_unapproved_count").html(nok_count -1); @@ -30,31 +40,51 @@ function count_reject () { } } -var success_approve = function(tag){ - // window.alert(__("AJAX approved tag: ") + tag); +//var success_approve = function (tag) { +// window.alert(__("AJAX approved tag: ") + tag); +//}; +var failure_approve = function (tag) { + window.alert(__("AJAX failed to approve tag: %s").format(escape_str(tag))); }; -var failure_approve = function(tag){ - window.alert( __("AJAX failed to approve tag: %s").format(decodeURIComponent(tag)) ); +//var success_reject = function (tag) { +// window.alert(__("AJAX rejected tag: ") + tag); +//}; +var failure_reject = function (tag) { + window.alert(__("AJAX failed to reject tag: %s").format(escape_str(tag))); }; -var success_reject = function(tag){ - // window.alert(__("AJAX rejected tag: ") + tag); +var success_test = function (tag) { + const verdict = document.querySelector("#verdict"); + if (verdict) { + verdict.textContent = __("%s is permitted!").format(tag); + } }; -var failure_reject = function(tag){ - window.alert( __("AJAX failed to reject tag: %s").format(decodeURIComponent(tag)) ); +var failure_test = function (tag) { + const verdict = document.querySelector("#verdict"); + if (verdict) { + verdict.textContent = __("%s is prohibited!").format(tag); + } }; -var success_test = function(tag){ - $('#verdict').html( __("%s is permitted!").format(decodeURIComponent(tag)) ); -}; -var failure_test = function(tag){ - $('#verdict').html( __("%s is prohibited!").format(decodeURIComponent(tag)) ); -}; -var indeterminate_test = function(tag){ - $('#verdict').html( __("%s is neither permitted nor prohibited!").format(decodeURIComponent(tag)) ); +var indeterminate_test = function (tag) { + const verdict = document.querySelector("#verdict"); + if (verdict) { + verdict.textContent = __( + "%s is neither permitted nor prohibited!" + ).format(tag); + } }; -var success_test_call = function() { - $('#test_button').prop('disabled', false); - $('#test_button').html("" +__(" Test")); +var success_test_call = function (response) { + if (response.status == "success") { + success_test(response.tag); + } else if (response.status == "failure") { + failure_test(response.tag); + } else { + indeterminate_test(response.tag); + } + $("#test_button").prop("disabled", false); + $("#test_button").html( + "" + __(" Test") + ); }; $(document).ready(function() { @@ -155,7 +185,7 @@ $(document).ready(function() { { ...review_ajax_params, type: "GET" }, { data: { - tag: escape_str($("#test").val()), + tag: $("#test").val(), op: "test", }, success: success_test_call, // success_reject 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 0dae4f6a4d..8880ec85f3 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt @@ -998,9 +998,9 @@ $(function() { var shelf_number = $(this).data("shelfnumber"); var is_shared = $(this).data("shared"); var count = $(this).data("count"); - var message = "

        " + shelf_name + "

        "; + var message = "

        " + escape_str(shelf_name) + "

        "; if( count ){ - message += "

        " + _("Items on this list:") + " " + count + "

        "; + message += "

        " + _("Items on this list:") + " " + escape_str(count) + "

        "; } if( is_shared ){ message += "

        " + _("This list is shared. Other users will lose access to it.") + "

        "; @@ -1017,7 +1017,7 @@ $(function() { e.preventDefault(); var shelf_name = $(this).data("shelfname"); var shelf_number = $(this).data("shelfnumber"); - confirmModal( shelf_name, _("Are you sure you want to remove sharing? You will no longer have access to the list."), _("Yes, remove sharing"), _("No, do not remove sharing"), function( result ){ + confirmModal( escape_str(shelf_name), _("Are you sure you want to remove sharing? You will no longer have access to the list."), _("Yes, remove sharing"), _("No, do not remove sharing"), function( result ){ if( result ){ $("#unshare" + shelf_number ).submit(); } diff --git a/misc/release_notes/release_notes_24_11_15.html b/misc/release_notes/release_notes_24_11_15.html new file mode 100644 index 0000000000..fe1caa90c7 --- /dev/null +++ b/misc/release_notes/release_notes_24_11_15.html @@ -0,0 +1,286 @@ +

        RELEASE NOTES FOR KOHA 24.11.15

        + +

        22 Apr 2026

        + +

        Koha is the first free and open source software library automation +package (ILS). Development is sponsored by libraries of varying types +and sizes, volunteers, and support companies from around the world. The +website for the Koha project is:

        + + + +

        Koha 24.11.15 can be downloaded from:

        + + + +

        Installation instructions can be found at:

        + +
          +
        • Koha Wiki
        • +
        • OR in the INSTALL files that come in the tarball
        • +
        + +

        Koha 24.11.15 is a bugfix/maintenance release.

        + +

        It includes 2 enhancements, 4 bugfixes.

        + +

        System requirements

        + +

        You can learn about the system components (like OS and database) needed for running Koha on the community wiki.

        + +

        Security bugs

        + +
          +
        • 34000 Don't allow auto-generated cardnumbers to be re-used, it may give access of services to the next patron created
        • +
        • 42136 User-entered Template::Toolkit allows information disclosure
        • +
        • 42252 Stored XSS when deleting a list or removing a list share
        • +
        • 42253 Stored XSS in advanced editor in Macro name
        • +
        • 42254 DOM XSS via tag search
        • +
        + +

        Bugfixes

        + +

        Enhancements

        + +

        Continuous Integration

        + +

        Enhancements

        + +

          +
        • 41368 Tools/ManageMarcImport_spec.ts is failing

          + +
          +

          26.05.00

        • +

        + +

        New system preferences

        + + +
          +
        • autoMemberNumValue
        • +
        + +

        Documentation

        + +

        The Koha manual is maintained in Sphinx. The home page for Koha +documentation is

        + + + +

        The Git repository for the Koha manual can be found at

        + + + +

        Translations

        + +

        Complete or near-complete translations of the OPAC and staff +interface are available in this release for the following languages:

        + +
        + +- Arabic (ar_ARAB) (95%) +- Armenian (hy_ARMN) (100%) +- Bulgarian (bg_CYRL) (100%) +- Chinese (Simplified Han script) (86%) +- Chinese (Traditional Han script) (99%) +- Czech (68%) +- Dutch (88%) +- English (100%) +- English (New Zealand) (63%) +- English (USA) +- Finnish (99%) +- French (100%) +- French (Canada) (99%) +- German (100%) +- Greek (67%) +- Hindi (97%) +- Italian (83%) +- Norwegian Bokmål (73%) +- Persian (fa_ARAB) (96%) +- Polish (99%) +- Portuguese (Brazil) (99%) +- Portuguese (Portugal) (88%) +- Russian (94%) +- Slovak (61%) +- Spanish (99%) +- Swedish (88%) +- Telugu (67%) +- Tetum (52%) +- Turkish (83%) +- Ukrainian (76%) +- Western Armenian (hyw_ARMN) (62%) +
        + +

        Partial translations are available for various other languages.

        + +

        The Koha team welcomes additional translations; please see

        + + + +

        For information about translating Koha, and join the koha-translate +list to volunteer:

        + + + +

        The most up-to-date translations can be found at:

        + + + +

        Release Team

        + +

        The release team for Koha 24.11.15 is

        + +
          +
        • Release Manager: Lucas Gass

        • +
        • QA Manager: Martin Renvoize

        • +
        • QA Team:

          + +
            +
          • Marcel de Rooy
          • +
          • Martin Renvoize
          • +
          • Jonathan Druart
          • +
          • Laura Escamilla
          • +
          • Lucas Gass
          • +
          • Tomás Cohen Arazi
          • +
          • Lisette Scheer
          • +
          • Nick Clemens
          • +
          • Paul Derscheid
          • +
          • Emily Lamancusa
          • +
          • David Cook
          • +
          • Matt Blenkinsop
          • +
          • Andrew Fuerste-Henry
          • +
          • Brendan Lawlor
          • +
          • Pedro Amorim
          • +
          • Kyle M Hall
          • +
          • Aleisha Amohia
          • +
          • David Nind
          • +
          • Baptiste Wojtkowski
          • +
          • Jan Kissig
          • +
          • Katrin Fischer
          • +
          • Thomas Klausner
          • +
          • Julian Maurice
          • +
          • Owen Leonard
          • +
        • +
        • Documentation Manager: David Nind

        • +
        • Documentation Team:

          + +
            +
          • Philip Orr
          • +
          • Aude Charillon
          • +
          • Caroline Cyr La Rose
          • +
        • +
        • Translation Manager: Jonathan Druart

        • +
        • Wiki curators:

          + +
            +
          • George Williams
          • +
          • Thomas Dukleth
          • +
        • +
        • Release Maintainers:

          + +
            +
          • 25.11 -- Jacob O'Mara
          • +
          • 25.05 -- Laura Escamilla
          • +
          • 24.11 -- Fridolin Somers
          • +
          • 22.11 -- Wainui Witika-Park (Catalyst IT)
          • +
        • +
        • Release Maintainer assistants:

          + +
            +
          • 25.11 -- Chloé Zermatten
          • +
          • 24.11 -- Baptiste Wojtkowski
          • +
          • 22.11 -- Alex Buckley & Aleisha Amohia
          • +
        • +
        + +

        Credits

        + +

        We thank the following individuals who contributed patches to Koha 24.11.15

        + +
        + +- David Cook (3) +- Jonathan Druart (4) +- Lucas Gass (1) +- Jacob O'Mara (3) +- Martin Renvoize (4) +- Marcel de Rooy (1) +
        + +

        We thank the following libraries, companies, and other institutions who contributed +patches to Koha 24.11.15

        + +
        + +- [ByWater Solutions](https://bywatersolutions.com) (1) +- Koha Community Developers (4) +- [OpenFifth](https://openfifth.co.uk) (7) +- [Prosentient Systems](https://www.prosentient.com.au) (3) +- Rijksmuseum, Netherlands (1) +
        + +

        We also especially thank the following individuals who tested patches +for Koha

        + +
        + +- Nick Clemens (1) +- David Cook (3) +- Jonathan Druart (1) +- Andrew Fuerste-Henry (3) +- Lucas Gass (1) +- Brendan Lawlor (1) +- David Nind (1) +- Jacob O'Mara (13) +- Phil Ringnalda (2) +- Marcel de Rooy (2) +
        + +

        We regret any omissions. If a contributor has been inadvertently missed, +please send a patch against these release notes to koha-devel@lists.koha-community.org.

        + +

        Revision control notes

        + +

        The Koha project uses Git for version control. The current development +version of Koha can be retrieved by checking out the main branch of:

        + + + +

        The branch for this version of Koha and future bugfixes in this release +line is 24.11.x-security.

        + +

        Bugs and feature requests

        + +

        Bug reports and feature requests can be filed at the Koha bug +tracker at:

        + + + +

        He rau ringa e oti ai. +(Many hands finish the work)

        + +

        Autogenerated release notes updated last on 22 Apr 2026 19:35:08.

        diff --git a/misc/release_notes/release_notes_24_11_15.md b/misc/release_notes/release_notes_24_11_15.md new file mode 100644 index 0000000000..9d9b2595e2 --- /dev/null +++ b/misc/release_notes/release_notes_24_11_15.md @@ -0,0 +1,253 @@ +# RELEASE NOTES FOR KOHA 24.11.15 +22 Apr 2026 + +Koha is the first free and open source software library automation +package (ILS). Development is sponsored by libraries of varying types +and sizes, volunteers, and support companies from around the world. The +website for the Koha project is: + +- [Koha Community](https://koha-community.org) + +Koha 24.11.15 can be downloaded from: + +- [Download](https://download.koha-community.org/koha-24.11.15.tar.gz) + +Installation instructions can be found at: + +- [Koha Wiki](https://wiki.koha-community.org/wiki/Installation_Documentation) +- OR in the INSTALL files that come in the tarball + +Koha 24.11.15 is a bugfix/maintenance release. + +It includes 2 enhancements, 4 bugfixes. + +**System requirements** + +You can learn about the system components (like OS and database) needed for running Koha on the [community wiki](https://wiki.koha-community.org/wiki/System_requirements_and_recommendations). + + +#### Security bugs + +- [34000](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=34000) Don't allow auto-generated cardnumbers to be re-used, it may give access of services to the next patron created +- [42136](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42136) User-entered Template::Toolkit allows information disclosure +- [42252](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42252) Stored XSS when deleting a list or removing a list share +- [42253](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42253) Stored XSS in advanced editor in Macro name +- [42254](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42254) DOM XSS via tag search + +## Bugfixes + +## Enhancements + +### Continuous Integration + +#### Enhancements + +- [41368](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41368) Tools/ManageMarcImport_spec.ts is failing + >26.05.00 + +## New system preferences + +- autoMemberNumValue + +## Documentation + +The Koha manual is maintained in Sphinx. The home page for Koha +documentation is + +- [Koha Documentation](https://koha-community.org/documentation/) +As of the date of these release notes, the Koha manual is available in the following languages: + +- [English (USA)](https://koha-community.org/manual/24.11/en/html/) +- [French](https://koha-community.org/manual/24.11/fr/html/) (80%) +- [German](https://koha-community.org/manual/24.11/de/html/) (89%) +- [Greek](https://koha-community.org/manual/24.11/el/html/) (93%) +- [Hindi](https://koha-community.org/manual/24.11/hi/html/) (63%) + +The Git repository for the Koha manual can be found at + +- [Koha Git Repository](https://gitlab.com/koha-community/koha-manual) + +## Translations + +Complete or near-complete translations of the OPAC and staff +interface are available in this release for the following languages: +
        + +- Arabic (ar_ARAB) (95%) +- Armenian (hy_ARMN) (100%) +- Bulgarian (bg_CYRL) (100%) +- Chinese (Simplified Han script) (86%) +- Chinese (Traditional Han script) (99%) +- Czech (68%) +- Dutch (88%) +- English (100%) +- English (New Zealand) (63%) +- English (USA) +- Finnish (99%) +- French (100%) +- French (Canada) (99%) +- German (100%) +- Greek (67%) +- Hindi (97%) +- Italian (83%) +- Norwegian Bokmål (73%) +- Persian (fa_ARAB) (96%) +- Polish (99%) +- Portuguese (Brazil) (99%) +- Portuguese (Portugal) (88%) +- Russian (94%) +- Slovak (61%) +- Spanish (99%) +- Swedish (88%) +- Telugu (67%) +- Tetum (52%) +- Turkish (83%) +- Ukrainian (76%) +- Western Armenian (hyw_ARMN) (62%) +
        + +Partial translations are available for various other languages. + +The Koha team welcomes additional translations; please see + +- [Koha Translation Info](https://wiki.koha-community.org/wiki/Translating_Koha) + +For information about translating Koha, and join the koha-translate +list to volunteer: + +- [Koha Translate List](https://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-translate) + +The most up-to-date translations can be found at: + +- [Koha Translation](https://translate.koha-community.org/) + +## Release Team + +The release team for Koha 24.11.15 is + + +- Release Manager: Lucas Gass + +- QA Manager: Martin Renvoize + +- QA Team: + - Marcel de Rooy + - Martin Renvoize + - Jonathan Druart + - Laura Escamilla + - Lucas Gass + - Tomás Cohen Arazi + - Lisette Scheer + - Nick Clemens + - Paul Derscheid + - Emily Lamancusa + - David Cook + - Matt Blenkinsop + - Andrew Fuerste-Henry + - Brendan Lawlor + - Pedro Amorim + - Kyle M Hall + - Aleisha Amohia + - David Nind + - Baptiste Wojtkowski + - Jan Kissig + - Katrin Fischer + - Thomas Klausner + - Julian Maurice + - Owen Leonard + +- Documentation Manager: David Nind + +- Documentation Team: + - Philip Orr + - Aude Charillon + - Caroline Cyr La Rose + +- Translation Manager: Jonathan Druart + + +- Wiki curators: + - George Williams + - Thomas Dukleth + +- Release Maintainers: + - 25.11 -- Jacob O'Mara + - 25.05 -- Laura Escamilla + - 24.11 -- Fridolin Somers + - 22.11 -- Wainui Witika-Park (Catalyst IT) + +- Release Maintainer assistants: + - 25.11 -- Chloé Zermatten + - 24.11 -- Baptiste Wojtkowski + - 22.11 -- Alex Buckley & Aleisha Amohia + +## Credits + + + +We thank the following individuals who contributed patches to Koha 24.11.15 +
        + +- David Cook (3) +- Jonathan Druart (4) +- Lucas Gass (1) +- Jacob O'Mara (3) +- Martin Renvoize (4) +- Marcel de Rooy (1) +
        + +We thank the following libraries, companies, and other institutions who contributed +patches to Koha 24.11.15 +
        + +- [ByWater Solutions](https://bywatersolutions.com) (1) +- Koha Community Developers (4) +- [OpenFifth](https://openfifth.co.uk) (7) +- [Prosentient Systems](https://www.prosentient.com.au) (3) +- Rijksmuseum, Netherlands (1) +
        + +We also especially thank the following individuals who tested patches +for Koha +
        + +- Nick Clemens (1) +- David Cook (3) +- Jonathan Druart (1) +- Andrew Fuerste-Henry (3) +- Lucas Gass (1) +- Brendan Lawlor (1) +- David Nind (1) +- Jacob O'Mara (13) +- Phil Ringnalda (2) +- Marcel de Rooy (2) +
        + + + + + +We regret any omissions. If a contributor has been inadvertently missed, +please send a patch against these release notes to koha-devel@lists.koha-community.org. + +## Revision control notes + +The Koha project uses Git for version control. The current development +version of Koha can be retrieved by checking out the main branch of: + +- [Koha Git Repository](https://git.koha-community.org/koha-community/koha) + +The branch for this version of Koha and future bugfixes in this release +line is 24.11.x-security. + +## Bugs and feature requests + +Bug reports and feature requests can be filed at the Koha bug +tracker at: + +- [Koha Bugzilla](https://bugs.koha-community.org) + +He rau ringa e oti ai. +(Many hands finish the work) + +Autogenerated release notes updated last on 22 Apr 2026 19:35:08. diff --git a/t/Template/Utils.t b/t/Template/Utils.t index 0f1a9ed307..a22683c53b 100755 --- a/t/Template/Utils.t +++ b/t/Template/Utils.t @@ -18,7 +18,8 @@ # along with Koha; if not, see . use Modern::Perl; -use Test::More tests => 3; +use Test::NoWarnings; +use Test::More tests => 4; use utf8; @@ -58,3 +59,24 @@ subtest 'Plugin related tests' => sub { "Processing template toolkit with a Koha plugin works" ); }; + +subtest 'Security-related tests' => sub { + plan tests => 2; + + is( + Koha::TemplateUtils::process_tt( + q{[% USE dir = Directory('/') ~%] +[% FOREACH entry IN dir.list ~%] +

        [% entry.abs %]

        +[% END %]}, + ), + q{}, + "Plugins not allowed for User-entered TT" + ); + + is( + Koha::TemplateUtils::process_tt(q{[% USE raw %][% 'foo' | $raw %]}), + q{foo}, + "Koha plugins are still available" + ); +}; diff --git a/t/cypress/integration/Tools/ManageMarcImport_spec.ts b/t/cypress/integration/Tools/ManageMarcImport_spec.ts index e27f3cf9a1..43df8625e9 100644 --- a/t/cypress/integration/Tools/ManageMarcImport_spec.ts +++ b/t/cypress/integration/Tools/ManageMarcImport_spec.ts @@ -54,10 +54,10 @@ describe("loads the manage MARC import page", () => { cy.get('select[name="format"]').should("have.value", "MARCXML"); //select some new options - cy.get("#matcher").select("3", { force: true }); cy.get("#matcher") .select("3", { force: true }) .should("have.value", "3"); + cy.get("#overlay_action") .select("create_new", { force: true }) .should("have.value", "create_new"); @@ -68,11 +68,19 @@ describe("loads the manage MARC import page", () => { .select("ignore", { force: true }) .should("have.value", "ignore"); + cy.intercept("GET", "/api/v1/jobs/*").as("jobPoll"); cy.get("#mainformsubmit").click(); - cy.get("#job_callback").should("exist"); + const waitForJobFinished = () => { + cy.wait("@jobPoll").then(interception => { + if (interception.response?.body?.status !== "finished") { + waitForJobFinished(); + } + }); + }; + waitForJobFinished(); - cy.contains("View batch").click(); + cy.get("#job_callback").contains("View batch").click(); // Now verify all values are retained cy.get("#new_matcher_id").should("have.value", "3"); diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index 2ce1a19488..842b7c8580 100755 --- a/t/db_dependent/Koha/Patron.t +++ b/t/db_dependent/Koha/Patron.t @@ -19,7 +19,8 @@ use Modern::Perl; -use Test::More tests => 38; +use Test::More tests => 40; +use Test::NoWarnings; use Test::Exception; use Test::Warn; use Time::Fake; @@ -369,7 +370,7 @@ subtest 'is_active' => sub { subtest 'add_guarantor() tests' => sub { - plan tests => 6; + plan tests => 7; $schema->storage->txn_begin; @@ -398,15 +399,16 @@ subtest 'add_guarantor() tests' => sub { is( $guarantors->count, 1, 'No guarantors added' ); - { - local *STDERR; - open STDERR, '>', '/dev/null'; - throws_ok - { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); } + warning_like( + sub { + throws_ok { + $patron_1->add_guarantor( { guarantor_id => $patron_2->borrowernumber, relationship => 'father2' } ); + } 'Koha::Exceptions::Patron::Relationship::DuplicateRelationship', - 'Exception is thrown for duplicated relationship'; - close STDERR; - } + 'Exception is thrown for duplicated relationship'; + }, + qr{Duplicate entry.* for key '(borrower_relationships\.)?guarantor_guarantee_idx'} + ); $schema->storage->txn_rollback; }; @@ -2962,3 +2964,99 @@ subtest 'preferred_name' => sub { is( $patron->preferred_name, "Preferred again", "Preferred name set on update when passed" ); }; + +subtest 'fixup_cardnumber and autoMemberNumValue counter' => sub { + plan tests => 10; + + $schema->storage->txn_begin; + + t::lib::Mocks::mock_preference( 'autoMemberNum', 1 ); + t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 ); + + my $pref_rs = $schema->resultset('Systempreference'); + + my $set_counter = sub { + my $value = shift; + my $row = $pref_rs->find('autoMemberNumValue'); + if ($row) { $row->update( { value => $value } ) } + else { $pref_rs->create( { variable => 'autoMemberNumValue', value => $value } ) } + }; + my $get_counter = sub { + my $row = $pref_rs->find('autoMemberNumValue'); + return $row ? $row->value : undef; + }; + + my $category = $builder->build_object( { class => 'Koha::Patron::Categories' } ); + my $library = $builder->build_object( { class => 'Koha::Libraries' } ); + + my $new_patron = sub { + return Koha::Patron->new( + { + surname => 'fixup-test', + categorycode => $category->categorycode, + branchcode => $library->branchcode, + cardnumber => undef, + } + ); + }; + + # Anchor well above any existing fixture cardnumbers for deterministic assertions. + my $baseline = Koha::Patrons->search( + { cardnumber => { -regexp => '^-?[0-9]+$' } }, + { + select => \'CAST(cardnumber AS SIGNED)', + as => ['m'], + } + )->_resultset->get_column('m')->max + || 0; + my $start = $baseline + 1_000; + $set_counter->($start); + + my $p1 = $new_patron->()->store; + is( $p1->cardnumber + 0, $start + 1, 'First auto-generated cardnumber is counter + 1' ); + is( $get_counter->() + 0, $start + 1, 'Counter persisted after auto-generation' ); + + # The bug fix: renaming a patron's cardnumber must not free the old number. + my $original = $p1->cardnumber; + $p1->cardnumber('MANUAL-XYZ')->store; + my $p2 = $new_patron->()->store; + isnt( $p2->cardnumber, $original, q{Original auto-generated number not reused after rename} ); + is( $p2->cardnumber + 0, $start + 2, 'Next patron uses counter + 1, skipping the freed slot' ); + + # Deleting a patron must not free their cardnumber either. + my $p2_cardnumber = $p2->cardnumber; + $p2->delete; + my $p3 = $new_patron->()->store; + isnt( $p3->cardnumber, $p2_cardnumber, q{Deleted patron's cardnumber not reused} ); + + # Counter ahead of DB max: counter wins. + $set_counter->( $start + 1_000_000 ); + my $p4 = $new_patron->()->store; + is( $p4->cardnumber + 0, $start + 1_000_001, 'Counter ahead of DB max wins' ); + + # DB max ahead of counter (e.g. admin imported high numbers directly): DB max wins. + my $high = $start + 2_000_000; + $set_counter->(5); + Koha::Patron->new( + { + surname => 'manual-high', + categorycode => $category->categorycode, + branchcode => $library->branchcode, + cardnumber => "$high", + } + )->store; + my $p5 = $new_patron->()->store; + is( $p5->cardnumber + 0, $high + 1, 'DB max ahead of counter wins' ); + is( $get_counter->() + 0, $high + 1, 'Counter catches up to DB max + 1' ); + + # Pre-upgrade fallback: syspref row absent -> behaves like pre-34000 code. + $pref_rs->find('autoMemberNumValue')->delete; + my $p6 = $new_patron->()->store; + is( $p6->cardnumber + 0, $high + 2, 'Legacy fallback when syspref row is absent' ); + is( + $pref_rs->find('autoMemberNumValue'), undef, + 'Syspref row stays absent when fallback path is taken' + ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/check_sysprefs.t b/t/db_dependent/check_sysprefs.t index 3d194e4a56..1391c11b37 100755 --- a/t/db_dependent/check_sysprefs.t +++ b/t/db_dependent/check_sysprefs.t @@ -66,6 +66,7 @@ subtest 'Compare sysprefs.sql with YAML files' => sub { @sysprefs_mod = grep !/UsageStatsID/, @sysprefs_mod; # Separate configuration page @sysprefs_mod = grep !/UsageStatsLastUpdateTime/, @sysprefs_mod; # Separate configuration page @sysprefs_mod = grep !/UsageStatsPublicID/, @sysprefs_mod; # Separate configuration page + @sysprefs_mod = grep !/autoMemberNumValue/, @sysprefs_mod; # Internal counter, not exposed in YAML my @missing_yaml = array_minus( @sysprefs_mod, @yaml_mod ); is( scalar @missing_yaml, 0, "No system preference entries missing from sysprefs.sql" ); diff --git a/tags/review.pl b/tags/review.pl index 1a576bdb29..bedb519113 100755 --- a/tags/review.pl +++ b/tags/review.pl @@ -23,8 +23,8 @@ use Modern::Perl; use POSIX qw( ceil ); use CGI qw ( -utf8 ); use CGI::Cookie; # need to check cookies before having CGI parse the POST request -use URI::Escape qw( uri_escape_utf8 uri_unescape ); -use C4::Auth qw( check_cookie_auth get_template_and_user ); +use JSON qw(encode_json); +use C4::Auth qw( check_cookie_auth get_template_and_user ); use C4::Context; use C4::Output qw( output_with_http_headers is_ajax pagination_bar output_html_with_http_headers ); use C4::Tags qw( @@ -38,13 +38,15 @@ use C4::Tags qw( my $script_name = "/cgi-bin/koha/tags/review.pl"; my $needed_flags = { tools => 'moderate_tags' }; # FIXME: replace when more specific permission is created. -sub ajax_auth_cgi { # returns CGI object - my $needed_flags = shift; - my %cookies = CGI::Cookie->fetch; - my $input = CGI->new; - my $sessid = $cookies{'CGISESSID'}->value; - my ($auth_status) = check_cookie_auth($sessid, $needed_flags); - if ($auth_status ne "ok") { +sub ajax_auth_cgi { # returns CGI object + my $needed_flags = shift; + my %cookies = CGI::Cookie->fetch; + my $input = CGI->new; + my $sessid = $cookies{'CGISESSID'}->value; + my ($auth_status) = check_cookie_auth( $sessid, $needed_flags ); + if ( $auth_status ne "ok" ) { + + #FIXME: This should return a HTTP error and not a script output_with_http_headers $input, undef, "window.alert('Your CGI session cookie ($sessid) is not current. " . "Please refresh the page and try again.');\n", 'js'; @@ -53,24 +55,25 @@ sub ajax_auth_cgi { # returns CGI object return $input; } -if (is_ajax()) { - my $input = &ajax_auth_cgi($needed_flags); - my $operator = C4::Context->userenv->{'number'}; # must occur AFTER auth - my $js_reply; - my $op = $input->param('op') || q{}; - my $tag = $input->param('tag'); +if ( is_ajax() ) { + my $input = &ajax_auth_cgi($needed_flags); + my $operator = C4::Context->userenv->{'number'}; # must occur AFTER auth + my $op = $input->param('op') || q{}; + my $tag = $input->param('tag'); + my $status; if ( $op eq 'test' ) { - $tag = uri_unescape($tag); my $check = is_approved($tag); - $js_reply = ( $check >= 1 ? 'success' : $check <= -1 ? 'failure' : 'indeterminate' ) . "_test('".uri_escape_utf8($tag)."');\n"; + $status = $check >= 1 ? 'success' : $check <= -1 ? 'failure' : 'indeterminate'; + } elsif ( $op eq 'cud-approve' ) { + $status = whitelist( $operator, $tag ) ? 'success' : 'failure'; + } elsif ( $op eq 'cud-reject' ) { + $status = blacklist( $operator, $tag ) ? 'success' : 'failure'; } - elsif ($op eq 'cud-approve') { - $js_reply = ( whitelist($operator,$tag) ? 'success' : 'failure') . "_approve('".uri_escape_utf8($tag)."');\n"; - } - elsif ($op eq 'cud-reject') { - $js_reply = ( blacklist($operator,$tag) ? 'success' : 'failure') . "_reject('".uri_escape_utf8($tag)."');\n"; - } - output_with_http_headers $input, undef, $js_reply, 'js'; + my $response = { + status => $status, + tag => $tag, + }; + output_with_http_headers $input, undef, encode_json($response), 'json'; exit; }