Commit Graph

62374 Commits

Author SHA1 Message Date
Jonathan Druart d80ca5f4e8
Bug 42875: Correct failure message when adding an item to a bundle
If bc_1 is checked out and I try to add bc_2 I get:
"Failure: Item 'bc_2' belongs to another bundle"

It should be:
"Failure: Bundle is currently checked out"

Test plan (using KTD):
1. Pick a record with items, for example: The definitive guide to Catalyst (146)
2. Edit the record and change 000 (the leader):
   - Click the tag editor
   - Change "7- Bibliographic level" to "C- Collection"
3. In the holdings table there is now a "Manage bundle (0|0)" option
   in the far right column for each item.
4. Check an item out for the record out, for example
   I checked out 39999000006117 to Mary Burton.
5. In the holdings table for the record, click on "Manage bundle" for the item
   you checked out (39999000006117).
6. In the expanded text area below the item row, click "+ Add to bundle".
7. In pop-up window, enter 39999000006131 "Item barcode" field (this is one
   of the other items for the record that is not checked out).
8. Click Submit.
9. You get an error:
     Failure: Item '39999000006131' belongs to another bundle
10. Apply the patch.
11. Restart everything: restart all
12. Hard refresh the page for the record.
13. Repeat steps 5 to 8.
14. You now get the correct error:
      "Failure: Bundle is currently checked out"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:39 +00:00
Jonathan Druart 0c952e9e09
Bug 43241: Add tests for timezone support in RFC3339 q= queries
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:38 +00:00
Tomás Cohen Arazi fd87c5f195
Bug 43241: (follow-up) Use form => for q= params in tests
The + character in RFC3339 timezone offsets (e.g. +00:00) is decoded
as a space when interpolated directly into a URL query string. Use
Test::Mojo form => { q => ... } which properly URL-encodes the
parameter value.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:37 +00:00
Tomás Cohen Arazi 450e6bf178
Bug 43241: Fix datetime/boolean conversion in q= queries with DBIC operators
_parse_dbic_query maps field names but did not convert values. When
datetime or boolean filters are nested inside DBIC operators (-and,
-or) in the q= parameter, attributes_from_api (which normally handles
the conversion via _recursive_fixup) cannot reach them because it only
iterates top-level keys.

This patch fixes the issue in two places:

1. _parse_dbic_query (Query.pm): When a key maps to a known column
   (on the primary table or a related object), call _recursive_fixup
   to convert RFC3339 timestamps to MySQL format and booleans to 0/1.
   Related object instances are cached on the result_set to avoid
   repeated prefetch_whitelist lookups during recursive parsing.

2. Objects.pm: Move attributes_from_api to process named query params
   before merging with q= params. This avoids double-processing since
   q= values are already fixed up by _parse_dbic_query.

Test plan:
1. Apply the regression tests from the previous commit
2. Run:
   $ prove t/Koha/REST/Plugin/Query.t
   $ prove t/db_dependent/Koha/REST/Plugin/Objects.t
=> FAIL: Tests fail!
3. Apply this patch
4. Repeat step 2
=> SUCCESS: Tests pass!
5. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:36 +00:00
Tomás Cohen Arazi 811e57c441
Bug 43241: Add tests for datetime/boolean fixup inside DBIC operators
This patch adds regression tests proving that RFC3339 datetime values
and boolean values nested inside DBIC operators (-and, -or, -between)
in q= queries are not converted to their DB-native format.

Unit tests (Query.t):
- Datetime: >=, <=, range, -or, -between, nested -and/-or, me. prefix
- Boolean: true/false inside -and, -or, nested -and within -or
- Non-datetime values pass through unchanged

Integration tests (Objects.t):
- Full API stack tests using action_logs with controlled data
- Same operator/nesting coverage as unit tests

Test plan:
1. Apply patch
2. Run:
   $ prove t/Koha/REST/Plugin/Query.t
   $ prove t/db_dependent/Koha/REST/Plugin/Objects.t
