Merge branch '24.11.x-security' into 24.11.x

This commit is contained in:
Fridolin Somers 2026-04-27 09:23:57 +02:00
commit 13fe0cbf0e
No known key found for this signature in database
GPG Key ID: 9A8E73E2A8B96537
18 changed files with 882 additions and 82 deletions

View File

@ -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') : '',

View File

@ -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;

View File

@ -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<autoMemberNumValue> 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<FOR UPDATE>
inside the enclosing transaction so that concurrent C<store()> 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

View File

@ -0,0 +1,7 @@
package Koha::Template::Plugin::noop;
use Modern::Perl;
use base qw( Template::Plugin );
1;

View File

@ -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') : '',

View File

@ -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" );
},
};

View File

@ -0,0 +1,7 @@
use Modern::Perl;
return {
bug_number => undef,
description => 'Koha 24.11.15 release',
up => sub { },
};

View File

@ -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'),

View File

@ -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 = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
var $li = $( '<li data-id="' + macro.macro_id + '"><a href="#">' + escape_str(macro.name) + '</a><ol class="macro-info"></ol></li>' );
$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);

View File

@ -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("<i class='fa fa-check-square' aria-hidden='true'></i>" +__(" 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(
"<i class='fa fa-check-square' aria-hidden='true'></i>" + __(" 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

View File

@ -998,9 +998,9 @@ $(function() {
var shelf_number = $(this).data("shelfnumber");
var is_shared = $(this).data("shared");
var count = $(this).data("count");
var message = "<p><em>" + shelf_name + "</em></p>";
var message = "<p><em>" + escape_str(shelf_name) + "</em></p>";
if( count ){
message += "<p>" + _("Items on this list:") + " <strong>" + count + "</strong></p>";
message += "<p>" + _("Items on this list:") + " <strong>" + escape_str(count) + "</strong></p>";
}
if( is_shared ){
message += "<p>" + _("This list is shared. Other users will lose access to it.") + "</p>";
@ -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();
}

View File

@ -0,0 +1,286 @@
<h1 id="releasenotesforkoha24.11.15">RELEASE NOTES FOR KOHA 24.11.15</h1>
<p>22 Apr 2026</p>
<p>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:</p>
<ul>
<li><a href="https://koha-community.org">Koha Community</a></li>
</ul>
<p>Koha 24.11.15 can be downloaded from:</p>
<ul>
<li><a href="https://download.koha-community.org/koha-24.11.15.tar.gz">Download</a></li>
</ul>
<p>Installation instructions can be found at:</p>
<ul>
<li><a href="https://wiki.koha-community.org/wiki/Installation_Documentation">Koha Wiki</a></li>
<li>OR in the INSTALL files that come in the tarball</li>
</ul>
<p>Koha 24.11.15 is a bugfix/maintenance release.</p>
<p>It includes 2 enhancements, 4 bugfixes.</p>
<p><strong>System requirements</strong></p>
<p>You can learn about the system components (like OS and database) needed for running Koha on the <a href="https://wiki.koha-community.org/wiki/System_requirements_and_recommendations">community wiki</a>.</p>
<h4 id="securitybugs">Security bugs</h4>
<ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=34000">34000</a> Don't allow auto-generated cardnumbers to be re-used, it may give access of services to the next patron created</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42136">42136</a> User-entered Template::Toolkit allows information disclosure</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42252">42252</a> Stored XSS when deleting a list or removing a list share</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42253">42253</a> Stored XSS in advanced editor in Macro name</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42254">42254</a> DOM XSS via tag search</li>
</ul>
<h2 id="bugfixes">Bugfixes</h2>
<h2 id="enhancements">Enhancements</h2>
<h3 id="continuousintegration">Continuous Integration</h3>
<h4 id="enhancements">Enhancements</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41368">41368</a> Tools/ManageMarcImport_spec.ts is failing</p>
<blockquote>
<p>26.05.00</li>
</ul></p>
<h2 id="newsystempreferences">New system preferences</h2>
</blockquote>
<ul>
<li>autoMemberNumValue</li>
</ul>
<h2 id="documentation">Documentation</h2>
<p>The Koha manual is maintained in Sphinx. The home page for Koha
documentation is</p>
<ul>
<li><p><a href="https://koha-community.org/documentation/">Koha Documentation</a>
As of the date of these release notes, the Koha manual is available in the following languages:</p></li>
<li><p><a href="https://koha-community.org/manual/24.11/en/html/">English (USA)</a></p></li>
<li><a href="https://koha-community.org/manual/24.11/fr/html/">French</a> (80%)</li>
<li><a href="https://koha-community.org/manual/24.11/de/html/">German</a> (89%)</li>
<li><a href="https://koha-community.org/manual/24.11/el/html/">Greek</a> (93%)</li>
<li><a href="https://koha-community.org/manual/24.11/hi/html/">Hindi</a> (63%)</li>
</ul>
<p>The Git repository for the Koha manual can be found at</p>
<ul>
<li><a href="https://gitlab.com/koha-community/koha-manual">Koha Git Repository</a></li>
</ul>
<h2 id="translations">Translations</h2>
<p>Complete or near-complete translations of the OPAC and staff
interface are available in this release for the following languages:</p>
<div style="column-count: 2;">
- 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%)
</div>
<p>Partial translations are available for various other languages.</p>
<p>The Koha team welcomes additional translations; please see</p>
<ul>
<li><a href="https://wiki.koha-community.org/wiki/Translating_Koha">Koha Translation Info</a></li>
</ul>
<p>For information about translating Koha, and join the koha-translate
list to volunteer:</p>
<ul>
<li><a href="https://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-translate">Koha Translate List</a></li>
</ul>
<p>The most up-to-date translations can be found at:</p>
<ul>
<li><a href="https://translate.koha-community.org/">Koha Translation</a></li>
</ul>
<h2 id="releaseteam">Release Team</h2>
<p>The release team for Koha 24.11.15 is</p>
<ul>
<li><p>Release Manager: Lucas Gass</p></li>
<li><p>QA Manager: Martin Renvoize</p></li>
<li><p>QA Team:</p>
<ul>
<li>Marcel de Rooy</li>
<li>Martin Renvoize</li>
<li>Jonathan Druart</li>
<li>Laura Escamilla</li>
<li>Lucas Gass</li>
<li>Tomás Cohen Arazi</li>
<li>Lisette Scheer</li>
<li>Nick Clemens</li>
<li>Paul Derscheid</li>
<li>Emily Lamancusa</li>
<li>David Cook</li>
<li>Matt Blenkinsop</li>
<li>Andrew Fuerste-Henry</li>
<li>Brendan Lawlor</li>
<li>Pedro Amorim</li>
<li>Kyle M Hall</li>
<li>Aleisha Amohia</li>
<li>David Nind</li>
<li>Baptiste Wojtkowski</li>
<li>Jan Kissig</li>
<li>Katrin Fischer</li>
<li>Thomas Klausner</li>
<li>Julian Maurice</li>
<li>Owen Leonard</li>
</ul></li>
<li><p>Documentation Manager: David Nind</p></li>
<li><p>Documentation Team:</p>
<ul>
<li>Philip Orr</li>
<li>Aude Charillon</li>
<li>Caroline Cyr La Rose</li>
</ul></li>
<li><p>Translation Manager: Jonathan Druart</p></li>
<li><p>Wiki curators: </p>
<ul>
<li>George Williams</li>
<li>Thomas Dukleth</li>
</ul></li>
<li><p>Release Maintainers:</p>
<ul>
<li>25.11 -- Jacob O'Mara</li>
<li>25.05 -- Laura Escamilla</li>
<li>24.11 -- Fridolin Somers</li>
<li>22.11 -- Wainui Witika-Park (Catalyst IT)</li>
</ul></li>
<li><p>Release Maintainer assistants:</p>
<ul>
<li>25.11 -- Chloé Zermatten</li>
<li>24.11 -- Baptiste Wojtkowski</li>
<li>22.11 -- Alex Buckley &amp; Aleisha Amohia</li>
</ul></li>
</ul>
<h2 id="credits">Credits</h2>
<p>We thank the following individuals who contributed patches to Koha 24.11.15</p>
<div style="column-count: 2;">
- David Cook (3)
- Jonathan Druart (4)
- Lucas Gass (1)
- Jacob O'Mara (3)
- Martin Renvoize (4)
- Marcel de Rooy (1)
</div>
<p>We thank the following libraries, companies, and other institutions who contributed
patches to Koha 24.11.15</p>
<div style="column-count: 2;">
- [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)
</div>
<p>We also especially thank the following individuals who tested patches
for Koha</p>
<div style="column-count: 2;">
- 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)
</div>
<p>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.</p>
<h2 id="revisioncontrolnotes">Revision control notes</h2>
<p>The Koha project uses Git for version control. The current development
version of Koha can be retrieved by checking out the main branch of:</p>
<ul>
<li><a href="https://git.koha-community.org/koha-community/koha">Koha Git Repository</a></li>
</ul>
<p>The branch for this version of Koha and future bugfixes in this release
line is 24.11.x-security.</p>
<h2 id="bugsandfeaturerequests">Bugs and feature requests</h2>
<p>Bug reports and feature requests can be filed at the Koha bug
tracker at:</p>
<ul>
<li><a href="https://bugs.koha-community.org">Koha Bugzilla</a></li>
</ul>
<p>He rau ringa e oti ai.
(Many hands finish the work)</p>
<p>Autogenerated release notes updated last on 22 Apr 2026 19:35:08.</p>

View File

@ -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:
<div style="column-count: 2;">
- 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%)
</div>
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
<div style="column-count: 2;">
- David Cook (3)
- Jonathan Druart (4)
- Lucas Gass (1)
- Jacob O'Mara (3)
- Martin Renvoize (4)
- Marcel de Rooy (1)
</div>
We thank the following libraries, companies, and other institutions who contributed
patches to Koha 24.11.15
<div style="column-count: 2;">
- [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)
</div>
We also especially thank the following individuals who tested patches
for Koha
<div style="column-count: 2;">
- 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)
</div>
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.

View File

@ -18,7 +18,8 @@
# along with Koha; if not, see <http://www.gnu.org/licenses>.
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 ~%]
<p>[% entry.abs %]</p>
[% 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"
);
};

View File

@ -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");

View File

@ -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;
};

View File

@ -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" );

View File

@ -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;
}