Compare commits

...

55 Commits

Author SHA1 Message Date
Jacob O'Mara c70b1d1a5e
Update release notes for 25.11.01 release
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 11:23:21 +00:00
Jacob O'Mara af6de22633
Increment version for 25.11.01 release
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 10:00:15 +00:00
David Cook b1917a70c0
Bug 41662: Prevent CSRF on opac-patron-consent.pl
This change prevents CSRF on opac-patron-consent.pl by
adding checking of the "op".

Test plan:
0. Apply the patch and koha-plack --restart kohadev
1. Set system preference PrivacyPolicyConsent to "permissive"
2. Log into the OPAC and go to this path:
/cgi-bin/koha/opac-patron-consent.pl?check_GDPR_PROCESSING=1
3. Note that it doesn't change the consent
4. Go to this path:
/cgi-bin/koha/opac-patron-consent.pl?check_GDPR_PROCESSING=0
5. Note that it doesn't change the consent
6. Click the "Yes" radio button and click "Save"
7. Note it changes the consent to Yes
8. Click the "No" radio button and click "Save"
9. Note it changes the consent to No

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 09:59:19 +00:00
Lisette Scheer 0b804e8f85
Bug 41593: (QA follow-up) Move cypress test
This patch moves the cypress test into the Acqusitions folder to help keep t/cypress/integration organized as we add more tests.

Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 09:59:11 +00:00
Jonathan Druart c1d4162a8b
Bug 41593: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 09:58:18 +00:00
David Cook a80f3429f0
Bug 41593: Validate displayby parameter in suggestion.pl
This change validates the displayby parameter in suggestion.pl
in order to prevent SQL injection.

Test plan:
0. Do not apply the patch
1. Log into the staff interface
2. Visit the path /cgi-bin/koha/suggestion/suggestion.pl?
op=else&displayby=status+AND+EXTRACTVALUE(1,CONCAT(0x7e,@@version))
3. Notice a 500 error is generated
4. In the error logs, the MySQL version number will appear

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

7. Visit the path /cgi-bin/koha/suggestion/suggestion.pl?
op=else&displayby=status+AND+EXTRACTVALUE(1,CONCAT(0x7e,@@version))
8. Notice that no error is generated
9. Try each of the "Organize by" options on the left nav bar
10. Note that none of them generate an error and that the option is
retained after clicking "Go"

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-28 09:57:59 +00:00
Jacob O'Mara 2c2c04dfb2
Reapply "Bug 41328: Fix incorrect use of ref in KohaTable.vue"
This reverts commit 6cefe064d4.

Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-27 17:36:12 +00:00
Jacob O'Mara 6cefe064d4
Revert "Bug 41328: Fix incorrect use of ref in KohaTable.vue"
This reverts commit 6a8b160a2f.

Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-26 13:40:52 +00:00
Jacob O'Mara 695743328b
Bug 38426: (RMaint follow-up) update package.json and yarn lock
Clamp @types/express to v4.17.13 as there is a circular dep in v5
causing builds to fail

Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-25 20:32:31 +00:00
Lucas Gass 4d3e5f4c98
Bug 39014: Remove unnecessary tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 94ba3d5344)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:10:57 +00:00
Lari Taskula f4d343bcd7
Bug 39014: (follow-up) Refactoring and renaming to can_be_guaranteed_by
To test:
1. prove t/db_dependent/Koha/Patron.t

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit d214d441a8)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:10:49 +00:00
Emmi Takkinen 32354c3370
Bug 39014: Add new method validate_guarantor
Adding a new guarantee and storing it fails with "Creating a patron"
selected in  TrackLastPatronActivityTriggers when guarantors are set
to be mandatory. The problem comes from Koha::Patron->store called
in update_lastseen. Since we validate guarantor in Koha::Patron->store
but don't store them in there, new call to store fails because it
doesn't receive guarantor info as parameter.

As a solution, we should validate guarantors in separated method.
Validation can't be moved e.g. to Koha::Patron::Relationship->store
since this could lead to situations where guarantee is stored without
guarantor. This patch adds new method validate_guarantor and adjusts
old guarantor requirements tests to match this new method.

To test:
1. Select option "Creating a patron" in "TrackLastPatronActivityTriggers" syspref.
2. Enable syspref "ChildNeedsGuarantor" and make sure you have options in
"borrowerRelationship" syspref.
3. Create a guarantee patron, fill all required fields and add guarantor for patron
(either add Patron guarantor or fill Non-patron guarantor inputs).
4. Attempt to save patron.
=> Error box is displayed with text "The following fields are wrong. Please fix them.".
=> Logs show error "Patron creation failed! - [Child patron needs a guarantor]".
5. Apply this patch.
6. Create a guarantee patron again and save.
=> Patron should be saved correctly.
7. Create a new guarantee patron but this time don't add patron guarantor or fill
non-patron guarantor inputs.
8. Attempt to save patron.
=> Save should fail with error "A child patron needs a guarantor."
9. Add patron guarantor who can also be a guarantee and save.
=> Save should fail with error "A guarantor cannot be a guarantee."

Also prove t/db_dependent/Koha/Patron.t

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Magnus Enger <magnus@libriotech.no>
Works as advertised.
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 382ba17a8c)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:10:48 +00:00
Lucas Gass 2d5e288bb9
Bug 41456: (follow-up) Change item_type to item_type_id and add _strings in test data
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit c1efa1f810)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:08:39 +00:00
Jonathan Druart 8e87a7fa5c
Bug 41456: Rename more item_type => item_type_id in hold's api spec
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit f47ab213fd)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:08:24 +00:00
Jonathan Druart a97137a9d2
Bug 41456: Fix item type searching and filtering on the holds history table
Use should use coded_values for the library and item type column so that
we can search using the code and the description.

With this patch we now have a dropdown list with the libraries and item
types in the table's headers.

Test plan:
0. Turn on AllowHoldItemTypeSelection
1. Create a hold and select an item type
Note: I think there is a bug if you select it from "Hold next available
item", use "Hold details". Please confirm?
2. Go to the holds history view
3. Filter and search by item type description and code. Same for
   library.
   Use the dropdown list in the headers.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 66d92ba6c6)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:08:16 +00:00
Martin Renvoize c0bb19f1cd
Bug 41404: (QA follow-up) Add tests for empty string and zero edge cases
This QA follow-up adds comprehensive test coverage for edge cases that
the original patch handles:

1. Empty string values ('') - the default value in sysprefs
   - Verifies that empty strings are treated as "not set"
   - Confirms charge calculations are skipped (performance optimization)
   - Ensures patrons are not blocked

2. Zero values (0) - valid numeric limit
   - Verifies that zero triggers charge calculation (valid number)
   - Confirms patrons are not blocked (0 means "no limit")
   - Documents intended behavior that 0 disables blocking

3. Positive numeric values - normal case
   - Confirms charges are calculated and blocking works correctly

These tests validate that looks_like_number() is the correct approach
rather than simple truthiness checking, as it properly distinguishes
between:
- Empty string '' (not a number, skip calculation)
- Zero 0 (is a number, calculate but don't block)

Test plan:
1. Apply this patch
2. Run the tests:
   prove t/db_dependent/Koha/Patron.t
3. Verify all tests pass

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5810633b67)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:02:44 +00:00
Nick Clemens eb6cefae4e
Bug 41404: Use looks_like_a_number to verify if prefs are set
This check adds a check to ensure we have number, not just defined

To test:
1 - Apply only unit tests patch
2 - Run the tests
    prove -v t/db_dependent/Koha/Patron.t
3 - Apply this patch
4 - Run the tests
5 - They pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 154f807f6b)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:02:43 +00:00
Lucas Gass 9b58889359
Bug 39482: (follow-up) Adjust tests
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit e2943ae5e1)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:00:12 +00:00
Owen Leonard 51d8bbc3f5
Bug 39482: Correct the link to edit OpacLibraryInfo from library edit page
When editing a library which has an associated OpacLibraryInfo HTML
customization the "Edit HTML content" link passes the wrong id
parameter.

To test, apply the patch and restart services.

- Rebuild OPAC CSS.
- Go to Administration -> Libraries.
- Edit a library.
- If there is no OpacLibraryInfo customization associated with that
  library, add at least two OpacLibraryInfo entries and then return to
  the library edit form.
- By the "OPAC information" label you should see all of the
  OpacLibraryInfo entries you added for that library.
- If you have permission to edit HTML customizations you should see
  an edit link.
- Test each to confirm that it opens the correct entry for editing.
- Return to Administration -> Libraries and click the library name to
  view it (rather than edit).
- The correct HTML customizations should appear. If you have permission
  to edit HTML customizations you should see corresponding "Edit" links.
- Test each to confirm that it opens the correct entry for editing.

- In the OPAC, locate a bibliographic record which has one or more items
  from the library you added OpacLibraryInfo to.
- In the holdings table, click the one of the library links to trigger
  the info modal.
- Confirm that all the correct information displays.
- Go to "Libraries" link in the menu under the quick search bar.
- Click the library you modified. Confirm that the correct
  OpacLibraryInfo entries display.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit bfa29d0dc0)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 17:00:10 +00:00
David Nind c8f869180d
Bug 41363: (follow-up) Add full stop at the end of the hint text
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit dca9a6e23c)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:16:37 +00:00
Owen Leonard 70a09fdcc5
Bug 41363: Don't hide patron category limitation warning behind icon
This patch takes the warning text hidden behind an icon and puts
it into a "hint" <div> in order to make the information more accessible.

To test, apply the patch and go to Administration -> Patron categories.

- Edit a patron category to lmit it to a single library.
- Switch to that library and locate a patron in that category. Copy the
  card number.
- Switch to different library and search for the patron by card number.
- Edit the patron.
- Scroll down to the "Library management" section. Under the category
  dropdown you should see a "hint" style div with the library limitation
  warning.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 096cc9088e)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:16:35 +00:00
Tomás Cohen Arazi 42da679145
Bug 41339: Fix 'Too many checkout' typo
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit af756eb19d)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:16:00 +00:00
Owen Leonard 431d7e7fd6
Bug 41395: Terminology: Target item cannot be reserved from other branches
Testing Steps
1. Set the system preference CanReserveFromOtherBranches to “Don’t allow.”
2. Enable the system preference IndependentBranches.
3. Place a hold on an item from your local branch.
4. View the existing hold and select it.
5. Click “Move selected”, then choose “Record level holds to a different record.”
    * For testing, the original hold was placed on biblionumber 10 (Centerville item).
    * The hold was then moved to biblionumber 41 (Fairview item), which should not allow holds from another branch.
6. Observe the error message:
“Hold ID: 4 Target item cannot be reserved from other branches.”

7. Apply the patch and refresh the page.
8. Observe the updated error message:
“Hold ID: 4 Target item cannot be placed on hold from other libraries.”


Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 18f4410734)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:15:27 +00:00
Paul Derscheid 8c5c16f4ec
Bug 41352: Add link to the manual for the Bookings to Collect page
To test:
- Go to Circulation > Bookings to collect
- Click Help
- Verify it links to the correct manual section

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit f53159bfa4)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:14:58 +00:00
Martin Renvoize 5e2246ce53
Bug 41408: Added e.preventDefault() and e.stopPropagation() to edit
Prevent inline editing in POS for the cost and quantity fields from submitting the form on 'Enter'.

Test plan:
1. Ensure the POS page is accessible (Set UsePointOfSale, add a Cash
   Register and some Debit types for sale)
2. Navigate to the Point of Sale page (pos/pay.pl)
3. Add an item to the "This sale" table
4. Click on the "Cost" cell to edit the price inline
5. Change the value and press Enter
6. Expected: The cell value should update without submitting the form
7. Try editing the "Quantity" field as well (same fix applies)
8. Complete a normal transaction to ensure form submission still works with the submit button

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit eedb92ce36)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:14:07 +00:00
Owen Leonard 9657018b08
Bug 41396: Capitalization: 'Transport Settings' and other
This patch fixes incorrect capitalization in the EDI accounts template.
See:
https://wiki.koha-community.org/wiki/Coding_Guidelines#HTML4:_Upper_and_lower_cases_in_strings

To test, apply the patch and go to Administration -> EDI accounts.

- Click "New account"
- The headings for each section of the form should have correct
  capitalization.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit be89df7470)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:13:14 +00:00
Jonathan Druart e96aba99e6
Bug 41451: Prevent 500 when searching hold history
It explodes if the "Item type" column is displayed (ie. if
AllowHoldItemTypeSelection is set).

Test plan:
1) Create at least one hold on any record
2) Navigate to the patron who has the hold and click Holds history in the left menu
3) The table should load correctly and the search bar above it should work
4) In system preferences, enable AllowHoldItemTypeSelection
5) Refresh the holds history, the Requested item type column should now be visible
6) Try a search, a 500 error should be thrown
7) Apply patch
8) Repeat step 5, the search should now work correctly

Note that a couple of other bugs will be fixed on follow-up bug reports.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 6d491b2d95)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:06:27 +00:00
Nick Clemens a5d9d82b6e
Bug 41432: Prefetch items and borrowers for holds
To test:
1 - Add a bunch of holds to a record
    https://github.com/kidclamp/handy-koha-script/blob/main/randhold.pl
2 - Enable profiling:
    https://wiki.koha-community.org/wiki/Profiling_with_Devel::NYTProf#NYTProf_with_Plack
3 - Browse to the record and load the holds table
4 - View the flame graph for the request like instructions in #2
5 - Note that Koha::Hold->borrower and Koha::Hold->item take a lot of time
6 - Apply patch, restart all
7 - Reload page
8 - Reload flame graph
9 - Note time for fetching borrowers and items is significantly reduced
10 - Sing off!

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 7776239d6c)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:05:50 +00:00
Jonathan Druart c2749c8151
Bug 38345: Remove warnings
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 9f15e5911e)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:03:47 +00:00
Jonathan Druart 8e7d76c60d
Bug 38345: Restore support for OpenSearch
The Koha_Main_OS1 and Koha_Main_OS2 jobs are currently failing on
Jenkins.

```
[NoNodes] ** No nodes are available: [http://es:9200], called from sub Search::Elasticsearch::Role::Client::Direct::__ANON__ at /kohadevbox/koha/Koha/SearchEngine/Elasticsearch/Indexer.pm line 417. # Looks like your test exited with 11 just after 3.
```

We need to workaround Elasticsearch/OpenSearch product check
incompatibility.

This patch suggests to monkey-patch
Search::Elasticsearch::Role::Cxn::process_response to disable the strict
product header check at runtime. This allows the same codebase to talk
to both Elasticsearch 8+ and OpenSearch without forking or patching
upstream.

Test plan:
0. Set KOHA_ELASTICSEARCH=yes in ktd's .env
1. Startup ktd with the compose file for OS1
 ktd -f docker-compose.os1.yml up
2. Set SearchEngine=Elasticsearch
3. reindex
 ktd --shell
 perl misc/search_tools/rebuild_elasticsearch.pl -a -b
4. run the tests
 prove -r t/db_dependent/Koha/SearchEngine/Elasticsearch
5. search!
http://localhost:8081/cgi-bin/koha/catalogue/search.pl?q=perl
should show results

6. ktd down then repeat using the compose file for OS2 (docker-compose.os2.yml), ES6 and ES7
for ES6 and ES7 you can use the --es6 and --es7 ktd flag.

IF you get a 403 during the reindex you can try to add
  cluster.routing.allocation.disk.threshold_enabled: "false"
to the environment section of the compose file.
Or
```
curl -X PUT http://es:9200/_cluster/settings \
  -H 'Content-Type: application/json' \
  -d '{
    "persistent": {
      "cluster.blocks.create_index": false
    }
  }'
```
OS thinks we lack disk space and force all indices into read-only.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit afefb8a485)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 16:03:45 +00:00
David Nind 17aa1fd2e1
Bug 41427: (QA follow-up) Add new line at end of file
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 052c93dbb5)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:59:48 +00:00
Caroline Cyr La Rose 3ffe6d10ab
Bug 41427: Rephrase FilterSearchResultsByLoggedInBranch description
This patch rephrases the description of the
FilterSearchResultsByLoggedInBranch system preference to make it
clear it filters items by the library where the user is currently
logged in at. It also changes "branch" to "library" in the
system preference description.

To test:
1. Apply patch
2. Go to Administration > System preferences
3. Search for FilterSearchResultsByLoggedInBranch
4. Read the description, make sure it makes sense, and that the
   grammar and spelling are correct

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5fbe31b52b)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:59:47 +00:00
Martin Renvoize e8b4fa90eb
Bug 40219: Add unit test to verify welcome email is not sent on validation failure
This test ensures that when extended attribute validation fails during
patron creation via the API, no welcome email is sent. This validates
the fix where extended_attributes() validation was moved before the
welcome email is enqueued.

Test plan:
1. Apply this patch
2. Run: prove t/db_dependent/api/v1/patrons.t
3. Verify all tests pass, including the new test that confirms no welcome
   email is sent when extended attribute validation fails

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit d1e82657f4)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:58:41 +00:00
Leo Stoyanov a244d7a6a0
Bug 40219: Moved Extended Attributes Validation Before Enqueuing Welcome Email
Test Plan:
1. Configure Koha with: AutoEmailNewUser = "Send" and create a mandatory extended attribute that will cause validation to fail; for example, set for the attribute to display in the OPAC, to be editable in the OPAC, and to be mandatory. That means this bug must be tested before Bug 40220, where the validation failure is resolved.
2. Setup your Postman, or any other mock API software, to obtain an OAuth token (/api/v1/oauth/token) and to register a patron (/api/v1/patrons).
3. With the OAuth token, send a POST request to /api/v1/patrons with valid patron data and invalid or missing mandatory extended attribute data.
4. Observe the API response with the 400 error and notice that the welcome email was delviered despite the fact that no patron account was created.
5. Apply the patch and repeat step 3. Notice that the API response still contains the 400 error, but no welcome email was sent.

Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5c66438cc3)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:58:40 +00:00
Paul Derscheid 6948a53333
Bug 40949: Remove second sentence of guidance text on Bookings to Collect page
- We don't need it since we don't have to trigger anything by checkin like we do with holds
- The first sentence already communicates all relevant information

Signed-off-by: Aude Charillon <aude.charillon@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit da8db7dcbb)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:57:52 +00:00
Tomás Cohen Arazi 20ee4fbd72
Bug 40989: Fix OAI-PMH tests for CGI.pm 4.68+
CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behavior of
self_url() to include a trailing slash when there's no path component.
This causes OAI-PMH tests to fail as they expect URLs without trailing
slashes.