=> FAIL: Tests fail! Values inside -and are not converted
3. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:35 +00:00
Martin Renvoize 537a64a01b
Bug 42634: Force JWT cookie deletion on self-checkout logout
The web-based self-checkout (opac/sco/sco-main.pl) builds the
response JWT cookie with -expires => $jwt ? '+1d' : ''. On logout
$jwt is undef, so the cookie is sent with an empty value and no
Expires attribute - i.e. a session cookie with an empty value
rather than a deletion. Some browsers/proxies retain the previous
valid JWT in this scenario, which presents as the Finish/close
button "doing nothing" or working only intermittently: the patron
appears to remain logged in despite clicking Finish.

Use a past Expires ('-1d') when no JWT is present so the browser
deletes the cookie entirely. The subsequent ?op=logout render
cannot then rediscover the patron from a stale cookie.

Test plan:
1. Enable web-based self-checkout (SelfCheckoutByLogin or
   SelfCheckAllowByIPRanges).
2. Log in as a patron at /cgi-bin/koha/sco/sco-main.pl.
3. Open DevTools -> Application -> Cookies and note the JWT cookie.
4. Click the green "Finish" button.
5. Without the patch: Set-Cookie on the ?op=logout response shows
   JWT= with no Expires; the cookie persists as an empty session
   cookie.
6. With the patch: Set-Cookie shows JWT=; expires=<past date>; the
   browser removes the cookie entirely.
7. Confirm the kiosk reliably returns to the login screen and
   subsequent page loads do not re-authenticate from a stale JWT.

Sponsored-by: OpenFifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:34 +00:00
Andrii Nugged b719532f24
Bug 42605: Add automated tests
Store scalar preference values when saving the acquisition preferences
and clean up the invoice explicitly.

Exercise the rendered receiving behavior for a basket that inherits
AcqCreateItem, one that explicitly overrides it with "ordering", and a
basket that creates items on receiving. Keep the explicit "ordering"
case as a control for behavior that already works without the production
fix.

This changes tests only.

Automated tests:

yarn cypress run --spec \
  t/cypress/integration/Acquisitions/OrderReceivePreferences_spec.ts

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:33 +00:00
Andrii Nugged 757b347bed
Bug 42605: Preserve acquisition preference string values
Bug 41563 converted AcqCreateItem and UniqueItemFields to booleans when
adding them to Koha.prefs. Both preferences are strings: AcqCreateItem
controls where items are created, while UniqueItemFields contains a
pipe-separated list.

When a basket inherits AcqCreateItem=ordering, the boolean value matches
none of the receiving page branches and existing items are hidden. When
items are created on receiving, the numeric UniqueItemFields value also
fails when the page calls split().

Preserve both string values in Koha.prefs. Add a Cypress regression test
which renders orderreceive.pl and verifies the values exposed to
JavaScript.

Test plan:

Before applying the patch:

1. Set AcqCreateItem to "placing an order" and UniqueItemFields to
   "barcode".
2. Create a basket that inherits the system preference. Add an order with one
   item, close the basket, and start receiving it.
3. Confirm that the Items table has no existing item row and no Receive
   checkbox.
4. Set AcqCreateItem to "receiving an order" and open the same order for
   receiving.
5. Confirm that the Items section is empty: no item-entry fields and no Add
   item button are shown.

Apply the patch.

After applying the patch:

1. Set AcqCreateItem back to "placing an order" and open the order for
   receiving again.
2. Confirm that its existing item is listed and has a Receive checkbox.
3. Set AcqCreateItem to "receiving an order" and open the same order again.
4. Confirm that the Items section contains item-entry fields and an Add item
   button.

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:32 +00:00
Tomás Cohen Arazi 3dc90004ca
Bug 39769: Flush L1 cache in es_indexer_daemon.pl before each batch
The daemon runs as a long-lived process and never clears its L1 cache.
System preference changes (e.g. IncludeSeeFromInSearches) are not
picked up until the daemon is manually restarted.

Flush L1 caches at the start of each commit() call so that preference
changes take effect on the next indexing batch without requiring a
daemon restart.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:31 +00:00
Pedro Amorim 279448539c
Bug 30144: DBRev 26.06.00.017
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:29 +00:00
Martin Renvoize 08a2e0475b
Bug 30144: (QA follow-up) Exclude servicing_instruction from order API representation
The servicing_instruction column was not mapped in
Koha::Acquisition::Order->to_api_mapping, so it was exposed verbatim in
the REST API order representation. Since api/v1/swagger/definitions/order.yaml
does not declare this property and sets additionalProperties: false, any
request touching the acquisitions/orders endpoint (e.g. the "Receive
shipments" page) failed with a 500 error: "Properties not allowed:
servicing_instruction".

Exclude it from the API representation, consistent with the other
EDIFACT-only columns (line_item_id, suppliers_reference_number, etc.)
that are already excluded the same way.

Test plan:
1) Go to a vendor page, create a basket, add an order line, and close
   the basket.
