commit e386b282b605cb844a132f4023b90779a1d6d84e Author: Martin Renvoize Date: Wed Oct 21 14:38:31 2026 +0100 Bug 43782: (RM follow-up) Return nothing from GenerateCSRF when there is no session Bug 43782 builds the cache key for the CSRF token from the session id, but GenerateCSRF is also rendered, through csrf-token.inc, in templates that have no sessionID in their stash. It then warned twice: Use of uninitialized value $session_id in concatenation (.) or string Use of uninitialized value $csrf_token in concatenation (.) or string which fails t/db_dependent/Koha/Plugins/TemplateIncludePathHook.t on its "no warnings" check, and cached a token under a session-less key. A CSRF token is bound to a session, and Koha::Token->generate_csrf already returns nothing when it is not given one, as GenerateCSRF did before bug 43782. Return early in the same way, before the cache key is built. Test plan: 1. prove t/db_dependent/Koha/Plugins/TemplateIncludePathHook.t => it passes (it failed with "not ok 6 - no warnings") 2. prove t/db_dependent/Koha/Template/Plugin/Koha.t => it passes, including the new 'GenerateCSRF - No session' subtest Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Martin Renvoize commit fc319cab2d244baaa9981284a3c410a3a6a1ddb2 Author: Robin Sheat Date: Tue Oct 6 10:58:37 2026 +0200 Bug 43732: actually output the message about enabling email Sixteen years ago, I added email support to the packages, and added a bug at the same time. This finally fixes that bug so now the message: Email for this instance is disabled. When you're ready to enable it, use: koha-email-enable $name is correctly output after koha-create completes. Signed-off-by: Tadeusz „tadzik” Sośnierz Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit ccd084137c1239a6167507910d5d913656f3f8ad Author: Andrew Fuerste-Henry Date: Tue Oct 6 13:31:41 2026 +0000 Bug 43740: Make pluralization of patron slip module consistent To test: Apply patch 1 - go to Tools, Notices and Slips 2 - click New notice 3 - select "Patrons (custom slip)" 4 - in the notice creation screen, see the module reads "Patrons (custom slip)" 5 - save your notice 6 - in the list of existing notices, see your notice shows "Patrons (custom slip)" Signed-off-by: Rae Schumann Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit 0373f5bedbac4552bd76de116786a480958db10a Author: Robin Sheat Date: Tue Oct 6 11:55:24 2026 +0200 Bug 43734: make --create-db the default for koha-create The documentation for koha-create says that if nothing else is specified, then it will make --create-db the default. However it wasn't doing this. This patch actually sets it as the default. It also fixes a minor typo. Signed-off-by: Tomás Cohen Arazi Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit fef28c2a223f584418657a92b6cbc2e3c3ac95f6 Author: David Cook Date: Wed Feb 19 05:58:12 2025 +0000 Bug 39160: Use optimal Mojolicious plugin name This change uses "OAuth2" instead of "Mojolicious::Plugin::OAuth2" when setting the plugin, so that Mojolicious can find the plugin faster. Test plan: 0. Apply the patch 1. koha-plack --restart kohadev 2. Run through OpenID Connect setup from "Testing SSO" on Koha Wiki https://wiki.koha-community.org/wiki/Testing_SSO 3. Confirm that you can log into Koha using the Keycloak OIDC Signed-off-by: Katrin Fischer Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit b904e9f6d71df568b6b2c7e55ec040c594e01b95 Author: Emily Lamancusa Date: Thu Oct 15 17:02:12 2026 +0000 Bug 43825: Fix link to Edit page on patron account Fixes the "Edit" link in the patron page header, to pass in the correct page to return to after saving the edits. To test: 1. Pull up a patron account on the Details tab (moremember.pl) 2. Click the Edit button at the top of the page 3. Click Save --> You're now on the Checkouts tab instead of returning to the Details tab! 4. Apply patch 5. Go back to the Details tab 6. Click the Edit button at the top of the page 7. Click Save --> You're still on the Details tab as expected 8. Go to the Checkouts tab 9 Click the Edit button at the top of the page 10. Click Save --> You're still on the Checkouts tab as expected Signed-off-by: David Nind Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit b606b1c16e12265939f36eb42ee594672cbf6849 Author: Andrew Fuerste-Henry Date: Wed Oct 14 20:19:18 2026 +0000 Bug 43813: Remove To Do message To test: 1 - perl misc/cronjobs/runreport.pl --man 2 - see "To Do: * Allow Saved Results option." 3 - apply patch 4 - perl misc/cronjobs.runreport.pl --man 5 - no more To Do section Signed-off-by: Michelle Spinney Signed-off-by: Marcel de Rooy Signed-off-by: Martin Renvoize commit cf1cf8c719fd944898f1f1362c89a589639042fc Author: Martin Renvoize Date: Wed Sep 16 18:38:31 2026 +0100 Bug 43548: Simplify runreport.pl's option-handling using Getopt::Long::Descriptive A few of runreport.pl's own option-handling clauses can lean on more of Getopt::Long::Descriptive's existing constraint vocabulary, with no behaviour change: - --to and --from now declare { implies => 'email' }, so GLD itself sets $opt->email when either is given, replacing the manual "if ($to or $from or $send_email) { $send_email = 1 }" check. - The --separator/--quote "only meaningful with --format csv" warnings stay as manual post-parse checks -- GLD's constraints are pass/fail (a failing one aborts the script via $usage->die), so turning this warn-and-continue behaviour into a declarative constraint would make it a hard failure instead, which would be a real behaviour change. They're simplified to flat checks against $opt->separator/$opt->quote directly, dropping the now-unneeded mutable local copies. - The "unless ($format) { assume 'text' }" fallback is dropped, since { default => 'text' } on the format option already guarantees this (it was only reachable via an explicit --format '', which behaves differently now, but nobody relies on that). - The positional report IDs are copied to @report_ids once, right after describe_options, instead of using @ARGV directly throughout -- purely a readability nicety pairing the declared 'args' name with a named variable at the point of use. Test plan: 1. Run misc/cronjobs/runreport.pl --separator ';' (without --format csv) and confirm it still warns "Cannot specify separator if not using CSV format" and still produces default tab-separated output, unaffected by the warning. 2. Run misc/cronjobs/runreport.pl --format csv --separator ';' --quote "'" --csv-header and confirm the custom separator/quote are applied. 3. Run misc/cronjobs/runreport.pl --to someone@example.org and confirm it still attempts to email the report (implied by --to). 4. Run misc/cronjobs/runreport.pl and confirm both reports run. 5. Confirm koha-qa.pl passes for the changed file. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Alex Carver [Acerock7] Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit eb48557c02baa81dd7c3f2ddbf2d0acc8badfd8e Author: Martin Renvoize Date: Wed Sep 16 18:38:12 2026 +0100 Bug 43548: Convert runreport.pl to the Getopt::Long::Descriptive convention Converts runreport.pl's GetOptions call to Koha::Script->describe_options (bug 43546), using the --man and args support added in the previous commit. No option here is genuinely mandatory -- --format defaults to 'text', --to/--from/--email default from KohaAdminEmailAddress, and the script's real requirement (at least one saved report ID) is a positional argument, declared via C { min => 1, name => 'reportID', variadic => 1 }> rather than a manual "unless (@ARGV) { ... die }" check. Test plan: 1. Run: misc/cronjobs/runreport.pl --help Confirm it lists all documented options, the reportID argument, and usage examples, and exits 0. 2. Run: misc/cronjobs/runreport.pl --man Confirm it prints the script's full existing POD (NAME, SYNOPSIS, OPTIONS, DESCRIPTION, USAGE EXAMPLES, etc.) and exits 0. 3. Run: misc/cronjobs/runreport.pl Confirm it fails immediately with "ERROR: At least 1 reportID argument required (got 0)" and the usage text, before any database access, and exits non-zero. 4. Create a saved SQL report (Reports > Guided reports), note its ID, then run: misc/cronjobs/runreport.pl misc/cronjobs/runreport.pl --format csv --csv-header misc/cronjobs/runreport.pl --verbose Confirm output matches this script's behavior before this patch (tab-separated by default, comma-separated with a header row when requested, and verbose logging of the SQL/argument count/result count). 5. Run misc/cronjobs/cart_to_shelf.pl --help and --hours 24 (bug 43546) and confirm both are unaffected. 6. Confirm koha-qa.pl passes for the changed files. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Alex Carver [Acerock7] Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 26474a2689e338969ff944c62bf592a0e31b122c Author: Martin Renvoize Date: Wed Oct 21 11:49:15 2026 +0100 Bug 43546: (RM follow-up) Lower the Getopt::Long::Descriptive minimum to 0.109 The 0.116 minimum is the version installed in KTD (Debian trixie), not one Koha needs. The packaged versions are 0.109 in Ubuntu jammy, 0.111 in Debian bookworm and Ubuntu noble, and 0.116 in Debian trixie, so a 0.116 minimum would be reported as too old on the first three even with the distribution package installed. The Getopt::Long::Descriptive features Koha::Script relies on are all older than 0.109: required, default, implies, callbacks, shortcircuit (0.095), prog_name, and Usage->text and Usage->die. Of the changes in 0.110 to 0.117 none affects them: 0.112 drops the redundant option warning (Getopt::Long 2.55+) 0.113 reflows usage output 0.114 stops treating (required => 0) as required, which is not used 0.115 handles a missing format string, which is always passed 0.116 restores the Getopt::Long configuration after parsing 0.117 adds experimental completion script generation t/Koha/Script.t and the converted cart_to_shelf.pl and runreport.pl give identical results with 0.116, with 0.111 on Getopt::Long 2.54 and with 0.109 on Getopt::Long 2.52 (the pairs shipped by bookworm and jammy). Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Martin Renvoize commit 103c68db4de549fd8ad5b144ba56925abfba4750 Author: Martin Renvoize Date: Wed Oct 21 11:42:55 2026 +0100 Bug 43546: (RM follow-up) Add libgetopt-long-descriptive-perl to debian/control Bug 43546 added Getopt::Long::Descriptive to the cpanfile and Koha::Script now loads it at compile time, so every script using Koha::Script depends on it at runtime. Add the Debian package to both dependency lists in debian/control, as was done for the removal of UNIVERSAL::require in bug 42860. The package is available in Debian bookworm (0.111) and trixie (0.116) and in Ubuntu jammy (0.109), noble (0.111) and later. Like the other entries it is left unversioned. debian/control is normally regenerated from debian/control.in by debian/update-control, which maps the whole cpanfile using apt-file on the build machine; edit the file directly here to avoid unrelated churn. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Martin Renvoize commit ae0e7f0e8d9f5a14a72dd2729a52cab536054b0b Author: Frédéric Demians Date: Tue Oct 13 22:00:06 2026 +0200 Bug 43546: (follow-up) Exit with status 1 on command-line errors Getopt::Long::Descriptive reports errors with die, and an uncaught die exits with the value of $! if it is not 0. So a script converted to Koha::Script->describe_options exits with an unpredictable status on an unknown option, a missing required option or a $usage->die call: 11 for cleanup_database.pl in KTD, 2 after a failed open... The scripts exited with status 1 before, with pod2usage(1) or usage(1). describe_options now: - returns a Koha::Script::Usage object, whose die makes the script exit with status 1, - also exits with status 1 when Getopt::Long::Descriptive itself fails (unknown option, missing required option). The exceptions can still be caught with eval or throws_ok. Test plan: 1. prove t/Koha/Script.t 2. misc/cronjobs/cart_to_shelf.pl --bogus; echo $? => 1 3. misc/cronjobs/cart_to_shelf.pl; echo $? => 1 (--hours is required) 4. misc/cronjobs/cart_to_shelf.pl --help; echo $? => 0 Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 689a6da1a52b342dfce6a236dba00271f4b20403 Author: Martin Renvoize Date: Thu Sep 17 17:09:04 2026 +0100 Bug 43546: (follow-up) Require --hours to be a positive integer in cart_to_shelf.pl --hours was only checked for being present (required => 1), not for being a sensible value. The pre-conversion script used "unless ($hours) { die ... }", which treated 0 the same as not-supplied and rejected it; required => 1 only checks that the option was specified, so --hours 0 silently started passing instead of dying, and a negative --hours would make the TIMESTAMPDIFF(HOUR, ...) > ? comparison true for essentially every item ever placed on the cart, reverting far more than intended. Add a callback requiring --hours to be a positive integer, restoring the original rejection of 0 and closing the negative-number gap at the same time. TIMESTAMPDIFF(HOUR, ...) truncates to whole hours in MySQL, so there's no finer-grained value (e.g. 0.5) this query could ever act on regardless of type. Test plan: 1. cart_to_shelf.pl --hours 0 Rejected: --hours must be a positive integer (matches the pre-conversion script's behaviour). 2. cart_to_shelf.pl --hours -5 Rejected: --hours must be a positive integer. 3. cart_to_shelf.pl --hours 24 Still works exactly as before. 4. koha-qa.pl passes. Signed-off-by: Alex Carver [Acerock7] Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit a859f988b02c0028da878faa6e0d022671d55add Author: Martin Renvoize Date: Wed Sep 16 19:30:03 2026 +0100 Bug 43546: (follow-up) Add --man and positional-argument support to Koha::Script->describe_options Converting the first real scripts to this convention (runreport.pl and update_patrons_category.pl, in dependent bugs) surfaced two gaps in what this bug set out to establish: - Scripts with substantial existing POD (like runreport.pl and update_patrons_category.pl) previously had --help (brief) and --man (full Pod::Usage manual), often with --help+--verbose also triggering the full manual. describe_options' own --help covers the brief case, but had no equivalent to --man. Rather than reinvent that per-script, --man is now added automatically alongside --help: it prints the calling script's own POD in full (Pod::Usage -verbose 2, defaulting to $0) and exits. --help+--verbose no longer triggers the full manual as a side effect; --verbose keeps its own, unrelated meaning of execution verbosity. Every script using describe_options gets --man for free. - A positional-argument requirement (e.g. runreport.pl's report ID(s)) has no per-option 'required' flag to declare it with, so it would otherwise be left as manual post-parse validation -- exactly the kind of ad hoc, unenforced, unparseable check this bug exists to replace for named options. describe_options now accepts a declarative 'args' key (in the same trailing hashref as 'epilog'), e.g. C { min => 1, name => 'reportID', variadic => 1 }>, enforced by describe_options itself with the same consistent error/usage output as a missing required option. This also gives static tooling (e.g. the koha-plugin-crontab plugin, which reads script source rather than executing it) a literal marker for a required positional argument, the same way it already reads 'required => 1' for named options. describe_options also returns ($opt, $usage) in list context (unchanged in scalar context) for any post-parse validation 'args' doesn't cover. Test plan: 1. prove t/Koha/Script.t 2. koha-qa.pl passes for Koha/Script.pm (--man and args are exercised functionally by the scripts converted in the dependent bugs) Co-Authored-By: Claude Sonnet 5 Signed-off-by: Alex Carver [Acerock7] Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 3c8ecdade4caa7ee9d9e0f878a902bf67279d9ec Author: Martin Renvoize Date: Wed Sep 16 15:45:26 2026 +0100 Bug 43546: Add Getopt::Long::Descriptive and convert cart_to_shelf.pl Koha's command-line scripts use plain Getopt::Long, which has no way to declare an option as genuinely mandatory -- scripts that need one bolt on their own ad hoc post-parse check, each with its own bespoke error text and none of it understood by Getopt::Long itself. This adds Getopt::Long::Descriptive to cpanfile and a thin Koha::Script->describe_options wrapper that establishes the convention for new/converted scripts: declare options (including required ones) declaratively, get a consistent --help and a consistent, fail-fast error for missing required options for free. Getopt::Long::Descriptive's usage_desc format string always renders the per-option description list immediately after itself, so there is no built-in slot for trailing "Examples:" text. describe_options accepts an optional trailing hashref with an 'epilog' key, printed after the option list, so more complex scripts than this proof of concept can still document usage examples. %c/%% are expanded in the epilog for consistency with the main format string. cart_to_shelf.pl is converted as a proof of concept, since its -h/--hours option is genuinely mandatory today and was enforced only by a manual "unless ($hours) { ... die }" check after parsing. Its short description and example, previously in a hand-rolled usage heredoc, are preserved via the new format string and epilog. This is a small, staged first step, not a mass conversion. The other cronjobs scripts are left on plain Getopt::Long for now. Test plan: 1. Update Perl dependencies (Getopt::Long::Descriptive is a new cpanfile requirement, already present on most systems as a transitive dependency, but confirm it's importable): perl -MGetopt::Long::Descriptive -e 1 2. Run: misc/cronjobs/cart_to_shelf.pl --help Confirm it prints a short description, the --hours/--help options, and a worked example -- and exits 0. 3. Run the script with no options: misc/cronjobs/cart_to_shelf.pl Confirm it fails immediately with a clear "Mandatory parameter 'hours' missing" error and the usage text, without querying the database, and exits non-zero. 4. Set an item's location to CART (e.g. via the staff interface or directly in the items table), then run: misc/cronjobs/cart_to_shelf.pl --hours 0 Confirm the item's location is reverted to its original shelving location, same as before this patch. 5. Confirm koha-qa.pl passes for the changed files. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Alex Carver [Acerock7] Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 9f27f8edd8f3e3cfc24bb2418911dc4dd9440235 Author: Emily Lamancusa Date: Thu Oct 15 16:47:57 2026 +0000 Bug 42860: (QA follow-up) Update SIP README file Signed-off-by: Emily Lamancusa Signed-off-by: Martin Renvoize commit 05230baa836d2fa3459ddaa24519362cb5b5427a Author: Kyle M Hall Date: Tue Jun 16 11:47:50 2026 -0400 Bug 42860: Replace UNIVERSAL::require with Module::Load::Conditional UNIVERSAL::require is deprecated. It is used only by the SIP server, to load the configured ILS implementation module in login_core(). Module::Load::Conditional is already a Koha dependency and is used elsewhere in Koha, so we can use can_load() instead and drop UNIVERSAL::require from the dependency lists. Test Plan: 1) Apply this patch 2) prove t/db_dependent/SIP/MsgType.t Signed-off-by: David Nind Signed-off-by: Emily Lamancusa Signed-off-by: Martin Renvoize commit 4718f85749567435e70d9aef5260c5ca10a1ee42 Author: Arthur Suzuki Date: Wed Oct 14 09:48:52 2026 +0000 Bug 43803: Do not set a hold waiting on an item checked out in the meantime When confirming a hold at checkin, the "Confirm hold" modal can stay open while the item is checked out again at another workstation (the checkout of a reserved item can be forced without cancelling the hold). Confirming the modal then called ModReserveAffect() unconditionally and set the hold to waiting even though the item was on loan, leaving the item both on loan and on the hold shelf. ModReserveAffect() now returns 0 and leaves the hold unchanged when the item is currently on loan and the hold is not already waiting. This protects all callers (returns.pl, circulation.pl, SIP checkin, hold autofill, branch transfers). circ/returns.pl displays a warning and skips the transfer creation in that case. Test plan: 1) Place an item-level hold on an item checked out to patron A (hold for patron B). 2) Check in the item: the "Hold found" modal appears. Leave it open. 3) From another tab or workstation, check out the item to patron C, confirming the checkout without cancelling the hold. 4) Back on the first tab, click "Confirm hold". => A warning is displayed ("The item has been checked out in the meantime: the hold has not been set aside and remains pending"), the hold is NOT set to waiting, and no transfer is created. 5) Check the item in again and confirm the hold normally. => The hold is set to waiting, the item appears on the hold shelf, and the transfer to the pickup library is created as usual. 6) Run: prove t/db_dependent/Circulation.t Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit f4c5d12f35f8e52c90450ad278a67419f7f8ba34 Author: Arthur Suzuki Date: Wed Oct 14 09:46:29 2026 +0000 Bug 43803: Unit tests Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit cc241b27672a1c7fb475e9b2abda6dadc6d3dfda Author: Thibaud Guillot Date: Tue Sep 29 09:16:52 2026 +0000 Bug 43665: correct handling of html in values Pass the values in a single hash, expose them as data-* attributes on a hidden element (where the "html" filter is correct, the browser decodes entities) and read them with dataset in the script. This also replaces 19 repeated "if (code.value == 'x')" blocks. Test plan: 1. In a MARC21 bibliographic framework, link 773$t to the plugin marc21_linking_section.pl. 2. Create a bibliographic record (the host record) with: 100 $a Doe, John 245 $a Tom & Jerry : "the best" episodes 250 $a 2nd ed. "revised & corrected" 260 $a London : $b Smith's Books & Co, $c 2020 3. Create a second record (the analytical record) and open the plugin from 773$t. 4. Search "Jerry" and click "Choose" on the host record. 5. Without the patch: 773$t, $b, $d and $k show literal & " and ' instead of & " and '. With the patch: the values are shown exactly as in the host record and the popup closes normally, even with in the title. 6. Create a record with 245 ind1=1 ind2=4 and 245 $a The Beatles & co. : "greatest" hits Repeat steps 3 and 4 with the search "Beatles". 773$t must be: Beatles & co. : "greatest" hits 7. Sign off. Sponsored-by: BibLibre Signed-off-by: Frédéric Demians I can confirm points 5/6 Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 21f54d61a0daf9239994ab6eafccee19bc416182 Author: Jonathan Druart Date: Mon Oct 12 13:13:19 2026 +0200 Bug 43769: (follow-up) Add Cypress tests for coded_value search Use the existing holds table of reserve/request.pl, its pickup library column is a coded_value:library. Cover searching on the code, on the description (not only its beginning, case insensitive), with no matching description and with regex metacharacters, and check the generated query. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Frédéric Demians Signed-off-by: Lucas Gass Signed-off-by: Martin Renvoize commit 4f320dddd1c1274e486b7195a2e810041e3fd47e Author: Jonathan Druart Date: Mon Oct 12 13:13:16 2026 +0200 Bug 43769: (follow-up) Keep searching on the code when a description matches Searching 'test' matched the description 'Institut Protestant de Théologie' and only searched on its code (IPT), the library with the code 'TEST' was not found anymore. If the search could match a code as well, keep the like on the code next to the codes found using the description. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Frédéric Demians Signed-off-by: Lucas Gass Signed-off-by: Martin Renvoize commit 52cd62d3828d8d01c55b2d79489e6a1e36baf92d Author: Jonathan Druart Date: Mon Oct 12 12:00:43 2026 +0200 Bug 43769: Allow searching on partial library name in global table search The coded value datatype only searched on decriptions for the global search, always using "starts with". To test: 1. Make a branch with a code 'TEST' and name 'Sparkly Unicorn Land' 2. Place some holds, more than 2, on a record. Make the pickup location for one be 'Sparkly Unicorn Land'. 3. On Use the dT search to search the existing holds table on request.tt 4. Search by 'TEST' - the hold appears 5. Search by 'Sparkly' - the hold appears 6. Search by 'Unicorn' - the hold does not appear 7. APPLY PATCH 8. Do the searching again, this time steps 4, 5, and 6 should all return the row Signed-off-by: Frédéric Demians Signed-off-by: Lucas Gass Signed-off-by: Martin Renvoize commit c21294735aa8cec34ac924a9181ddcb1772abf06 Author: Lucas Gass Date: Wed Sep 16 16:04:23 2026 +0000 Bug 43547: set when doing update_sql to ensure right stored version of report is used To test: 1- save and run a report "SELECT * FROM items LIMIT 1" 2- confirm you are shown 1 item 3- edit report, change your limit to 2 4- click Update and run SQL 5- confirm you are shown just 1 item 6- click Show SQL code, confirm it still shows "LIMIT 1" 7- click Run report 8- page reloads, now shows 2 items 9- APPLY PATCH and restart_all 10- Try steps 1 -5 again 11- Now at step 6 the SQL code should show 'LIMIT 2' Signed-off-by: Marisha Sullivan Signed-off-by: Martin Renvoize commit 209e275707f533451abe426a952b92d55d9f2a2f Author: Lucas Gass Date: Sat Oct 3 16:58:51 2026 +0000 Bug 43714: Fix prefetch in stockrotation.pl To test: 0. Enable StockRotation 1. Add a rota 2. Give the rota at least 2 stages 3. The rota needs lots of items, 1500+ ( i will add a small script to help build this ) 4. Got Cataloging -> Manage -> Items for that rota 5. The page will not load, or it will take a very long time to do so. ( in KTD with my hardware, my laptop crashed at 2x at this step ) 6. For me, it never loads. Eventually it times out 7. APPLY PATCH and restart all. 8. Repeat sept 4. 9. This time the page should load much faster. Signed-off-by: David Nind Signed-off-by: Martin Renvoize commit d18618cb9a91cc98406c76038ce5d45a9e5ab928 Author: Jan Kissig Date: Thu Oct 15 14:52:01 2026 +0000 Bug 43540: (QA follow-up) Add opac-search.pl to regex This adds the opac-search.pl to the regex which used to check if the referer is local. Both /search and the hereby introduced /opac-search.pl are set as a ScriptAlias in apache-shared-opac.conf. To test: a) follow the test plan for the main patch and additionally: b) go to /opac-search.pl and perform a search and change the language c) without patch you will be redirected to opac home, with patch applied you will be redirected to the correct referring page. Signed-off-by: Martin Renvoize commit 5310f1d145bfcdb5e19ef7c99e9ae9c61778de99 Author: Martin Renvoize Date: Tue Sep 15 13:13:03 2026 +0100 Bug 43540: Recognize core-shipped short urls as local in local_referer Koha ships Apache rewrites/aliases for several "short" or friendly OPAC and staff URLs (apache-shared-opac.conf, apache-shared-intranet.conf, koha-httpd.conf): /bib/ - single record, rewritten internally [PT] /isbn/, /issn/ - rewritten internally [PT] to /search?q=... /search - ScriptAlias'd straight to opac-search.pl / catalogue/search.pl None of these ever appear as /cgi-bin/koha/... in the browser's Referer header: the [PT] rewrites happen internally (the browser keeps showing the original short URL), and /search is aliased to a script outside /cgi-bin/koha/ altogether. Koha::Util::Navigation::local_referer(), used by opac-changelanguage.pl and changelanguage.pl to redirect back to the referring page after a language switch, only recognized a referer as local if it matched the site's base URL AND contained the literal substring /cgi-bin/koha/. Since none of the above contain that substring, the language switch fell back to the site root, dropping the user on the homepage instead of returning them to the record or search they were viewing. This patch extends local_referer()'s recognized local-path pattern to also accept /bib/, /isbn/, /issn/ and /search, in both the base-URL branch and the no-base-URL fallback branch. Test plan: 1. Enable mod_rewrite and confirm /bib/, /isbn/ and /issn/ all load the expected OPAC page. 2. On each of those pages, switch OPAC language via the language selector. 3. Before this patch: redirected to the OPAC homepage. 4. After this patch: redirected back to the same short URL. 5. prove t/Koha/Util/Navigation.t - all tests pass. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Owen Leonard Signed-off-by: Jan Kissig Signed-off-by: Martin Renvoize commit a444ca4d619668ced2f61225f6dc62085f269e3c Author: Emily Lamancusa Date: Thu Oct 15 15:18:34 2026 +0000 Bug 43672: (QA follow-up) Handle "Always update without asking" case The same change is needed for the case where UpdateMessagingPrefsOnCategoryChange is set to "Always update without asking" Signed-off-by: Emily Lamancusa Signed-off-by: Martin Renvoize commit 93489c9fb766a1ab8768b123dcbdfa0e61fe982b Author: Nick Clemens Date: Tue Sep 29 15:03:31 2026 -0400 Bug 43672: Pass a change event when setting the messaging preferences This patch bubbles up a change event when a days drodown is set by javascript To test: 1 - Have two patron categories one with messaging preference default with advance notices days and email set one with no messaging preference defaults 2 - Save a patron in the category with no defaults 3 - Edit the patron, change their category to the one with a default 4 - Note the checkbox for email is disabled, but days dropdown is set 5 - Save 6 - Note the patron has days set, but no transport 7 - Apply patch 8 - Repeat 9 - Confirm values set correctly on change of category 10 - Confirm value saves correctly Signed-off-by: Kristi Krueger Signed-off-by: Emily Lamancusa Signed-off-by: Martin Renvoize commit e655049839a644a07466ee5db62e99446c4b746a Author: Kyle M Hall Date: Thu Aug 27 12:05:42 2026 -0400 Bug 43397: SIP accounts with a blank AE field template send an empty patron name Bug 37893 moved SIP accounts from SIPconfig.xml into the database. The new SIP account editor initializes every text field to an empty string rather than null, so an account created through the staff interface gets ae_field_template = '' instead of NULL which is defined and so the empty template is used thus giving an empty AE field in responses for newly created accounts! Test Plan: 1) Create a SIP account in the staff interface leaving the AE field template blank 2) Restart all the things! 3) Send a patron status or patron information request for a valid patron 4) Note the AE field in the response is empty 5) Apply this patch 6) Restart all the things! 7) Repeat the request, note the AE field now contains the patron's name! Signed-off-by: Rae Schumann Signed-off-by: Jacob O'Mara Signed-off-by: Martin Renvoize commit 360543d562cd6da993e9378e23439e2fd1276998 Author: Kyle M Hall Date: Thu Aug 27 12:05:42 2026 -0400 Bug 43397: Add unit tests Signed-off-by: Rae Schumann Signed-off-by: Jacob O'Mara Signed-off-by: Martin Renvoize commit e0c512ef3afc233bd4ad3cf0e0101a8c960f2b15 Author: Kyle M Hall Date: Mon Oct 5 14:28:55 2026 +0000 Bug 43727: SIP no block checkouts and offline checkouts leave another patron's waiting hold in place When a SIP2 client sends a checkout with the no block flag set to Y, Koha has to accept it, so the checkout skips every check and goes through ProcessOfflineIssue. ProcessOfflineIssue calls AddIssue without a cancelreserve value, so a hold that is waiting, in transit, or in processing for a different patron is left alone. The item ends up checked out to one patron while it is still on another patron's holds waiting list. The hold sits there until it expires off the hold shelf, so the patron who placed it never gets the item. When staff check out an item with a waiting hold in the staff interface, Koha defaults to reverting the hold's waiting status. No block and offline checkouts should do the same, so the hold goes back to the top of the queue and is filled by the next copy. This patch makes ProcessOfflineIssue revert a hold found for another patron, the same as the staff interface does by default. Pending holds are still left alone, as they have been since bug 30114. Test Plan: 1) Apply the unit tests patch 2) prove t/db_dependent/Circulation/OfflineCirculation.t 3) Note the new subtest fails! 4) Apply this patch 5) Restart all the things! 6) prove t/db_dependent/Circulation/OfflineCirculation.t 7) Note the tests pass! 8) Place a hold for patron A on an item, check the item in and confirm the hold so it is waiting 9) Do a no block SIP checkout of that item to patron B: perl misc/sip_cli_emulator.pl -a localhost -p 6001 -su term1 -sp term1 -l CPL -m checkout --patron -i -n Y 10) Note the item is checked out to patron B 11) Note patron A's hold is no longer waiting, it is back in the queue at priority 1! Signed-off-by: Kyle M Hall Signed-off-by: David Nind Signed-off-by: Jacob O'Mara Signed-off-by: Martin Renvoize commit 5d7d56352037469e97450f043691a384210cc82e Author: Kyle M Hall Date: Mon Oct 5 14:27:40 2026 +0000 Bug 43727: Add unit tests Signed-off-by: Kyle M Hall Signed-off-by: David Nind Signed-off-by: Jacob O'Mara Signed-off-by: Martin Renvoize commit d0aa34cb4085db9170128c8514679ce68bc87d75 Author: Jonathan Druart Date: Thu Oct 15 13:08:56 2026 +0200 Bug 38181: (follow-up) Fix test plan in XSLT.t Signed-off-by: Jonathan Druart Signed-off-by: Martin Renvoize commit bbe8219787781bdf8f7359c2924eb1fa40222949 Author: Jonathan Druart Date: Thu Oct 15 13:07:35 2026 +0200 Bug 38181: (follow-up) Add missing use statement in C4::XSLT Signed-off-by: Jonathan Druart Signed-off-by: Martin Renvoize commit 666111c78ff27dc9c9714e7bc26a23346a864010 Author: Nick Clemens Date: Wed Oct 14 15:42:24 2026 +0000 Bug 38181: (QA follow-up) Use HoldsQueueItems objects rather than DBIC directly We have these objects, we should use them for consistency Signed-off-by: Nick Clemens Signed-off-by: Jonathan Druart Signed-off-by: Martin Renvoize commit 108e9494486ab76c3cec7830abef9a48d1f6a7f5 Author: Ilker Dagli Date: Mon Oct 12 09:10:38 2026 +0000 Bug 38181: Look up item statuses once per record in buildKohaItemsNamespace buildKohaItemsNamespace prefetches the holds, transfers and holds queue rows of every item (bug 28702) and then asks each Koha::Item about its status. Prefetching three has_many relationships makes DBIx::Class collapse the result set, which costs close to a millisecond per item on its own: with a thousand items the routine spends most of its time there rather than in the database. Fetch the itemnumbers that have a waiting recall, a holds queue entry, a waiting hold or a current transfer with one query per table and look the items up in those lists instead. The statuses and their precedence are unchanged. On a record with 1000 items (koha-testing-docker) the routine goes from 0.85s to 0.09s, and an OPAC searchResults call for that record from 1.2s to 0.45s. Test plan: 1. prove t/db_dependent/XSLT.t 2. Pick a record with a few hundred items. Give some of its items a waiting hold, a holds queue entry (build_holds_queue.pl), a transfer in progress, a lost status 3. Search for the record on the OPAC: the availability line and the item statuses are the same before and after the patch, the page is faster 4. With UseRecalls enabled, recall one item and check that "Recall waiting" is still shown Signed-off-by: Nick Clemens Signed-off-by: Jonathan Druart Signed-off-by: Martin Renvoize commit 8662b30e2e4993f7763c7022abf96f2d5907e058 Author: Tomás Cohen Arazi Date: Tue Oct 13 11:01:51 2026 -0300 Bug 43786: Fix string concatenation in batch_item_record_modification.inc Bug 41340 introduced the batch item modification report count string using the TT numeric addition operator (+) instead of the string concatenation operator (_). As a result, Template::Toolkit tries to add the strings numerically, emitting "isn't numeric in addition (+)" warnings and rendering only the leading item count instead of the full message. This patch replaces the + operators with _ so the translated strings are concatenated as intended. Test plan: 1. Reproduce the current behavior: $ ktd --shell k$ perl -we 'use Template; my $t=q{[% tnx("{count} item modified","{count} items modified",c,{count=c}) + " (" + tnx("with {count} field modified","with {count} fields modified",f,{count=f}) + ")." %]}; Template->new->process(\$t,{c=>3,f=>3,tnx=>sub{my($s,$p,$n,$h)=@_;my $x=$n==1?$s:$p;$x=~s/\{count\}/$h->{count}/g;$x}},\my $o); print "[$o]\n"' => FAIL: Four "Argument ... isn't numeric in addition (+)" warnings and the render is "[3]" 2. Apply this patch, repeat step 1 using _ instead of + in the template 3. Repeat the render => SUCCESS: No warnings and the render is "[3 items modified (with 3 fields modified).]" 4. Sign off :-D Assisted-by: Claude Sonnet 4.5 (Anthropic) Signed-off-by: Frédéric Demians Signed-off-by: Jonathan Druart Signed-off-by: Martin Renvoize commit ecd1bd6819e68c300fe5f4d58642951acb5ee98a Author: Jonathan Druart Date: Thu Sep 3 16:40:03 2026 +0200 Bug 42274: Use JSON in opac-tags Replace eval with JSON in opac-tags. Test plan: At the OPAC, add several tags to a bibliographic record (on the detail page, or the list page) Then add more tags and pass at least one existing tag => Tags should be added correctly, and you should get a warning about the existing tag. On the "tag cloud"/"All tags" view, delete some tags Signed-off-by: Owen Leonard Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit 89133541b82821aeba9e27a8cd4d06a3bace5590 Author: Owen Leonard Date: Wed Oct 7 11:54:25 2026 -0400 Bug 43220: Patron name and card number should not be clickable in headings This patch updates the circulation template so that the 'no_html' flag is passed to patron-title.inc so that the patron name in the page heading is not a clickable link. To test, apply the patch and check out to a patron. The "Checking out to [name]" heading above the barcode input field should be plain text. Sponsored-by: Athens County Public Libraries Signed-off-by: David Nind Signed-off-by: Emmi Takkinen Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize commit bf9e0e202ca73b11445862f099ce3c7acf8707b6 Author: Adolfo Rodríguez Date: Mon Sep 28 17:01:42 2026 +0200 Bug 43654: Item search: DataTables column filters ignored when 2+ conditions are combined Bug 36930 fixed item search returning wrong results when 2+ added conditions were combined, by making the filter constructor check, independently for each of 'f'/'q'/'op'/'c', whether that parameter arrived as the bracketed array name (e.g. 'f[]', sent by JS when a field repeats) or the plain name (single value), and read/write consistently. However, the JSON-preprocessing block that merges DataTables column filters (free-text search box and dropdowns for library, item type, etc. in the results table) still unconditionally read from multi_param('f')/('q')/('op')/('c') without brackets. With 2+ conditions in the "Search for" row builder, the JS sends those as bracketed arrays, so multi_param('f') etc. returned empty: the original conditions were lost, and the column filter value was pushed onto an otherwise-empty array and written back to the plain param, which the filter constructor further down (fixed by Bug 36930) never reads, since it looks at the bracketed name. The column filters were silently ignored and the results table got stuck on "Processing". Note that 'c' (the AND/OR conjunction) does not necessarily follow the same bracket convention as 'f'/'q'/'op': its