This patch strips the trailing slash from both the requestURL field in
OAI responses and the baseURL field in Identify responses to maintain
compatibility across CGI.pm versions.

Test plan:
1. Start a 'trixie' KTD:
   $ KOHA_IMAGE=main-trixie ktd --proxy --name trixie up -d
   $ ktd --name trixie --wait-ready 120
2. Run the tests:
   $ ktd --name trixie --shell
  k$ prove t/db_dependent/OAI/Server.t
=> FAIL: Tests fail!
3. Apply this patch
5. Repeat 2
6. Repeat 1-2 with a 'bookworm' image
=> SUCCESS: Tests pass too!
7. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 2224de5a5a)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:56:42 +00:00
Pedro Amorim 0f2ad151de
Bug 40980: [ALTERNATIVE] Sanitize operators param
Ensure it does not contain 'cud-login'

Test plan, k-t-d with elastic search:
1) Make sure you're logged out
2) Access a search results page directly e.g.:
http://localhost:8081/cgi-bin/koha/catalogue/search.pl?q=music
3) You're prompted to login, login.
4) You're shown the search results directly, click a facet result (any facet)
5) You get a Error 403:
Programming error - op 'cud-login' must not start with 'cud-' for GET
6) Apply patch, restart plack, repeat

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit c597046a9e)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:55:24 +00:00
Owen Leonard a7d22d5533
Bug 40287: (follow-up) Use GetDescriptionByKohaField for item fields
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit ed2556e7f3)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:54:34 +00:00
Owen Leonard de44fac6a3
Bug 40287: Fix untranslatable strings in more statistics wizards
This patch makes some updates to the acquisitions, patrons, and catalog
statistics wizards in order to make more information translatable.

To test apply the patch and go to Reports.

- Under "Statistics wizards," test the acquisitions, patrons,
  circulation, and catalog statistics reports:
  - Test setting some or all options in the "Filter column" and running
    the report.
  - At the top of the results there should be a list of the filter
    options you selected and their values.
  - All the filter labels should be correct, and the filter values
    should be in their full/description form rather than in code form
    (e.g. library, item type, collection code, sorting field, etc.)

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 676b220687)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:54:33 +00:00
Andrew Fuerste Henry 338bb59ae1
Bug 41292: Rename whitelist to allowlist and add force_password_reset_when_set_by_staff
To test:
1 - Attempt to save a report containing "select force_password_reset_when_set_by_staff from categories"
2 - observe koha prevents this save
3 - apply patch, restart services
4 - repeat step 1
5 - observe your report now saves and runs successfully

While adding this new field to the list of allowed fields, I changed "whitelist" to "allowlist" as suggested by the terminology guide: https://wiki.koha-community.org/wiki/Terminology

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit fdc57489a1)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:53:27 +00:00
Nick Clemens 73d6a7c035
Bug 41238: Update progress for peseudonmyzation bacgkround jobs
To test:
Enable pseudonymization, may need to add bcrypt settings (see bug 28911)
Perform some circulations
View the jobs (Admin->Manage jobs)
Filter for Pseudonymize statistic
Note the progress is 0/1 even when job finished
Apply patch, restart all
Do more circulations
Confirm jobs now say 1/1 complete

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 82561ff33d)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 15:49:36 +00:00
Owen Leonard 1cd5f8ec61
Bug 41361: Fix markup in category code confirmation modal
This patch fixes the markup in the modal, introduced by Bug 32581,
asking whether to update the patron's expiration date when changing the
patron category. The markup needs to be updated to conform with
Bootstrap 5.

To test, apply the patch and go to Patrons.

- Search for any patron and edit it.
- Scroll down to the "Library management" section.
- Change the category.
  - You should see a confirmation modal: "Confirm expiration date"
  - Test each of the controls to confirm that they work correctly: The
    "X" close button, the "Yes" and "No" buttons.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 247a43eca6)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:58 +00:00
Jan Kissig b75f760946
Bug 40455: SIP2 patron information fails when no currency is set
This fixes an error in SIP2 patron information response (64) when no currency is set, which can be the case on newly created instances.

Test plan for KTD:

a) Delete currency and related tables
 - run 'koha-mysql kohadev' to start SQL client. Then run the following queries:
 - delete from aqbasket;
   delete from aqbooksellers;
   delete from currency;
b) exit
c) run 'perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1 -l CPL -m patron_information --patron 42'

  Attempting socket connection to 127.0.0.1:6001...connected!
  SEND: 9300CNterm1|COterm1|CPCPL|
  READ: 941

  Trying 'patron_information'
  SEND: 6300120251202    115652          AOCPL|AA42|ACterm1|
  READ: undef

d) recognize the last response: READ: undef
e) apply patch and restart sip2 server via 'sudo koha-sip --restart kohadev'
e) rerun 'perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1 -l CPL -m patron_information --patron 42'

  Attempting socket connection to 127.0.0.1:6001...connected!
  SEND: 9300CNterm1|COterm1|CPCPL|
  READ: 941

  Trying 'patron_information'
  SEND: 6300120251202    122545          AOCPL|AA42|ACterm1|
  READ: 64              00120251202    122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|PIY|AFGreetings from Koha. |

f) check the last response is now a patron information response (64)

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit e1f1c39e83)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:58 +00:00
Jonathan Druart 2abee1cc83
Bug 40006: Remove dbh->do return value from updatedatabase output
There are 2 occurrences of "0E0" in the test suite output.

We should remove them.

Test plan:
root@kohadevbox:koha$ perl /kohadevbox/misc4dev/run_tests.pl --run-all-tests
And confirm that the 0E0 does no longer appear

You can abort when the update DB process is done

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5a219c4083)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:58 +00:00
Owen Leonard b6fbded69a
Bug 40567: Correct eslint errors in recalls.js
This patch corrects a few minor errors in recalls.js. There should be no
change to how the pages work.

The following functions have been changed:

- Cancel a recall from the recalls tab on a patron record
- Expire a recall or revert the waiting status of a recall from the
  "Recalls awaiting pickup" page.
- Cancel a recall which has been transferred from the "Recalls queue"
  page.
- Mark a recall "Overdue" from the "Recalls queue" page.
- Cancel selected recalls on the "Recalls queue" page.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 971028cf3a)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:58 +00:00
Owen Leonard f331ff3419
Bug 40619: Remove OverDrive star ratings from the OPAC
Since OverDrive no longer supplies star ratings through their API we can
remove the corresponding JS from the template.

To test, apply the patch and enable OverDrive integration.

- In the OPAC, perform a search which will return results from your
  OverDrive collection.
- Follow the link a the top of Koha search results, "Found X results in
  the library's OverDrive collection"
- Confirm that OverDrive search results load correctly and that nothing
  looks broken.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit e0bef69d47)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:58 +00:00
Nick Clemens 64f8ab25fd
Bug 39514: Only mark the affected rows as problematic
Before this patch we were setting a variable and nebver unsetting it

Now we can add the conditional directly to the row, so don't need a variable

To test:
1. Go to Acquisitions
2. Click 'Search' in the vendor search
3. Click 'Add to basket' next to 'My basket'
4. Click 'From a new (empty) record'
5. Fill out the form
   - Title: enter a title
   - Koha item type: choose an item type
   5.1. Click 'Add item'
   - Fund: choose a fund
   - Uncertain price: check
6. Click 'Save'
7. Go back to Acquisitions
8. Click 'Search' in the vendor search
9. Click 'New' > 'Basket'
10. Enter a basket name
11. Click 'Save'
12. Go back to Acquistions
13. Click 'Search' in the vendor search
--> Notice the empty basket is also in bold red, even if it doesn't have uncertain prices
14. Click 'Add to basket'
--> Notice everything is in bold and some in red
15. Apply patch, reload
16. Only affected basket is in red

Signed-off-by: Samuel <sam.sowanick@corvallisoregon.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 7b1d11da74)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Owen Leonard 194e3f21bd
Bug 41348: Capitalization: "List Files" and others
This patch corrects two strings in js/transport_status.js so that it has
correct capitalization.

I'm not sure how to test every way these strings are seen in the
interface, but checking the patch should show only capitalization
changes.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 6eabfe0d50)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Alex Buckley 315807fd8c
Bug 41145: Add unit test coverage for stopping duplicated borrowerslog logging due to extended patron attributes being deleted on import
This adds to the unit tests in t/db_dependent/Koha/Patrons/Import.t. It confirms that repeating the same patron import (with extended patron attributes) will not generate duplicated borrowersLog entries.

Test plan:
1. prove t/db_dependent/Koha/Patrons/Import.t

Sponsored-by: Auckland University of Technology
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit e027ed2dca)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Alex Buckley 873767c6c8
Bug 41145: Do not delete the existing extended_attribute values when importing patrons
Since bug 26744 was upstreamed when you use the Import patron tool or the import_patrons.pl script from the commandline and pass a preserve-extended-attributes parameter the borrowerlogs inaccurately have an entry added where the attribute 'before' value is empty.

Test plan:
1. Create a patron attribute type named 'INSTID': Koha administration > Patrons and circulation > Patron attribute types > New patron attribute type.
2. Download the patron started CSV from: Tools > Import patrons > click the 'Download a Starter CSV file with all the columns.' link
3. Save values in the: cardnumber, surname, firstname, branchcode, categorycode, auth_method, lang, and patron_attributes columns
4. Make sure your value in the patron_attributes column is INSTID:1234
5. Import this csv file:
cd misc
./import_patrons.pl --file <filename>.csv --matchpoint cardnumber --default branchcode=CPL --overwrite --preserve-extended-attributes --verbose --verbose --confirm

6. Navigate to the patron account in your browser
7. Click 'Modification log' and observe how many entries there are
8. Repeat step 5
9. Notice there is a new entry in your Modification log with an info column value of: { "attribute.INSTID" : { "after" : "1234", "before" : "" } }
10. Repeat step 5 and notice another new entry in the Modification log with an empty "before" value

11. Apply patch
12. Repeat step 5 and notice no new entry in the Modification log
13. Edit your csv and change the patron_attributes value to: INSTID:5678
14. Repeat step 5 and notice the new modification log entry with an info column value of: { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }

15. Add another new patron attribute type to your Koha named 'home'
16. edit your csv and change the patron_attributes value to: INSTID:5678,home:town
17. Repeat step 5
18. Notice a new Modification log entry with the value of: { "attribute.home" : { "after" : "town", "before" : "" } }

Sponsored-by: Auckland University of Technology
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 2608f48952)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Andrew Fuerste Henry 35dba14c1e
Bug 41345: Restore ignore button functionality
To test:
1 - place a hold
2 - check an item in to fill your hold, confirm so item goes to Waiting status
3 - check item in again, get Hold Already Waiting modal
4 - clck Ignore, confirm modal reloads immediately
5 - apply patch, restart_all
6 - reload checkin page
7 - check item in again, get Hold Already Waiting modal
8 - click Ignore, confirm modal goes away and doesn't return

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 994f091a32)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Marcel de Rooy f23d71721a
Bug 37796: (QA follow-up) nextpublisheddate in t/db../GetNextSeq.t
Just adding the fifth parameter here, since that actually should
be the next published date. But note that it is just a placeholder,
since the test does not use the associated date substitutions.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 1ed55841cd)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Nick Clemens 6f3e2c1b1b
Bug 37796: (follow-up) Correct call in showpredictionpattern.pl
Signed-off-by: Wesley Owen <wesleyowen@churchofjesuschrist.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 7c4f8d00c2)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Nick Clemens c55e114091
Bug 37796: Pass correct publication date when generating the next serial form the serial collection page
Before this patch, we pass the previous issue date to generate the next seq - this works for the pattern as we are incrementing.

When the patter is translated ot words, we need to do this from the date of the next issue, so we must pass in the next
published date

This patch changes the call to GetNextSeq from serial-collection.pl to ensure the new issue publication date is used to
generate the numbers.

To test:
0 - Follow the plan to recreate from the initial bug description and recreate
1 - Apply patch
2 - On the serial used in previous patch go to 'Serial collection'
3 - Generate next issue
4 - Confirm the numbering is correctly generated after this patch

Signed-off-by: Wesley Owen <wesleyowen@churchofjesuschrist.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit f2f30ad9b9)
Signed-off-by: Jacob O'Mara <jacob.omara@openfifth.co.uk>
2026-01-16 12:59:57 +00:00
Lucas Gass 19f8f483ae
Bug 41421: [25.11.x] Fix DB update
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-10 10:31:55 -07:00
69 changed files with 2132 additions and 420 deletions

View File

@ -131,7 +131,10 @@ sub new {
}
# Get currency 3 chars max
my $currency = substr Koha::Acquisition::Currencies->get_active->currency, 0, 3;
my $currency = Koha::Acquisition::Currencies->get_active;
if ($currency) {
$currency = substr $currency->currency, 0, 3;
}
my $circ_blocked =
( C4::Context->preference('OverduesBlockCirc') ne "noblock" && defined $flags->{ODUES}->{itemlist} ) ? 1 : 0;

View File