2) From the vendor page, click "Receive shipments".
3) Without this patch, the orders table fails to load with a 500 error
   mentioning "Properties not allowed: servicing_instruction".
4) With this patch applied, the orders table loads correctly.
5) prove t/db_dependent/api/v1/acquisitions_orders.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:28 +00:00
Martin Renvoize 2e1f8b8660
Bug 30144: (QA follow-up) Make servicing instruction UI strings translatable
The servicing instructions widget added new user-facing strings (alerts,
button labels, hints) built entirely in JS via string concatenation, none
of which were wrapped for translation - unlike the pre-existing JS in the
same file, which already uses _() throughout. Wrap them the same way,
using _("...").format(...) for the strings that interpolate a value,
matching the existing pattern used elsewhere in this file.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:27 +00:00
Martin Renvoize bbb7f406d2
Bug 30144: (QA follow-up) Fix stored XSS in servicing instructions
The RM's review found two stored XSS vectors in the servicing
instruction free-text (LVT) handling:

1. neworderempty.tt embedded the servicing instruction JSON (and the
   EDIFACT_SI authorised values list) directly into an inline <script>
   block via JSON.parse('[% ... | $raw %]'). TT's html filter does not
   escape single quotes, so a free-text value like
   test' + alert(1) + ' broke out of the JS string literal and
   executed on the next page load of the edit form.

2. basket.pl built the "Servicing instruction" column display by
   concatenating raw LVC/LVT values with '<br>' and rendering the
   result with | $raw in basket.tt, so HTML/script content in a
   free-text value executed directly on the basket page.

Fix the first by moving the JSON into existing/new hidden input
value="" attributes (escaped with the html filter, which is safe for
double-quoted HTML attributes) and reading them back via jQuery's
.val() before JSON.parse(), instead of interpolating untrusted data
into a JS string literal.

Fix the second by passing the display parts to the template as a list
and escaping each with the html filter at render time, instead of
building a pre-escaped raw HTML string in Perl.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:26 +00:00
Martin Renvoize 42ae1e0bf0
Bug 30144: (QA follow-up) Add EDIFACT_SI authorised values to installer data
Tests were failing on upgrade route because they tried to create
EDIFACT_SI authorised values that already existed from the atomicupdate
script, causing unique constraint violations.

This patch:
- Removes test code that created EDIFACT_SI values (tests should assume
  database defaults exist)
- Adds EDIFACT_SI category to auth_val_cat.sql for fresh installs
- Adds all 32 EDItEUR List 3B servicing instruction codes to
  auth_values.yml for fresh installs

Now the authorised values exist in both installation paths:
- Upgrade route: Created by atomicupdate script
- Fresh install: Created by installer SQL/YAML files

Test plan:
1. prove t/db_dependent/Koha/EDI.t
2. prove t/db_dependent/Koha/Edifact/Order.t
3. Verify both tests pass without warnings

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:25 +00:00
Martin Renvoize 711bb2dd76
Bug 30144: Add tests for EDIFACT servicing instruction handling
This commit adds comprehensive tests for servicing instruction
processing in both incoming quotes and outgoing orders:

1. Integration tests for incoming quote processing (Koha/EDI.pm)
   - Tests LVC (coded) and LVT (freetext) extraction
   - Tests multiple GIR occurrences with different servicing instructions
   - Tests all combinations: LVC only, LVT only, and LVC+LVT together

2. Test EDIFACT quote file (QUOTES_SERVICING.CEQ)
   - Contains 3 line items with 4 total orders
   - Demonstrates different servicing instruction patterns

3. Fix for multi-occurrence servicing instruction extraction
   - Modified quote_item() to extract servicing instructions
     per GIR occurrence instead of across all occurrences
   - Ensures each order gets only its own servicing instructions
   - Aligns with existing multi-item processing for funds and stock rotation

Test coverage:
- t/db_dependent/Koha/EDI.t: 29 tests for incoming quote processing
- t/db_dependent/Koha/Edifact/Order.t: 18 tests for outgoing order generation
  (previously added)

All tests pass successfully.

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:24 +00:00
Martin Renvoize b02cd5e1fe
Bug 30144: Add unit tests for servicing instructions
This patch adds comprehensive unit tests for the servicing instruction
functionality in t/db_dependent/Koha/Edifact/Order.t.

Changes:
- Updated test count from 6 to 7
- Added servicing_instruction => undef to test orders to prevent
  TestBuilder from generating random invalid JSON
- Added servicing instruction subtest with 18 tests covering:
  * Single LVC instruction
  * Single LVT instruction
  * Multiple LVC in one group (tests GIR segment overflow)
  * Mixed LVC/LVT in one group
  * Multiple groups
  * Empty servicing instruction
  * Invalid JSON handling (logged to EDI log)
- Tests correctly handle GIR segment 5-element limit and overflow
  to additional segments

Test plan:
1. Run: prove t/db_dependent/Koha/Edifact/Order.t
2. Verify all tests pass

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:23 +00:00
Martin Renvoize c215f2dd43
Bug 30144: Add UI for servicing instructions
This patch adds the user interface for managing and displaying
EDIFACT servicing instructions in the acquisitions module.

Changes to acqui/neworderempty.pl:
- Parses servicing_instruction JSON for editing existing orders
- Fetches EDIFACT_SI authorized values for dropdown
- Passes data to template in both array and JSON formats

Changes to acqui/addorder.pl:
- Accepts servicing_instruction_groups_json from form submission
- Validates and stores JSON in database

Changes to acqui/basket.pl:
- Fetches EDIFACT_SI authorized values
- Formats servicing instructions for display in get_order_infos()
- Creates human-readable display strings with:
  * LVC codes replaced with descriptions
  * Groups separated by <br> tags
  * Format: "Description" or "Description: free text"

Changes to neworderempty.tt:
- Dynamic UI for adding/removing servicing instruction groups
- Each group can contain:
  * One LVC (coded) instruction only, OR
  * One LVT (free-text) instruction only, OR
  * One LVC + one LVT combination
- Validation prevents:
  * More than 2 instructions per group
  * Duplicate LVC or LVT in same group
- LVC instructions use dropdown populated from EDIFACT_SI authorized values
- LVT instructions use free-text input
- Groups serialized to JSON hidden field for form submission
- Smart button labels show what can be added to each group

Changes to basket.tt:
- Displays servicing instructions in basket table
- Pre-formatted in Perl (no JavaScript parsing needed)
- Shows LVC descriptions instead of codes
- Multiple groups displayed on separate lines

Changes to columns_settings.yml:
- Adds servicing_instruction column to basket table configuration

Display format examples:
- LVC only: "Barcode labelling"
- LVT only: "Handle with care"
- Combined: "Binding: Apply to spine"
- Multiple groups (on separate lines):
  Barcode labelling
  Binding: Special instructions

Test plan:
1. Create/edit order
2. Add servicing instruction groups
3. Try to add more than 2 instructions to a group (should prevent)
4. Try to add duplicate LVC or LVT to group (should prevent)
5. Save order
6. View basket and verify instructions display correctly
7. Edit order and verify instructions load into form

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:22 +00:00
Martin Renvoize 4f2829c639
Bug 30144: Process and send servicing instructions in EDI messages
This patch updates the EDIFACT processing to handle multiple servicing
instruction groups in both incoming quotes and outgoing orders.

Changes to Koha/EDI.pm (quote_item):
- Reads both LVC (coded) and LVT (freetext) instructions from incoming quotes
- Validates LVC codes against EDIFACT_SI authorized values
- Stores instructions as JSON array structure
- Groups instructions that work together

Changes to Koha/Edifact/Order.pm (gir_segments):
- Parses JSON servicing instruction data from order
- Generates multiple GIR segments for outgoing orders
- Each instruction becomes a separate GIR element
- Logs JSON parse errors to EDI log using Koha::Logger
- Fixed element counter reset bug when segments exceed 5 elements