@ -810,7 +810,7 @@ sub GetPreviousSerialid {
my (
$nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate, $count_forward );
) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate, $nextpublisheddate, $count_forward );
$subscription is a hashref containing all the attributes of the table
'subscription'.
@ -818,13 +818,14 @@ $pattern is a hashref containing all the attributes of the table
'subscription_numberpatterns'.
$frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
$planneddate is a date string in iso format.
$nextpublishedddate is a date string in iso format.
$count_forward is the number of issues to count forward, defaults to 1 if omitted
This function get the next issue for the subscription given on input arg
=cut
sub GetNextSeq {
my ( $subscription, $pattern, $frequency, $planneddate, $count_forward ) = @_;
my ( $subscription, $pattern, $frequency, $planneddate, $nextpublisheddate, $count_forward ) = @_;
return unless ( $subscription and $pattern );
@ -915,7 +916,7 @@ sub GetNextSeq {
$calculated =~ s/\{Z\}/$newlastvalue3string/g;
}
my $dt = dt_from_string($planneddate);
my $dt = dt_from_string($nextpublisheddate);
$calculated =~ s/\{Month\}/$dt->month/eg;
$calculated =~ s/\{MonthName\}/$dt->month_name/eg;
$calculated =~ s/\{Year\}/$dt->year/eg;
@ -1167,15 +1168,16 @@ sub ModSerialStatus {
my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern( $subscription->{numberpattern} );
my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency( $subscription->{periodicity} );
# next date (calculated from actual date & frequency parameters)
my $nextpublisheddate = GetNextDate( $subscription, $publisheddate, $frequency, 1 );
my $nextpubdate = $nextpublisheddate;
# next issue number
my (
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $count );
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $nextpublisheddate, $count );
# next date (calculated from actual date & frequency parameters)
my $nextpublisheddate = GetNextDate( $subscription, $publisheddate, $frequency, 1 );
my $nextpubdate = $nextpublisheddate;
$query =
"UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
WHERE subscriptionid = ?";

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 = "25.11.00.000";
$VERSION = "25.11.01.000";
sub version {
return $VERSION;

View File

@ -53,6 +53,8 @@ sub process {
my $statistic = $args->{statistic};
my $stat_object = Koha::Statistic->new($statistic);
Koha::PseudonymizedTransaction->create_from_statistic($stat_object);
$self->progress(1)
; #We could use step here, but we are going to save the object during finish and this saves a DB write
$self->finish( { data => "" } ); # We want to clear the job data to avoid storing patron information
}

View File

@ -32,7 +32,10 @@ use Exception::Class (
'Koha::Exceptions::Patron::Relationship::InvalidRelationship' => {
isa => 'Koha::Exceptions::Patron::Relationship',
description => 'The specified relationship is invalid',
fields => [ 'relationship', 'no_relationship', 'invalid_guarantor' ]
fields => [
'relationship', 'no_relationship', 'invalid_guarantor', 'child_guarantor',
'guarantor_cant_have_guarantors', 'guarantor'
],
},
'Koha::Exceptions::Patron::Relationship::NoGuarantor' => {
isa => 'Koha::Exceptions::Patron::Relationship',
@ -50,6 +53,8 @@ sub full_message {
if ( $self->no_relationship ) {
$msg = sprintf("No relationship passed.");
} elsif ( $self->invalid_guarantor ) {
$msg = sprintf("Guarantee patron cannot be a guarantor.");
} elsif ( $self->child_guarantor ) {
$msg = sprintf("Child patron cannot be a guarantor.");
} else {
$msg = sprintf( "Invalid relationship passed, '%s' is not defined.", $self->relationship );

View File

@ -695,6 +695,19 @@ sub item {
return Koha::Item->_new_from_dbic($rs);
}
=head3 item_type
Returns the related Koha::ItemType object for this hold
=cut
sub item_type {
my ($self) = @_;
my $rs = $self->_result->itemtype;
return unless $rs;
return Koha::ItemType->_new_from_dbic($rs);
}
=head3 item_group
Returns the related Koha::Biblio::ItemGroup object for this Hold
@ -1281,7 +1294,7 @@ sub to_api_mapping {
lowestPriority => 'lowest_priority',
suspend => 'suspended',
suspend_until => 'suspended_until',
itemtype => 'item_type',
itemtype => 'item_type_id',
item_level_hold => 'item_level',
};
}
@ -1319,6 +1332,10 @@ sub strings_map {
pickup_library_id => { str => $self->pickup_library->branchname, type => 'library' },
};
if ( defined $self->itemtype ) {
$strings->{item_type_id} = { str => $self->item_type->description, type => 'item_type' };
}
if ( defined $self->cancellation_reason ) {
my $av = Koha::AuthorisedValues->search(
{

View File

@ -377,7 +377,7 @@ sub to_api_mapping {
$library->opac_info({ lang => $lang });
Returns additional contents block OpacLibraryInfo for $lang or 'default'.
Returns all additional contents blocks OpacLibraryInfo by library and $lang (or 'default').
Note: This replaces the former branches.opac_info column.
@ -385,7 +385,7 @@ Note: This replaces the former branches.opac_info column.
sub opac_info {
my ( $self, $params ) = @_;
return Koha::AdditionalContents->find_best_match(
return Koha::AdditionalContents->search_for_display(
{
category => 'html_customizations',
location => 'OpacLibraryInfo',

View File

@ -196,6 +196,7 @@ our $mapping = {
'circ/offline' => '/circulation.html#offline-circulation-in-koha',
'circ/on-site_checkouts' => '/circulation.html#pending-on-site-checkouts',
'circ/overdue' => '/circulation.html#overdues',
'circ/pendingbookings' => '/circulation.html#bookings-to-collect',
'circ/pendingreserves' => '/circulation.html#holds-to-pull',
'circ/renew' => '/circulation.html#renewing',
'circ/reserveratios' => '/circulation.html#hold-ratios',

View File

@ -33,6 +33,7 @@ sub new {
my $baseURL = $repository->self_url();
$baseURL = $+{base_url}
if $baseURL =~ m/(?<base_url>.*)\?.*/;
$baseURL =~ s{/$}{}; # Strip trailing slash for CGI.pm 4.68+ compatibility
my $self = $class->SUPER::new(
baseURL => $baseURL,

View File

@ -157,6 +157,11 @@ sub new {
$response = $class->new( $self, %attr );
}
# Strip trailing slash from requestURL for CGI.pm 4.68+ compatibility
my $url = $response->requestURL();
$url =~ s{/$}{};
$response->requestURL($url);
$response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
$response->xslt("/opac-tmpl/xslt/OAI.xslt");
$response->generate;

View File

@ -22,6 +22,7 @@ use Modern::Perl;
use List::MoreUtils qw( any none uniq notall zip6);
use JSON qw( to_json );
use Scalar::Util qw(looks_like_number);
use Unicode::Normalize qw( NFKD );
use Try::Tiny;
use DateTime ();
@ -206,8 +207,6 @@ sub store {
my $self = shift;
my $params = @_ ? shift : {};
my $guarantors = $params->{guarantors} // [];
$self->_result->result_source->schema->txn_do(
sub {
if (
@ -328,20 +327,6 @@ sub store {
$self->borrowernumber(undef);
if ( C4::Context->preference('ChildNeedsGuarantor')
and ( $self->is_child or $self->category->can_be_guarantee )
and $self->contactname eq ""
and !@$guarantors )
{
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
}
foreach my $guarantor (@$guarantors) {
if ( $guarantor->is_child ) {
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 );
}
}
$self = $self->SUPER::store;
$self->add_enrolment_fee_if_needed(0);
@ -385,23 +370,6 @@ sub store {
}
my @existing_guarantors = $self->guarantor_relationships()->guarantors->as_list;
push @$guarantors, @existing_guarantors;
if ( C4::Context->preference('ChildNeedsGuarantor')
and ( $self->is_child or $self->category->can_be_guarantee )
and ( !defined $self->contactname || $self->contactname eq "" )
and !@$guarantors )
{
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
}
foreach my $guarantor (@$guarantors) {
if ( $guarantor->is_child ) {
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 );
}
}
# Actionlogs
if ( C4::Context->preference("BorrowersLog") || C4::Context->preference("CardnumberLog") ) {
my $info;
@ -2834,6 +2802,61 @@ sub _anonymize_column {
$self->$col($val);
}
=head3 can_be_guaranteed_by
$patron->can_be_guaranteed_by( [
$guarantor1,
$guarantor2,
...
] );
Given an array of guarantors, checks if they can be guarantors for this patron.
Throws Koha::Exceptions::Patron::Relationship::InvalidRelationship if a proposed
guarantor cannot be a guarantor for this patron.
$patron->can_be_guaranteed_by();
Given no guarantors, checks if this patron must be guaranteed by someone. Read
"can be guaranteed by nobody".
Throws Koha::Exceptions::Patron::Relationship::NoGuarantor if this patron must
have a guarantor.
=cut
sub can_be_guaranteed_by {
my ( $self, $guarantors ) = @_;
my $category = $self->category;
if ( C4::Context->preference('ChildNeedsGuarantor')
and ( $category->category_type eq 'C' or $category->can_be_guarantee )
and scalar @$guarantors == 0 )
{
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
}
if ( $self->is_guarantor && scalar @$guarantors > 0 ) {
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( guarantor_cant_have_guarantors => 1 );
}
foreach my $guarantor (@$guarantors) {
next if ref($guarantor) ne 'Koha::Patron';
if ( $guarantor->is_guarantee ) {
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw(
invalid_guarantor => 1,
guarantor => $guarantor,
);
} elsif ( $guarantor->is_child ) {
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( child_guarantor => 1 );
}
}
return 1;
}
=head3 add_guarantor
my $relationship = $patron->add_guarantor(
@ -3519,7 +3542,7 @@ sub is_patron_inside_charge_limits {
my $guarantors_non_issues_charges = 0;
# Check the debt of this patrons guarantees
if ( defined $no_issues_charge_guarantees ) {
if ( defined $no_issues_charge_guarantees && looks_like_number($no_issues_charge_guarantees) ) {
my @guarantees = map { $_->guarantee } $patron->guarantee_relationships->as_list;
foreach my $g (@guarantees) {
$guarantees_non_issues_charges += $g->account->non_issues_charges;
@ -3527,7 +3550,9 @@ sub is_patron_inside_charge_limits {
}
# Check the debt of this patrons guarantors *and* the guarantees of those guarantors
if ( defined $no_issues_charge_guarantors_with_guarantees ) {
if ( defined $no_issues_charge_guarantors_with_guarantees
&& looks_like_number($no_issues_charge_guarantors_with_guarantees) )
{
$guarantors_non_issues_charges = $patron->relationships_debt(
{ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 } );
}

View File

@ -377,9 +377,6 @@ LINE: while ( my $borrowerline = <$handle> ) {
$patron_attributes =
$patron->extended_attributes->merge_and_replace_with($patron_attributes);
}
# We do not want to filter by branch, maybe we should?
Koha::Patrons->find($borrowernumber)->extended_attributes->delete;
$patron->extended_attributes($patron_attributes);
}
$overwritten++;

View File

@ -81,7 +81,7 @@ sub add {
my $pickup_library_id = $body->{pickup_library_id};
my $item_id = $body->{item_id};
my $patron_id = $body->{patron_id};
my $item_type = $body->{item_type};
my $item_type_id = $body->{item_type_id};
my $expiration_date = $body->{expiration_date};
my $notes = $body->{notes};
my $hold_date = $body->{hold_date};
@ -209,7 +209,7 @@ sub add {
title => $biblio->title,
itemnumber => $item_id,
found => undef, # TODO: Why not?
itemtype => $item_type,
itemtype => $item_type_id,
non_priority => $non_priority,
item_group_id => $item_group_id,
confirmations => $confirmations,

View File

@ -122,6 +122,9 @@ sub add {
my $patron = Koha::Patron->new_from_api($body)->store;
$patron->extended_attributes(
[ map { { code => $_->{type}, attribute => $_->{value} } } @$extended_attributes ] );
my $overrides = $c->stash('koha.overrides');
if ( $overrides->{welcome_yes}
|| ( C4::Context->preference("AutoEmailNewUser") && !$overrides->{welcome_no} ) )
@ -157,8 +160,6 @@ sub add {
}
}
$patron->extended_attributes(
[ map { { code => $_->{type}, attribute => $_->{value} } } @$extended_attributes ] );
if ( C4::Context->preference('EnhancedMessagingPreferences') ) {
C4::Members::Messaging::SetMessagingPreferencesFromDefaults(
{

View File

@ -29,8 +29,8 @@ use base qw(Koha::Object);
# TODO Koha::Report->store should check this before saving
use constant FORBIDDEN_COLUMN_MATCHES => qw(password token uuid secret);
use constant WHITELISTED_COLUMN_NAMES =>
qw(password_expiration_date password_expiry_days reset_password change_password min_password_length require_strong_password password_expiration_date);
use constant ALLOWLISTED_COLUMN_NAMES =>
qw(password_expiration_date password_expiry_days reset_password change_password min_password_length require_strong_password password_expiration_date force_password_reset_when_set_by_staff);
=head1 NAME
@ -99,8 +99,8 @@ sub check_columns {
@columns = grep { /$regex/i } @columns;
}
# Check for whitelisted variants
$regex = '(^' . ( join '|', WHITELISTED_COLUMN_NAMES ) . ')$'; # should be full match
# Check for allowlisted variants
$regex = '(^' . ( join '|', ALLOWLISTED_COLUMN_NAMES ) . ')$'; # should be full match
@columns = grep { !/$regex/i } @columns;
return @columns;
}

View File

@ -38,6 +38,24 @@ use Clone qw( clone );
use Modern::Perl;
use Readonly qw( Readonly );
use Search::Elasticsearch;
use Search::Elasticsearch::Role::Cxn;
{
# Monkey-patch Search::Elasticsearch::Role::Cxn::process_response
no warnings 'redefine';
my $orig_sub = \&Search::Elasticsearch::Role::Cxn::process_response;
# Override method
*Search::Elasticsearch::Role::Cxn::process_response = sub {
my ( $self, $params, $code, $msg, $body, $headers ) = @_;
unless ( $headers && $headers->{'x-elastic-product'} && $headers->{'x-elastic-product'} eq 'Elasticsearch' ) {
$Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE = '';
}
$orig_sub->(@_);
};
}
use Try::Tiny qw( catch try );
use YAML::XS;

View File

@ -101,7 +101,7 @@ properties:
non_priority:
description: Set this hold as non priority
type: boolean
item_type:
item_type_id:
type:
- string
- "null"

View File

@ -201,7 +201,7 @@
type:
- string
- "null"
item_type:
item_type_id:
description: Limit hold on one itemtype (ignored for item-level holds)
type:
- string

View File

@ -263,7 +263,8 @@ $template->param( advancedsearchesloop => $advancedsearchesloop );
$template->param( searchid => scalar $cgi->param('searchid'), );
my $default_sort_by = C4::Context->default_catalog_sort_by;
my $default_sort_by = C4::Context->default_catalog_sort_by;
my @sanitized_operators = grep { $_ ne 'cud-login' } $cgi->multi_param('op');
# The following should only be loaded if we're bringing up the advanced search template
if ( $template_type eq 'advsearch' ) {
@ -274,7 +275,7 @@ if ( $template_type eq 'advsearch' ) {
my $expanded = $cgi->param('expanded_options');
if ( $cgi->param('edit_search') ) {
@operands = $cgi->multi_param('q');
@operators = $cgi->multi_param('op');
@operators = @sanitized_operators;
@indexes = $cgi->multi_param('idx');
$template->param(
sort => scalar $cgi->param('sort_by'),
@ -370,7 +371,7 @@ $template->param( 'sort_by' => $sort_by[0] );
# operators include boolean and proximity operators and are used
# to evaluate multiple operands
my @operators = map uri_unescape($_), $cgi->multi_param('op');
my @operators = map uri_unescape($_), @sanitized_operators;
# indexes are query qualifiers, like 'title', 'author', etc. They
# can be single or multiple parameters separated by comma: kw,right-Truncation

View File

@ -8,7 +8,7 @@ return {
my ($args) = @_;
my ( $dbh, $out ) = @$args{qw(dbh out)};
say $dbh->do(
$dbh->do(
q{
UPDATE columns_settings
SET columnname = SUBSTRING(columnname,LOCATE("_",columnname)+1)

View File

@ -16,7 +16,7 @@ return {
say $out "Added new permission 'merge_borrowers'";
$dbh->do(
q{INSERT INTO user_permissions (borrowernumber, module_bit, code) SELECT borrowernumber, module_bit, 'merge_borrowers' FROM user_permissions where module_bit = 4 and code = 'edit_borrowers';}
q{INSERT IGNORE INTO user_permissions (borrowernumber, module_bit, code) SELECT borrowernumber, module_bit, 'merge_borrowers' FROM user_permissions where module_bit = 4 and code = 'edit_borrowers';}
);
say $out "Added 'merge_borrowers' permission to existing users with 'edit_borrowers'";

View File

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

View File

@ -204,7 +204,7 @@
[% CASE 'PATRON_CANT' %]
<span>Max renewals reached</span>
[% CASE 'TOO_MANY' %]
<span>Too many checkout</span>
<span>Too many checkouts</span>
[% CASE 'PREVISSUE' %]
<span>Patron was notified of previous issues</span>
[% CASE 'NOT_FOR_LOAN' %]

View File

@ -114,10 +114,7 @@
</thead>
<tbody>
[% FOREACH basket IN supplier.loop_basket %]
[% IF ( basket.uncertainprices ) %]
[% SET tr_class="problem" %]
[% END %]
<tr class="[% tr_class | html %]">
<tr [% IF ( basket.uncertainprices ) %]class="problem"[% END %]>
<td>[% basket.basketno | html %]</td>
<td><a href="/cgi-bin/koha/acqui/basket.pl?basketno=[% basket.basketno | uri %]">[% basket.basketname | html %]</a></td>
<td data-order="[% basket.total_items | html %]">

View File

@ -29,6 +29,18 @@
[% INCLUDE 'doc-head-close.inc' %]
[% Asset.css("lib/codemirror/codemirror.min.css") | $raw %]
[% Asset.css("lib/codemirror/lint.min.css") | $raw %]
[% FILTER collapse %]
<style>
.opac-library-info {
border: 1px solid #dedede;
border-radius: 2px;
padding: 0.375rem 0.75rem;
}
.opac-library-info + .opac-library-info {
margin-top: 0.5rem;
}
</style>
[% END %]
</head>
<body id="admin_branches" class="admin">
@ -330,10 +342,20 @@
<div class="fg-input">
[% IF OpacLibraryInfo %]
<!-- opac_info -->
<div>[% OpacLibraryInfo.content | $raw %]</div>
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% OpacLibraryInfo.id | $raw %]&editmode=wysiwyg" target="_blank">Edit HTML content</a>
[% FOREACH info IN OpacLibraryInfo %]
[% IF library.branchcode == info.branchcode %]
<div id="opac_library_info_[% info.additional_content_id | $raw %]" class="opac-library-info">[% info.content | $raw %]</div>
[% IF ( CAN_user_tools_edit_additional_contents ) %]
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% info.additional_content_id | $raw %]&editmode=wysiwyg" target="_blank">
<i class="fa fa-pencil"></i> Edit HTML content
</a>
[% END %]
[% END %]
[% END %]
[% ELSE %]
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank">Add HTML content</a>
[% IF ( CAN_user_tools_edit_additional_contents ) %]
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank"> <i class="fa fa-plus"></i> Add HTML content </a>
[% END %]
[% END %]
</div>
</div>
@ -741,7 +763,23 @@
<div class="row page-section">
<div class="col-sm-12">
<h2>OPAC information</h2>
<div>[% OpacLibraryInfo.content | $raw %]</div>
[% IF OpacLibraryInfo %]
<!-- opac_info -->
[% FOREACH info IN OpacLibraryInfo %]
[% IF library.branchcode == info.branchcode %]
<div id="opac_library_info_[% info.additional_content_id | $raw %]" class="opac-library-info">[% info.content | $raw %]</div>
[% IF ( CAN_user_tools_edit_additional_contents ) %]
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% info.additional_content_id | $raw %]&editmode=wysiwyg" target="_blank">
<i class="fa fa-pencil"></i> Edit HTML content
</a>
[% END %]
[% END %]
[% END %]
[% ELSE %]
[% IF ( CAN_user_tools_edit_additional_contents ) %]
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank"> <i class="fa fa-plus"></i> Add HTML content </a>
[% END %]
[% END %]
</div>
</div>
[% END %]

View File

@ -97,9 +97,9 @@
[% END %]
</h1>
<!-- Basic Information -->
<!-- Basic information -->
<fieldset class="rows">
<legend>Basic Information</legend>
<legend>Basic information</legend>
<ol>
<li>
<label for="vendor_id">Vendor: </label>
@ -170,9 +170,9 @@
</ol>
</fieldset>
<!-- Transport Settings -->
<!-- Transport settings -->
<fieldset class="rows">
<legend>Transport Settings</legend>
<legend>Transport settings</legend>
<ol>
<li>
<label for="file_transport_id">File transport: </label>
@ -191,9 +191,9 @@
</ol>
</fieldset>
<!-- Message Types -->
<!-- Message types -->
<fieldset class="rows">
<legend>Message Types</legend>
<legend>Message types</legend>
<ol>
<li>
<label for="quotes_enabled">Quotes enabled: </label>
@ -230,9 +230,9 @@
</ol>
</fieldset>
<!-- Functional Switches -->
<!-- Functional switches -->
<fieldset class="rows">
<legend>Functional Switches</legend>
<legend>Functional switches</legend>
<ol>
<li>
<label for="auto_orders">Automatic ordering: </label>

View File

@ -133,7 +133,7 @@ Staff interface:
choices:
1: Do
0: "Don't"
- add a filter to the location column on staff interface search results to filter items by the logged in user branch.
- add a filter to the location column on staff interface search results to filter items by the library the user is currently logged into.
-
- pref: viewMARC
choices:
@ -216,4 +216,4 @@ Staff interface:
"enforced": Enforce
"enabled": Enable
"disabled": "Don't enable"
- two-factor authentication (2FA) for staff members.
- two-factor authentication (2FA) for staff members.

View File

@ -41,7 +41,7 @@
[% INCLUDE 'messages.inc' %]
<h1>Items required for bookings between <span id="from_date">[% from | $KohaDates %]</span> and <span id="to_date">[% to | $KohaDates %]</span></h1>
<h2>Reported on [% todaysdate | $KohaDates %]</h2>
<p>The following items have not been collected for bookings. Please retrieve them and check them in.</p>
<p>The following items have not been collected for bookings.</p>
<div id="searchresults">
<table id="bookingst"></table>
</div>

View File

@ -763,7 +763,7 @@
<input type="hidden" name="bn-[% inputloo.counter | html %]" value="[% inputloo.borrowernumber | html %]" />
<input type="hidden" name="nr-[% inputloo.counter | html %]" value="[% inputloo.not_returned | html %]" />
[% END %]
<button type="submit" data-bs-dismiss="modal" class="btn btn-default deny" accesskey="i"> <i class="fa fa-times"></i> Ignore (I) </button>
<button type="submit" data-bs-dismiss="modal" class="btn btn-default ignore deny" accesskey="i"> <i class="fa fa-times"></i> Ignore (I) </button>
</form>
<fieldset class="action">

View File

@ -1,3 +1,4 @@
[% USE To %]
[% USE raw %]
[% USE Asset %]
[% USE KohaDates %]
@ -158,6 +159,29 @@
[% INCLUDE 'str/members-menu.inc' %]
[% Asset.js("js/members-menu.js") | $raw %]
[% INCLUDE 'js-biblio-format.inc' %]
<script>
const all_libraries = [% To.json(Branches.all) | $raw %].map(e => {
e["_id"] = e["branchcode"];
e["_str"] = e["branchname"];
return e;
});
const all_item_types = [% To.json(ItemTypes.Get) | $raw %].map(e => {
e["_id"] = e["itemtype"];
e["_str"] = e["translated_description"];
return e;
});
var coded_values = {
library: new Map(all_libraries.map( l => [l.branchname, l.branchcode] )),
item_type: new Map(all_item_types.map( i => [i.translated_description, i.itemtype] )),
};
let filters_options = {
item_types: all_item_types,
libraries: all_libraries,
};
</script>
<script>
$(document).ready(function() {
var table_settings = [% TablesSettings.GetTableSettings('members', 'holdshistory', 'holdshistory-table', 'json') | $raw %];
@ -165,6 +189,7 @@
//Remove item type column settings
table_settings['columns'] = table_settings['columns'].filter(function(c){return c['columnname'] != 'itemtype';});
[% END %]
let current_holds_table = build_holds_table("#table_holdshistory");
let old_holds_table = build_holds_table("#table_oldholdshistory", 1);
function build_holds_table(table_id, old){
@ -253,11 +278,13 @@
}
},
{
data: "pickup_library_id:pickup_library.name",
data: "me.pickup_library_id",
datatype: "coded_value:library",
dataFilter: "libraries",
searchable: true,
orderable: true,
render: function (data, type, row, meta) {
return row.pickup_library.name;
return escape_str(row._strings.pickup_library_id ? row._strings.pickup_library_id.str : row.pickup_library_id);
}
},
{
@ -298,12 +325,14 @@
},
[% IF show_itemtype_column %]
{
data: "item_type.item_type",
data: "item_type_id",
datatype: "coded_value:item_type",
dataFilter: "item_types",
searchable: true,
orderable: true,
render: function (data, type, row, meta) {
if ( row.item_type ) {
return row.item_type.item_type;
if ( row.item_type_id ) {
return row._strings.item_type_id ? row._strings.item_type_id.str : row.item_type_id;
} else {
return _("Any item type");
}
@ -343,7 +372,12 @@
}
},
],
}, table_settings, true, additional_filters);
},
table_settings,
true,
additional_filters,
filters_options,
);
}
$("#current_holds_filters :checkbox, #old_holds_filters :checkbox").on("change", function(e){
e.preventDefault();

View File

@ -195,7 +195,16 @@
<li id="ERROR_child_is_guarantor">Child patron cannot be a guarantor.</li>
[% END %]
[% IF ( ERROR_guarantor_is_guarantee ) %]
<li id="ERROR_guarantor_is_guarantee">A guarantor cannot be a guarantee.</li>
<li id="ERROR_guarantor_is_guarantee">
Cannot add a guarantor. The proposed guarantor
<a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% guarantor_is_guarantee.borrowernumber | uri %]" target="blank">
[% guarantor_is_guarantee.firstname | html %] [% guarantor_is_guarantee.surname | html %] ([% guarantor_is_guarantee.cardnumber | html %])
</a>
is a guarantee of someone else, and guarantees cannot be guarantors.</li
>
[% END %]
[% IF ( ERROR_guarantor_cant_have_guarantors ) %]
<li id="ERROR_guarantor_cant_have_guarantors">Cannot add a guarantor. This patron is already a guarantor, and guarantors cannot have guarantors.</li>
[% END %]
[% IF ( ERROR_cannot_delete_guarantor ) %]
<li id="ERROR_cannot_delete_guarantor">Cannot delete guarantor(s). A child patron needs a guarantor.</li>
@ -959,10 +968,13 @@
</optgroup>
[% END %]
</select>
[% IF limited_category %]
<span title="The patron's current category ([% patron_category.description | html %]) is limited to other libraries"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i></span>
[% END %]
<span class="required">Required</span>
[% IF limited_category %]
<div class="hint">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
The patron's current category ([% patron_category.description | html %]) is limited to other libraries.
</div>
[% END %]
</li>
[% UNLESS nosort1 %]
@ -1663,18 +1675,18 @@
<!-- Expiration date modal-->
<div id="expirationDateModal" class="modal" tabindex="-1" role="dialog" aria-labelledby="expirationDateModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="expirationDateModalLabel">Confirm expiration date</h4>
<h1 class="modal-title" id="expirationDateModalLabel">Confirm expiration date</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Change expiration date to default for this category?</p>
</div>
<div class="modal-footer">
<button id="expirationDateConfirmBtn" class="btn btn-primary"><i class="fa fa-check" aria-hidden="true"></i> Yes</button>
<button id="expirationDateCancelBtn" data-dismiss="modal" class="btn btn-secondary"><i class="fa fa-times" aria-hidden="true"></i> No</button>
<button id="expirationDateCancelBtn" data-bs-dismiss="modal" class="btn btn-secondary"><i class="fa fa-times" aria-hidden="true"></i> No</button>
</div>
</div>
</div>

View File

@ -423,8 +423,12 @@
// Handle blur and Enter key events
input.addEventListener('blur', updateCell);
input.addEventListener('keyup', function(e) {
if (e.key === 'Enter') updateCell();
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
updateCell();
}
});
function updateCell() {

View File

@ -1,5 +1,9 @@
[% USE raw %]
[% USE Koha %]
[% USE Branches %]
[% USE ItemTypes %]
[% USE KohaDates %]
[% USE AuthorisedValues %]
[% PROCESS 'i18n.inc' %]
[% SET footerjs = 1 %]
[% INCLUDE 'doc-head-open.inc' %]
@ -46,38 +50,51 @@
[% IF ( do_it ) %]
[% FOREACH mainloo IN mainloop %]
[% IF ( mainloo.loopfilter.size ) %]
<p><strong>Filtered on:</strong></p>
[% FOREACH loopfilte IN mainloo.loopfilter %]
<p>
[% SWITCH loopfilte.crit %]
[% CASE '0' %]
<span>Placed on (from)</span>
[% CASE '1' %]
<span>Placed on (to)</span>
[% CASE '2' %]
<span>Received on (from)</span>
[% CASE '3' %]
<span>Received on (to)</span>
[% CASE '4' %]
<span>Vendor</span>
[% CASE '5' %]
<span>Home library</span>
[% CASE '6' %]
<span>Collection</span>
[% CASE '7' %]
<span>Item type</span>
[% CASE '8' %]
<span>Fund</span>
[% CASE '9' %]
<span>Sort1</span>
[% CASE '10' %]
<span>Sort2</span>
[% CASE %]
<span>Unknown filter</span>
[% END %]
: [% loopfilte.filter | html %]
</p>
[% END %]
<h2>Filtered on:</h2>
<ul>
[% FOREACH loopfilte IN mainloo.loopfilter %]
<li>
[% SWITCH loopfilte.crit %]
[% CASE '0' %]
<strong>Placed on (from): </strong>
[% loopfilte.filter | $KohaDates %]
[% CASE '1' %]
<strong>Placed on (to): </strong>
[% loopfilte.filter | $KohaDates %]
[% CASE '2' %]
<strong>Received on (from): </strong>
[% loopfilte.filter | html %]
[% CASE '3' %]
<strong>Received on (to): </strong>
[% loopfilte.filter | html %]
[% CASE '4' %]
<strong>Vendor: </strong>
[% loopfilte.filter | html %]
[% CASE '5' %]
<strong>Home library: </strong>
[% Branches.GetName( loopfilte.filter ) | html %]
[% CASE '6' %]
<strong>Collection:</strong>
[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
[% CASE '7' %]
<strong>Item type: </strong>
[% ItemTypes.GetDescription( loopfilte.filter ) | html %]
[% CASE '8' %]
<strong>Fund: </strong>
[% loopfilte.filter | html %]
[% CASE '9' %]
<strong>Sort1: </strong>
[% loopfilte.filter | html %]
[% CASE '10' %]
<strong>Sort2: </strong>
[% loopfilte.filter | html %]
[% CASE %]
<strong>Unknown filter: </strong>
[% loopfilte.filter | html %]
[% END %]
</li>
[% END %]
</ul>
[% END %]
<div class="page-section">
<table>

View File

@ -2,6 +2,10 @@
[% USE Branches %]
[% USE Koha %]
[% USE Asset %]
[% USE Categories %]
[% USE KohaDates %]
[% USE AuthorisedValues %]
[% USE ExtendedAttributeTypes %]
[% PROCESS 'i18n.inc' %]
[% SET footerjs = 1 %]
[% INCLUDE 'doc-head-open.inc' %]
@ -56,11 +60,75 @@
<h1>Patrons statistics</h1>
[% IF ( do_it ) %]
[% FOREACH mainloo IN mainloop %]
[% IF ( mainloo.loopfilter.size>0 ) %]
<p>Filtered on:</p>
[% FOREACH loopfilte IN mainloo.loopfilter %]
<p> [% loopfilte.crit | html %] = [% loopfilte.filter | html %] </p>
[% END %]
[% IF ( mainloo.loopfilter.size > 0 ) %]
<h2>Filtered on:</h2>
<ul>
[% FOREACH loopfilte IN mainloo.loopfilter %]
<li>
[% SWITCH loopfilte.crit %]
[% CASE 'Cat code' %]
<strong>Patron category:</strong>
[% Categories.GetName( loopfilte.filter ) | html %]
[% CASE 'ZIP/Postal code' %]
<strong>ZIP/Postal code: </strong>
[% loopfilte.filter | html %]
[% CASE 'Branch code' %]
<strong>Library: </strong>
[% Branches.GetName( loopfilte.filter ) | html %]
[% CASE 'Date of birth1' %]
<strong>Date of birth > </strong>
[% loopfilte.filter | html %]
[% CASE 'Date of birth2' %]
<strong>Date of birth < </strong>
[% loopfilte.filter | html %]
[% CASE 'Sex' %]
<strong>Gender: </strong>
[% SWITCH ( loopfilte.filter ) %]
[% CASE 'F' %]
<span>Female</span>
[% CASE 'M' %]
<span>Male</span>
[% CASE 'O' %]
<span>Other</span>
[% CASE %]
<span>None specified</span>
[% END %]
[% CASE 'Sort1' %]
<strong>Patron sort1:</strong> [% AuthorisedValues.GetByCode('Bsort1', loopfilte.filter ) | html %]
[% CASE 'Sort2' %]
<strong>Patron sort2:</strong> [% AuthorisedValues.GetByCode('Bsort2', loopfilte.filter ) | html %]
[% CASE 'Status' %]
<strong>Status: </strong>
[% SWITCH loopfilte.filter %]
[% CASE 'debarred' %]
<span>Restricted</span>
[% CASE 'gonenoaddress' %]
<span>Gone no address</span>
[% CASE 'lost' %]
<span>Lost card</span>
[% CASE %]
[% loopfilte.filter | html %]
[% END %]
[% CASE 'Activity' %]
<strong>Activity: </strong>
[% SWITCH loopfilte.filter %]
[% CASE 'active' %]
<span>Active</span>
[% CASE 'nonactive' %]
<span>Inactive</span>
[% CASE %]
[% loopfilte.filter | html %]
[% END %]
[% CASE "PA_CLASS" %]
<strong>[% loopfilte.label | html %]:</strong>
[% loopfilte.filter | html %]
[% CASE %]
<strong>[% loopfilte.crit | html %]: </strong>
[% loopfilte.filter | html %]
[% END %]
</li>
[% END %]
</ul>
[% END %]
<div class="page-section">
<table>
@ -227,7 +295,7 @@
<select id="sort1" name="Filter">
<option value="" />
[% FOREACH SORT1_LOO IN SORT1_LOOP %]
<option value="[% SORT1_LOO.authorized_value | html %]">[% SORT1_LOO.lib | html %]</option>
<option value="[% SORT1_LOO.authorised_value | html %]">[% SORT1_LOO.lib | html %]</option>
[% END %]
</select>
</td>
@ -244,7 +312,7 @@
<select id="sort2" name="Filter">
<option value="" />
[% FOREACH SORT2_LOO IN SORT2_LOOP %]
<option value="[% SORT2_LOO.value | html %]">[% SORT2_LOO.value | html %]</option>
<option value="[% SORT2_LOO.value | html %]">[% AuthorisedValues.GetByCode('Bsort2', SORT2_LOO.value ) | html %]</option>
[% END %]
</select>
</td>

View File

@ -1,6 +1,8 @@
[% USE raw %]
[% USE Koha %]
[% USE Branches %]
[% USE ItemTypes %]
[% USE AuthorisedValues %]
[% PROCESS 'i18n.inc' %]
[% SET footerjs = 1 %]
[% INCLUDE 'doc-head-open.inc' %]
@ -46,12 +48,52 @@
<h1>Catalog statistics</h1>
[% IF ( do_it ) %]
[% FOREACH mainloo IN mainloop %]
[% IF ( mainloo.loopfilter ) %]
<p>Filtered on:</p>
<h2>Filtered on:</h2>
<ul>
[% FOREACH loopfilte IN mainloo.loopfilter %]
<p>[% loopfilte.crit | html %] =[% loopfilte.filter | html %]</p>
<li>
[% SWITCH loopfilte.crit %]
[% CASE "Item CallNumber From" %]
<strong>Item call number from:</strong> [% loopfilte.filter | html %]
[% CASE "Item CallNumber To" %]
<strong>Item call number to:</strong> [% loopfilte.filter | html %]
[% CASE "Item type" %]
<strong>Item type:</strong> [% ItemTypes.GetDescription( loopfilte.filter ) | html %]
[% CASE "Publisher" %]
<strong>Publisher:</strong> [% loopfilte.filter | html %]
[% CASE "Publication year From" %]
<strong>Publication year from:</strong> [% loopfilte.filter | html %]
[% CASE "Publication year To" %]
<strong>Publication year to:</strong> [% loopfilte.filter | html %]
[% CASE "Library" %]
<strong>Library:</strong> [% Branches.GetName( loopfilte.filter ) | html %]
[% CASE "Shelving Location" %]
<strong>Location:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => loopfilte.filter ) %]
[% CASE "Collection Code" %]
<strong>Collection:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
[% CASE "Status" %]
<strong>Not for loan:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.notforloan', authorised_value => loopfilte.filter ) %]
[% CASE "Materials" %]
<strong>Materials specified:</strong> [% loopfilte.filter | html %]
[% CASE "Barcode (not like)" %]
<strong>Barcode (not like):</strong> [% loopfilte.filter | html %]
[% CASE "Barcode (like)" %]
<strong>Barcode (like):</strong> [% loopfilte.filter | html %]
[% CASE "Date acquired (item) from" %]
<strong>Date acquired (item) from:</strong> [% loopfilte.filter | html %]
[% CASE "Date acquired (item) to" %]
<strong>Date acquired (item) to:</strong> [% loopfilte.filter | html %]
[% CASE "Date deleted (item) from" %]
<strong>Date deleted (item) from:</strong> [% loopfilte.filter | html %]
[% CASE "Date deleted (item) to" %]
<strong>Date deleted (item) to:</strong> [% loopfilte.filter | html %]
[% CASE %]
<strong>[% loopfilte.crit | html %]:</strong>
[% loopfilte.filter | html %]
[% END %]
</li>
[% END %]
[% END %]
</ul>
<div class="page-section">
<table id="catalogue_stats">

View File

@ -3,7 +3,9 @@
[% USE Branches %]
[% USE Categories %]
[% USE ItemTypes %]
[% USE KohaDates %]
[% USE AuthorisedValues %]
[% USE ExtendedAttributeTypes %]
[% PROCESS 'i18n.inc' %]
[% SET footerjs = 1 %]
[% INCLUDE 'doc-head-open.inc' %]
@ -55,7 +57,7 @@
[% IF ( do_it ) %]
[% FOREACH mainloo IN mainloop %]
[% IF ( mainloo.loopfilter ) %]
<p>Filtered on:</p>
<h2>Filtered on:</h2>
<ul>
[% FOREACH loopfilte IN mainloo.loopfilter %]
[% SET li_class = '' %]
@ -80,9 +82,9 @@
[% CASE 4 %]
<strong>Issuing library:</strong> [% Branches.GetName( loopfilte.filter ) | html %]
[% CASE 5 %]
<strong>Collection:</strong> [% AuthorisedValues.GetByCode( 'CCODE', loopfilte.filter ) | html %]
<strong>Collection:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
[% CASE 6 %]
<strong>Location:</strong> [% AuthorisedValues.GetByCode( 'LOC', loopfilte.filter ) | html %]
<strong>Location:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => loopfilte.filter ) %]
[% CASE 7 %]
<strong>Item call number &gt; [% loopfilte.filter | html %] </strong>
[% CASE 8 %]
@ -127,8 +129,11 @@
[% CASE "4" %]
<span>Hour</span>
[% END %]
[% CASE "PA_CLASS" %]
<strong>[% loopfilte.label | html %]:</strong>
[% loopfilte.filter | html %]
[% CASE %]
[% loopfilte.crit | html %]:
<strong>[% loopfilte.crit | html %]:</strong>
[% loopfilte.filter | html %]
[% END %]
</li>

View File

@ -206,7 +206,7 @@
<span>[% fail.hold_id | html %]</span>
[% SWITCH fail.error %]
[% CASE 'cannotReserveFromOtherBranches' %]
<span>Target item cannot be reserved from other branches</span>
<span>Target item cannot be placed on hold from other libraries</span>
[% CASE 'ageRestricted' %]
<span>The target record and/or items are age restricted</span>
[% CASE 'alreadypossession' %]
@ -219,8 +219,6 @@
<span>Target item has too many holds placed on it</span>
[% CASE 'notReservable' %]
<span>Target item is not reservable</span>
[% CASE 'cannotReserveFromOtherBranches' %]
<span>Target item cannot be placed on reserve for this branch</span>
[% CASE 'branchNotInHoldGroup' %]
<span>Tagret item is not in the local hold group</span>
[% CASE 'notforloan' %]
@ -1607,7 +1605,7 @@
tooManyReservesToday: _("Daily hold limit reached for patron"),
tooManyReserves: _("Too many holds"),
notReservable: _("Not holdable"),
noReservesAllowed: _("No reserves allowed"),
noReservesAllowed: _("No holds allowed"),
cannotReserveFromOtherBranches: _("Patron is from different library"),
itemAlreadyOnHold: _("Patron already has hold for this item"),
cannotBeTransferred: _("Cannot be transferred to pickup library"),

View File

@ -1,10 +1,12 @@
/* global APIClient __ confirmDelete */
$(document).ready(function () {
const client = APIClient.recall;
$(".cancel_recall").click(function (e) {
$(".cancel_recall").click(function () {
if (confirmDelete(__("Are you sure you want to remove this recall?"))) {
let td_node = $(this).parents("td");
let recall_id = $(this).data("id");
let message;
client.recalls.cancel(recall_id).then(
success => {
if (success.success == 0) {
@ -23,10 +25,11 @@ $(document).ready(function () {
}
});
$(".expire_recall").click(function (e) {
$(".expire_recall").click(function () {
if (confirmDelete(__("Are you sure you want to expire this recall?"))) {
let td_node = $(this).parents("td");
let recall_id = $(this).data("id");
let message;
client.recalls.expire(recall_id).then(
success => {
if (success.success == 0) {
@ -45,7 +48,7 @@ $(document).ready(function () {
}
});
$(".revert_recall").click(function (e) {
$(".revert_recall").click(function () {
if (
confirmDelete(
__(
@ -55,6 +58,7 @@ $(document).ready(function () {
) {
let td_node = $(this).parents("td");
let recall_id = $(this).data("id");
let message;
client.recalls.revert(recall_id).then(
success => {
if (success.success == 0) {
@ -73,7 +77,7 @@ $(document).ready(function () {
}
});
$(".overdue_recall").click(function (e) {
$(".overdue_recall").click(function () {
if (
confirmDelete(
__("Are you sure you want to mark this recall as overdue?")
@ -81,6 +85,7 @@ $(document).ready(function () {
) {
let td_node = $(this).parents("td");
let recall_id = $(this).data("id");
let message;
client.recalls.overdue(recall_id).then(
success => {
if (success.success == 0) {
@ -99,7 +104,7 @@ $(document).ready(function () {
}
});
$(".transit_recall").click(function (e) {
$(".transit_recall").click(function () {
if (
confirmDelete(
__(
@ -109,6 +114,7 @@ $(document).ready(function () {
) {
let td_node = $(this).parents("td");
let recall_id = $(this).data("id");
let message;
client.recalls.transit(recall_id).then(
success => {
if (success.success == 0) {
@ -132,7 +138,7 @@ $(document).ready(function () {
pagingType: "full_numbers",
});
$("#cancel_selected").click(function (e) {
$("#cancel_selected").click(function () {
if ($("input[name='recall_ids']:checked").length > 0) {
return confirmDelete(
__("Are you sure you want to remove the selected recall(s)?")

View File

@ -2,6 +2,6 @@ let operationLabels = {
connection: __("Connection"),
upload: __("Upload"),
download: __("Download"),
list: __("List Files"),
change_directory: __("Change Directory"),
list: __("List files"),
change_directory: __("Change directory"),
};

View File

@ -907,6 +907,12 @@ div {
background-color: #333333;
}
.library-description {
& + .library-description {
margin-top: .5rem;
}
}
fieldset {
&.rows {
clear: left;

View File

@ -1360,10 +1360,9 @@
<td class="location" property="seller" data-order="[% holding_library_name | html %]">
<link property="itemOffered" href="#record" />
<link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut" />
[% IF ( ITEM_RESULT.holding_library_info ) %]
<a href="[% holding_library_url | url %]" class="library_info" data-info="[% ITEM_RESULT.holding_library_info | html %]" data-name="[% holding_library_name | html %]">
<i class="fa fa-info-circle" aria-hidden="true"></i> [% holding_library_name | html %]
<a href="[% holding_library_url | url %]" class="library_info" data-index="[% loop.index | html %]" data-name="[% holding_library_name | html %]">
<i class="fa fa-info-circle" aria-hidden="true"></i> [% holding_library_name | html %][% my_library_info | html %]
</a>
[% ELSIF holding_library_url %]
<a href="[% holding_library_url | url %]"> [% holding_library_name | html %] </a>
@ -1389,7 +1388,7 @@
<link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut" />
[% IF ( ITEM_RESULT.home_library_info ) %]
<a href="[% home_library_url | url %]" class="library_info" data-info="[% ITEM_RESULT.home_library_info | html %]" data-name="[% home_library_name | html %]">
<a href="[% home_library_url | url %]" class="library_info" data-index="[% loop.index | html %]" data-name="[% home_library_name | html %]">
<i class="fa fa-info-circle" aria-hidden="true"></i> [% home_library_name | html %]
</a>
[% ELSIF home_library_url %]
@ -1604,6 +1603,16 @@
[% END %]
</tbody>
</table>
[% FOREACH ITEM_RESULT IN items %]
[% IF ( ITEM_RESULT.holding_library_info ) %]
<div style="display:none" id="library_info_[% loop.index | html %]">
[% FOREACH info IN ITEM_RESULT.holding_library_info %]
<div id="library_description_[% info.additional_content_id | $raw %]" class="library-description">[% info.content | $raw %]</div>
[% END %]
</div>
[% END %]
[% END %]
[% END %][%# end of items_table block %]
[% IF Koha.Preference('OpacCatalogConcerns') %]
@ -2189,41 +2198,18 @@
$(".library_info").on("click", function(e){
e.preventDefault();
var library_name = $(this).data("name");
var opac_info = $(this).data("info");
var index = $(this).data("index");
var url = $(this).attr("href");
if( 1 ) {
$("#libraryInfoModalLabel").html( library_name );
$("#libraryInfo").html( opac_info );
if( url ){
$("#libraryInfoLink").attr("href", url );
$("#libraryInfoLink").show();
} else {
$("#libraryInfoLink").hide();
}
$("#libraryInfoModal").modal("show");
} else { // FIXME Temporary disabled: see BZ 29144
$.ajax({
url: "/api/v1/public/libraries/" + branchcode,
type: 'GET',
dataType: 'json',
success: function(result) {
$("#libraryInfoModalLabel").html( result.name );
$("#libraryInfo").html( opac_info );
if( result.url ){
$("#libraryInfoLink").attr("href", result.url );
$("#libraryInfoLink").show();
} else {
$("#libraryInfoLink").hide();
}
$("#libraryInfoModal").modal("show");
},
error: function(xhr, status, error) {
if( url ){
location.href = url;
}
}
});
let library_info = $("#library_info_" + index).html();
$("#libraryInfoModalLabel").html( library_name );
$("#libraryInfo").html( library_info );
if( url ){
$("#libraryInfoLink").attr("href", url );
$("#libraryInfoLink").show();
} else {
$("#libraryInfoLink").hide();
}
$("#libraryInfoModal").modal("show");
});
$("#libraryInfoModal").on("hide.bs.modal", function(){
$("#libraryInfoModalLabel, #libraryInfo").html("");

View File

@ -36,7 +36,11 @@
[% INCLUDE 'masthead.inc' %]
[% BLOCK library_description %]
<div property="description"> [% IF OpacLibraryInfo %][% OpacLibraryInfo.content | $raw %][% END %] </div>
<div property="description">
[% FOREACH info IN OpacLibraryInfo %]
<div id="library_description_[% info.additional_content_id | $raw %]" class="library-description">[% info.content | $raw %]</div>
[% END %]
</div>
[% END %]
[% BLOCK library_info %]

View File

@ -155,27 +155,6 @@
if ( prod.primaryCreator ) results.push( '<p>' + _("by") + ' ', prod.primaryCreator.name, '</p>' );
results.push( '<span class="results_summary mediatype"><span class="label">' + _("Type:") + ' </span>', prod.mediaType, '</span>' );
if ( prod.starRating ) {
var rating_value = Math.round( prod.starRating );
results.push( '<div class="results_summary ratings">' );
results.push( '<div class="br-wrapper br-theme-fontawesome-stars">');
results.push( '<div class="br-widget br-readonly">');
for ( var rating = 1; rating <= 5; rating++ ) {
results.push( '<a href="#"' );
if( rating == rating_value ){
results.push( ' class="br-selected br-current"></a>' );
} else if( rating_value > rating ){
results.push( ' class="br-selected"></a>' );
} else {
results.push( '></a>');
}
}
results.push( '</div>' );
results.push( '</div>' );
results.push( '</div>' );
}
results.push( '</td>' );
results.push( '<td>' );

View File

@ -23,7 +23,8 @@ use Modern::Perl;
use Try::Tiny;
# external modules
use CGI qw ( -utf8 );
use CGI qw ( -utf8 );
use Scalar::Util qw( blessed );
# internal modules
use C4::Auth qw( get_template_and_user haspermission );
@ -52,7 +53,8 @@ use Koha::SMS::Providers;
my $input = CGI->new;
my %data;
my $dbh = C4::Context->dbh;
my $dbh = C4::Context->dbh;
my $logger = Koha::Logger->get( { interface => 'intranet ' } );
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
{
@ -273,29 +275,61 @@ if (@delete_guarantor) {
if ( C4::Context->preference('ChildNeedsGuarantor') && $will_remove_last ) {
push @errors, 'ERROR_cannot_delete_guarantor';
} else {
my @deleted_guarantors;
foreach my $id (@delete_guarantor) {
my $r = Koha::Patron::Relationships->find($id);
$r->delete() if $r;
if ($r) {
$r->delete();
push @deleted_guarantors, $id;
}
}
foreach my $deleted_guarantor_id (@deleted_guarantors) {
@guarantors = grep { ref($_) eq 'Koha::Patron' && $_->id ne $deleted_guarantor_id } @guarantors;
}
}
}
#Check if guarantor requirements are met
my $valid_guarantor = @guarantors ? @guarantors : $newdata{'contactname'};
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' )
&& C4::Context->preference('ChildNeedsGuarantor')
&& ( $category->category_type eq 'C' || $category->can_be_guarantee )
&& !$valid_guarantor )
{
push @errors, 'ERROR_child_no_guarantor';
}
my $guarantors_to_be_validated =
@guarantors ? \@guarantors : $newdata{'contactname'} ? [ $newdata{'contactname'} ] : [];
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) && $guarantors_to_be_validated ) {
try {
if ($patron) {
if ( $patron->categorycode eq $categorycode ) {
$patron->can_be_guaranteed_by($guarantors_to_be_validated);
} else {
foreach my $guarantor (@guarantors) {
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' )
&& ( $guarantor->is_child || $guarantor->is_guarantee || ( $patron && $patron->is_guarantor ) ) )
{
push @errors, 'ERROR_guarantor_is_guarantee';
}
# If $patron's categorycode is about to change, check guarantor requirements against
# the new category but keep the original $patron object intact
# (this clone()s the $patron)
( bless {%$patron}, ref $patron )->categorycode($categorycode)
->can_be_guaranteed_by($guarantors_to_be_validated);
}
} else {
Koha::Patron->new( { categorycode => $categorycode } )->can_be_guaranteed_by($guarantors_to_be_validated);
}
} catch {
unless ( blessed($_) ) {
$logger->error($_);
} else {
if ( $_->isa("Koha::Exceptions::Patron::Relationship::NoGuarantor") ) {
push @errors, "ERROR_child_no_guarantor";
} elsif ( $_->isa("Koha::Exceptions::Patron::Relationship::InvalidRelationship") ) {
if ( $_->invalid_guarantor ) {
push @errors, "ERROR_guarantor_is_guarantee";
$template->param( guarantor_is_guarantee => $_->guarantor );
} elsif ( $_->child_guarantor ) {
push @errors, "ERROR_child_is_guarantor";
} elsif ( $_->guarantor_cant_have_guarantors ) {
push @errors, "ERROR_guarantor_cant_have_guarantors";
}
} else {
$logger->error( $_->error );
$_->rethrow;
}
}
return;
};
}
my @valid_relationships = split( /\|/, C4::Context->preference('borrowerRelationship'), -1 );
@ -447,7 +481,7 @@ if ( ( !$nok ) and $nodouble and ( $op eq 'cud-insert' or $op eq 'cud-save' ) )
delete $newdata{password2};
$success = 1;
$patron = try {
Koha::Patron->new( \%newdata )->store( { guarantors => \@guarantors } );
Koha::Patron->new( \%newdata )->store();
} catch {
$success = 0;
$nok = 1;
@ -561,7 +595,7 @@ if ( ( !$nok ) and $nodouble and ( $op eq 'cud-insert' or $op eq 'cud-save' ) )
delete $newdata{guarantor_relationship};
try {
$patron->set( \%newdata )->store( { guarantors => \@guarantors } ) if scalar( keys %newdata ) > 1;
$patron->set( \%newdata )->store() if scalar( keys %newdata ) > 1;
# bug 4508 - avoid crash if we're not updating any columns in the borrowers table (editing patron attrs or msg prefs)
$success = 1;

View File

@ -0,0 +1,674 @@
<h1 id="releasenotesforkoha25.11.01">RELEASE NOTES FOR KOHA 25.11.01</h1>
<p>28 Jan 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 25.11.01 can be downloaded from:</p>
<ul>
<li><a href="https://download.koha-community.org/koha-25.11.01.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 25.11.01 is a bugfix/maintenance release.</p>
<p>It includes 34 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=41593">41593</a> Authenticated SQL Injection in staff side suggestions</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41662">41662</a> CSRF-vulnerability in opac-patron-consent.pl.</li>
</ul>
<h2 id="bugfixes">Bugfixes</h2>
<h3 id="acquisitions">Acquisitions</h3>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39514">39514</a> If one basket has uncertain prices, all baskets are displayed in red</p>
<blockquote>
<p>This fixes the display of baskets in acquisitions so that only baskets with uncertain prices are shown in red. Previously, if one basket had an uncertain price, all the baskets in the page were shown in red, even those without uncertain prices, making it hard to know where to go to fix the price.</li>
</ul></p>
<h3 id="architectureinternalsandplumbing">Architecture, internals, and plumbing</h3>
</blockquote>
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38426">38426</a> Node.js v18 EOL around 25.05 release time</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40989">40989</a> t/db_dependent/OAI/Server.t fails on Debian 13</p>
<blockquote>
<p>This fixes OAI-PMH (tests and request) so that OAI-PMH responses work when Debian 13 (Trixie) is used as the operating system. Trailing slashes are stripped from requestURL for CGI.pm 4.68+ compatibility</p>
<p>Technical details:</p>
<p>CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behaviour of
self_url() to include a trailing slash when there's no path component.</p>
<p>This strips the trailing slash from both the requestURL field in OAI responses and the baseURL field in Identify responses to maintain compatibility across CGI.pm versions.</li>
</ul></p>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
</blockquote>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41238">41238</a> Pseudonymize statistic jobs don't update progress</p>
<blockquote>
<p>This fixes the progress shown for pseudonymize statistics background jobs. The progress was shown in the list of jobs (Administration > Jobs > Manage jobs) as "0/1" instead of "1/1", even though the background job was finished.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41404">41404</a> No need to check related guarantor/guarantee charges when the limits are not set</li>
</ul></p>
<h3 id="circulation">Circulation</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40949">40949</a> Bookings to collect shouldn't tell staff to check in items</p>
<blockquote>
<p>This removes the sentence "Please retrieve them and check them in." from the bookings to collect page (Circulation > Holds and bookings > Bookings to collect). This is not required, as checking in items is not relevant for bookings.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41345">41345</a> Regression: Clicking the 'Ignore' button on hold found modal for already-waiting hold does not dismiss the modal (again)
This fixes a regression when checking in an item. Clicking the "Ignore" option in the dialog box, when an item already has a waiting status, just reloaded the dialog box. Clicking the "Ignore" option now closes the dialog box and works as expected.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41352">41352</a> Bookings to Collect Help does not take you to the correct place in the manual
This fixes the link to the help for the Circulation > Holds and bookings > Bookings to collect page - it now links to the correct place in the documentation, instead of the documentation home page.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41451">41451</a> Hold history search fails when itemtype column present
This fixes filtering on a patron's holds history when AllowHoldItemTypeSelection is enabled. This previously produced a 500 error when searching, but now works as expected.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41456">41456</a> Item type filter on the hold history view does not work correctly
This fixes the patron's holds history table in the staff interface. The search filter now works as expected - using the library name or library code and requested item type name or item type code (when AllowHoldItemTypeSelection is enabled) now work as expected, and the column filters for both of these now use dropdown lists.</li>
</ul></p>
<h3 id="holdrequests">Hold requests</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41432">41432</a> Add prefetch to improve performance of holds page</p>
<blockquote>
<p>This improves the performance of the holds request page for a record in the staff interface.</li>
</ul></p>
<h3 id="i18nl10n">I18N/L10N</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40287">40287</a> Fix untranslatable strings in more statistics wizards</p>
<blockquote>
<p>This fixes and improves the acquisitions, patrons, catalog, and circulation statistics report wizard:</p>
<ul>
<li>Fixes some strings so that they are now translatable</li>
<li>Improves the "Filtered on" information shown before the report results:
. the filtered on options selected in the report are now shown in a bulleted list and in bold
. descriptions are now shown instead of codes (for example, the library name instead of the library code)</li>
</ul>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
</ul>
<h3 id="installationandupgradeweb-basedinstaller">Installation and upgrade (web-based installer)</h3>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40006">40006</a> Upgrading install.pl shows code vs HTML</p>
<blockquote>
<p>This fixes a database update (for bug 38436 in 24.11.00) that caused code to show in the browser instead of HTML output when running an upgrade.</li>
</ul></p>
<h3 id="opac">OPAC</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40619">40619</a> Remove OverDrive star ratings from the OPAC</p>
<blockquote>
<p>This removes the code for OverDrive star ratings from OPAC pages, as OverDrive no longer supplies star ratings through their API.</p>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
</ul>
<h3 id="patrons">Patrons</h3>
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41145">41145</a> Logging patron attributes logs even if there's no changes</p>
<blockquote>
<p>This prevents misleading patron attribute modification logs, when a library batch imports patrons with the BorrowersLog system preference set to 'Log'. It now correctly only shows a log entry when a patron attribute value is changed.</p>
<p>Example: </p>
<ul>
<li>Before the change: for an existing patron with a patron attribute of INSTID:1234, with a re-import the log shows { "attribute.INSTID" : { "after" : "1234", "before" : "" } }, even though there is no change to the patron attribute.</li>
<li>After the change:
. No log entry is shown if there is no change to the patron attribute.
. If there is a change to the patron attribute (for example, changed to 5678 on a re-import), it is now correctly shown - { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }</li>
</ul>
</blockquote>
<p><strong>Sponsored by</strong> <em>Auckland University of Technology</em></p></li>
</ul>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39014">39014</a> Storing a guarantee fails due to TrackLastPatronActivityTriggers "creating a patron"</p>
<blockquote>
<p>This fixes an error when creating a patron that requires a guarantor, when this combination of settings is used:</p>
<ul>
<li><code>TrackLastPatronActivityTriggers</code>: 'Creating a patron' is selected</li>
<li><code>ChildNeedsGuarantor</code>: is set to 'requires'</li>
<li>The patron category is a 'Child' and 'Can be guarantee' is set to 'Yes'</li>
</ul>
<p>With these settings, this error message "The following fields are wrong. Please fix them." (without a list of fields) was incorrectly shown when creating a child patron, even though you had correctly added the guarantor information.</p>
</blockquote>
<p><strong>Sponsored by</strong> <em>Koha-Suomi Oy</em></p></li>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41363">41363</a> Don't hide patron category limitation warning behind icon</p>
<blockquote>
<p>This moves the hint text for the warning on the Library management > Category field (when editing a patron record) from a tool tip on the warning icon to standard hint text under the input field, to make it more accessible</p>
<p>Note: This warning only appears under certain circumstances (when a patron category is limited to a specific library, and you edit a patron when the library is set to another location).</p>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
</ul>
<h3 id="pointofsale">Point of Sale</h3>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41408">41408</a> POS Inline Editing Triggers Form Submission on Enter Key</p>
<blockquote>
<p>Inline editing the cost or quantity fields on a point of sale transaction, then pressing enter, incorrectly submitted the form--instead of just updating the field.</p>
<p>Now, the values in the fields are updated without submitting the form, and you can continue entering sale details and completing the transaction.</li>
</ul></p>
<h3 id="restapi">REST API</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40219">40219</a> Welcome Email Sent on Failed Patron Registration via API</p>
<blockquote>
<p>This fixes patron registrations using the API - a welcome email notice was sent even if there were validation failures.</li>
</ul></p>
<h3 id="reports">Reports</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41292">41292</a> Add "force_password_reset_when_set_by_staff" to the allowed column name list</p>
<blockquote>
<p>This adds the force_password_reset_when_set_by_staff field in the categories table to the list of allowed password-related fields that can be used in SQL reports.</p>
<p>Currently, this field is treated as containing sensitive password-related data and generates an error when creating a report that uses it.</li>
</ul></p>
<h3 id="sip2">SIP2</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40455">40455</a> A patron information request fails when no currency is set</p>
<blockquote>
<p>This fixes an error in SIP2 patron information responses (64) when no currency is set, which can be the case for new Koha instances.</p>
<p>Example, if no currency is set:
- Before the fix, there is an 'undef' error:
...
Trying 'patron_information'
SEND: 6300120251202 115652 AOCPL|AA42|ACterm1|
READ: undef
- After the fix, patron informatin is returned and there is no error:
...
Trying 'patron_information'
SEND: 6300120251202 122545 AOCPL|AA42|ACterm1|
READ: 64 00120251202 <br />
122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|
PIY|AFGreetings from Koha. |</li>
</ul></p>
<h3 id="searching-elasticsearch">Searching - Elasticsearch</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38345">38345</a> Restore support for OpenSearch</p>
<blockquote>
<p>This fixes Koha so that OpenSearch now works, and is now covered again by continuous integration tests (these have been failing for some time in Jenkins).</p>
<p>Note: See the test plan for guidance on how to start KTD successfully with OS1 and OS2 if you have issues.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40980">40980</a> Clicking a search facet without logging in may trigger a cud-login error
This fixes using facets in the OPAC for searching when not logged in, where Elasticsearch or OpenSearch is used as the search engine. In some circumstances, a 403 Forbidden Error was incorrectly generated (this is related to changes made in previous versions of Koha to improve form security).</li>
</ul></p>
<h3 id="serials">Serials</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37796">37796</a> Generated issue has incorrect number in pattern when receiving</p>
<blockquote>
<p>This fixes an issue with incorrect numbering patterns and the next expected issue shown, after a serial is received.</p>
<p>Example of an incorrect numbering pattern before this fix:
- Monthly serial received for August 2025 (its status shows as 'Arrived' for August 2025).
- The next expected serial was then shown as August 2025 instead of September 2025 (its status was shown as 'Expected' with a incorrect numbering pattern of August 2025)</li>
</ul></p>
<h3 id="staffinterface">Staff interface</h3>
</blockquote>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<p><ul>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41427">41427</a> Terminology: branch should be library in FilterSearchResultsByLoggedInBranch</p>
<blockquote>
<p>This fixes the terminology and improves the description for the FilterSearchResultsByLoggedInBranch system preference - branch should be library.</li>
</ul></p>
<h3 id="systemadministration">System Administration</h3>
</blockquote>
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39482">39482</a> Link to edit OpacLibraryInfo from library edit page broken</p>
<blockquote>
<p>This fixes editing and showing OpacLibraryInfo HTML customizations information in the staff interface and OPAC:</p>
<ol>
<li><p>Staff interface:</p>
<ul>
<li>When editing a library (Koha administration > Libraries > Actions > Edit)
. the links to edit OpacLibraryInfo entries for the OPAC information field are now correct (previously, they may have linked to an incorrect HTML customization)
. if there is more than one OpacLibraryInfo entry, all entries are now shown (only one entry was shown previously)</li>
<li>When viewing library information (Koha administration > Libraries > Name > [click library name]), all the OpacLibraryInfo entries are now shown</li>
</ul></li>
<li><p>OPAC:</p>
<ul>
<li>All the entries for a library (including any 'All libraries' entries for OpacLibraryInfo) are now shown on the library information page (from the 'Libraries' link under the quick search bar)</li>
<li>In a record's holdings table, the pop-up window when you click on the current library for an item now correctly shows all entries</li>
</ul></li>
</ol>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
</ul>
<h3 id="templates">Templates</h3>
<h4 id="otherbugsfixed">Other bugs fixed</h4>
<ul>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40567">40567</a> Correct eslint errors in recalls.js</p>
<blockquote>
<p>This fixes a few minor coding guideline errors in the JavaScript used on recalls pages (JS8: Follow guidelines set by ESLint). There are no changes to how the recalls pages work.</p>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41339">41339</a> Typo 'Too many checkout'
<blockquote>
<p>Changes "Too many checkout" to "Too many checkouts" message that shows in the log viewer when circulation overrides are logged.</li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41348">41348</a> Capitalization: "List Files" and others
This fixes the capitalization for messages used with FTP/SFTP file transfers (Administration > Additional parameters > File transports).</p>
<p>Changes:
- "List Files" to "List files"
- "Change Directory" to "Change directory"</li>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41361">41361</a> Incorrect markup in category code confirmation modal</p></p>
</blockquote>
<blockquote>
<p>This fixes the "Confirm expiration date" dialog box that is shown when changing an individual patron's category:</p>
<ul>
<li>The "No" option now works.</li>
<li>It is now formatted using our standard Bootstrap 5 styles.</li>
</ul>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41395">41395</a> Terminology: Target item cannot be reserved from other branches
<blockquote>
<p>Updates the terminology for two circulation-related messages (reserves to holds, branches to libraries):</p>
<ul>
<li>"Target item cannot be reserved from other branches" to "Target item cannot be placed on hold from other libraries"</li>
<li>"No reserves allowed" to "No holds allowed"</li>
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41396">41396</a> Capitalization: 'Transport Settings' and other</p></li>
</ul>
</blockquote>
<blockquote>
<p>This fixes the capitalization for the section headings on the EDI account create and edit form - they are now in sentence case (Administration > Acquisition parameters > EDI accounts):</p>
<ul>
<li>Basic Information => Basic information</li>
<li>Transport Settings => Transport settings</li>
<li>Message Types => Message types</li>
<li>Functional Switches => Functional switches</li>
</ul>
</blockquote>
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></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/25.11/en/html/">English (USA)</a></p></li>
<li><a href="https://koha-community.org/manual/25.11/fr/html/">French</a> (74%)</li>
<li><a href="https://koha-community.org/manual/25.11/de/html/">German</a> (90%)</li>
<li><a href="https://koha-community.org/manual/25.11/el/html/">Greek</a> (94%)</li>
<li><a href="https://koha-community.org/manual/25.11/hi/html/">Hindi</a> (64%)</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) (90%)
- Armenian (hy_ARMN) (100%)
- Bulgarian (bg_CYRL) (100%)
- Chinese (Simplified Han script) (82%)
- Chinese (Traditional Han script) (95%)
- Czech (65%)
- Dutch (85%)
- English (100%)
- English (New Zealand) (60%)
- English (USA)
- Finnish (99%)
- French (99%)
- French (Canada) (97%)
- German (99%)
- Greek (64%)
- Hindi (93%)
- Italian (79%)
- Norwegian Bokmål (69%)
- Persian (fa_ARAB) (91%)
- Polish (99%)
- Portuguese (Brazil) (99%)
- Portuguese (Portugal) (86%)
- Russian (91%)
- Slovak (57%)
- Spanish (96%)
- Swedish (88%)
- Telugu (64%)
- Turkish (79%)
- Ukrainian (73%)
- Western Armenian (hyw_ARMN) (59%)
</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 25.11.01 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 libraries, companies, and other institutions who are known to have sponsored
new features in Koha 25.11.01</p>
<div style="column-count: 2;">
- Athens County Public Libraries
- Auckland University of Technology
- [Koha-Suomi Oy](https://koha-suomi.fi)
</div>
<p>We thank the following individuals who contributed patches to Koha 25.11.01</p>
<div style="column-count: 2;">
- Pedro Amorim (1)
- Tomás Cohen Arazi (2)
- Alex Buckley (2)
- Nick Clemens (6)
- David Cook (2)
- Paul Derscheid (2)
- Jonathan Druart (7)
- Andrew Fuerste-Henry (2)
- Lucas Gass (3)
- Jan Kissig (1)
- Owen Leonard (10)
- David Nind (2)
- Jacob O'Mara (4)
- Martin Renvoize (3)
- Marcel de Rooy (1)
- Caroline Cyr La Rose (1)
- Lisette Scheer (1)
- Leo Stoyanov (1)
- Emmi Takkinen (1)
- Lari Taskula (1)
</div>
<p>We thank the following libraries, companies, and other institutions who contributed
patches to Koha 25.11.01</p>
<div style="column-count: 2;">
- Athens County Public Libraries (10)
- [ByWater Solutions](https://bywatersolutions.com) (13)
- [Catalyst](https://www.catalyst.net.nz/products/library-management-koha) (2)
- David Nind (2)
- [Hypernova Oy](https://www.hypernova.fi) (1)
- Koha Community Developers (7)
- [Koha-Suomi Oy](https://koha-suomi.fi) (1)
- [LMSCloud](https://www.lmscloud.de) (2)
- [Open Fifth](https://openfifth.co.uk/) (8)
- [Prosentient Systems](https://www.prosentient.com.au) (2)
- Rijksmuseum, Netherlands (1)
- [Solutions inLibro inc](https://inlibro.com) (1)
- [Theke Solutions](https://theke.io) (2)
- Wildau University of Technology (1)
</div>
<p>We also especially thank the following individuals who tested patches
for Koha</p>
<div style="column-count: 2;">
- Christopher Brannon (1)
- Aude Charillon (1)
- Jonathan Druart (3)
- Magnus Enger (1)
- Laura Escamilla (8)
- Lucas Gass (42)
- Victor Grousset (1)
- Owen Leonard (1)
- David Nind (29)
- Jacob O'Mara (49)
- Wesley Owen (2)
- Martin Renvoize (12)
- Marcel de Rooy (11)
- Samuel (1)
- Lisette Scheer (3)
</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 25.11.x.</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 28 Jan 2026 11:17:10.</p>

View File

@ -0,0 +1,514 @@
# RELEASE NOTES FOR KOHA 25.11.01
28 Jan 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 25.11.01 can be downloaded from:
- [Download](https://download.koha-community.org/koha-25.11.01.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 25.11.01 is a bugfix/maintenance release.
It includes 34 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
- [41593](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41593) Authenticated SQL Injection in staff side suggestions
- [41662](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41662) CSRF-vulnerability in opac-patron-consent.pl.
## Bugfixes
### Acquisitions
#### Other bugs fixed
- [39514](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39514) If one basket has uncertain prices, all baskets are displayed in red
>This fixes the display of baskets in acquisitions so that only baskets with uncertain prices are shown in red. Previously, if one basket had an uncertain price, all the baskets in the page were shown in red, even those without uncertain prices, making it hard to know where to go to fix the price.
### Architecture, internals, and plumbing
#### Critical bugs fixed
- [38426](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38426) Node.js v18 EOL around 25.05 release time
- [40989](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40989) t/db_dependent/OAI/Server.t fails on Debian 13
>This fixes OAI-PMH (tests and request) so that OAI-PMH responses work when Debian 13 (Trixie) is used as the operating system. Trailing slashes are stripped from requestURL for CGI.pm 4.68+ compatibility
>
>Technical details:
>
>CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behaviour of
>self_url() to include a trailing slash when there's no path component.
>
>This strips the trailing slash from both the requestURL field in OAI responses and the baseURL field in Identify responses to maintain compatibility across CGI.pm versions.
#### Other bugs fixed
- [41238](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41238) Pseudonymize statistic jobs don't update progress
>This fixes the progress shown for pseudonymize statistics background jobs. The progress was shown in the list of jobs (Administration > Jobs > Manage jobs) as "0/1" instead of "1/1", even though the background job was finished.
- [41404](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41404) No need to check related guarantor/guarantee charges when the limits are not set
### Circulation
#### Other bugs fixed
- [40949](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40949) Bookings to collect shouldn't tell staff to check in items
>This removes the sentence "Please retrieve them and check them in." from the bookings to collect page (Circulation > Holds and bookings > Bookings to collect). This is not required, as checking in items is not relevant for bookings.
- [41345](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41345) Regression: Clicking the 'Ignore' button on hold found modal for already-waiting hold does not dismiss the modal (again)
>This fixes a regression when checking in an item. Clicking the "Ignore" option in the dialog box, when an item already has a waiting status, just reloaded the dialog box. Clicking the "Ignore" option now closes the dialog box and works as expected.
- [41352](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41352) Bookings to Collect Help does not take you to the correct place in the manual
>This fixes the link to the help for the Circulation > Holds and bookings > Bookings to collect page - it now links to the correct place in the documentation, instead of the documentation home page.
- [41451](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41451) Hold history search fails when itemtype column present
>This fixes filtering on a patron's holds history when AllowHoldItemTypeSelection is enabled. This previously produced a 500 error when searching, but now works as expected.
- [41456](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41456) Item type filter on the hold history view does not work correctly
>This fixes the patron's holds history table in the staff interface. The search filter now works as expected - using the library name or library code and requested item type name or item type code (when AllowHoldItemTypeSelection is enabled) now work as expected, and the column filters for both of these now use dropdown lists.
### Hold requests
#### Other bugs fixed
- [41432](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41432) Add prefetch to improve performance of holds page
>This improves the performance of the holds request page for a record in the staff interface.
### I18N/L10N
#### Other bugs fixed
- [40287](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40287) Fix untranslatable strings in more statistics wizards
>This fixes and improves the acquisitions, patrons, catalog, and circulation statistics report wizard:
>- Fixes some strings so that they are now translatable
>- Improves the "Filtered on" information shown before the report results:
> . the filtered on options selected in the report are now shown in a bulleted list and in bold
> . descriptions are now shown instead of codes (for example, the library name instead of the library code)
**Sponsored by** *Athens County Public Libraries*
### Installation and upgrade (web-based installer)
#### Other bugs fixed
- [40006](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40006) Upgrading install.pl shows code vs HTML
>This fixes a database update (for bug 38436 in 24.11.00) that caused code to show in the browser instead of HTML output when running an upgrade.
### OPAC
#### Other bugs fixed
- [40619](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40619) Remove OverDrive star ratings from the OPAC
>This removes the code for OverDrive star ratings from OPAC pages, as OverDrive no longer supplies star ratings through their API.
**Sponsored by** *Athens County Public Libraries*
### Patrons
#### Critical bugs fixed
- [41145](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41145) Logging patron attributes logs even if there's no changes
>This prevents misleading patron attribute modification logs, when a library batch imports patrons with the BorrowersLog system preference set to 'Log'. It now correctly only shows a log entry when a patron attribute value is changed.
>
>Example:
>- Before the change: for an existing patron with a patron attribute of INSTID:1234, with a re-import the log shows { "attribute.INSTID" : { "after" : "1234", "before" : "" } }, even though there is no change to the patron attribute.
>- After the change:
> . No log entry is shown if there is no change to the patron attribute.
> . If there is a change to the patron attribute (for example, changed to 5678 on a re-import), it is now correctly shown - { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }
**Sponsored by** *Auckland University of Technology*
#### Other bugs fixed
- [39014](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39014) Storing a guarantee fails due to TrackLastPatronActivityTriggers "creating a patron"
>This fixes an error when creating a patron that requires a guarantor, when this combination of settings is used:
>- `TrackLastPatronActivityTriggers`: 'Creating a patron' is selected
>- `ChildNeedsGuarantor`: is set to 'requires'
>- The patron category is a 'Child' and 'Can be guarantee' is set to 'Yes'
>
>With these settings, this error message "The following fields are wrong. Please fix them." (without a list of fields) was incorrectly shown when creating a child patron, even though you had correctly added the guarantor information.
**Sponsored by** *Koha-Suomi Oy*
- [41363](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41363) Don't hide patron category limitation warning behind icon
>This moves the hint text for the warning on the Library management > Category field (when editing a patron record) from a tool tip on the warning icon to standard hint text under the input field, to make it more accessible
>
>Note: This warning only appears under certain circumstances (when a patron category is limited to a specific library, and you edit a patron when the library is set to another location).
**Sponsored by** *Athens County Public Libraries*
### Point of Sale
#### Other bugs fixed
- [41408](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41408) POS Inline Editing Triggers Form Submission on Enter Key
>Inline editing the cost or quantity fields on a point of sale transaction, then pressing enter, incorrectly submitted the form--instead of just updating the field.
>
>Now, the values in the fields are updated without submitting the form, and you can continue entering sale details and completing the transaction.
### REST API
#### Other bugs fixed
- [40219](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40219) Welcome Email Sent on Failed Patron Registration via API
>This fixes patron registrations using the API - a welcome email notice was sent even if there were validation failures.
### Reports
#### Other bugs fixed
- [41292](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41292) Add "force_password_reset_when_set_by_staff" to the allowed column name list
>This adds the force_password_reset_when_set_by_staff field in the categories table to the list of allowed password-related fields that can be used in SQL reports.
>
>Currently, this field is treated as containing sensitive password-related data and generates an error when creating a report that uses it.
### SIP2
#### Other bugs fixed
- [40455](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40455) A patron information request fails when no currency is set
>This fixes an error in SIP2 patron information responses (64) when no currency is set, which can be the case for new Koha instances.
>
>Example, if no currency is set:
>- Before the fix, there is an 'undef' error:
> ...
> Trying 'patron_information'
> SEND: 6300120251202 115652 AOCPL|AA42|ACterm1|
> READ: undef
>- After the fix, patron informatin is returned and there is no error:
> ...
> Trying 'patron_information'
> SEND: 6300120251202 122545 AOCPL|AA42|ACterm1|
> READ: 64 00120251202
> 122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|
> PIY|AFGreetings from Koha. |
### Searching - Elasticsearch
#### Other bugs fixed
- [38345](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38345) Restore support for OpenSearch
>This fixes Koha so that OpenSearch now works, and is now covered again by continuous integration tests (these have been failing for some time in Jenkins).
>
>Note: See the test plan for guidance on how to start KTD successfully with OS1 and OS2 if you have issues.
- [40980](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40980) Clicking a search facet without logging in may trigger a cud-login error
>This fixes using facets in the OPAC for searching when not logged in, where Elasticsearch or OpenSearch is used as the search engine. In some circumstances, a 403 Forbidden Error was incorrectly generated (this is related to changes made in previous versions of Koha to improve form security).
### Serials
#### Other bugs fixed
- [37796](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37796) Generated issue has incorrect number in pattern when receiving
>This fixes an issue with incorrect numbering patterns and the next expected issue shown, after a serial is received.
>
>Example of an incorrect numbering pattern before this fix:
>- Monthly serial received for August 2025 (its status shows as 'Arrived' for August 2025).
>- The next expected serial was then shown as August 2025 instead of September 2025 (its status was shown as 'Expected' with a incorrect numbering pattern of August 2025)
### Staff interface
#### Other bugs fixed
- [41427](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41427) Terminology: branch should be library in FilterSearchResultsByLoggedInBranch
>This fixes the terminology and improves the description for the FilterSearchResultsByLoggedInBranch system preference - branch should be library.
### System Administration
#### Critical bugs fixed
- [39482](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39482) Link to edit OpacLibraryInfo from library edit page broken
>This fixes editing and showing OpacLibraryInfo HTML customizations information in the staff interface and OPAC:
>
>1. Staff interface:
> - When editing a library (Koha administration > Libraries > Actions > Edit)
> . the links to edit OpacLibraryInfo entries for the OPAC information field are now correct (previously, they may have linked to an incorrect HTML customization)
> . if there is more than one OpacLibraryInfo entry, all entries are now shown (only one entry was shown previously)
> - When viewing library information (Koha administration > Libraries > Name > [click library name]), all the OpacLibraryInfo entries are now shown
>
>2. OPAC:
> - All the entries for a library (including any 'All libraries' entries for OpacLibraryInfo) are now shown on the library information page (from the 'Libraries' link under the quick search bar)
> - In a record's holdings table, the pop-up window when you click on the current library for an item now correctly shows all entries
**Sponsored by** *Athens County Public Libraries*
### Templates
#### Other bugs fixed
- [40567](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40567) Correct eslint errors in recalls.js
>This fixes a few minor coding guideline errors in the JavaScript used on recalls pages (JS8: Follow guidelines set by ESLint). There are no changes to how the recalls pages work.
**Sponsored by** *Athens County Public Libraries*
- [41339](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41339) Typo 'Too many checkout'
>Changes "Too many checkout" to "Too many checkouts" message that shows in the log viewer when circulation overrides are logged.
- [41348](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41348) Capitalization: "List Files" and others
>This fixes the capitalization for messages used with FTP/SFTP file transfers (Administration > Additional parameters > File transports).
>
>Changes:
>- "List Files" to "List files"
>- "Change Directory" to "Change directory"
- [41361](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41361) Incorrect markup in category code confirmation modal
>This fixes the "Confirm expiration date" dialog box that is shown when changing an individual patron's category:
>- The "No" option now works.
>- It is now formatted using our standard Bootstrap 5 styles.
**Sponsored by** *Athens County Public Libraries*
- [41395](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41395) Terminology: Target item cannot be reserved from other branches
>Updates the terminology for two circulation-related messages (reserves to holds, branches to libraries):
>
>- "Target item cannot be reserved from other branches" to "Target item cannot be placed on hold from other libraries"
>- "No reserves allowed" to "No holds allowed"
- [41396](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41396) Capitalization: 'Transport Settings' and other
>This fixes the capitalization for the section headings on the EDI account create and edit form - they are now in sentence case (Administration > Acquisition parameters > EDI accounts):
>
>- Basic Information => Basic information
>- Transport Settings => Transport settings
>- Message Types => Message types
>- Functional Switches => Functional switches
**Sponsored by** *Athens County Public Libraries*
## 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/25.11/en/html/)
- [French](https://koha-community.org/manual/25.11/fr/html/) (74%)
- [German](https://koha-community.org/manual/25.11/de/html/) (90%)
- [Greek](https://koha-community.org/manual/25.11/el/html/) (94%)
- [Hindi](https://koha-community.org/manual/25.11/hi/html/) (64%)
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) (90%)
- Armenian (hy_ARMN) (100%)
- Bulgarian (bg_CYRL) (100%)
- Chinese (Simplified Han script) (82%)
- Chinese (Traditional Han script) (95%)
- Czech (65%)
- Dutch (85%)
- English (100%)
- English (New Zealand) (60%)
- English (USA)
- Finnish (99%)
- French (99%)
- French (Canada) (97%)
- German (99%)
- Greek (64%)
- Hindi (93%)
- Italian (79%)
- Norwegian Bokmål (69%)
- Persian (fa_ARAB) (91%)
- Polish (99%)
- Portuguese (Brazil) (99%)
- Portuguese (Portugal) (86%)
- Russian (91%)
- Slovak (57%)
- Spanish (96%)
- Swedish (88%)
- Telugu (64%)
- Turkish (79%)
- Ukrainian (73%)
- Western Armenian (hyw_ARMN) (59%)
</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 25.11.01 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 libraries, companies, and other institutions who are known to have sponsored
new features in Koha 25.11.01
<div style="column-count: 2;">
- Athens County Public Libraries
- Auckland University of Technology
- [Koha-Suomi Oy](https://koha-suomi.fi)
</div>
We thank the following individuals who contributed patches to Koha 25.11.01
<div style="column-count: 2;">
- Pedro Amorim (1)
- Tomás Cohen Arazi (2)
- Alex Buckley (2)
- Nick Clemens (6)
- David Cook (2)
- Paul Derscheid (2)
- Jonathan Druart (7)
- Andrew Fuerste-Henry (2)
- Lucas Gass (3)
- Jan Kissig (1)
- Owen Leonard (10)
- David Nind (2)
- Jacob O'Mara (4)
- Martin Renvoize (3)
- Marcel de Rooy (1)
- Caroline Cyr La Rose (1)
- Lisette Scheer (1)
- Leo Stoyanov (1)
- Emmi Takkinen (1)
- Lari Taskula (1)
</div>
We thank the following libraries, companies, and other institutions who contributed
patches to Koha 25.11.01
<div style="column-count: 2;">
- Athens County Public Libraries (10)
- [ByWater Solutions](https://bywatersolutions.com) (13)
- [Catalyst](https://www.catalyst.net.nz/products/library-management-koha) (2)
- David Nind (2)
- [Hypernova Oy](https://www.hypernova.fi) (1)
- Koha Community Developers (7)
- [Koha-Suomi Oy](https://koha-suomi.fi) (1)
- [LMSCloud](https://www.lmscloud.de) (2)
- [Open Fifth](https://openfifth.co.uk/) (8)
- [Prosentient Systems](https://www.prosentient.com.au) (2)
- Rijksmuseum, Netherlands (1)
- [Solutions inLibro inc](https://inlibro.com) (1)
- [Theke Solutions](https://theke.io) (2)
- Wildau University of Technology (1)
</div>
We also especially thank the following individuals who tested patches
for Koha
<div style="column-count: 2;">
- Christopher Brannon (1)
- Aude Charillon (1)
- Jonathan Druart (3)
- Magnus Enger (1)
- Laura Escamilla (8)
- Lucas Gass (42)
- Victor Grousset (1)
- Owen Leonard (1)
- David Nind (29)
- Jacob O'Mara (49)
- Wesley Owen (2)
- Martin Renvoize (12)
- Marcel de Rooy (11)
- Samuel (1)
- Lisette Scheer (3)
</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 25.11.x.
## 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 28 Jan 2026 11:17:10.

View File

@ -718,8 +718,8 @@ if ( not $viewallitems and $items->count > $max_items_to_display ) {
$library_info->{ $item->holdingbranch } = $opac_info_holding;
$opac_info_home = $library_info->{ $item->homebranch } // $item->home_branch->opac_info( { lang => $lang } );
$library_info->{ $item->homebranch } = $opac_info_home;
$item_info->{holding_library_info} = $opac_info_holding->content if $opac_info_holding;
$item_info->{home_library_info} = $opac_info_home->content if $opac_info_home;
$item_info->{holding_library_info} = $opac_info_holding if $opac_info_holding;
$item_info->{home_library_info} = $opac_info_home if $opac_info_home;
# We only need to check if we haven't determined holds can be placed
# and if we don't have patron, we have already decided

35
opac/opac-patron-consent.pl Executable file → Normal file
View File

@ -48,24 +48,27 @@ foreach my $consent_type ( sort keys %$consent_types ) {
push @consents, $patron->consent($consent_type);
}
# Handle saves here
my $needs_redirect;
foreach my $consent (@consents) {
my $check = $vars->{ "check_" . $consent->type };
next if !defined($check); # no choice made
$needs_redirect = 1
if $consent->type eq q/GDPR_PROCESSING/
&& !$check
&& C4::Context->preference('PrivacyPolicyConsent') eq 'Enforced';
next if $consent->given_on && $check || $consent->refused_on && !$check;
# No update if no consent change
$consent->set(
{
given_on => $check ? dt_from_string() : undef,
refused_on => $check ? undef : dt_from_string(),
}
)->store;
# Handle saves here
if ( $op && $op eq 'cud-save' ) {
foreach my $consent (@consents) {
my $check = $vars->{ "check_" . $consent->type };
next if !defined($check); # no choice made
$needs_redirect = 1
if $consent->type eq q/GDPR_PROCESSING/
&& !$check
&& C4::Context->preference('PrivacyPolicyConsent') eq 'Enforced';
next if $consent->given_on && $check || $consent->refused_on && !$check;
# No update if no consent change
$consent->set(
{
given_on => $check ? dt_from_string() : undef,
refused_on => $check ? undef : dt_from_string(),
}
)->store;
}
}
# If user refused GDPR consent and we enforce GDPR, logout (when saving)

View File

@ -66,7 +66,8 @@
},
"resolutions": {
"minimatch": "^3.0.2",
"lodash": "^4.17.12"
"lodash": "^4.17.12",
"@types/express": "^4.17.13"
},
"author": "",
"license": "GPL-3.0",

View File

@ -193,22 +193,25 @@ sub calculate {
foreach my $i ( 0 .. scalar @$filters ) {
my %cell;
if ( @$filters[$i] ) {
if ( $i == 0 ) { $cell{crit} = "Cat code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 1 ) { $cell{crit} = "ZIP/Postal code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 2 ) { $cell{crit} = "Branch code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 3 || $i == 4 ) { $cell{crit} = "Date of birth"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 5 ) { $cell{crit} = "Sex"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 6 ) { $cell{crit} = "Sort1"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 7 ) { $cell{crit} = "Sort2"; $cell{filter} = @$filters[$i]; }
else { $cell{crit} = "Unknown"; }
if ( $i == 0 ) { $cell{crit} = "Cat code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 1 ) { $cell{crit} = "ZIP/Postal code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 2 ) { $cell{crit} = "Branch code"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 3 ) { $cell{crit} = "Date of birth1"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 4 ) { $cell{crit} = "Date of birth2"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 5 ) { $cell{crit} = "Sex"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 6 ) { $cell{crit} = "Sort1"; $cell{filter} = @$filters[$i]; }
elsif ( $i == 7 ) { $cell{crit} = "Sort2"; $cell{filter} = @$filters[$i]; }
else { $cell{crit} = "Unknown"; }
push @loopfilter, \%cell;
}
}
foreach my $type ( keys %$attr_filters ) {
if ( $attr_filters->{$type} ) {
my $patron_attribute_type = Koha::Patron::Attribute::Types->find($type);
push @loopfilter, {
crit => "Attribute $type",
crit => "PA_CLASS",
label => $patron_attribute_type->description,
filter => $attr_filters->{$type}
};
}

View File

@ -230,10 +230,18 @@ sub calculate {
push @loopfilter, \%cell;
}
foreach ( keys %$attribute_filters ) {
next unless $attribute_filters->{$_};
push @loopfilter, { crit => "$_ =", filter => $attribute_filters->{$_} };
foreach my $type ( keys %$attribute_filters ) {
if ( $attribute_filters->{$type} ) {
my $patron_attribute_type = Koha::Patron::Attribute::Types->find($type);
push @loopfilter, {
crit => "PA_CLASS",
label => $patron_attribute_type->description,
filter => $attribute_filters->{$type}
};
}
}
push @loopfilter, { crit => "Event", filter => $type };
push @loopfilter, { crit => "Display by", filter => $dsp } if ($dsp);
push @loopfilter, { crit => "Select Day", filter => $daysel } if ($daysel);

View File

@ -683,8 +683,10 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
)->unblessed
}
};
my @reserves =
Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list;
my @reserves = Koha::Holds->search(
{ 'me.biblionumber' => $biblionumber },
{ prefetch => [ 'borrowernumber', 'itemnumber' ], order_by => 'priority' }
)->as_list;
foreach my $res (
sort {
my $a_found = $a->found() || '';

View File

@ -82,14 +82,20 @@ if ( $op eq 'cud-gennext' && @subscriptionid ) {
my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern( $subscription->{numberpattern} );
my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency( $subscription->{periodicity} );
my $expected = GetNextExpected($subscriptionid);
my (
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
) = GetNextSeq( $subscription, $pattern, $frequency, $expected->{publisheddate}, $count_forward );
## We generate the next publication date
my $nextpublisheddate = GetNextDate( $subscription, $expected->{publisheddate}, $frequency, 1 );
my $planneddate = $date_received_today ? dt_from_string : $nextpublisheddate;
my (
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
)
= GetNextSeq(
$subscription, $pattern, $frequency, $expected->{publisheddate}, $nextpublisheddate,
$count_forward
);
my $planneddate = $date_received_today ? dt_from_string : $nextpublisheddate;
## Creating the new issue
NewIssue(
$newserialseq, $subscriptionid, $subscription->{'biblionumber'},

View File

@ -174,7 +174,7 @@ while ( $i < 1000 ) {
(
$calculated, $subscription{'lastvalue1'}, $subscription{'lastvalue2'}, $subscription{'lastvalue3'},
$subscription{'innerloop1'}, $subscription{'innerloop2'}, $subscription{'innerloop3'}
) = GetNextSeq( \%subscription, \%pattern, $frequency, $date );
) = GetNextSeq( \%subscription, \%pattern, $frequency, $date, $date );
$issuenumber++;
$line{'number'} = $calculated;
$line{'issuenumber'} = $issuenumber;

12
suggestion/suggestion.pl Executable file → Normal file
View File

@ -103,6 +103,18 @@ my $filter_archived = $input->param('filter_archived') || 0;
my $new_itemtype = $input->param('suggestion_itemtype');
my $sugg_managedby = $input->param('suggestion_managedby');
#NOTE: Validate displayby as it gets passed to sensitive functions
my %valid_displayby = (
STATUS => 1,
branchcode => 1,
itemtype => 1,
managedby => 1,
acceptedby => 1,
);
if ( !$valid_displayby{$displayby} ) {
$displayby = '';
}
my $reasonsloop = GetAuthorisedValues("SUGGEST");
my $suggestion_ref = { $input->Vars };

View File

@ -259,6 +259,6 @@ sub _next_seq {
$seq, $subscription->{lastvalue1}, $subscription->{lastvalue2},
$subscription->{lastvalue3}, $subscription->{innerloop1},
$subscription->{innerloop2}, $subscription->{innerloop3}
) = GetNextSeq( $subscription, $pattern, undef, undef, $count_forward );
) = GetNextSeq( $subscription, $pattern, undef, undef, undef, $count_forward );
return $seq;
}

View File

@ -0,0 +1,12 @@
describe("Suggestions", () => {
beforeEach(() => {
cy.login();
cy.title().should("eq", "Koha staff interface");
});
it("Should sanitize displayby", () => {
cy.visit("/cgi-bin/koha/suggestion/suggestion.pl?displayby=foo");
// Do not 500 and default to "STATUS" if displayby is not valid
cy.get("#displayby").should("have.value", "STATUS");
});
});

View File

@ -733,7 +733,8 @@ const insertObject = async ({ type, object, baseUrl, authHeader }) => {
suspended,
suspended_until,
non_priority,
item_type,
item_type_id,
_strings,
hold_group_id,
item_level,
cancellation_requested,

View File

@ -7667,7 +7667,7 @@ subtest 'NoRefundOnLostFinesPaidAge' => sub {
};
subtest 'ChildNeedsGuarantor' => sub {
plan tests => 18;
plan tests => 12;
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
@ -7745,47 +7745,6 @@ subtest 'ChildNeedsGuarantor' => sub {
library => $library->branchcode,
}
);
warnings_like {
AddIssue( $child_patron, $item->barcode, undef );
}
qr/Problem updating lastseen/,
"AddIssue generates a warning when Child type patron is missing a guarantor and ChildsNeedsGuarantor";
warnings_like {
AddRenewal(
{
borrowernumber => $child_patron->borrowernumber, itemnumber => $item->itemnumber,
branch => $library->branchcode
}
);
}
qr/Problem updating lastseen/,
"AddRenewal generates a warning when Child type patron is missing a guarantor and ChildNeedsGuarantor";
warnings_like {
AddReturn( $item->barcode, $library->branchcode, undef, undef );
}
qr/Problem updating lastseen/,
"AddReturn generates a warning when Child type patron is missing a guarantor and ChildNeedsGuarantor";
warnings_like {
AddIssue( $guarantee_patron, $item->barcode, undef );
}
qr/Problem updating lastseen/,
"AddIssue generates a warning when can_be_guarantee type patron is missing a guarantor and ChildsNeedsGuarantor";
warnings_like {
AddRenewal(
{
borrowernumber => $guarantee_patron->borrowernumber, itemnumber => $item->itemnumber,
branch => $library->branchcode
}
);
}
qr/Problem updating lastseen/,
"AddRenewal generates a warning when can_be_guarantee type patron is missing a guarantor and ChildNeedsGuarantor";
warnings_like {
AddReturn( $item->barcode, $library->branchcode, undef, undef );
}
qr/Problem updating lastseen/,
"AddReturn generates a warning when can_be_guarantee type patron is missing a guarantor and ChildNeedsGuarantor";
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
warnings_like {

View File

@ -68,7 +68,7 @@ subtest 'enqueue() tests' => sub {
subtest 'process() tests' => sub {
plan tests => 3;
plan tests => 4;
$schema->storage->txn_begin;
@ -103,7 +103,8 @@ subtest 'process() tests' => sub {
is( $statistic->{datetime}, $pt_after->last->datetime, "'datetime' column preserved" );
$job->discard_changes;
is( $job->data, '{"data":""}', "Job data cleared after pseudonymization" );
is( $job->data, '{"data":""}', "Job data cleared after pseudonymization" );
is( $job->progress, 1, "Job progress is updated" );
$schema->storage->txn_rollback;
};

View File

@ -20,7 +20,7 @@
use Modern::Perl;
use Test::NoWarnings;
use Test::More tests => 20;
use Test::More tests => 21;
use Test::Exception;
use Test::MockModule;
@ -92,6 +92,27 @@ subtest 'biblio() tests' => sub {
$schema->storage->txn_rollback;
};
subtest 'item_type() tests' => sub {
plan tests => 2;
$schema->storage->txn_begin;
my $hold = $builder->build_object(
{
class => 'Koha::Holds',
value => { itemtype => undef },
}
);
is( $hold->item_type, undef, 'Koha::Hold->item_type returns undef if no item type selected' );
my $item_type = $builder->build_object( { class => "Koha::ItemTypes" } );
$hold->itemtype( $item_type->itemtype )->store;
is( $hold->item_type->itemtype, $item_type->itemtype, 'Koha::Hold->item_type returns the correct item type' );
$schema->storage->txn_rollback;
};
subtest 'pickup_library/branch tests' => sub {
plan tests => 1;
@ -1207,7 +1228,7 @@ subtest 'change_type() tests' => sub {
subtest 'strings_map() tests' => sub {
plan tests => 3;
plan tests => 4;
$schema->txn_begin;
@ -1233,7 +1254,8 @@ subtest 'strings_map() tests' => sub {
is_deeply(
$strings_map,
{
pickup_library_id => { str => $library->branchname, type => 'library' },
pickup_library_id => { str => $library->branchname, type => 'library' },
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
cancellation_reason => { str => $av->lib, type => 'av', category => 'HOLD_CANCELLATION' },
},
'Strings map is correct'
@ -1243,7 +1265,8 @@ subtest 'strings_map() tests' => sub {
is_deeply(
$strings_map,
{
pickup_library_id => { str => $library->branchname, type => 'library' },
pickup_library_id => { str => $library->branchname, type => 'library' },
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
cancellation_reason => { str => $av->lib_opac, type => 'av', category => 'HOLD_CANCELLATION' },
},
'Strings map is correct (OPAC)'
@ -1255,12 +1278,24 @@ subtest 'strings_map() tests' => sub {
is_deeply(
$strings_map,
{
pickup_library_id => { str => $library->branchname, type => 'library' },
pickup_library_id => { str => $library->branchname, type => 'library' },
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
cancellation_reason => { str => $hold->cancellation_reason, type => 'av', category => 'HOLD_CANCELLATION' },
},
'Strings map shows the cancellation_value when AV not present'
);
$hold->itemtype(undef)->store;
$strings_map = $hold->strings_map( { public => 1 } );
is_deeply(
$strings_map,
{
pickup_library_id => { str => $library->branchname, type => 'library' },
cancellation_reason => { str => $hold->cancellation_reason, type => 'av', category => 'HOLD_CANCELLATION' },
},
'Strings map does not show item_type_id if none selected'
);
$schema->txn_rollback;
};

View File

@ -159,16 +159,17 @@ subtest 'opac_info tests' => sub {
);
# Start testing
is( $library01->opac_info->content, '2', 'specific library, default language' );
is( $library01->opac_info( { lang => 'nl-NL' } )->content, '3', 'specific library, specific language' );
is( $library01->opac_info( { lang => 'nl-BE' } )->content, '2', 'specific library, unknown language' );
is( $library02->opac_info->content, '1', 'unknown library, default language' );
is( $library02->opac_info( { lang => 'fr-FR' } )->content, '4', 'unknown library, specific language' );
is( $library02->opac_info( { lang => 'de-DE' } )->content, '1', 'unknown library, unknown language' );
ok( $library01->opac_info, 'specific library, default language' );
ok( $library01->opac_info( { lang => 'nl-NL' } ), 'specific library, specific language' );
ok( $library01->opac_info( { lang => 'nl-BE' } ), 'specific library, unknown language' );
ok( $library02->opac_info, 'unknown library, default language' );
ok( $library02->opac_info( { lang => 'fr-FR' } ), 'unknown library, specific language' );
ok( $library02->opac_info( { lang => 'de-DE' } ), 'unknown library, unknown language' );
$html01->delete;
is( $library02->opac_info, undef, 'unknown library, default language (after removing html01)' );
is(
$library02->opac_info( { lang => 'de-DE' } ), undef,
ok( $library02->opac_info, 'unknown library, default language (after removing html01)' );
ok(
$library02->opac_info( { lang => 'de-DE' } ),
'unknown library, unknown language (after removing html01)'
);

View File

@ -19,7 +19,7 @@
use Modern::Perl;
use Test::More tests => 45;
use Test::More tests => 46;
use Test::NoWarnings;
use Test::Exception;
use Test::Warn;
@ -2624,93 +2624,78 @@ subtest 'get_lists_with_patron() tests' => sub {
$schema->storage->txn_rollback;
};
subtest 'guarantor requirements tests' => sub {
subtest 'can_be_guaranteed_by() tests' => sub {
plan tests => 6;
plan tests => 5;
$schema->storage->txn_begin;
my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
my $child_category =
$builder->build( { source => 'Category', value => { category_type => 'C', can_be_guarantee => 1 } } )
->{categorycode};
my $patron_category =
$builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
->{categorycode};
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
my $guarantee_category =
$builder->build(
{ source => 'Category', value => { categorycode => "GUARANTEE", category_type => 'C', can_be_guarantee => 1 } }
);
my $guarantor_category =
$builder->build(
{ source => 'Category', value => { categorycode => "GUARANTOR", category_type => 'A', can_be_guarantee => 0 } }
);
my $child = Koha::Patron->new(
{
branchcode => $branchcode,
categorycode => $child_category,
categorycode => $guarantee_category->{categorycode},
contactname => ''
}
);
$child->store();
ok(
Koha::Patrons->find( $child->id ),
'Child patron can be stored without guarantor when ChildNeedsGuarantor is off.'
);
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
my $child2 = Koha::Patron->new(
{
branchcode => $branchcode,
categorycode => $child_category,
contactname => ''
}
);
my $child3 = $builder->build_object(
my $child2 = $builder->build_object(
{
class => 'Koha::Patrons',
value => { categorycode => $child_category }
value => { categorycode => $guarantee_category->{categorycode} }
}
);
my $patron = $builder->build_object(
{
class => 'Koha::Patrons',
value => { categorycode => $patron_category }
value => { categorycode => $guarantor_category->{categorycode} }
}
);
my $patron2 = $builder->build_object(
{
class => 'Koha::Patrons',
value => { categorycode => $guarantor_category->{categorycode} }
}
);
throws_ok { $child2->store(); }
my @guarantors;
my $contactname = "";
my $category = Koha::Patron::Categories->find( $guarantee_category->{categorycode} );
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
lives_ok { $child->can_be_guaranteed_by( \@guarantors ); }
'Validation passes when ChildNeedsGuarantor syspref is disabled';
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
throws_ok { $child->can_be_guaranteed_by( \@guarantors ); }
'Koha::Exceptions::Patron::Relationship::NoGuarantor',
'Exception thrown when guarantor is required but not provided.';
my @guarantors = ( $patron, $child3 );
throws_ok { $child2->store( { guarantors => \@guarantors } ); }
@guarantors = ( $patron, $child2 );
throws_ok { $child->can_be_guaranteed_by( \@guarantors ); }
'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
'Exception thrown when child patron is added as guarantor.';
#test ModMember
@guarantors = ($patron);
$child2->store( { guarantors => \@guarantors } )->discard_changes();
t::lib::Mocks::mock_preference( 'borrowerRelationship', '' );
my $relationship = Koha::Patron::Relationship->new(
{
guarantor_id => $patron->borrowernumber,
guarantee_id => $child2->borrowernumber,
relationship => ''
}
);
$relationship->store();
ok( $child2->store(), 'Child patron can be modified and stored when guarantor is stored' );
@guarantors = ($child3);
throws_ok { $child2->store( { guarantors => \@guarantors } ); }
t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
$child2->add_guarantor( { guarantor_id => $patron->id, relationship => 'parent' } );
@guarantors = ($patron2);
throws_ok { $patron->can_be_guaranteed_by( \@guarantors ); }
'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
'Exception thrown when child patron is modified and child patron is added as guarantor.';
'Exception thrown when trying to add a guarantor to a guarantor.';
$relationship->delete;
throws_ok { $child2->store(); }
'Koha::Exceptions::Patron::Relationship::NoGuarantor',
'Exception thrown when guarantor is deleted.';
@guarantors = ($patron);
lives_ok { $child->can_be_guaranteed_by( \@guarantors ); }
'Validation passes when valid guarantors are passed in array';
$schema->storage->txn_rollback;
};
@ -2952,6 +2937,131 @@ subtest 'is_patron_inside_charge_limits() tests' => sub {
$schema->storage->txn_rollback;
};
subtest 'is_patron_inside_charge_limits - edge cases for empty string and zero' => sub {
plan tests => 14;
$schema->storage->txn_begin;
my $patron_category = $builder->build(
{
source => 'Category',
value => {
categorycode => 'TEST_CAT',
category_type => 'P',
enrolmentfee => 0,
noissuescharge => undef,
noissueschargeguarantees => undef,
noissueschargeguarantorswithguarantees => undef
}
}
);
my $patron = $builder->build_object(
{
class => 'Koha::Patrons',
value => { categorycode => $patron_category->{categorycode} }
}
);
my $child = $builder->build_object( { class => 'Koha::Patrons' } );
$child->add_guarantor( { guarantor_id => $patron->borrowernumber, relationship => 'parent' } );
# Add some charges to the child
my $fee = $builder->build_object(
{
class => 'Koha::Account::Lines',
value => {
borrowernumber => $child->borrowernumber,
amountoutstanding => 5.50,
debit_type_code => 'OVERDUE',
}
}
)->store;
# Test 1: Empty string values should be treated as "not set"
# Empty string is the default value in sysprefs for these preferences
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', '' );
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantorsWithGuarantees', '' );
my $patron_borrowing_status = $patron->is_patron_inside_charge_limits();
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{limit}, '',
"Empty string limit is preserved in return value"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 0,
"Charges should be 0 when limit is empty string (calculation skipped)"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 0,
"Patron should not be overlimit when preference is empty string"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{limit}, '',
"Empty string limit is preserved for guarantors preference"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{charge}, 0,
"Guarantors charges should be 0 when limit is empty string (calculation skipped)"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit}, 0,
"Patron should not be overlimit when guarantors preference is empty string"
);
# Test 2: Zero values should trigger calculation but not block
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', 0 );
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantorsWithGuarantees', 0 );
$patron_borrowing_status = $patron->is_patron_inside_charge_limits();
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{limit}, 0,
"Zero limit is preserved in return value"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 5.50,
"Charges should be calculated when limit is 0 (valid number)"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 0,
"Patron should not be overlimit when limit is 0 (0 means no blocking)"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{limit}, 0,
"Zero limit is preserved for guarantors preference"
);
# Note: This will be 5.50 because the child is included in guarantor debt calculation
ok(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{charge} > 0,
"Guarantors charges should be calculated when limit is 0 (valid number)"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit}, 0,
"Patron should not be overlimit when guarantors limit is 0 (0 means no blocking)"
);
# Test 3: Verify that a small positive number triggers calculation and can block
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', 5.00 );
$patron_borrowing_status = $patron->is_patron_inside_charge_limits();
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 5.50,
"Charges are calculated with positive limit"
);
is(
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 1,
"Patron is correctly marked overlimit when charges exceed limit"
);
$schema->storage->txn_rollback;
};
subtest 'Scrub the note fields' => sub {
plan tests => 4;

View File

@ -30,6 +30,7 @@ use Test::MockModule;
use Koha::Database;
use Koha::Patron::Relationships;
use Koha::DateUtils qw(dt_from_string);
use Koha::ActionLogs;
use File::Temp qw(tempfile tempdir);
my $temp_dir = tempdir( 'Koha_patrons_import_test_XXXX', CLEANUP => 1, TMPDIR => 1 );
@ -1497,7 +1498,9 @@ subtest 'patron_attributes' => sub {
}
subtest 'update existing patron' => sub {
plan tests => 19;
plan tests => 22;
t::lib::Mocks::mock_preference( 'BorrowersLog', 0 );
my $patron = $builder->build_object(
{
@ -1523,8 +1526,19 @@ subtest 'patron_attributes' => sub {
is( $result->{overwritten}, 1 );
my $action_logs = Koha::ActionLogs->search(
{
module => "MEMBERS",
action => "MODIFY",
object => $patron->borrowernumber,
}
);
is( $action_logs->count, 0, ' ' );
compare_patron_attributes( $patron->extended_attributes->unblessed, {%$attributes} );
t::lib::Mocks::mock_preference( 'BorrowersLog', 1 );
# Adding a new non-repeatable attribute
my $new_attributes = {
$normal_attribute_type->code => ['my normal attribute 2'],
@ -1544,6 +1558,35 @@ subtest 'patron_attributes' => sub {
# The normal_attribute_type has been replaced with 'my normal attribute 2'
compare_patron_attributes( $patron->extended_attributes->unblessed, { %$attributes, %$new_attributes } );
$action_logs = Koha::ActionLogs->search(
{
module => "MEMBERS",
action => "MODIFY",
object => $patron->borrowernumber,
}
);
is( $action_logs->count, 1, ' ' );
$result = $patrons_import->import_patrons(
{
file => $fh,
matchpoint => 'cardnumber',
overwrite_cardnumber => 1,
preserve_extended_attributes => 1
}
);
$action_logs = Koha::ActionLogs->search(
{
module => "MEMBERS",
action => "MODIFY",
object => $patron->borrowernumber,
}
);
is( $action_logs->count, 1, ' ' );
# UniqueIDConstraint
$patron->extended_attributes->delete; # reset
$builder->build_object(

View File

@ -727,14 +727,16 @@ subtest "test numbering pattern with dates in GetSeq GetNextSeq" => sub {
'GetSeq correctly calculates numbering from first aqui date, leap year'
);
my $planneddate = '1970-11-01';
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate );
is( $numbering, '1970 1 Sunday 11 November', 'GetNextSeq correctly calculates numbering from planned date' );
$planneddate = '2024-02-29';
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate );
my $planneddate = '1970-10-01';
my $nextpublisheddate = '1970-11-01';
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate, $nextpublisheddate );
is( $numbering, '1970 1 Sunday 11 November', 'GetNextSeq correctly calculates numbering from next published date' );
$planneddate = '2024-01-29';
$nextpublisheddate = '2024-02-29';
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate, $nextpublisheddate );
is(
$numbering, '2024 29 Thursday 2 February',
'GetNextSeq correctly calculates numbering from planned date, leap year'
'GetNextSeq correctly calculates numbering from next published date, leap year'
);
};

View File

@ -274,6 +274,8 @@ sub _next_seq {
$seq, $subscription->{lastvalue1}, $subscription->{lastvalue2},
$subscription->{lastvalue3}, $subscription->{innerloop1},
$subscription->{innerloop2}, $subscription->{innerloop3}
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
)
= GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $publisheddate )
; # 5th parameter not really used btw
return $seq;
}

View File

@ -277,7 +277,7 @@ subtest 'Reserves with itemtype' => sub {
patron_id => int( $patron_1->borrowernumber ),
biblio_id => int( $biblio_1->biblionumber ),
pickup_library_id => $branchcode,
item_type => $itemtype,
item_type_id => $itemtype,
};
$t->delete_ok("//$userid_3:$password@/api/v1/holds/$reserve_id")->status_is( 204, 'REST3.2.4' )
@ -294,7 +294,7 @@ subtest 'Reserves with itemtype' => sub {
$reserve_id = $t->tx->res->json->{hold_id};
$t->get_ok( "//$userid_1:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber )->status_is(200)
->json_is( '/0/hold_id', $reserve_id )->json_is( '/0/item_type', $itemtype );
->json_is( '/0/hold_id', $reserve_id )->json_is( '/0/item_type_id', $itemtype );
};
subtest 'test AllowHoldDateInFuture' => sub {
@ -769,7 +769,7 @@ subtest 'add() tests (maxreserves behaviour)' => sub {
patron_id => $patron->borrowernumber,
biblio_id => $biblio_1->biblionumber,
pickup_library_id => $item_1->home_branch->branchcode,
item_type => $item_1->itype,
item_type_id => $item_1->itype,
};
$t->post_ok( "//$userid:$password@/api/v1/holds" => json => $post_data )->status_is(201);

View File

@ -586,7 +586,7 @@ subtest 'add() tests' => sub {
subtest 'extended_attributes handling tests' => sub {
plan tests => 25;
plan tests => 29;
$mocked_patron->mock(
'extended_attributes',
@ -622,6 +622,23 @@ subtest 'add() tests' => sub {
is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
# Bug 40219: Test that welcome email is not sent when extended attribute validation fails
t::lib::Mocks::mock_preference( 'AutoEmailNewUser', 1 );
$letter_enqueued = 0;
$extended_attrs_exception = 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute';
$t->post_ok(
"//$userid:$password\@/api/v1/patrons" => json => {
"firstname" => "Bug",
"surname" => "FortyZeroTwoOneNine",
"address" => "Somewhere",
"category_id" => "ST",
"city" => "TestCity",
"library_id" => "MPL",
"email" => 'bug40219@test.com'
}
)->status_is(400)->json_is( '/error' => "Missing mandatory extended attribute (type=$type)" );
is( $letter_enqueued, 0, 'Bug 40219: No welcome email sent when extended attribute validation fails' );
$extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::InvalidType';
$t->post_ok(
"//$userid:$password@/api/v1/patrons" => json => {

View File

@ -1823,7 +1823,7 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
"@types/express-serve-static-core@*":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b"
integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==
@ -1843,16 +1843,7 @@
"@types/range-parser" "*"
"@types/send" "*"
"@types/express@*":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.5.tgz#3ba069177caa34ab96585ca23b3984d752300cdc"
integrity sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^5.0.0"
"@types/serve-static" "^1"
"@types/express@^4.17.13", "@types/express@^4.17.21":
"@types/express@*", "@types/express@^4.17.13", "@types/express@^4.17.21":
version "4.17.25"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b"
integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==