Structure allows vendors to specify:
- Single coded instruction: [[{type:"LVC",value:"BB"}]]
- Single text instruction: [[{type:"LVT",value:"Handle with care"}]]
- Combined instructions: [[{type:"LVC",value:"BB"},{type:"LVT",value:"Apply on spine"}]]
- Multiple services: [[{type:"LVC",value:"BB"}],[{type:"LVC",value:"BI"}]]

Test plan:
1. Receive EDIFACT quote with servicing instructions
2. Verify instructions are stored correctly in aqorders
3. Create EDI order from basket
4. Verify GIR segments are generated correctly in outgoing message
5. Check EDI log for any JSON parse errors

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:21 +00:00
Martin Renvoize 8d1e208a7b
Bug 30144: Update DBIC schema
Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:20 +00:00
Martin Renvoize b78a308e60
Bug 30144: Add servicing_instruction field to aqorders table
This patch adds a new TEXT field 'servicing_instruction' to the aqorders
table to store EDIFACT servicing instructions (GIR LVT/LVC segments) as JSON.

The field stores an array of instruction groups, where each group is an
array of instruction objects with 'type' (LVC or LVT) and 'value' fields.

Structure: [[{type:"LVC",value:"BB"},{type:"LVT",value:"text"}],[...]]

This allows for:
- Multiple servicing instruction groups per order line
- Combinations of coded (LVC) and free-text (LVT) instructions
- Instructions that work together in groups

Also adds EDIFACT_SI authorized value category with EDItEUR List 3B
servicing instruction codes for coded (LVC) instructions.

Test plan:
1. Run database update
2. Verify aqorders.servicing_instruction field exists
3. Verify EDIFACT_SI authorized value category is created
4. Verify authorized values include codes like BB, BI, etc.

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:19 +00:00
Amit Gupta 561ef2f025
Bug 42904: Prevent XSS in patron restriction comments
Test plan

1. Search for a patron and open the patron details page.
2. Under "Patron restrictions", add a manual restriction with the following comment:
   <img src=x onerror="alert(document.domain)">
3. Save the restriction.
4. Navigate to the patron checkouts page:
   /cgi-bin/koha/circ/circulation.pl?borrowernumber=xxx
   Verify that the JavaScript payload executes.
5. Edit the patron record:
   /cgi-bin/koha/members/memberentry.pl?op=edit_form&destination=circ&borrowernumber=xxx
   Verify that the JavaScript payload executes.
6. For OPAC - Go to OPAC Verify that the JavaScript payload executes - /cgi-bin/koha/opac-user.pl
7. Apply the patch.
8. Repeat steps 4, 5 and 6.
9. Verify that:
   1. No JavaScript is executed.
   2. The page loads normally without any alert dialog. Applicable for both the staff and opac.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:34 +00:00
Martin Renvoize d32ae7f064
Bug 42800: (QA follow-up) Fix backslash breaking the shelf list script block
erm.tt built the logged_in_user_lists JS array by manually
interpolating each shelf's id/name into a JS string literal, escaped
with the TT html filter:

    id: "[% shelf.shelfnumber | html %]",
    name: "[% shelf.shelfname | html %]",

The html filter escapes <, >, &, and " -- enough to stop the
<script>-tag and </script>-breakout scenario from the original bug --
but it does not escape backslashes. A list name ending in a backslash
(e.g. "foo\") escapes the closing quote of the JS string literal,
unbalancing it and throwing a JS syntax error that breaks the whole
inline <script> block on every ERM page (including the unrelated
`const logged_in_user = ...` a few lines below), reproducing the
"broken UI" half of the original bug via a different character.

Build the id/name pairs as an actual array of hashes and JSON-encode
them with To.json(), after escaping each value with the html
vmethod. JSON encoding (via the JSON module) correctly escapes
backslashes and quotes for use inside a JS string, while keeping the
values HTML-escaped so they stay inert if ever written into the DOM
via innerHTML (as the DataTables render callback in
EHoldingsLocalTitlesFormImport.vue does).

Test plan:
1. Enable the ERMModule system preference.
2. Create a list named: backslash test \
3. Go to /cgi-bin/koha/erm/eholdings/local/titles/import (or any
   other ERM page).
   => Without this patch: the page's inline script throws a syntax
      error ("Invalid or unexpected token") and
      "logged_in_user_lists is not defined", breaking the Vue
      component.
   => With this patch: the page loads without errors and the list is
      shown correctly in the table.
4. Also verify the original bug's script-tag scenario still works:
   create a list named <script>alert("booh!");</script> and confirm
   it displays as inert text, not executing, on the same pages.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:33 +00:00
Jonathan Druart 22a6ab9729
Bug 42800: Fix XSS in shelf list in the ERM module
const logged_in_user_lists = [% To.json(logged_in_user.virtualshelves.unblessed) | $raw %];

If you have a script tag in a list name the UI will be broken, and there
is potentially a XSS vulnerability

Test plan:
Create a new list with name="<script>alert("booh!");</script>"
Go to /cgi-bin/koha/erm/eholdings/local/titles/import
or any ERM views
=> Without this patch the UI is broken
=> With this patch applied it's not and the table is correctly populated
with the lists

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:32 +00:00
Sanjar Tulkinov Anvar o'g'li 074e0b7261
Bug 42736: Prevent SQL injection in reports/cat_issues_top.pl
The "Most-circulated items" report built its SQL by concatenating the
Criteria and Filter request parameters directly into the query, with no
bind placeholders and no whitelist on the Criteria identifier. An
authenticated staff user with the reports permission could inject
arbitrary SQL and read any data reachable by the Koha database user.

This validates the Criteria parameter against a fixed allow-list of the
report's own column options (matching reports/cat_issues_top.tt) so it
is safe to use as a SQL identifier, and binds every Filter value with a
placeholder in both the column-header query and the main calculation
query. The Limit parameter is validated as a plain integer.

This mirrors the fix already applied to the sibling
reports/bor_issues_top.pl (bug 42369).

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Go to Reports > Most-circulated items (reports/cat_issues_top.pl)
3. Run the report with various Criteria and Filter values, confirm the
   output is correct and no errors appear
4. Before the patch, a Filter value or the Criteria containing SQL (a
   single quote followed by a subquery) broke out of the query. After
   the patch, confirm such input is treated as a literal value: you get
   normal output or an empty result, with no SQL error, no delay, and no
   data leak

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:29 +00:00
Jonathan Druart e8137a2c94
Bug 42322: Add Cypress tests
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:46 +00:00
Owen Leonard fc8f56b88f
Bug 42322: (follow-up) Correct markup of ILL sidebar menu
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:45 +00:00
Owen Leonard 71d8564789
Bug 42322: (follow-up) Switch to mobile-first handling of sidebar toggle
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:44 +00:00
Jonathan Druart c407f73309
Bug 42322: Hide collapsed sidebar on small screens
When the sidebar is collapsed and the screen is resized to <= 1000px,
'#sidebar-container' should be hidden

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:43 +00:00
Jonathan Druart 7fb0345f7f
Bug 42322: Correctly toggle aria-expanded
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:42 +00:00
Jonathan Druart e8c7901788
Bug 42322: Add missing raw filter for aside_block
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:41 +00:00
Jonathan Druart 4d87ae3424
Bug 42322: Hide .patroninfo, .browse-controls and #search-facets when collapsed
Fix sidebar when collapsed on:
 * /members/moremember.pl?borrowernumber=42
 * /catalogue/search.pl?advsearch=1&idx=kw&q=d&idx=kw&q=&idx=kw&q=&weight_search_submitted=1&weight_search=1&sort_by=relevance
 * /catalogue/detail.pl?biblionumber=438&searchid=scs_1776086577193
   (with BrowseResultSelection ON)

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:40 +00:00
Owen Leonard 34644298e4
Bug 42322: (follow-up) A version with just the button
Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:39 +00:00
Owen Leonard f9dc282e26
Bug 42322: (follow-up) Proposed alternative style
This version keeps a visual indicator of the collapsed sidebar.

I've also added handling of the "aria-expanded" attribute.

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:38 +00:00
Jonathan Druart 4784d952e7
Bug 42322: Vue apps
Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:37 +00:00
Owen Leonard b804854865
Bug 42322: Auto tidy
To ease rebase!

Re-run.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:36 +00:00
Jonathan Druart be9a2c7c5f
Bug 42322: Use main-container.inc from reports
Pass the aside block from the report module

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:35 +00:00
Jonathan Druart add690d0f4
Bug 42322: Pass the aside to main-container
Improve main-container.inc so we can pass a specific aside block

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:34 +00:00
Jonathan Druart ab7602b0f3
Bug 42322: Add a 'collapse sidebar' button
On bug 42190 we discuss the possibility to collapse the sidebar in order to enlarge the screen and specifically the table in the main div.

This patch adds the button to main-container.inc that is supposed to be
used from everywhere.

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:33 +00:00
Matt Blenkinsop ce3643073a
Bug 39959: Add a total column to the invoices table
Test plan:
1. Create an order and receive it to create an invoice
2. In the invoices search, look for your invoice
3. There will be no total against the invoice
4. Apply patch and restart_all
5. Refresh the page, the total column should now be present

Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:32 +00:00
Matt Blenkinsop 2ce97a58e2
Bug 12278: (follow-up) Use invoice quantity to sum total
Prevents additional orders from subscriptions from being swallowed into the invoice total calculation

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:31 +00:00
Matt Blenkinsop f7bbdd4976
Bug 12278: Add unit tests
Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Sponsored-by: Anne Arundel County Public Library <https://www.aacpl.net/>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:30 +00:00
Matt Blenkinsop b114eb709f
Bug 12278: Add a total column to the table in parcels.pl
Test plan:
1. Create an order with multiple items and receive the order in full
2. From the vendor record, click Receive shipments
3. You should be in parcels.pl and the table should show your shipments
4. There will be no column displaying the total
5. Apply patch and restart_all
6. Refresh the page, the column should now be visible showing the total

Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:29 +00:00
Olivia Reynolds e886c3a7e0
Bug 39337: (follow-up) Default to not suppressing OAI records
In the case when a biblio is deleted, we should not suppress its
orphaned records. Therefore, invert the conditions in the WHERE clause
to not suppress the record if no biblio could be found.

Test plan:
1.  Enable the system preferences "OAI-PMH" and "OpacSuppression".
    Ensure "OpacSuppressionByIPRange" is blank.
2.  Navigate to
    /cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc
3.  Select a displayed record, and delete its corresponding biblio.
4.  Refresh the OAI page, verify the record has vanished.
5.  Apply patch.
6.  Refresh the OAI page, verify the record now appears and is marked as
    deleted. It may have moved to the top of the list.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:28 +00:00
Olivia Reynolds a03c119ccd
Bug 39337: (follow-up) Explicitly name tables
As we're aliasing the primary tables as `main`, we need to use that name
to refer to their columns.

Test plan:
1.  Apply patch.
2.  Prove t/db_dependent/OAI/Server.t and verify no tests fail.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:27 +00:00
Olivia Reynolds 091f0d7a89
Bug 39337: Filter out suppressed OAI records in SQL
Rather than fetching each biblio before choosing to suppress it, this
patch performs this filtering in the initial SQL query that fetches the
biblionumbers.

Note: for queries including items, this additional filtering is
performed in each of the three subqueries, necessitating three
quasi-joins against biblio. If we were to hoist this filtering to the
outer layer, we would also need to remove the LIMIT from each subquery
so as to allow each of them to be fairly weighted. As the preceding
comment implies keeping the subqueries separate has been beneficial for
performance, I've elected not to hoist the WHERE.

Test plan (specific URLs given for KTD, with thanks to David Cook):

1.  Apply patch.
2.  Navigate to http://localhost:8081/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=2
3.  Set 942$n to "Yes" and save the record.
4.  Disable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.

5a. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=GetRecord&metadataPrefix=oai_dc&identifier=KOHA-OAI-TEST:2
5b. Verify KOHA-OAI-TEST-2 appears on
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc

6.  Enable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.
7.  Repeat steps 5a-5b, verifying that:
    - the OAI GetRecord page returns "There is no biblio record with
      this identifier".
    - the OAI ListRecords page does not show KOHA-OAI-TEST-2.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:26 +00:00
Olivia Reynolds 4dd0bfc107
Bug 39337: Add is_opac_suppressed helper to C4::Context
Previously, OPAC suppression was applied inconsistently, with only some
callers taking OpacSuppressionByIPRange into account. This patch adds a
new helper to C4::Context to determine whether OPAC suppression should
be active for this request, and switches to using it everywhere that was
previously manually checking the system preferences.

Test plan (specific URLs given for KTD, with thanks to David Cook):

1.  Apply patch.
2.  Navigate to http://localhost:8081/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=2
3.  Set 942$n to "Yes" and save the record.
4.  Disable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.

5a. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=2
5b. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-MARCdetail.pl?biblionumber=2
5c. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-ISBDdetail.pl?biblionumber=2
5d. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-search.pl?idx=&q=The+ideas+behind&weight_search=1
5e. Verify you can successfully access
    http://localhost:8080/api/v1/public/biblios/2
5f. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=GetRecord&metadataPrefix=oai_dc&identifier=KOHA-OAI-TEST:2
5g. Verify KOHA-OAI-TEST-2 appears on
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc

6.  Enable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.
7.  Repeat steps 5a-5g, verifying that:
    - the OPAC details pages return "Record blocked".
    - the OPAC search page returns "No results found".
    - the API returns "Bibliographic record not found".
    - the OAI GetRecord and ListRecords show "Record Status: deleted"
      for KOHA-OAI-TEST-2.
8.  Keep "OpacSuppression" enabled, but set "OpacSuppressionByIPRange"
    to "." (to allow all IPs to bypass suppression).
9.  Repeat steps 5a-5g, verifying that every page is successful again.
10. Run `prove t/Context.t`, and verify all tests are successful.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:25 +00:00
Jonathan Druart 8316c01bac
Bug 41706: Add a test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Amended:
-    $marc->append_fields( MARC::Field->new( '100', ' ', ' ', a => 'Aristotle.' ) );
-    marc2cites($marc);
+    $marc_record->append_fields( MARC::Field->new( '100', ' ', ' ', a => 'Aristotle.' ) );
+    marc2cites($marc_record);

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:24 +00:00
Roman Dolny b60b7c3a3f
Bug 41706: "Use of uninitialized value..." warning in C4/Record.pm
The warning:
[WARN] Use of uninitialized value $2 in split
    at /kohadevbox/koha/C4/Record.pm line 924.
appears in the plack-opac-error.log file.
It happens during the preparation of data for the "Cite" feature
in OPAC for some of author's fields.

To test:
========
1. Observe plack-opac-error.log.
2. In OPAC go to record with one word author like Aristophanes,
   Aristotle or Xenophon (biblionumbers: 64, 176, 254).
3. Show it in any of "detail" view (Normal, MARC, ISBD).
4. Warning appears in plack-opac-error.log.
5. Apply the patch; restart_all.
6. Repeat 2-3. Warning doesn't appear.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:20 +00:00
Paul Derscheid 138d2b64ed
Bug 42207: Allow library embed on GET /patrons/{patron_id}
- Add 'library' to the allowed x-koha-embed values for the
  GET /patrons/{patron_id} endpoint
- Add unit test verifying the library embed returns library_id
  and name

Test plan:
1. Apply patch
2. yarn api:bundle
3. prove t/db_dependent/api/v1/patrons.t
4. Verify all tests pass, including the new 'library embed'
   subtest under get() which verifies that requesting
   x-koha-embed: library returns the patron's library object

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.de/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:25 +00:00
Olivia Reynolds ad30c815ce
Bug 34166: Add total column to suggestions table
This patch displays the "total_price" field from the API in the
acquisition suggestions table. In line with other totals, it is
displayed unitless after currency conversion.

Test plan:
1.  Navigate to Acquisitions > Suggestions.
2.  Click "New purchase suggestion", then add a title, price, and
    number of copies.
3.  Make note of the calculated total price.
3.  Save the suggestion to return to the table.
4.  Verify there is no "Total" column on the suggestions table.
5.  Apply patch, update database, and refresh.
6.  Verify there is a "Total" column on the suggestions table that
    matches the previously displayed total price.
7.  Verify the "Total" column is listed under the "Columns" dropdown.
8.  Above the suggestions table, click "Configure".
9.  In the column settings for suggestions, check both "Is hidden by
    default" and "Cannot be toggled" for "total".
10. Underneath the column settings for suggestions, uncheck "Save
    configuration state on page change".
11. Save the column settings.
12. Return to the suggestions table.
13. Verify the "Total" column is hidden.
14. Verify the "Total" column is not listed under the "Columns"
    dropdown.

Sponsored-by: Open Fifth
Signed-off-by: Michaela <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:24 +00:00