Compare commits

...

375 Commits

Author SHA1 Message Date
Tomás Cohen Arazi 44cc4b367e
Bug 42719: [26.05.x] Handle missing CGISESSID in OAuth login (IdP-initiated flow)
When a user initiates login from the IdP (e.g. Okta app tile), they
hit the OAuth endpoint without an existing session cookie. The code
crashed calling ->value on an undefined cookie object.

This patch:
- Removes the premature 'No user session found' guard that blocked
  IdP-initiated flows entirely
- Creates a session on-the-fly for the initial request when no
  cookie exists (IdP-initiated flow)
- On callback, redirects with wrong_csrf_token error instead of
  crashing when the cookie is missing

CSRF protection is preserved: the state token is bound to the
session created in the initial request, and the browser returns
the cookie on callback for validation.

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
(cherry picked from commit cd02922675f42f1c404acaa274d14fd155778941)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-25 09:42:15 -06:00
Tomás Cohen Arazi 417ac6fb03
Bug 42719: [26.05.x] Add regression tests
Add tests for the OAuth/OIDC login endpoint covering:
- IdP-initiated flow (no CGISESSID cookie on initial request)
- Callback without CGISESSID cookie (session lost)
- Normal SP-initiated flow (cookie present)

Tests create a real identity provider in the database and assert:
- Initial request without cookie redirects to the IdP authorize URL
- A CGISESSID cookie is set for the new session
- Callback without cookie returns wrong_csrf_token error (not 500)
- Normal flow with cookie redirects to IdP as expected

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
(cherry picked from commit 4d7869ccb1155cda2be2f0b09933528de2dd9a85)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-25 09:42:14 -06:00
Jonathan Druart 5edd58772f
Bug 43267: [26.05.x] Reports - only send visible records to batch operations from DT view
When opening report results in the new DataTables view, batch
operations should only send visible records. Same as the normal view.

Test plan:
1. Create and run a report that will display more resulsts than fit on
   one page
2. Click "Open in DataTables".
3. Note "Batch operations with X visible records" shows the default
   page size (=20), and open the "Batch operations" dropdown to
   send results to e.g. batch item modification or batch record
   modification.
4. Confirm only the values on the currently visible page are sent, not
   the entire set.
5. Change the DataTable's page, or filter/search within it, and repeat
=> Confirm the count and the sent values update to the number of visible records
6. Confirm the standard (non-DataTables) report view is unaffected.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-25 09:42:02 -06:00
Matt Blenkinsop f19d25d60d
Bug 43320: (QA follow-up) Add html filter and fix translatability
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-24 16:01:36 -06:00
Nick Clemens 69904f28ae
Bug 43320: OPAC adjustments
Similar test plan, but on the OPAC

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-24 16:01:35 -06:00
Nick Clemens 8bf971217c
Bug 43320: Don't quoted already quoted searches and add feedback
Bug 31213 added a quoted search when no results were found,
this patch builds on that to:
1 - Notify the user when their search is repeated quoted
2 - Not repeat a search when already quoted

To test:
 0 - Set system preferences
     RedirectToSoleResult to 'Don't redirect'
     IntranetAddMastheadLibraryPulldown to Show
     IntranetCatalogSearchPulldown to Show
 1 - Add the sample records to your catalog
 2 - Search for the quoted phrase "happy family big" in the 'title' index
 3 - Note you get a result
 4 - Check the record - the phrase is not found, but the record contains all the terms
 5 - Search for the unquoted phrase: 6:20 man
 6 - Confirm search is quoted and result found
 7 - Apply patch, restart all
 8 - Repeat 5
 9 - Confirm you are now told search was quoted and result found
10 - Repeat 2
11 - Confirm no result found, no notice of repeated search

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-24 16:01:35 -06:00
Andrii Nugged 199e65af66
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: Lucas Gass <lucas@bywatersolutions.com>
2026-09-24 16:01:34 -06:00
Andrii Nugged 8a68a3cda7
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: Lucas Gass <lucas@bywatersolutions.com>
2026-09-24 16:01:26 -06:00
Olivia Reynolds 4a7d08942c
Bug 40668: Don't depend on checkboxes existing in drawCallback
The checkboxes this callback listens to events on are conditionally
rendered, depending on whether the user has permission to edit each
item. When moving from a page with no checkboxes displayed to one with
checkboxes displayed, the checkbox elements may not exist at the time of
this callback, so the listener is not attached and the checkboxes are
inoperable.

This patch fixes this by using a delegated event handler on the row,
which doesn't depend on the checkbox existing at the time.

Test plan:
1.  Enable the system preference EnableItemGroups.
2.  Set up a non-super-librarian patron, with permission to:
    - view the catalogue (catalogue)
    - create update and delete item groups (manage_item_groups)
3.  Place this patron's library in a group by itself, with "limit item
    editing" enabled. The patron should only be able to edit items from
    their own library.
4.  Pick an item in the catalogue, and add at least 40 items to it from
    a library the patron cannot edit items from. Add at least one item
    from the patron's library that they can edit. The 40 items are to
    ensure there is a complete (20 item) page that the patron cannot
    edit anything on.
5.  Create an (empty) item group on this catalogue entry.
6.  Log in as the unprivileged patron and navigate to the holdings view
    for the relevant catalogue entry.
7.  Navigate through the holdings table pages until there is a page with
    no checkboxes.
8.  Navigate to a table page with a checkbox. Select one of the
    checkboxes, and verify no actions appear at the top of table, even
    though you should have permission to add this item to a group.
9.  Apply this patch, and hard refresh to load the new JavaScript.
10. Repeat steps 6-8, verifying the checkbox now appears correctly.
11. Verify you can add the item to an item group using the now-appearing
    action.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:46 -06:00
Jennifer Sutton 6b7bd06697
Bug 30759: Add hint about the data that is sent via the Google Books API to OPACSuggestionAutoFill
Add information about the data that is sent to Google if the
OPACSuggestionAutoFill system preference is enabled.

Test plan:
1. Go to Administration > System preferences.
2. Search for the OPACSuggestionAutoFill system preference.
3. Note that there is no note about the data that is sent to
   Google if the system prference is enabled.
4. Apply the patch.
5. Refresh the page.
6. There is now a note about the data that is sent to Google.
7. Check that it reads well and makes ense.

Sponsored-by: Catalyst IT
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>
2026-09-14 14:21:46 -06:00
Owen Leonard 38e48ae236
Bug 43177: Use archived link for Bridge Material Type Icon Set
This patch updates the About page in the section for the Bridge Material
Type Icon Set. The link is updated to use the last valid saved version
on the Internet Archive.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:45 -06:00
Pedro Amorim cf5fbc98ec
Bug 43091: (RM follow-up) Give the OPAC self-renewal island a Pinia store
hydrate() only installs Pinia for an island when its config.stores is
non-empty. patron-self-renewal kept config: {} after islands-opac.ts
was split out, so it never got one.

http-client.js's post() calls submitting(), which needs Pinia's
useMainStore(). With none active, it throws before fetch() runs -
get() skips submitting() by default, so only the actual renewal
submission failed.

Test plan:
prove t/cypress/integration/Islands/SelfRenewal.ts

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:44 -06:00
Paul Derscheid daa731ca20
Bug 43091: Clean the OPAC Vue dist directory in js:clean
js:clean predates the OPAC islands build (bug 26355) and only removes
the intranet and Cypress dist directories, so stale chunks survive in
koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/ across rebuilds.

- add the OPAC Vue dist directory to js:clean

Test plan:
1. yarn js:build, then apply this patch series and yarn js:clean
2. ls koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/
   => empty, no stale staff-only chunks remain
3. yarn js:build
   => only islands.esm.js and a patron-self-renewal chunk are emitted

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:44 -06:00
Paul Derscheid 485a7038e9
Bug 43091: Stop emitting staff-only Vue islands into the OPAC dist
Both islands builds share modules/islands.ts, which hardcodes every
island. The OPAC compilation therefore emits staff-only lazy chunks
(acquisitions-menu, vendor-menu, admin-menu) into
koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/, and the OPAC islands.esm.js
bundles the staff Pinia stores.

- split modules/islands.ts into shared machinery and per-application
  entry points (islands-intranet.ts, islands-opac.ts)
- declare each application's islands as a Map literal and feed it
  through registerIsland(), the same path plugin islands take
- pass store definitions into hydrate() instead of importing every
  store in the shared module
- keep the full plugin import surface (registerIsland, hydrate, Vue
  re-exports) on both entries via star re-exports
- point the rspack islands entries at the per-application modules

Test plan:
1. yarn build
2. ls koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/
   => only islands.esm.js and a patron-self-renewal chunk are emitted
3. ls koha-tmpl/intranet-tmpl/prog/js/vue/dist/
   => acquisitions-menu, vendor-menu and admin-menu chunks are still
   present
4. In the staff interface, visit pages using the acquisitions, vendor
   and admin menu islands and verify they render
5. Enable self-renewal for a patron's category and put the patron
   inside the availability window, e.g.:
   UPDATE categories SET self_renewal_enabled = 1
     WHERE categorycode =
     (SELECT categorycode FROM borrowers WHERE userid = 'koha');
   UPDATE borrowers
     SET dateexpiry = DATE_ADD(CURDATE(), INTERVAL 7 DAY),
     debarred = NULL WHERE userid = 'koha';
6. Log into the OPAC as that patron and open 'your summary'
   => the patron-self-renewal chunk loads from
   /opac-tmpl/bootstrap/js/vue/dist/ and the self-renewal modal
   completes successfully

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:43 -06:00
Martin Renvoize 72bb057ecf
Bug 43088: Reuse _current_directory() instead of duplicating pwd() calls
_change_directory(), _list_files() and _is_connected() each called
$self->{connection}->pwd directly for their log payloads/connectivity
check, duplicating exactly what _current_directory() now provides as a
proper, documented internal accessor. Route them through it instead -
no behaviour change, just removing the duplication now that the
wrapper exists.

Test plan:
1. prove -r t/db_dependent/Koha/File/
   => All tests successful (32 tests), including the existing
      current_directory() and list_files()/change_directory() coverage.
2. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:43 -06:00
Martin Renvoize 04c67b7276
Bug 43088: Add a consistent current_directory() accessor to Koha::File::Transport
FTP, Local and SFTP list_files() were unified to return the same flat
shape, but consumers still had no portable way to ask a transport for
its current remote directory - the underlying libraries' own methods
of that name don't agree on what "cwd" means. Net::SFTP::Foreign's
cwd() is a pure read-only accessor, but Net::FTP's cwd() is a mutator:
called with no argument it defaults to "/", issues a real CWD command,
and returns a boolean rather than a path.

The koha-plugin-transport-browser plugin discovered this the hard way:
it read $transport->{connection}->cwd directly to capture the initial
working directory, which happened to work for SFTP but silently reset
the FTP connection to root and captured "1" (a boolean) as the "path"
on every request, breaking directory navigation for FTP transports.

Add a public current_directory() method to the Koha::File::Transport
base class, following the same connect-on-demand pattern as
list_files()/upload_file()/etc, with a _current_directory() hook each
subclass implements using its own real read-only accessor: pwd() for
FTP, cwd() for SFTP, and the tracked {current_directory} (falling back
to download_directory) for Local. Consumers should use this instead of
reaching into transport internals.

Test plan:
1. prove t/db_dependent/Koha/File/Transport.t
        t/db_dependent/Koha/File/Transport/FTP.t
        t/db_dependent/Koha/File/Transport/Local.t
        t/db_dependent/Koha/File/Transport/SFTP.t
   => All tests successful, including the new current_directory()
      tests subtest in each transport test file, which mock the
      underlying libraries (Net::FTP / Net::SFTP::Foreign) to assert
      each backend reads its own real read-only accessor.
2. koha-qa.pl -v 2 --more-tests
   => [PASS] for all four changed modules, no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:42 -06:00
Martin Renvoize f901ad6d67
Bug 43078: Exclude '.' and '..' entries from SFTP file listings
The FTP and Local transports both explicitly filter out '.' and '..'
pseudo-entries when listing a directory, but the SFTP transport never
did - it relied on the assumption that Net::SFTP::Foreign's ls() already
excludes them. It doesn't: ls() returns exactly whatever entries the
remote server includes in its SSH_FXP_READDIR response, with no default
filtering. Whether '.'/'..' show up therefore depends entirely on the
remote SFTP server implementation, producing inconsistent results between
otherwise identically configured SFTP accounts.

Test plan:
1. prove t/db_dependent/Koha/File/Transport/SFTP.t
   => fails before this patch (listing includes '.' and '..')
   => passes after this patch
2. prove -r t/db_dependent/Koha/File/Transport/ t/db_dependent/Koha/File/Transports.t
   => all still pass

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:42 -06:00
Martin Renvoize 4f39b08db6
Bug 43078: Include longname and perms keys in FTP's NLST fallback listing
The MLSD path, SFTP, and Local always return all six documented
list_files() keys (filename, longname, size, perms, mtime, type) - undef
where the value is unknown, but the key always present. The NLST
fallback path (servers without MLSD support) only set filename, size,
mtime and type, omitting longname and perms entirely rather than
setting them to undef.

Functionally harmless for consumers reading $file->{perms} (Perl
returns undef for a missing hash key too), but it breaks the "same flat
shape" contract this bug established, and would trip up any consumer
using exists() instead of defined() to check for a key.

Test plan:
1. prove t/db_dependent/Koha/File/Transport/FTP.t
   => All tests successful, including the new assertion that longname
      and perms keys are present (as undef) on the NLST fallback path.
2. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:41 -06:00
Martin Renvoize 2f1eb2db84
Bug 43078: Unify file transport list_files() across FTP, Local and SFTP
Net::SFTP::Foreign's ls() returned file metadata with size, permissions
and modification time nested inside a Net::SFTP::Foreign::Attributes
object, while FTP and Local returned flat hash keys and silently
dropped directories. Callers of list_files() needed backend-specific
parsing to get at file metadata or to know a directory even existed,
defeating the purpose of the shared transport API.

All three transports now return the same flat hashref shape per entry:
filename, longname, size, perms, mtime, type ('file'/'directory'/
'other'). Directories are included everywhere; '.' and '..' remain
excluded everywhere. FTP's MLSD path prefers the UNIX.mode fact for
exact Unix permissions, falling back to an approximation from the RFC
3659 perm capability fact when UNIX.mode isn't available, and parses
the modify fact into mtime; its NLST fallback (servers without MLSD)
probes each entry with SIZE/MDTM to recover size, mtime and a
best-effort type.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:41 -06:00
Owen Leonard c9b1c20c6f
Bug 43192: Cast escape_str(obj) arg to string to use escapeHtml func
The escape_str function in the OPAC requires the argument to be a string,
and will throw a fatal error if passed a Number. This is because
String.prototype.escapeHtml is created by Koha and is not a JS standard.
This patch converts the object to a string before applying escapeHtml.

To test, apply the patch and log in to the OPAC.

- If necessary, add a list.
- Search for records in the catalog and add one or more of the results
  to your new list.
- Go to Lists -> Your lists.
- In the list of your lists, click the "Delete" link for the list you
  added records to.
  - You should see a confirmation modal, "Are you sure you want to
   delete this list?" showing the name of the list and the number of
   items on it.
  - Clicking "Yes, delete" should delete the list.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:40 -06:00
David Nind 1c4f539c73
Bug 43173: Update the terminology for the log viewer - "cardnumber" to "card number"
Update the log viewer to change "Modify cardnumber" to
"Modify card number", as per the terminology guidelines
https://wiki.koha-community.org/wiki/Terminology#C

Test plan:
1. Edit a patron and change their card number under the library
   management section.
2. Go to Tools > Additional tools > Log viewer.
3. Note under the actions section that there is an action:
   Modify cardnumber
4. Click "Submit" to see the log viewer results.
5. Note that there is an entry for the patron card number change
   with the action: "Modify cardnumber".
6. Apply the patch.
7. Refresh the log viewer page.
8. Note that "Modify cardnumber" is now "Modify card number"
   in both places.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Gretchen <maxeinergl@gcc.edu>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:40 -06:00
Jonathan Druart cadb36fcaf
Bug 43168: Add a test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:39 -06:00
Nick Clemens c277de2e70
Bug 43168: Check if there are errors, not if variable defined
$errors is an array ref returned from a blank array variable

if( $errors ){ #this will be true

if( scalar @$errors ){ #only true when array has members

To test:
1 - tail -f /var/log/koha/kohadev/intranet-error.log
2 - Stage a marc file
3 - Note the error:
==> /var/log/koha/kohadev/intranet-error.log <==
[2026/07/23 19:11:37] [WARN] The following error(s) occurred during ISO2709 record import:
ERROR:  Koha::ImportBatch::try {...}  /kohadevbox/koha/Koha/ImportBatch.pm (105)
4 - Apply patch
5 - Restart all
6 - Stage a file
7 - No error!

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:39 -06:00
David Cook 8dda84df37
Bug 43159: Restore localisation of PERL5LIB and KOHA_HOME
This change localises PERL5LIB and KOHA_HOME,
which means that any changes made to PERL5LIB or KOHA_HOME
within _do_instance() in koha-plack are confined to the specific
instance being worked on in that function.

See Bugzilla for steps on how to reproduce the problem.

Test plan:
- Apply the patch
- sudo vi debian/scripts/koha-plack

Within _do_instance after PERL5LIB is set, add the following:
echo $PERL5LIB

- sudo koha-create --request-db test1
- sudo ./debian/scripts/koha-plack --restart test1 test1 test1 test1
- Note that now PERL5LIB stays constant:

/usr/share/koha/lib:/usr/share/koha/installer:/usr/share/koha/lib/installer

- git restore debian/scripts/koha-plack

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>
2026-09-14 14:21:38 -06:00
Laura_Escamilla 2baaa00a31
Bug 43134: Add missing colons to OPAC advanced search section headings
To test :
1. Apply the patch
2. Go to the OPAC advanced search page http://kohadev.mydnsname.org:8080/cgi-bin/koha/opac-search.pl
3. Click More options
4. Notice that all section titles now use colons
   - "Search for:" = colon
   - "Limit to any of the following:" = colon
   - "Location and availability:" = colon
   - "Sort by:" = colon
   - "Publication range" = colon
   - "Language" = colon
   - "Audience" = colon
   - "Content" = colon
   - "Format" = colon
   - "Additional content types..." = colon
5. Sign off and have an incredible day! :)

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:38 -06:00
Kyle M Hall b9156c1feb
Bug 43103: Switch OR chains to INs for batch item modification and patron deletion
Three searches pass large ID lists:
* Koha::BackgroundJob::BatchUpdateItem passes every itemnumber of the
  batch, and batch item modifications can cover tens of thousands of
  items
* Koha::Items::batch_update does the same with the modified
  itemnumbers when collecting biblionumbers for reindexing
* Koha::Patrons::filter_by_safe_to_delete does it with the
  borrowernumbers found safe to delete ( unlikely to approach
  the same numbers as items/bibs, but still good to fix )

This patch switches all three to IN. The lists are still passed as
values, but the query stays a flat IN list the optimizer handles well
instead of a chain of ORs that can't be optimized.

Test Plan:
1) Apply this patch
2) prove t/db_dependent/Koha/Items/BatchUpdate.t
3) prove t/db_dependent/Koha/Patrons.t
4) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:37 -06:00
Martin Renvoize b07514bcfb
Bug 43082: Fix flaky object filter test in action_logs.t
The default `_match=contains` matching builds a LIKE query even on
integer columns, so a plain `?object=42` also matches object values
like 420 or 142. The test's dynamically-assigned patron borrowernumber
(auto_increment, never reset by transaction rollback) could drift into
a value containing "42" as a substring, intermittently inflating the
match count in CI. Add `_match=exact` since the test's intent is an
exact object id match.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:37 -06:00
Jonathan Druart 06eafc0d60
Bug 43053: Simplify code, only 1 return statement
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:36 -06:00
Matt Blenkinsop db56e2f581
Bug 43053: Fix display of vendor payment methods
Test plan:
1) Add multiple vendor payment method authorised values
2) Create a vendor and add multiple payment methods
3) Click to edit, the payment method will be displaying one option of a "|" concatenated string
4) Apply patch and run yarn js:build
5) Hard refresh the browser, the payment methods should now display correctly
6) Edit the payment methods and save, the saved changes should be correctly displayed on the vendor details page
7) Add a new vendor with multiple payment methods, it should save correctly

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>
2026-09-14 14:21:36 -06:00
Jonathan Druart a077c111a8
Bug 43053: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:35 -06:00
Matt Blenkinsop 65210cf55a
Bug 42827: Restore values to the Items expected column 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. The Items expected column will show 0 even though you ordered multiple items
5. Apply patch and restart_all
6. Refresh the page, the column should now show a value

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:35 -06:00
Owen Leonard 07691a589a
Bug 41116: (follow-up) Fix template variable filter
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>
2026-09-14 14:21:34 -06:00
Owen Leonard 5ba8ab39ab
Bug 41116: (follow-up) Some more fixes for consistency
- There was a mix of "patronphone" and "patron-phone" and I fixed it to
  standardize on "patron-phone" (and patron-email) which I find easier
  to read.
- I made similar changes to the branchtransfers template
- Corrected the TT filter on mailto links from "html" to "uri"

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>
2026-09-14 14:21:34 -06:00
Andrew Fuerste Henry bf451c7fe9
Bug 41116: Standardize selectors in patron contact information in hold found modals
This patch modifies both hold-found-modal and hold-found1. It adds a
class to the patron phone number. For patron email, it changes the
existing id to a class, expands the selector to include the bullet
point in hold-found-modal, and standardizes the name (changing from
"#boreamil' to ".patronemail").

To test:
1 - have a patron with both a phone number and an email address
2 - create a hold for your patron
3 - check an item in to fill your hold, triggering the hold-found-modal
4 - inspect the patron contact info, confirm there is no class on the
    phone number
5 - confirm there is an id on the patron email but it does not include
    the bullet point and is inconsistent with the names of other classes
    in the modal
6 - click Confirm Hold
7 - check your item in again, triggering the hold-found1 modal
8 - confirm there is no class on the patron phone number
9 - confirm there is an id on the patron email with an inconsistent
    name
10 - revert the waiting status of your hold
11 - apply patch, restart_all
12 - check your item in again, triggering hold-found-modal
13 - confirm the patron phone now has a class, .patronphone
14 - confirm the patron email now has a class, .patronemail, that the
     class includes the bullet point, and that the #boremail id has been
     removed
15 - click Confirm Hold
16 - check your item in again, triggering hold-found1 modal
17 - confirm the patron phone now has a class, .patronphone
18 - confirm the patron email now has a class, .patronemail, and that
     the #boremail id has been removed

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
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>
2026-09-14 14:21:33 -06:00
Jan Kissig d95de3d3c3
Bug 40201: Add addidtional fields to subscription search on serials home
This patch adds additional fields (table subscription, searchable) to the subscription search form on serials home.

To test (ktd):
a) create some additional fields in Admin -> Additional fields -> Serials subscriptions. Make sure the 'Searchable' checkbox is checked.
b) go to serials home: More -> Serials
c) Check that the form is missing the additional fields just defined.
d) apply patch and call restart_all
e) reload serials home and check that the search form is now extended with the additional fields

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:33 -06:00
Chris Nighswonger 17d3a9ca1c
Bug 41717: Bump PDF::Reuse and PDF::Reuse::Barcode versions
PDF::Reuse 0.43 fixes a TTFont/docProxy lifecycle bug that caused
prEnd() to crash when TrueType fonts were used.

PDF::Reuse::Barcode 0.09 is the current stable release.

Testing plan:

- Start ktd, note older perl modules versions under About Koha
- Apply patch
- ktd --shell
- sudo cpan -i PDF:Reuse; sudo cpan -i PDF:Reuse:Barcode
- Let cpan auto set itself up (not sure if sudo is needed above)
- `restart_all`
- Revisit About Koha; module versions should be 0.43 & 0.09 respectively

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:32 -06:00
Andrii Nugged ca098dd521
Bug 42845: Add ILL config endpoint for Vue app
The ILL Vue app currently reads ILLModule and ILLPartnerCode through the system preferences service. That service requires parameters/manage_sysprefs, so a staff user with ILL permissions but without system preference administration permissions cannot load the ILL module.

Add an ILL-specific /ill/config REST endpoint protected by the ill permission, and switch the ILL Vue app to load its configuration from that endpoint. This follows the module-specific configuration endpoint pattern introduced by bug 33606 for the same manage_sysprefs problem in the ERM Vue app.

Security/compatibility considerations: the endpoint returns only allow-listed, non-secret ILL configuration values needed by the ILL Vue app. It does not expose the generic system preferences API, and /api/v1/sysprefs remains restricted to parameters/manage_sysprefs.

Regression test coverage: the new API test verifies that a staff user with the ill permission can read /ill/config, that a staff user without the ill permission cannot read it, and that the same ILL-only user still cannot access /api/v1/sysprefs.

Test plan:

1. prove -I. -It/lib t/db_dependent/api/v1/ill_config.t

2. prove -I. -It/lib t/db_dependent/api/v1/ill_users.t

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

Amended-by: Jonathan Druart
chmod +x t/db_dependent/api/v1/ill_config.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 14:21:26 -06:00
David Cook 2a8adb8b63
Bug 43401: (QA follow-up) tweak rspack.config.js comment and fix Makefile.PL
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:35 -06:00
Paul Derscheid e95ef486c2
Bug 43401: (follow-up) Note the Makefile.PL coupling in rspack.config.js
- Add a file-level comment reminding that every shipped output.path needs a
  matching 'NONE' target-map entry and cp step in Makefile.PL

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:34 -06:00
Lucas Gass 7e9beb0bb0
Bug 43401: Send compiled js vue for the OPAC to the right dir
The OPAC vue bundle (koha-tmpl/opac-tmpl/bootstrap/js/vue/dist) was not
handled like its intranet counterpart in Makefile.PL: exclude it from the
generic target map and copy it explicitly into blib in move_compiled_js.

Test plan:
1. In a KTD shell, from the koha checkout:
     perl Makefile.PL
   (accept the defaults, 'standard' install mode is fine)
2. make -n move_compiled_js | grep 'js/vue/dist'
   => without this patch only the intranet cp line is printed;
      with it there is also a line copying
      koha-tmpl/opac-tmpl/bootstrap/js/vue/dist into
      blib/OPAC_TMPL_DIR/bootstrap/js/vue/
3. make
4. ls blib/INTRANET_TMPL_DIR/prog/js/vue/dist blib/OPAC_TMPL_DIR/bootstrap/js/vue/dist
   => both directories exist and contain the compiled bundles
      (e.g. islands.esm.js); no vue/dist directory is left anywhere
      else under blib.
5. git clean -xdf blib pm_to_blib Makefile Makefile.old && git checkout -- . to
   clean up.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:34 -06:00
Aleisha Amohia a32e722060
Bug 35729: Normalize ISBNs as ISBN-13 if cannot fallback to ISBN-10
ISBNs beginning with 979 do not have an ISBN-10 version, and need to be normalized to an ISBN-13 format. All others, if valid, can safely be normalized to ISBN-10 format (current behaviour).

To set up

1. Download the attached sample records with 979 ISBNs
2. Log into the staff interface and go to Cataloguing
3. Go to Stage MARC records for import and upload the sample MARC file
4. Stage and import the biblio records
5. Once imported, open the detail pages for some of the records
6. In another tab, do a catalogue search and open the detail pages for ISBN-10 records, for example biblionumbers 6 and 54

To test

1. Apply the patch and restart services
2. Log into the staff interface and go to Koha Administration, Global system preferences, Enhanced Content tab
3. Cycle through each cover image service, enabled by the system preferences
4. With each cover image service is enabled, refresh the record detail pages you have open. The ISBN-10 records should CONTINUE to show their cover images. The newly imported ISBN-13 records should NOW show their cover images.
5. Confirm tests pass: t/db_dependent/Koha.t

testing with ISBN 9798885795692 should return this image from Amazon: https://images-na.ssl-images-amazon.com/images/P/9798885795692.01.LZZZZZZZ.jpg
testing with ISBN 9798876536914 should return this image from Amazon: https://images-na.ssl-images-amazon.com/images/P/9798876536914.01.LZZZZZZZ.jpg

Sponsored-by: Horowhenua Libraries Trust
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:33 -06:00
Pedro Amorim 4ac8795e95
Bug 42973: (RM follow-up) Update tests number
Because of bug 42866
prove t/db_dependent/Koha/AdditionalContents.t

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:33 -06:00
Laura_Escamilla eeb73bd789
Bug 42973: Unit test
Added a unit test to confirm that search_for_display orders manually positioned news first, followed by unnumbered news by publication date descending.

Test passes:

prove t/db_dependent/Koha/AdditionalContents.t

Signed-off-by: Gretchen Maxeiner <MaxeinerGL@gcc.edu>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:32 -06:00
Laura_Escamilla 2c29581606
Bug 42973: Restore manual ordering for news
To test:
1. Create four OPAC news items:

   - "Pinned news item" with "Appear in position" = 1 and an older publication date.

   - "Newest unnumbered news item" with no position and the newest publication date.

   - "Middle unnumbered news item" with no position and an intermediate publication date.

   - "Oldest unnumbered news item" with no position and the oldest publication date.

2. View the OPAC main page.

3. Confirm the news items are ordered by publication date, with the newest item first.

Apply the patch.

4. Reload the OPAC main page.

5. Confirm the manually positioned news item appears first.

6. Confirm the remaining news items are ordered by publication date descending.

7. Confirm news items without an "Appear in position" value continue to display newest first.

8. Sign off and have a wonderful day!

Signed-off-by: Gretchen Maxeiner <MaxeinerGL@gcc.edu>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:31 -06:00
Jan Kissig a859898aba
Bug 41681: (follow-up) Fix record number count when broken records are processed
When running the script bulkmarcimport.pl, the number of MARC records reported at the end of the execution does not match the actual number of records processed from the input file.

This patch fixes 3 issues
- broken (invalid) records from batch get now counted (record_number)
- when using the number param (n) to limit the number of records to be processed valid and invalid records get counted
- when using th offset param (o) the scipt errored when broken records were part of the offset.

To test:

1. Download the MARC file with the broken record
2. Run the following import commands and compare the counted import numbers:
  a) all records
     perl misc/migration_tools/bulkmarcimport.pl -b -v -m MARCXML --file 2_records_1st_broken.xml
     1 MARC records done in 0.0421221256256104 seconds
  b) limit to 1 record in total
     perl misc/migration_tools/bulkmarcimport.pl -b -v -n 1 -m MARCXML --file 2_records_1st_broken.xml
     1 MARC records done in 0.0420489311218262 seconds
  c) limit to 1 record  offset 1 record
     perl misc/migration_tools/bulkmarcimport.pl -b -v -n 1 -o 1 -m MARCXML --file 2_records_1st_broken.xml
     :2: parser error : Opening and ending tag mismatch: leader2 line 2 and leader
  <leader2>00216nam a22001097a 4500</leader>

3. Apply the patch
4. Repeat steps in 2 and compare the results
  a) 2 MARC records done in 0.0405838489532471 seconds
  b) 1 MARC records done in 0.00948596000671387 seconds  *
  c) 1 MARC records done in 0.0294840335845947 seconds

*though this one was skipped

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:31 -06:00
Hammat Wele 237d7166d0
Bug 41681: bulkmarcimport.pl reports an incorrect number of MARC records processed
When running the script bulkmarcimport.pl, the number of MARC records reported at the end of the execution does not match the actual number of records processed from the input file.

To test:

1. Download the joined MARC file containing 5 records.
2. Run the import command:
   ./misc/migration_tools/bulkmarcimport.pl -b --file=marc_file.mrc
3. Check the final script report
   ==> 11 MARC records done in 0.566349983215332 seconds
   Expected output should show 5 records
4. Apply the patch
5. Repeat step 2, 3
   ==> The correct number is shown

Signed-off-by: Janusz Kaczmarek <januszop@gmail.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:30 -06:00
Jonathan Druart 05840d64af
Bug 42632: Fix "Manage bundle" after filters are shown/hidden
If "Show/Hide filters" link is clicked, the "Manage bundle" buttons do not longer work.

Test plan:
Edit a record, set leader 7=collection
In the holding list click "Manage bundle"
Click "Show filters"
Click one of the "Manage bundle" button
=> Without this patch nothing happens
=> With this patch applied the table displays the nested table with the
bundle details

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Anni <anni.maki-mantila@turku.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:30 -06:00
Jonathan Druart 32a6c63872
Bug 42396: Adjust 'use' statements
Always include the plural version, not the singular one (the plural
imports the singular)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:29 -06:00
Olivia Reynolds 2acaf4360a
Bug 42396: Remove C4::Stats::UpdateStats and delete module
This patch replaces all calls to C4::Stats::UpdateStats with the wrapped
function from the Koha namespace. As this is the last function in C4::Stats, the
module is also deleted.

Several tests depended on transitive includes to Koha::Statistics through
C4::Stats. I've identified uses of that module by searching, and added the
necessary use in each case. I've run `prove -r t xt` with this patch applied and
didn't hit any relevant errors.

Test plan:
1. Inspect the patch, and verify each changed call maintains the same parameters.
2. Apply patch.
3. Prove the following files:
   - t/db_dependent/Accounts.t
   - t/db_dependent/Circulation.t
   - t/db_dependent/Koha/Account.t
   - t/db_dependent/Koha/ILL/Request.t
   - t/db_dependent/Koha/Item.t
   - t/db_dependent/Koha/Pseudonymization.t
   - t/db_dependent/Koha/Recalls.t

Sponsored-by: Open Fifth

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:29 -06:00
Lari Taskula 7412a3e38d
Bug 42298: Automatic renewals should only respect the "No automatic renewals before" rule
To test:
1. prove t/db_dependent/Circulation.t
2. Observe success

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>
2026-09-14 09:08:28 -06:00
Lari Taskula 5fa5012ebe
Bug 42298: Add a failing test
To test:
1. prove t/db_dependent/Circulation.t
2. Observe the following failures

t/db_dependent/Circulation.t .. 17/87
    #   Failed test 'Bug 42298: No autorenew before rule is respected'
    #   at t/db_dependent/Circulation.t line 1134.
    #          got: '0'
    #     expected: '1'

    #   Failed test 'Bug 42298: No autorenew before rule is respected'
    #   at t/db_dependent/Circulation.t line 1135.
    #          got: 'too_soon'
    #     expected: 'auto_renew'

    #   Failed test 'soonest_renew_date is not returned because this issue can be renewed'
    #   at t/db_dependent/Circulation.t line 1136.
    #          got: '2026-04-21T23:59:00'
    #     expected: undef

    #   Failed test 'No renewal before is 99, patron opted out of auto_renewal so can renew'
    #   at t/db_dependent/Circulation.t line 1141.
    #          got: '0'
    #     expected: '1'
    # Looks like you failed 4 tests of 124.

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>
2026-09-14 09:08:28 -06:00
Emily Lamancusa d72439dc89
Bug 38361: (QA follow-up) Set required variable for input validation
Built-in validation checks weren't functioning because the variable
required.sex was not getting set. Ensure that variable gets set, and
remove manual validation code that is no longer needed.

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:27 -06:00
Bo Gustavsson 6d200ca5ef
Bug 38361: Gender field does not include a Required if the field is set to required in PatronSelfRegistrationBorrowerMandatoryField
Solves this bug and enhances the user experiance by replacing the old radio buttons with a less-confusing dropdown menu.

Test plan:

1. Open the setting "PatronSelfRegistrationBorrowerMandatoryField", find Gender(sex) and mark it as not required.
2. Go to the self registration form. No 'Required' label under gender.
3. Go back to the setting  "PatronSelfRegistrationBorrowerMandatoryField", find Gender(sex) and mark it as required.
4. Go to the self registration form. The 'Required' label is there.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:27 -06:00
Emily Lamancusa d4fa6c54c9
Bug 20573: Make NotifyBorrowerDeparture's date math consistent with MembershipExpiryDaysNotice
To test:
1. Set both system preferences NotifyBorrowerDeparture and
   MembershipExpiryDaysNotice to 10 days
2. Create or edit a patron account with the following settings:
    - expiry date is exactly 10 days in the future
    - add an email address on the patron account
    - set messaging preferences to receive email notices for Patron
      account expiry
3. Run the membership expiry cron job to generate a notice:
   ktd --shell
   perl misc/cronjobs/membership_expiry.pl -c
4. Check notices tab on patron account
--> A membership expiry notice has been queued for the patron
5. Click back to the patron details
--> Note that there is no alert that the patron account will be expiring
    soon
6. Apply patch and restart_all
7. Refresh the patron details page
--> You should now see an alert that the account will be expiring soon

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:26 -06:00
Pedro Amorim 1ed715b009
Bug 43139: Sort by ID DESC instead of ID ASC
Test plan:
1) Enable ILLModule
2) Ensure at least 2 ILL requests exist
3) Go to <staff_url>/cgi-bin/koha/ill/home
4) Confirm the most recent ILL request (highest ID) shows first

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:26 -06:00
Matt Blenkinsop 54c67a3913
Bug 43119: Restore item and hold links to the log viewer
INSERT INTO action_logs (timestamp, user, module, action, object, info, interface) VALUES
    (NOW(), 5, 'CIRCULATION', 'ISSUE', 5,
     '{"issue":1,"branchcode":"CPL","itemnumber":1,"confirmations":["TOO_MANY"],"forced":["DEBT"]}', 'intranet'),
    (NOW(), 5, 'CIRCULATION', 'RETURN', 5,
     '1', 'intranet'),
    (NOW(), 5, 'HOLDS', 'CREATE', 7,
     '{"hold":7,"biblionumber":1,"itemnumber":1,"confirmations":["HIGHHOLDS"],"forced":["HOLD_POLICY_OVERRIDE"]}', 'intranet');

Test plan:
1) Without the patch applied, run the populate SQL above against the kohadev database.
2) Go to Tools > Log viewer
3) Under Modules, select only Circulation - leave the date range empty and click Submit.
4) Observe the ISSUE row's Info column shows the raw JSON payload as plain text, and the RETURN row shows the bare itemnumber "1" as plain text - neither is a clickable link to the item.
5) Go back, select only the Holds module, click Submit, and observe the CREATE row's Info column likewise shows raw JSON with no hold/item links.
6) Apply the patch.
7) Run yarn build && restart_all, then hard refresh the browser.
8) Repeat step 3.
9) Confirm the ISSUE row's Info column now shows a link reading "Item <barcode> CPL" (e.g. Item 3999900000001 CPL) - the branchcode comes from the JSON payload - and clicking it opens the item page. Override information is also displayed correctly
10) Confirm the RETURN row's Info column now shows a link reading "Item <barcode>" (no branchcode) opening the same item's detail page - verifying the bare-itemnumber payload path.
11) Repeat step 5. Confirm the CREATE row now shows "Hold 7 on biblio 1" linking to reserve/request.pl?biblionumber=1, followed by " - Item 1" linking to the item detail page. Override information is also displayed correctly

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:25 -06:00
Jonathan Druart 9eb20fc82d
Bug 43110: (bug 28308 follow-up) Disable checkboxes on the detail view
Checkboxes must be disabled on the detail page as we cannot submit the
changes.

Test plan:
1 - Edit a patron, set a value for advance notices, do not check any transports
2 - View the details for the patron:
    http://localhost:8081/cgi-bin/koha/members/moremember.pl?borrowernumber=5
3 - Note the checkboxes are disabled

QA: The logic is a bit weird as messaging_form_inactive is not set if the
pref is off, but the table is not displayed in this case.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-09-14 09:08:25 -06:00
Lucas Gass 93eee931a3
Bug 43005: Remove messaging preferences check
1. Go to Patron record -> Details
2. Under "Library use" click 'Edit'
3. From here change the patron category, no pop-up
4. APPLY PATCH and try again
5. Now you should see the pop-up modal

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>
2026-09-14 09:08:17 -06:00
Lucas Gass dd056df457
Update release notes for 26.05.03.000 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-26 10:34:47 +10:00
Lucas Gass 0e8e076d2d
Increment version for 26.05.03.000 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-26 10:34:47 +10:00
Sanjar Tulkinov Anvar o'g'li 31679305e7
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>
2026-08-26 10:34:47 +10:00
Amit Gupta 21ef112bd7
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>
2026-08-26 10:34:47 +10:00
Martin Renvoize 9a63c3983a
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.
2026-08-26 10:34:47 +10:00
Jonathan Druart dd3af862e3
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>
2026-08-26 10:34:47 +10:00
Owen Leonard d3fc229bc5
Bug 43206: cn_browser plugin red background on exact match is hard to read
This patch updates the cn_browser plugin output so that table rows are
highlighted using row classes and in colors which have sufficient
contrast.

To test, apply the patch and set your 952$o tag to use the cn_browser
plugin:

- Administration -> MARC bibliographic frameworks -> BKS -> MARC
  Structure -> Tag 952 -> Edit subfields.
  - Under the "Full call number" tab (o), set the "Plugin" dropdown to
   "cn_browser.pl" and save your changes.
- Go to Cataloging and locate a bibliographic record using the
  cataloging search.
- Add or edit items. NOTE: In the testing data there is only one item
  with a callnumber and it is "FIC/kas"
  - In the "o - Full call number" field, type "FIC" and then click the
    plugin editor image.
    - You should see the record with the "FIC/kas" call number and a
      green row right above it (indicating where an item with
      the call number "FIC" would be in the results).
  - Revise your search using the form at the top, entering "FIC/kas".
    - The record row should now be highlighted in pale yellow.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Gretchen <maxeinergl@gcc.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:27 -06:00
Hammat Wele 97857da4be
Bug 41988: (follow-up) Fix opac-news-rss page title
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:26 -06:00
Hammat Wele 85593df7c2
Bug 41988: OPAC News RSS links with HTML not working
The news RSS link on the OPAC appears to be invalid when the news
content contains HTML. Users cannot subscribe to the feed using
standard RSS readers or email clients.

To reproduce:

1- Go to Tools -> HTML customizations -> News
2- Click New Entry with the following settings:
   2-1- Display location: OPAC
   2-2- Publication date: today
   2-3- Title: "News test"
   2-4- Content: click on <> and paste some HTML code, for example:
        <span style="color: #ba372a; font-size: 12pt;">Le Pr&ecirc;t
        entre biblioth&egrave;ques est mis en pause.
        D&eacute;sol&eacute;s de
        l'inconv&eacute;nients.<br />Merci de votre
        compr&eacute;hension.</span>
3- Save the entry.
4- Go to the OPAC and copy the "RSS feed for system-wide library news"
   link.
5- Execute: curl -v
   http://opac/cgi-bin/koha/opac-news-rss.pl?branchcode= and copy the
   XML output to https://validator.w3.org/feed/ to check the feed.
     ==> The feed is not valid.
6- Try to create a subscription to this link in an email client (e.g.,
   Thunderbird).
     ==> It does not work, giving the error "Feed URL is not valid".
7- Apply the patch.
8- Repeat steps 5 and 6.
     ==> The feed is now a valid RSS.
9- Repeat step 7.
     ==> You can now subscribe to the RSS feed using standard readers
         and email clients.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:26 -06:00
Emily Lamancusa 5e84dcd7a7
Bug 40492: Improvements to offline circ actions table
Update the pending offline circulation actions page to link each item
barcode directly to the item details, rather than the biblio record, and
open the links in a new tab.

Add a column to show the last seen date of the items being processed,
with a warning if the item's last seen date is more recent than the
offline circulation action date.

To test:
1. Upload a Koha Offline Circulation (koc) file
   (the file attached to this bug can be used if you are using a
   sandbox or KTD with the standard sample data)
   i. Go to Circulation > Upload offline circulation file
   ii. Upload the file
   iii. Click "Add to offline circulation queue"
2. Go to Circulation > Pending offline circulation actions
3. Click on the item barcodes in the table
--> Note that each barcode links to the biblio record, and navigates
    away from the page within the same tab.
4. Apply patch and restart_all
5. Return to (or refresh) the pending offline circulation actions page
   and click on the item barcodes again
--> The barcodes now open the item details page in a new tab
--> There is a new column for the item last seen date
6. Without processing the offline circulation actions, check in one of
   the items listed in the table
7. Return to the Pending offline circulation actions page
--> Confirm that the last seen date of the item you just checked in is
    highlighted in red with a warning icon, since its last seen date is
    newer than the offline transaction date
8. Hover over the warning icon
--> Confirm that a tooltip appears with an explanation of why the date
    is in red

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:25 -06:00
Kyle M Hall ed504389b9
Bug 43101: search_for_data_inconsistencies.pl inlines every biblionumber in the catalog as an OR chain
Queries that use OR instead of IN are less efficient and can lead to
situations where the query takes a very long time to complete due to
row level scanning. We found one instance where they query had been
running for over 10 days!

This patch makes ids() return a subquery instead of a list of results,
and switches the callers to using IN. The database now resolves the set
itself and the queries stay a constant size no matter how large the
catalog is. This is super efficient because the subquery doesn't even
execute as a separate query since biblionumber is the primary key!

Test Plan:
1) Apply the unit tests patch
2) prove t/db_dependent/Koha/Database/DataInconsistency.t
3) Note the ids() subtest fails
4) Apply this patch
5) prove t/db_dependent/Koha/Database/DataInconsistency.t
6) Note all tests pass!
7) Run misc/maintenance/search_for_data_inconsistencies.pl
8) Note the output is the same as before the patch
9) Bonus: Re-run it with DBIC_TRACE=1 and note the queries now use
   IN (SELECT ...) instead of one OR clause per bib!

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>
2026-08-13 08:53:25 -06:00
Kyle M Hall 892d1eb2e9
Bug 43101: Add unit tests
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>
2026-08-13 08:53:24 -06:00
Martin Renvoize c049b9d7fe
Bug 43071: Isolate holds_per_day-sensitive tests from ambient circulation rules
t/db_dependent/Holds.t, t/db_dependent/ILSDI_Services.t, and
t/db_dependent/api/v1/holds.t all fail if a site has a global
'Holds Allowed (Daily)' circulation rule configured, because
C4::Reserves::CanItemBeReserved checks holds_per_day before
holds_per_record/reservesallowed, so an ambient daily-cap rule
returns tooManyReservesToday and masks the status these tests
actually exercise (tooManyReserves, noReservesAllowed, OK, or the
API's hold_limit override). The affected subtests never set
holds_per_day themselves, so they unintentionally depend on the
database having no such rule configured.

Test plan:
1. On a fresh db, set a global 'Holds Allowed (Daily)' circulation
   rule (Administration > Circulation and fine rules) to 1.
2. prove t/db_dependent/Holds.t t/db_dependent/ILSDI_Services.t
   t/db_dependent/api/v1/holds.t
   => fails before this patch, passes after.
3. Remove the circulation rule and re-run the same tests.
   => still passes (no regression for the default/unconfigured case).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:24 -06:00
Jonathan Druart cc1d8dd36f
Bug 42981: Prevent toLowerCase to fail when comparing int and string
If stateSave is turned on and one of the option has a value with _id set
to an Integer, toLowerCase is not available and a JS error is raised:
 Uncaught (in promise) TypeError: this._id.toLowerCase is not a function

Test plan:
1.) Admin - table settings: cgi-bin/koha/admin/columns_settings.pl#eholdings|packages
Activate checkbox "Save search state on page change"

2.) Go to ERM : cgi-bin/koha/erm/eholdings/local/packages
And create two packages:
 - One package with My Vendor assigned.
 - One package without a vendor.

3.) Filter the package results list by My Vendor. --> List is filtered to one package

4.) Leave the page, for example by navigating to Agreements.
Return to the Packages list and you will see the issue  (See also attached Screenshot)

=> Without the patch
The package results list becomes unresponsive / hangs after returning to the page.

=> With the patch
The package results list should load and display the filtered results correctly.

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:23 -06:00
Lucas Gass 7a392f111e
Bug 42930: Replace title with borrowertitle
To test:
1. Have some overdues
2. Go to circ/overdue.pl
3. Notice the the 'Patron' column the name is prefixed with the biblio title
4. This is meant to be the borrowertitle
5. APPLY PATCH, restart_all
6. Now you should see only the borrower/parton name and info.
7. Edit the patron and add a " Salutation"
8. In the overdue report this should now display

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:22 -06:00
Samuel Sowanick 3e9d8d032c
Bug 42928: Update log viewer action terminology - Return to Check-in
The log viewer results table uses "Return" for "Check-in" actions.

See the terminology list:
https://wiki.koha-community.org/wiki/Terminology#C

Test plan:
1. Check out an item to a patron, then check it back in again.
2. Go to the log view (Tools > Additional tools > Log viewer).
3. Click "Submit".
4. Note that the action column for the results shows "Return".
5. Apply the patch.
6. Refresh the log viewer page.
6. Note that the action column now correctly shows "Check-in".

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:22 -06:00
Saiful Amin 7e28073bd9
Bug 42861: System warning about missing encryption_key on a fresh instance
This patch updates the koha-create command to generate the random encryption key
using pwgen with the number of characters set to 32.

Test plan:
1. In ktd, create a new instance:
    - sudo koha-create --create-db test1
    - Check the /etc/koha/sites/test1/koha-conf.xml file and verify that the encryption_key
    is having the placeholder text '__ENCRYPTION_KEY__'

2. Apply the patch and copy the new koha-create command
    - git bz apply 42861
    - sudo cp /kohadevbox/koha/debian/scripts/koha-create /usr/sbin/koha-create

3. In ktd, create a new instance:
    - sudo koha-create --create-db test2
    - Check the /etc/koha/sites/test2/koha-conf.xml file and verify that the encryption_key
    is generated and has 32 characters random string.

4. Sign-off

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:21 -06:00
Emmi Takkinen 17a524010e
Bug 42699: Display Koha error message when attempting to save non-unique barcode in Edit serials
When you edit an already received serial from Serial collection page
using Edit serials and edit items buttons, and try to save a barcode
that already exists on another item, saving leads to Error 500.
There is no barcode validation for editing serials, this patch adds it.

To test:
1. Go to serials module.
2. Search for a serial that is not expired to edit and click on the title.
3. Go to Serial collection from the menu.
4. Use the checkboxes to mark an already received (eg. usually older,
arrived, not expected) serial and click Edit serials.
5. Click on Edit item.
6. Replace the barcode with a unique barcode that is already in use
and hit save.
=> Error 500 is raised.
7. Apply this patch and restart services.
8. Attempt to save serial with already used barcode again.
=> A standard Koha error message is displayed stating that barcode
must be unique.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:21 -06:00
Aleisha Amohia 9fa02f8212
Bug 33364: Prevent Holds to Pull from including items with pending recalls
This patch checks for waiting or in_transit items allocated to recalls, and excludes them from the Holds to Pull report.

To test:

1. Enable UseRecalls and set the recalls-related circulation rules
2. Check out an item to Patron A
3. Place an item-level hold on the item for Patron B
4. Log into the OPAC as Patron C and search for the item. Place an item-level recall on the item.
5. Go back to the staff interface and check in the item. This should prompt a pop-up to confirm the recall as waiting. Confirm the recall as waiting.
6. Go to Circulation -> Holds to pull.
7. Notice that even though the item is allocated for a recall, the hold is showing. It should not be showing in this page.

8. Apply the patch, restart services and refresh the page

9. The item allocated to a recall should no longer be showing in the Holds to pull report

10. Go to the record detail page and go to the Recalls tab
11. Revert the recall from its waiting status
12. Go back to the Holds to pull page. The record will now show for the hold.
13. Go to the Circulation -> Check in page
14. Go to the menu in the top-right of the page and click Set library
15. Change your library to a different one
16. Check in the item to confirm the recall as waiting and trigger a transfer
17. Go back to the Holds to pull page
19. The item should no longer show on the Holds to pull report

20. Confirm tests pass t/db_dependent/Koha/Holds.t

Sponsored-by: Catalyst IT
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:20 -06:00
Jonathan Druart a3c645a87f
Bug 42522: Add link to biblio from eholding show view
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:20 -06:00
Andrew Fuerste Henry 70be9214e7
Bug 36762: [QA Follow-up]: Tidy branchtransfers.pl
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:19 -06:00
Emmi Takkinen 0a9d8bf111
Bug 36762: Allow to ignore hold when creating a transfer
When one tries to transfer an item that could fill a pending hold
to a library that is not the holds pickup library, Koha gives only
a option to transfer item to pickup library. This patch adds
new option to ignore hold and transfer item to the library user
selected in transfer tool.

To test:
1. Log into Library A.
2. Find or create a biblio with 1 holdable item at Library A.
3. Place a hold for the biblio with pickup at Library B.
4. Navigate to Circulation > Transfers.
5. Attempt to transfer item to Library C.
=> Note that Koha only allows to transfer item to Library B,
cancel hold and attempt transfer or return to the transfer tool.
6. Apply this patch.
7. Attempt to transfer item again.
=> Note that there is now an option to ignore the hold and transfer
item to Library C.
=> When option to transfer item to the Library C is selected, confirm
that Koha tells that transfer was made to Library C and a new transfer
was made to database.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:19 -06:00
Emmi Takkinen b7c0e28bb6
Bug 36762: Fix setting transfer for hold via branchtransfers.pl
When one tries to transfer an item that could fill a pending hold
to a library that is not the holds pickup library, Koha gives only
a option to transfer item to pickup library. When this option
is chosen, Koha tells that item is being transfered to selected
library not to pickup library but fails to create the transfer.
This patch fixes this by setting op variable as "cud-transfer"
and tobranchcd as holds pickup library so that transfer is actually
made and target library is displaued correctly.

To test:
1. Log into Library A.
2. Find or create a biblio with 1 holdable item at Library A.
3. Place a hold for pickup at Library B.
4. Navigate to Circulation > Transfers.
5. Attempt to send your item to Library C.
6. Koha gives you three options. Pick the first one:
"Transfer to Library B."
7. Koha tells you it has transferred the item to Library C.
8. Go to detail.pl for your title, confirm the holdings table
says "there is an item level hold" on your item.
9. Click to the Holds tab for your title, confirm the hold claims
to be in transit.
10. Query the branchtransfers table
SELECT * FROM branchtransfers WHERE itemnumber = [your item]
=> Note that no new transfer was made.
11. Query reserves SELECT * FROM reserves WHERE biblionumber = [your biblio]
=> Confirm your hold has found=T, itemnumber=[your item], and item_level_hold=0
12. Revert trasfer from biblios holds page.
13. Apply this patch.
14. Attempt the transfer again.
=> Koha should now tell that transfer was made to Libary B.
15. Check branchtransfers table again.
=> New transfer should have been made to Library B.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-13 08:53:18 -06:00
Kyle M Hall 7162eed0f7
Bug 43034: Fix t/db_dependent/Koha/EDI.t failure when SearchEngine is Elasticsearch
Bug 21820 made the Zebra queue update conditional on the new
ElasticsearchEnableZebraQueue system preference, which defaults to off.
The 'basic_quote_processing' subtest in t/db_dependent/Koha/EDI.t counts
the log lines process_quote emits, and two of those are the
"specialUpdate biblioserver" traces that ModZebra logs only when the
Zebra queue path runs. When the SearchEngine preference is set to
Elasticsearch, that path no longer runs by default, so the subtest now
records 6 log lines instead of 8 and fails:

    #   Failed test '8 log lines recorded for passed quote file'
    #          got: '6'
    #     expected: '8'
    #   Failed test 'Trace recorded biblioserver index'

Test Plan:
1) Set the SearchEngine system preference to 'Elasticsearch'
2) prove t/db_dependent/Koha/EDI.t
3) Note the basic_quote_processing subtest fails with "got: '6', expected: '8'"
4) Apply this patch
5) prove t/db_dependent/Koha/EDI.t
6) Note the tests 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>
2026-08-13 08:53:10 -06:00
Matt Blenkinsop 810ff07321
Bug 43121: Convert all groups to use explicit labels
Test plan:
1) Install another language
2) Navigate to a vendor record
3) The group names should translate correctly and display in the split screen view

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:48 -06:00
Matt Blenkinsop c56a2b7e76
Bug 43121: Use an explicit label for group and tab names rather than a translated string
Signed-off-by: Michaela <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:48 -06:00
Martin Renvoize fad48b3123
Bug 42992: FTP file transport: fix list_files() corrupting filenames
Koha::File::Transport::FTP::_list_files parsed the LIST / "ls -l" output
with a non-greedy date capture that only consumed the first of the three
date tokens (the month), leaving the day and time glued to the front of
the filename. Every filename returned for an FTP transport was therefore
prefixed with the listing date, e.g.

    -rw-r--r-- 1 user group 1234 Jan 01 12:00 QUOTES_413514.CEQ
    => filename "01 12:00 QUOTES_413514.CEQ"

Callers (Koha::Edifact::Transport, marc_ordering_process.pl) then asked
the server for a file that does not exist, so no files were downloaded,
renamed or deleted over FTP. SFTP and Local transports were unaffected.

This regressed when the EDI transport was moved onto the file_transports
system (bug 38489). The previous Koha::Edifact::Transport::ftp_download
listed files with Net::FTP->ls (NLST, bare names); the refactor switched
to Net::FTP->dir (LIST) so it could return the richer hashref shape used
by the SFTP and Local transports, but the parse was faulty.

The date field in "ls -l" output is a fixed run of three whitespace
separated tokens (Mon DD HH:MM or Mon DD YYYY). Consume it explicitly so
the filename is captured cleanly, preserving spaces and still skipping
directories. The LIST-based approach and the filename/longname/size/perms
return shape are kept so the structure stays consistent across transport
classes (relied on by consumers, including out-of-tree ones).

Test plan:
1) prove t/db_dependent/Koha/File/Transport/FTP.t
2) git checkout Koha/File/Transport/FTP.pm and re-run: the list_files()
   subtest fails on the filename/size/perms assertions
3) prove t/db_dependent/Koha/Edifact/Transport.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:47 -06:00
Martin Renvoize 27a4dd8daa
Bug 42915: (QA follow-up) Defer coded_values declaration until document ready
holds.js declared `coded_values` as a bare top-level global, evaluated
immediately at script-parse time, reading `all_libraries` which is only
ever defined by the including template (request.tt, or
patron-detail-tabs.inc for circulation.tt/moremember.tt). This works
today only because those templates happen to emit their `all_libraries`
script block before Asset.js("js/holds.js") in the rendered HTML - a
fragile, easy-to-break ordering dependency between separate template
files, not something holds.js itself can guarantee.

Move the assignment into the existing $(document).ready() handler,
setting it explicitly on `window` so it remains visible as a global to
datatables.js's build_query(), which reads `coded_values` unqualified.
This defers evaluation until all synchronous inline scripts (including
whichever template defined all_libraries) have already run, regardless
of future include/ordering changes.

To test:
1. Place holds on a title for two different pickup libraries
2. Confirm the holds table on reserve/request.tt, the patron's Holds
   tab (circulation.pl and moremember.pl) all load with no console
   errors
3. Confirm filtering the holds table by pickup library name/branchcode
   still works on all three pages

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:47 -06:00
Martin Renvoize da5a337632
Bug 42915: (QA follow-up) Remove unused filters_options variable
filters_options was introduced with the coded_values library filtering
patch but never referenced anywhere in the file.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:46 -06:00
Martin Renvoize 30b28ea350
Bug 42915: (QA follow-up) Restore fallback message for holds with no patron data
The "Add patron names to data in column def in holds table" patch
dropped the null-patron handling in the patron column's render
function, so a hold row whose patron embed comes back null (e.g. a
user without permission to view the patron on that hold, per Bug
23269's test plan) now renders a blank cell instead of a useful
message.

Restore the fallback, sourced from `all_libraries` (populated by this
same table's include) rather than the separate `libraries` global,
and check `row.patron` directly instead of the now-composite `data`
column key.

To test:
1. Have holds on a bib from patrons the logged in user cannot see
   (e.g. IndependentBranches, or a reduced permission set)
2. View the record's Holds tab
3. Confirm rows with no visible patron show "A patron from library
   <library>" instead of a blank Patron cell

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:46 -06:00
Martin Renvoize b610f86595
Bug 42915: (QA follow-up) Declare all_libraries once, unconditionally, in request.tt
holds_table.inc is INCLUDEd in a loop from reserve/request.tt whenever
HoldsSplitQueue splits the holds queue by branch/itemtype, or when
holds for more than one biblio are shown at once (multi_hold). Each
inclusion appended its own <script> block declaring `all_libraries`,
and since top-level `let`/`const` in separate inline <script> tags
share the page's global lexical scope, the second and subsequent
occurrences threw "Identifier 'all_libraries' has already been
declared", breaking those script blocks and the pickup-library
filtering that depends on `all_libraries` being set.

Worse, that script block is only emitted when a biblio's holds are
actually rendered (biblioloo.total_holds > 0), but holds.js is loaded
unconditionally by reserve/request.tt and reads `all_libraries` at
top-level parse time. Placing the very first hold on a title (0
existing holds) therefore threw:

    ReferenceError: all_libraries is not defined

breaking every hold-management handler defined below that line in
holds.js.

Fix both by declaring `all_libraries` exactly once, unconditionally,
in request.tt's existing page-global script block (alongside the
other unconditional globals such as HidePatronName and
HoldsSplitQueue), before Asset.js("js/holds.js") loads, instead of
duplicating it inside the per-title/per-split holds_table.inc
include.

To test:
1. Search for a title with no existing holds and start placing one
2. Without this patch: browser console shows "ReferenceError:
   all_libraries is not defined" as soon as the page loads
3. With this patch: no console errors
4. Set HoldsSplitQueue to "pickup library", place holds on a title
   for two different pickup libraries, confirm both split tables
   render with working patron-name and pickup-library filtering and
   no console errors
5. Confirm the patron's own Holds tab (moremember.pl/circulation.pl)
   still loads without console errors

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:45 -06:00
Brendan Lawlor 01362891d2
Bug 42915: (follow-up) Fix holds table display on patron record holds tab
To test:
1. Have a patron with multiple holds
2. Go to the patron record's hold tab
3. Confirm the table loads and no errors are logged to the console

Sponsored-by: CLAMS
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:45 -06:00
Brendan Lawlor f3f6ba601a
Bug 42915: Add patron names to data in column def in holds table
This patch adds the patron firstname and surname to the searchable
data in the patron column definition for the holds table and
removes unnecessary code from the render function.
This enables searching holds by patron names.

To test:
1. Have holds on a bib for different patrons and pickup locations
2. Try filtering by a patron name
3. See 'No matching records found'
4. Apply patch and repeat step 2
5. Filtering by patron name works!
6. Confirm filtering by cardnumber still works too
7. Set system preference HidePatronName to 'Don't show'
8. Confirm searching by patron names doesn't work ;)

Sponsored-by: CLAMS
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:44 -06:00
Brendan Lawlor 43701c3ab1
Bug 42915: Use coded value for library filtering in holds table
This patch adds coded_values for libraries to the holds table
and sets the data type in the pickup library column definition.
This enables searching holds by pickup location.

1. Have holds on a bib for different patrons and pickup locations
2. Try filtering by a pickup library name or branchcode
3. See 'No matching records found'
4. Apply patch and repeat step 2
5. Filtering works!
6. Note: if two libraries share the same beginning text
    both results are returned :)
    For example: 'Fair' returns holds for pickup at Fairview and Fairfield

Sponsored-by: CLAMS
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:44 -06:00
Paul Derscheid 93fa7e817b
Bug 43174: (QA follow-up) Restore user choices on redraw independent of search state
To test:
- cypress run --spec t/cypress/integration/KohaTable/PatronSearch_spec.ts
- cypress run --spec t/cypress/integration/KohaTable/KohaTable_spec.ts

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:43 -06:00
Jonathan Druart bf55b2bc3b
Bug 43174: Restore defer loading for patron searches
On the hold request and the patron search pages, the search is not
deferred when a state exists.

Coming from
  commit 2a34413601
  Bug 41604: Save user decision per column when visibility changes
-        if (default_save_state_search) {
+        if (default_save_state || default_save_state_search) {
             $("#" + settings.nTable.id).data("loaded_from_state", true);
-        } else {
+        }

+        if (!default_save_state_search) {
             delete state.search;
             state.columns.forEach(c => delete c.search);

I think this was a leftover from another try to fix bug 41604. I don't
see how it could help in the context of saving the user decision.
In the different patches from bug 41604 we don't test loaded_from_state.

Test plan:
Go to /cgi-bin/koha/members/members-home.pl
=> The table is not displayed
Search for patrons
=> The table is displayed
Reload the page
=> Without this patch the table is displayed
=> With this patch the table is not displayed

If you have "Save search state on page change" enabled for this table,
the table should be displayed if a previous search was made.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:43 -06:00
Jonathan Druart a987d4dfa7
Bug 43174: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:42 -06:00
Lucas Gass 54a2cdca84
Bug 42988: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:42 -06:00
Lucas Gass 4fa57fd897
Bug 42988: DBRev 26.05.02.002
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:41 -06:00
Kyle M Hall e60880e1ff
Bug 42988: Default holds_get_captured to yes for new SIP accounts
The account editor preselects "No" for "Holds get captured" when
creating a new SIP account, but the SIP checkin default is to capture
holds. This patch makes the editor default the option to "Yes" so a
new account behaves the same as one that never set the option.

Test Plan:
1) Apply this patch
2) yarn js:build
3) Restart all the things!
4) Create a new account in the SIP2 administration module
5) Note "Holds get captured" defaults to "Yes"!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:41 -06:00
Kyle M Hall 5f4fc10df5
Bug 42988: Update database, set holds_get_captured default to 1
The migration from SIPconfig.xml to the database ( bug 37893 ) leaves
holds_get_captured NULL for any account that didn't define it in the
config file. At SIP checkin an unset holds_get_captured defaults to
on, but the REST API returns NULL boolean columns as false, so the
SIP account editor shows "No" selected. Saving the account without
touching the option then stores 0 and holds are no longer captured
at SIP checkin.

This patch changes the column default to 1 and sets existing NULL
values to 1 so the stored value matches the actual checkin behavior.

Test Plan:
1) If you have no SIP accounts, create one
2) Simulate an account migrated from a SIPconfig.xml that doesn't define holds_get_captured:
   UPDATE sip_accounts SET holds_get_captured = NULL;
3) Edit the account, note "Holds get captured" shows "No" even though
   holds *are* captured at SIP checkin while the value is unset
4) Apply this patch
5) Run updatedatabase.pl
6) SELECT login_id, holds_get_captured FROM sip_accounts;
7) Note holds_get_captured is now 1!
8) Edit the account again, note "Yes" is now selected for "Holds get captured"!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:40 -06:00
Owen Leonard c6af8b9768
Bug 42118: Update various admin pages to use grid layout for forms - Part 2
This patch updates several more administration templates with new markup
and CSS for layout forms using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the "New" and
"Edit" versions of the form look correct in each case:

- Administration -> Item search fields
- Administration -> OAI sets
  * Updated OAI set mappings form to add maxlength attributes to field
    and subfield inputs.
- Administration -> OAI repositories
- Administration -> SMS cellular providers
- Administration -> SMTP servers

Sponsored-by: Athens County Public Libraries
Signed-off-by: Felicie <felicie.thiery@biblibre.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:40 -06:00
Fridolin Somers 3b9877426c
Bug 43098: Typo edit_formid in guided_reports_start.tt
Typo in edit SQL when authorised values category is wrong : 'op=edit_formid='

Test plan:

1) Create a new authorised values category WRONG
2) Create a new SQL report with : SELECT * FROM biblio WHERE title LIKE <<val|WRONG>>
3) Save the report
4) Delete authorised values category WRONG
5) Run the SQL report
6) You see message 'The authorized value category (WRONG) you selected does not exist.'
7) Click on 'Edit SQL'
=> You comme back to editing the correct SQL report

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>
2026-08-11 08:23:39 -06:00
Slava Shishkin de2ecf096c
Bug 41227: Respect SuspendHoldsIntranet on patron holds page
Test plan:
1. Set SuspendHoldsIntranet to "Don't allow"
2. Place a hold for a patron
3. Open the patron holds table
4. Confirm the Suspend button is visible in the Suspend column
5. Apply the patch
6. Reload the patron holds table
7. Confirm the Suspend button is no longer visible
8. Set SuspendHoldsIntranet to "Allow"
9. Reload the patron holds table
10. Confirm the Suspend button is visible again

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:39 -06:00
Brendan Lawlor 1a6931a943
Bug 41879: (QA follow-up) Add pod for new sub _pickup_valid_for_item
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:38 -06:00
Lucas Gass ab30e5a5bc
Bug 41879: (follow-up) Add a test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:38 -06:00
Lucas Gass 670c4cc35a
Bug 41879: (follow-up) Bail with error code when no items on target bib
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:37 -06:00
Lucas Gass c9e5e6c5cb
Bug 41879: (follow-up) Add helper function to avoid repeating code
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:37 -06:00
Lucas Gass 5c47184853
Bug 41879: update move_hold unit tests
Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:36 -06:00
Lucas Gass c17fa6e0fd
Bug 41879: Disallow moves to invalid pickup locations for next available holds
Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:36 -06:00
Lucas Gass 8aba37ecd5
Bug 41879: Disallow moves to invalid pickup locations for items
To test:

1. Set ReservesControlBranch is set to 'item's home library'
2. Set the deafult 'Holds and bookings policies by item type' for the itype BK to allow holds from patrons from any library, but pickup at CPL only
3. Have a bib owned by another library ( I used MPL ), and bib owned by CPL. Both are the item type BK, and each has one item on the bib.
4. Place a hold for a patron on bib A for pickup at MPL
5. Move the hold to bib B
6. Note that the hold moves successfully, and is still set for pickup at MPL
7. Login and try to trigger the hold, nothing happens
8. APPLY PATCH, restart_all
9. Try spets 3 - 6 again. This time on step 6 you should see a message that the hold cannot be moved.

Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:35 -06:00
Lucas Gass de5917821a
Bug 42166: Check for 008 before adding the date to it
To test:
1. Edit or create a framework that does not require the 008.
2. Make or edit a record in that framework without a 008.
3. Turn on EnableAdvancedCatalogingEditor, and make sure you are using the advanced editor.
4. Go to the record from step 2 and click "Edit as new (duplicate)"
5. The record won't load in the advanced editor.
6. APPLY PATCH
7. Try again, this time the record should load when using "Edit as new (duplicate)".

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:35 -06:00
Jonathan Druart 6aa88ceb28
Bug 41061: Remove unused calendar JS functions
Since the introduction of Maskito by bug 33703 we do no longer need to
valid dates when the flatpickr widget is closed.

This patch removes the following JS function from staff and OPAC:
* is_valid_date
* validate_date
* get_dateformat_str

Test plan:
0. Do not apply this patch and confirm that the date string received by
   those functions has already been correctly formatted
1. Apply the patch and git grep to make sure no occurrence exists in the
   code
2. Confirm that you are still able to select dates with flatpickr

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:34 -06:00
Martin Renvoize 6f6817d691
Bug 42799: (QA follow-up) Close scroll listener leak in apply_sticky
apply_sticky is invoked from a watch() in Toolbar.vue every time the
toolbar element (re)mounts. The scroll listener it registers was never
removed, so long-lived SPA modules (ERM, ILL, Acquisitions,
Preservation, SIP2) that mount/unmount this Toolbar as the user
navigates between records leaked another window-level scroll listener
on every remount.

An IntersectionObserver + sentinel node was tried instead, since it's
the standard technique and doesn't poll on every scroll frame, but
verified against KTD/Cypress it fails
t/cypress/integration/Toolbar_spec.ts consistently: the observer's
callback fires once on attach and never fires again after
cy.scrollTo("bottom"), even though the sentinel is confirmed (via
instrumentation) to have scrolled fully out of the viewport. The
scroll-listener approach passes the same spec reliably across repeated
runs. So the scroll listener stays; apply_sticky now returns a
disconnect() callback and Toolbar.vue calls it before re-applying and
on unmount, closing the leak without touching detection behaviour.

Test plan:
1. Rebuild assets (yarn build) and confirm:
     yarn cypress run --spec t/cypress/integration/Toolbar_spec.ts
   passes consistently across several runs.
2. Manually confirm sticky toolbars in ERM, Acquisitions (vendors),
   ILL, Preservation and SIP2 still float correctly on scroll, and that
   navigating between several records doesn't accumulate extra "scroll"
   listeners on window (DevTools -> getEventListeners(window)).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:34 -06:00
Jonathan Druart 68da66a429
Bug 42799: Replace IntersectionObserver with scroll listener for sticky behaviour
Assisted-by: Mistral Vibe <vibe@mistral.ai>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:33 -06:00
Jonathan Druart f4b03d881e
Bug 42799: Fix sticky behavior by using a sentinel node
Highlighted by the Cypress upgrade (bug 42772): the sticky element is always
visible and IntersectionObserver rely on the visibility of the element.

Cypress test Toolbar_spec.ts use scrollTo("bottom") to test the
behaviour of the sticky toolbar. We test that the "floating" class is
correctly added to the element. To do that we rely on
IntersectionObserver that will check the visibility of the element. But
the sticky element is always visible.
I actually do not recreate the problem when using the UI but the current
code may be broken in some browsers (or new/future versions).

To fix this problem we insert a new "sentinel" element into the DOM and
rely on its visibility to toggle the "floating" class.

Test plan:
Confirm that the sticky toolbar behaviour is the same after this patch
is applied, ie. that the "floating" class is added when we scroll down.

On top of bug 42772 confirm that the following command passes (run it
several times):
  yarn cypress run --spec t/cypress/integration/Toolbar_spec.ts

QA: Note that the apply_sticky function takes only one node in
parameter, that's why it has been renamed nodes => node

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:33 -06:00
Lucas Gass 10ece3b8f4
Bug 41084: DBRev 26.05.02.001
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:32 -06:00
Martin Renvoize 61c18071ab
Bug 41084: (QA follow-up) Add new pref to UsageStats
Add the new preference to UsageStats for HEA

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:32 -06:00
Katrin Fischer 846eefccca
Bug 41084: Re-format database update for new way of addind system preferences
... also adds say_success.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:31 -06:00
Tomás Cohen Arazi 59e3bfc022
Bug 41084: Allow utf8 export format when EnableZotero is enabled
When the EnableZotero system preference is enabled, allow the utf8 format
in opac-export.pl regardless of the OpacExportOptions settings.

This enables Zotero integration by ensuring the required UTF-8 encoded
MARC format is always available when the feature is enabled.

To test:
1. Install Zotero (https://www.zotero.org)
2. Install the browser extension (I did in in Chrome, not sure it exists for FF)
3. Perform a search in your koha instance. e.g. http://kohadev.localhost/cgi-bin/koha/opac-search.pl?idx=&q=agrovoc&weight_search=1
4. Open the Zotero extension
=> SUCCESS: It lists the search results for saving them
5. Choose one and save
=> FAIL: It says there was an error saving
6. Apply this patches
7. Run:
   $ ktd --shell
  k$ updatedatabase
8. Make sure EnableZotero is enabled
9. Repeat 3-5
=> SUCCESS: All good this time
10. Sign off :-D

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:31 -06:00
Tomás Cohen Arazi 646321adb6
Bug 41084: Add EnableZotero system preference
This system preference will be used to enable opac-export.pl?format=utf8
regardless of the OpacExportOptions values, specifically for Zotero
integration support.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:30 -06:00
Pedro Amorim ff6ee4057d
Bug 43069: Add tests
1) Set system preference 'PatronDuplicateMatchingAddFields' to empty (no fields ticked)
2) Go to <staff_url>/cgi-bin/koha/members/memberentry.pl?op=add and fill in the mandatory fields for a brand new patron (one that definitely doesn't exist yet)
3) Click 'Save'
4) Notice you get the 'Duplicate patron record?' dialog, even though no patron like this exists
5) Apply the patch. Restart plack
6) Repeat steps 2-3 with another new patron
7) Confirm the patron is created fine this time, no false duplicate error
8) Also try setting 'PatronDuplicateMatchingAddFields' to a field (e.g. 'city') and submit a new patron with that field left blank - confirm no false duplicate there either
9) Run:
prove t/db_dependent/Koha/Patrons.t

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>
2026-08-11 08:23:30 -06:00
Pedro Amorim 8697929514
Bug 43069: Don't match every patron when no duplicate-match fields apply
check_for_existing_matches() called Koha::Patrons->search(undef) when
PatronDuplicateMatchingAddFields left no usable field to match on,
which matches every patron instead of none.

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>
2026-08-11 08:23:29 -06:00
Nick Clemens 2adc268464
Bug 43043: Pass logged in branchcode to OpacMySummaryNote
This patch uses the logged in branchcode to find the correct entries

To test:
1 - Create two new HTML customizations - OpacMySummaryNote - one for all libraries, one for a specific library
2 - Log in to opac as a user from the specific library
3 - Only see the all libaries note
4 - Apply patch
5 - Reload
6 - See both of the notes as expected!
7 - Sign off!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:29 -06:00
Lucas Gass 2dd6be2b94
Bug 43001: Restore non-priority hold text to reserve/request.pl
To test:

1. Place a item level hold, making sure to mark it as "Non priority hold"
2. After placing the hold, on reserve/request.pl look at the "Details" column
3. Notice the hold is missing "Non priority hold" text in the details column
4. APPLY PATCH
5. Look again at reserve/request.pl and now the "Non priority hold" text should be restored to 'Details column'

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>
2026-08-11 08:23:28 -06:00
Adolfo Rodríguez 963ad03e6b
Bug 42999: Fix $valid_items flag contamination in reserve/request.pl
$valid_items was set inside the per-item availability check but used as
the gate of a separate if block, causing it to carry over across loop
iterations. Once any item set it to 1, all subsequent items entered the
if ($valid_items) branch and received $item->{available} = 1 regardless
of their individual availability.

Fix: merge the two consecutive if blocks into one so the pickup-location
logic only executes when the current item individually passes all
availability conditions. $valid_items continues to serve its correct
post-loop role (line 661: none_available_override).

Regression introduced by Bug 38412 (commit 1900effc9b).

Steps to follow:
1. Create a biblio with at least two items: one with a holdable item
   type and one with a not-for-loan item type (itemtypes.notforloan=1).
   The not-for-loan item must have a higher itemnumber than at least
   one holdable item (i.e. it is processed later in the loop).
2. Open reserve/request.pl?biblionumber=X&borrowernumber=Y
3. Observe that the not-for-loan item shows a radio button and a pickup
   location dropdown despite the Information column showing "Not for
   loan (Itemtype not for loan)" — bug confirmed.
4. Apply this patch and restart services. If running under Plack,
   restarting Plack is required for the change to take effect.
5. Reload the place-hold page.
6. Verify the not-for-loan item now shows "X Not for loan" without a
   radio button or pickup location dropdown.
7. Verify the holdable items still show radio buttons and pickup
   location dropdowns correctly.

Signed-off-by: Adolfo Rodríguez <adolfo.rodriguez@xercode.es>
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>
2026-08-11 08:23:28 -06:00
Jonathan Druart b206917e47
Bug 42933: Reinstate license.status as a required field
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:27 -06:00
Jonathan Druart 25beec17ac
Bug 42933: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:27 -06:00
Jan Kissig 56dffe1640
Bug 42874: Fix missing index param on z3950_auth_search
This patch will enable the forwarding of index param in all z3950_auth_search forms to keep the reference which auth field to fill in addbiblio.

Testplan:
a) edit f.e. 262: /cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=262
b) go to tab 7 and create a new 700 field (icon next to ADDED ENTRY--PERSONAL NAME)
c) click on the icon next to Personal name input field to open Authority search *
d) click create new authority (Adding authority Personal Name) *
e) click Z39.50/SRU search *
f) search an author f.e. Name (any): Dalei
g) on the result list every 'Import' button still has index (via hovering the import button) - but do not click import yet.
h) click on 'Go' to page 1 - see result list again. The 'Import' buttons do not have index param anymore
i) close that search window and go back to page 'Adding authority Personal Name'
j) click Z39.50/SRU search again *
k) search again for an author f.e. Name (any): Dalei
l) click 'Try another search'
m) check that new URL does not have index param anymore
n) search again - the results will not have an index param

Any imported authority will not be set/linked in the biblio that was initially edited.

p) apply patch and redo steps a) - n)
q) Import an author either via a 'Try another search' or via a result on a different page ('Go' button)
r) save authority
s) check that authority is added in your biblio in field 700.

* check that the URL in the popup window has a param like &index=tag_700_subfield_a_13631_9534947903 as this is the reference to the authority you want to add in the biblio.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-11 08:23:11 -06:00
Laura_Escamilla f979fbde3c
Bug 42919: Retain suggestion form values when duplicate warning is shown
To test:

1. Make a suggestion that will trigger a duplicate warning, in KTD I like "Clothes, clothes, clothes, music, music, music, boys, boys, boys".
2. Fill out some other details, like notes and public notes.
3. The warning says "Click on "Confirm your suggestion" to ignore this message."
4. Notice none of the details are still there.
5. Apply the patch
6. Repeat steps 1-3. Notice that the details remain now after you get the warning message.
7. Submit the form to ensure all details are saved correctly.
8. Sign off and have a lovely day. :D

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:51 -06:00
Martin Renvoize 7eb80270e8
Bug 42868: (follow-up) Add regression test for non-UTC library timezone
The bug this fixed only manifests when the library's configured
timezone (C4::Context->tz) is not UTC: Koha::Object::_recursive_fixup
converts an incoming RFC3339 instant to that timezone before storing
it in the timezone-naive 'bookings.start_date'/'end_date' columns, so
a UTC-anchored boundary shifts by the timezone offset on write.

None of the existing Cypress specs catch this, because they never run
with the browser/test-runner timezone diverging from the configured
library timezone - both always come from the same container clock.

Add a Perl-level API test that mocks C4::Context->tz to a non-UTC zone
(America/New_York) and asserts the boundaries sent by place_booking.js
round-trip to the correct library-local date, pinning this regression
independently of any particular environment's timezone.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:50 -06:00
Martin Renvoize a7546bbcbe
Bug 42868: (follow-up) Fix stale comment referencing removed UTC boundary logic
The comment above the day-boundary calculation still described the
previous dayjs.utc() approach, which was replaced by dayjs.tz() in the
parent commit. Update it to describe the current behaviour so it
doesn't mislead future maintainers into re-introducing the timezone
shift bug this patch fixed.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:50 -06:00
Lucas Gass e0b81c9788
Bug 42868: Use local timezone instead of UTC when calculating start and end dates
To test:
1 - Edit koha-conf.xml
2 - Set timezone:
    <timezone>America/New_York</timezone>
3 - In Koha, enable bookings syspref
4 - Find a record, click the items tab, make an item bookable
5 - Place new booking, choose a date range
6 - View the bookings tab - note the booking extends into the date before
7- Check the DB, the start_date and end_date are wrong
8. APPLY PATCH, restart_all, clear browsewr cache
9. Place a new booking.
10. MariaDB [koha_kohadev]> select * from bookings WHERE booking_id={new booking}
11. Notice the start time should be {start_date} 00:00:00 and the end time should be {end_date} 23:59:59

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>
2026-08-10 15:52:49 -06:00
Jonathan Druart 3289dffbda
Bug 42349: Adjust tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:49 -06:00
Jonathan Druart b38236830e
Bug 42349: Fix filtering items by recalls
On the holdings table the filter by status=recalls is not working correctly.

Test plan:
Add a recall on an item of a bibliographic record that has several
items.
Filter the items table by status=recall
=> Without this patch all items are displayed
=> With this patch applied only the item with a recall is displayed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:48 -06:00
Jonathan Druart 1101564290
Bug 42066: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:48 -06:00
Jonathan Druart ef43cee19b
Bug 42066: Prevent 403 after an expired OPAC session
If a session has expired, the login form will get an empty CSRF token
and the login form will return 403

To recreate:
hit /cgi-bin/koha/sco/sco-main.pl
then hit /cgi-bin/koha/opac-main.pl
login
=> 403
Apply this patch, try again
=> Success

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:47 -06:00
Nick Clemens 951245dbc6
Bug 35948: Add jobs_unfinished_days and jobs_unfinished_type to cleanup_database.pl
This patch adds two new parameters to control the deletion of background jobs that did not complete.
Some libraries may want to keep a failed job longer than a completed one for debugging reasons.

To test:
0 - Apply patch
1 - Make some old failed jobs:
    INSERT INTO background_jobs (status,type,enqueued_on) VALUES
    ('started','update_elastic_index','2020-01-01'),
    ('failed','update_elastic_index','2026-01-01'),
    ('started','other_type','2021-01-01'),
    ('failed','other_type','2026-01-01');
2 - Confirm defaults of 365 and update_elastic_index for new params:
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days
3 - Should indicate 1 job older than 365 of type update_elastic_index would have been deleted
4 - Confirm days param works
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days 30
5 - Shoudl find 2 elastic jobs
6 - Confirm type param
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days --jobs-unfinished-type=other_type
7 - Should find 1
8 - Confirm type and days
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days 30 --jobs-unfinished-type=other_type
9 - Should find 2
10 - Confirm repeated type
     perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days=30 --jobs-unfinished-type=other_type --jobs-unfinished-type=update_elastic_index
11 - Finds 4
12 - Confirm deletion
     perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days=30 --jobs-unfinished-type=other_type --jobs-unfinished-type=update_elastic_index --confirm
13 - All your made up jobs are removed

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:47 -06:00
Baptiste Wojtkowski 2b78fd1de0
Bug 8127: Fix filters use in 'Most-circulated items report'
Filters are broken for many reasons. Previous patch contains some
(little) tidying.
There are 3 parts in this patch:
1 - The column name is incorrectly named in the dropdown for "branch",
  it is added in this patch
2 - There is a definition for 'Day' and 'Month', but not for 'Week', it
is also added in this patch
3 - Dbh variables are passed in the string definition AND in the call. I
removed them from the string definition since it violates the coding
guidelines.

Test plan:

1. On an fresh ktd db, run the script provided here (https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=8127#c3)  to fill the database with checkins/checkouts:

Visit http://your_koha:8081/cgi-bin/koha/reports/cat_issues_top.pl
2. Run a baseline test with following params:
Checkout date from 01/01/2020
Checkout date to: tomorrow
Library: Any library
Fill nothing else

Notice it is works

3. Test with some limits (Library, Week, and any other), notice you encounter errors on Library and Week, and that any other is empty, without error.

4. Apply patch
5. Rerun 2-3 and notice you now have results. Check with each possible limit, limiting to 5. Test once with 10 as a limit.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:46 -06:00
Baptiste Wojtkowski 0f40b53044
Bug 8127: Remove useless commented warns
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:46 -06:00
Jason Robb c56810f836
Bug 43046: Checkbox labels in Currencies and EDI accounts have unnecessary colons
Removes unnecessary colons when adding currencies and edi accounts.

1. Enable EDIFACT syspref
2. Navigate to Admin > Currencies.. > New currency
3. Note unnecessary colons after checkbox labels
4. Navigate to Admin > EDI accounts > New account
5. Note unnecessary colons after checkbox labels
6. Apply patch
7. Repeat steps 2 and 4, note colons have been removed

Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:45 -06:00
Jan Kissig 43539d1fa8
Bug 42986: Fix Uncaught TypeError on paycollect
This patch introduces a check if the variable change is defined, before its property innerHTML is accessed.
When calling "Write off selected" an Uncaught TypeError is thrown on form submission when accessing change.innerHTML as the variable change is undefined.
This regression was introduced by bug 42466 in a new submit handler which did not check if 'change' was defined at all.

Test plan:
1. In the staff interface, go to a patron's account
2. Go to Accounting > Create manual invoice
3. Fill out the manual invoice form with an amount
4. Click Save
5. Go to Make a payment
6. Click "Write off selected" (not write off on a single row)
7. IMPORTANT : Open the browser console
8. Click Confirm
9. apply patch and redo steps 1 - 8.

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:44 -06:00
Jonathan Druart fec24d558e
Bug 42937: Remove unneeded flag in 00-strict.t
$Test::Strict::TEST_STRICT = 0 is only set for all_perl_files_ok
It's no longer used since bug 42126

Test plan:
prove t/db_dependent/00-strict.t
should still return green

Remove a use Modern::Perl for mainpage.pl
=> Should still return green

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:44 -06:00
Phil Ringnalda ae1bf93209
Bug 42920: Looking up authority records in Advanced editor shouldn't append spaces
The patch for bug 21453 replaced an Advanced editor-specific (in the "[% IF rancor %]" block) "[%- END -%]‡9[% authid | html %]";" which trims whitespace with several lines of  "[% ... %]" which do not trim whitespace. The result is that when you look up an authority from the Advanced editor, you get extra whitespace between the last subfield from the authority record and the $9 field with the authority id.

Test plan:
 1. Without the patch, set the preference EnableAdvancedCatalogingEditor to Enable
 2. Cataloging - Advanced editor
 3. Click in any line of the editor and press Return to get a blank line
 4. Type 655 and press Tab three times
 5. Type Ctrl+Shift+L to open the authority search popup
 6. Click Search (no need for search terms) and click Choose for any record
 7. Note that the editor shows twenty blank spaces between the end of the subfield a
    contents and the ‡9
 8. Apply patch
 9. Repeat steps 3-6, note that now there is no extra space between the end of the
    content and the ‡9

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:43 -06:00
Jonathan Druart 70f00783c0
Bug 42886: Add missing rtl
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:43 -06:00
Jonathan Druart f73065b3c1
Bug 42886: Add bookings.css and edifact_interchange.css
Caught by the xt test

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:42 -06:00
Jonathan Druart 294970b5d6
Bug 42886: Add xt test to prevent future omission of css files in Makefile
Note that t/Makefile.t and xt/check_makefile.t were doing the same test.
Merging them.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:42 -06:00
David Cook fc66d4da43
Bug 42850: Add ill-requests-rtl.css to Makefile
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:41 -06:00
Andrii Nugged 3ed26e8950
Bug 42850: Install generated ILL requests CSS
The ILL requests page loads css/ill-requests.css from the staff template. The stylesheet is generated by build-resources.PL and ignored by git, but Makefile.PL does not list it in the generated asset map.

As a result package installs do not copy the generated stylesheet into blib, so Asset.css cannot find it and no link tag is emitted.

Add ill-requests.css to the generated staff CSS file map so it is installed with the other generated staff stylesheets.

Test plan:

1. perl -c Makefile.PL

2. prove -v t/Makefile.t

3. Confirm that /cgi-bin/koha/ill/ill-requests.pl includes a link tag for css/ill-requests.css on a package/source-style install.

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:41 -06:00
Martin Renvoize 246d62da90
Bug 42669: Use exception type check for NoNodes and don't mask real failures
Two follow-up fixes from QA review of the previous commit:

- Detect Search::Elasticsearch::Error::NoNodes via ref($_) rather than
  regex-matching the stringified exception, matching the existing
  pattern already used in about.pl.
- Only reset jobs to 'new' for retry when NoNodes is the sole problem.
  If a genuine indexing error also occurred in the same batch, record
  the batch as 'failed' instead of silently discarding that error via
  the NoNodes retry path.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:40 -06:00
Tomás Cohen Arazi 3183061da9
Bug 42669: Fix es_indexer_daemon.pl silent job failure and NoNodes recovery
When update_index() throws, the daemon now:
- On NoNodes: aborts the batch, recreates the indexer object to reset
  the dead-node connection pool, and resets jobs to 'new' for retry.
- On other errors: marks jobs as 'failed' instead of 'finished'.

This prevents silent data loss where records disappear from search
results because their indexing jobs were incorrectly marked as done.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:40 -06:00
Pedro Amorim 9b3751f507
Bug 42617: (41512 follow-up) Remove ownPagination and fix recordsFiltered propagation
ownPagination set recordsFiltered on json.data instead of json, causing
DataTables to display "NaN total entries". Remove the artificial 5000
mechanism, propagate real counts from plugins that provide them, and
fall back to actual result counts for those that don't. Also removes a
dead drawCallback overwritten before it could fire.

Test plan:
1) Install this test version of the unpaywall plugin:
https://github.com/openfifth/koha-plugin-ill-avail-unpaywall/releases/tag/test
2) Enable ILLModule and ILLCheckAvailability
3) Configure the plugin and add any emaill address
4) Create a new ILL request:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
5) Enter '42' for cardnumber, any library, and any DOI e.g. '123'.
6) Verify that pagination works as expected

Before patch, pagination controls dont show and the following label is shown:
No entries to show (filtered from NaN total entries)

Signed-off-by: Edith Speller <Edith.Speller@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:39 -06:00
Owen Leonard 499f7e595e
Bug 42568: Match maxlength attributes to marc_order_accounts column sizes
This patch adds a maxlength attribute to a few form fields in the MARC
order accounts entry form. Text inputs should now allow the user the
enter more characters than the db will accept.

To test, apply the patch and enable the MarcOrderingAutomation system
preference if necessary.

- Go to Administration -> MARC order accounts -> New account.
- Test these inputs to confirm that you can't enter more characters than
  their maxlengths:
  - Description (250)
  - Match field (10)
  - Match value (50)
  - Basket name field (10)

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:39 -06:00
Samuel Sowanick 13dc7df819
Bug 42519: Terminology - Update 'branch' to 'library' in patron categories
The default message in the "Self-renewal failure message" field
(Administration > Patrons and circulation > Patron categories >
 Account expiry and self-renewal) uses incorrect terminology.

Test plan:
1. Go to Administration > Patrons and circulation > Patron categories.
2. Click on a patron category.
3. Scroll down to the "Self-renewal failure message" field (in the
   Account expiry and self-renewal section).
4. Note the text says "...your local branch...".
5. Apply the patch.
6. Reload the page.
7. Note that the text now says "...your library...".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:38 -06:00
Pedro Amorim e1fe0bbf57
Bug 41604: (follow-up) Wait for the page to be ready before searching in patrons_search.t
Bug 41604 made every page do a bit more work when it loads. This
Selenium test didn't wait for that work to finish before typing into
the search box, so sometimes it searched too early and got the wrong
results.

This adds a short wait after each page load so the test only searches
once the page is actually ready.

Introduced by: Bug 41604: Save user decision per column when visibility changes

Test plan:
prove t/db_dependent/selenium/patrons_search.t

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:38 -06:00
Lucas Gass fc78093a72
Bug 41604: (QA follow-up) Remove unused JS variable
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:37 -06:00
Lucas Gass 1444c145d5
Bug 41604: (QA follow-up) Add a semicolon
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:37 -06:00
Jonathan Druart fdc7e1c75b
Bug 41604: Correctly restore alert when bKohaColumnsUseNames is off
If we define visibility_condition we must have implemented
bKohaColumnsUseNames correctly.

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:36 -06:00
Jonathan Druart e616be2e42
Bug 41604: Hide column if the feature is disabled
Example: supplier_report on the basket page
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:36 -06:00
Jonathan Druart 8a5555238d
Bug 41604: Save user decision per column when visibility changes
- Modify column-visibility.dt event handler to store the visibility
  preference of the user
- Improve _dt_force_visibility() to correctly pick the correctly visibility value depending
  on visibility_condition, is_hidden, and the user preference
- Add _dt_get_expected_column_visibility() to guess what's the expected visibility
- Force boolean true/false (vs 1/0) using !!
- Rely on a global variable kohaTableUserChoices to store the user
  choice. It will be part of the state and store in localStorage.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:35 -06:00
Jonathan Druart 316b1477af
Bug 41604: Add one more Cypress test
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:35 -06:00
Jonathan Druart 7db7879ccc
Bug 41604: Add Cypress tests
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:34 -06:00
Lucas Gass d34a60e585
Bug 41604: (follow-up) Fix the visibility when visibility_condition is 0 or null, instead of false
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:34 -06:00
Jonathan Druart b88f695743
Bug 41604: Fix force display of a column
If a column must be displayed but was hidden before

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:33 -06:00
Jonathan Druart 45f8c99dbc
Bug 41604: Fix typo
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:33 -06:00
Jonathan Druart dec5715208
Bug 41604: QA fixes
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:32 -06:00
Jonathan Druart 72a9f1d4b6
Bug 41604: take visibility_condition when save state is disabled
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:32 -06:00
Jonathan Druart f3ff091165
Bug 41604: Use visibility_condition in other places
There are 3 other places where we force the visibility of columns. They
must als use this new visibility_condition flag, so they are all using
it.

Test plan:
The 3 tables are:
1. basket with the columns edifact, modify, cancel
2. hold history with the column itemtype
3. batch edit patron with the columns checkbox, password expiration,
   protected

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:31 -06:00
Jonathan Druart a5b5bf623a
Bug 41604: Centralize visibility condition in datatables.js
We want to hide column if:
 * the feature is not accessible (pref disabled or insufficient
   permissions)
or
 * the column is hidden in the settings
or
 * the column has been hidden by the user

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:31 -06:00
Hammat Wele 50205e82f6
Bug 41604: Impossible to hide Checkin column in issues-table in circ/circulation.pl
Bug 41314 caused a regression, making it impossible to hide the Checkin column in the issues table in the patron's circulation page.

To recreate:
1. Hide the checkin column by default in circulation issues-table
   1.1. Go to Administration > Table settings > Circulation tab > Jump to page : circulation > Table id: issues-table
   1.2. Check 'Is hidden by default' for 'checkin'
   1.3. Click 'Save'
2. logout and login
3. Checkout an item to a patron
   3.1. In the search bar, set to Checkout, search for Henry
   3.2. In the checkout field, enter the barcode 39999000010114 and press enter
   --> In the table of issues, Checkin column is visible, it should be hidden by default
4. Apply the patch
5. Repeat step 2, 3
   --> In the table of issues, Checkin column is not visible

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 15:52:07 -06:00
Nick Clemens 8c66e6ef72
Bug 42512: Pass already checked/cleaned MARC::Record to Metadata Extractor
To test:
1 - In Koha testing docker record 369 has invalid xml, otherwise corrupt a record like:
    sudo koha-mysql kohadev
    UPDATE biblio_metadata SET metadata = UpdateXML(metadata, '//datafield[@tag="040"]/subfield[@code="c"]',CONCAT('<subfield code',CHAR(27),'="c">OSt</subfield>')) WHERE
 biblionumber=438;
2 - View the record in the staff client - note bad metadata message
    http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=438
3 - Click 'Edit'
4 - Error 500, in logs:
    Invalid data, cannot decode metadata object
5 - Apply patch, restart all
6 - Reload record and click edit
7 - Success! Message about bad metadata, but can edit/save

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:44 -06:00
Paul Derscheid 554f398a68
Bug 42225: (QA follow-up) Use configured unitprice field in outer SELECT
The outer SELECT hardcoded unitprice_tax_included while the inner
subquery selects the field returned by FieldsForCalculatingFundValues.
With CalculateFundValuesIncludingTax disabled the derived table only
exposes unitprice_tax_excluded, so spent.pl dies with:
Unknown column 'unitprice_tax_included' in 'field list'

To test:
1. Set CalculateFundValuesIncludingTax to 'Exclude'
2. View spent.pl for a fund with received orders
3. Observe error 500
4. Apply patch, restart_all
5. Reload, page renders correctly
6. Set CalculateFundValuesIncludingTax back to 'Include'
7. Reload, page still renders correctly

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:44 -06:00
Nick Clemens 5ab93ba401
Bug 42225: Improve SQL on spent.pl
To test:
1 - Have some received orders and some invoice adjustments
2 - View spent.pl for a fund
3 - Apply patch, restart_all
4 - Reload
5 - Confirm the display has not changed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:43 -06:00
Martin Renvoize 8dba205b9a
Bug 41819: (follow-up) Reject 'AC' refunds for anonymous transactions server-side
The previous patch hides the 'Account credit' option in the refund modal
when the accountline has no borrowernumber, but that is a UI convenience
only. A crafted POST (or a stale page) could still submit
refund_type=AC against an anonymous accountline, which would create a
REFUND credit with no patron to attach it to and leave the register
unbalanced because the payout branch is skipped.

Add a server-side guard in pos/register.pl that refuses the operation
and surfaces a new error_refund_anonymous_ac flag, rendered in
pos/register.tt in the same style as the other refund errors.

Test plan:
1. Apply the patch
2. Log in as a user with cash_management/anonymous_refund permission
3. Record an anonymous sale against a register
4. Using the browser devtools (or curl), POST to pos/register.pl with
   op=cud-refund, refund_type=AC and the accountline id of the sale
5. => Confirm the request is rejected and the
   "Account credit refunds are not available for anonymous transactions"
   message is shown
6. => Confirm no REFUND accountline was created and the register
   balance is unchanged
7. Repeat the POST with refund_type=CASH (or any non-AC type) and
   confirm the refund + payout are created normally

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:43 -06:00
Martin Renvoize 39eae27a42
Bug 41819: POS refunds with 'AC' type incorrectly create PAYOUT accountlines
When processing a refund through the POS register with refund type 'AC'
(Account Credit), the system was incorrectly creating both a REFUND credit
and a PAYOUT debit with payment_type = 'AC'.

When 'AC' is selected, the refund should remain as a credit on the patron's
account without creating a payout. This behavior was already correct in the
member account refund workflow (members/boraccount.pl) but was missing
from the POS refund workflow.

This patch:
1. Adds the same check to pos/register.pl that exists in members/boraccount.pl
   to skip payout creation when refund_type is 'AC', making both workflows
   consistent.

2. Fixes the JavaScript in register.tt to properly hide the 'AC' option for
   anonymous transactions. The original check only tested for empty string,
   but borrowernumber can be undefined/null, which needs to be handled.

Test plan:
Part 1: Test patron transaction refund with Account Credit (AC)
1. Find or create a test patron
2. On the patron's Accounting tab, manually add a charge (e.g., Manual fee)
3. Pay the fee off from the patron's Accounting tab
4. Navigate to Point of Sale > Transaction History
5. Find the payment transaction you just created
6. Click "Issue refund" on the payment line item
7. Verify that "Account credit" appears as an option in the "Transaction type"
   dropdown
8. Select "Account credit" as the transaction type
9. Enter the refund amount and click "Confirm"
10. Navigate back to the patron's Accounting tab
11. Verify that a REFUND credit appears on their account
12. Verify that NO PAYOUT debit was created
13. Verify the credit remains on the patron's account (positive balance)

Part 2: Test patron transaction refund with Cash payout
16. Using the same patron, create another manual fee and payment
17. Navigate to Point of Sale > Transaction History
18. Click "Issue refund" on the new payment
19. Select "Cash" (or another payment type) as the transaction type
20. Enter the refund amount and click "Confirm"
21. Navigate back to the patron's Accounting tab
22. Verify both a REFUND credit and a PAYOUT debit appear
23. Verify the PAYOUT has the correct payment_type (e.g., CASH)

Part 3: Test anonymous transaction (no Account Credit option)
24. Navigate to Point of Sale
25. Add an item to sell and complete the payment
27. Navigate to Point of Sale > Transaction History
28. Find the anonymous payment you just created
29. Click "Issue refund" on the payment line item
30. Verify that "Account credit" does NOT appear in the "Transaction type"
   dropdown (only payment types like Cash, Check, etc. should appear)
31. This confirms anonymous transactions cannot have account credits since
    there's no patron account to credit

Expected results:
- Patron transactions show "Account credit" option in refund dropdown
- Anonymous transactions do NOT show "Account credit" option
- AC refunds create only REFUND credit (no PAYOUT debit)
- Non-AC refunds create both REFUND credit and PAYOUT debit
- Run: prove t/db_dependent/Koha/Cash/Register/Cashup.t

Signed-off-by: Laura Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:42 -06:00
Martin Renvoize 661c023dcc
Bug 41358: Apply consistent JSON logging to holds (Bug 40866)
Bug 40866 introduced JSON logging for HOLDS CREATE but stored null for
'confirmations' and 'forced' when no overrides were present. This patch
mirrors the earlier change to CIRCULATION ISSUE by defaulting them to
empty arrays, keeping the two log streams structurally identical and
simplifying reporting queries.

Changes:

C4/Reserves.pm:
- Default confirmations/forced to [] in the AddReserve log payload.

t/db_dependent/Reserves.t:
- Update the Bug 40866 subtest to assert the empty-array defaults on
  normal holds, and fix a 'my $log_data' redeclaration.

tools/viewlog.pl:
- Extend the CSV export JSON-decode block to also cover HOLDS logs,
  guarding the item lookup on itemnumber being present (HOLDS payloads
  may lack one) and filling the CSV item columns from the decoded
  payload. The legacy bare-itemnumber fallback is restricted to
  CIRCULATION so a legacy HOLDS hold_id is not mistaken for an
  itemnumber.

koha-tmpl/intranet-tmpl/prog/js/viewlog.js:
- Extend the info column override rendering (renderCircInfo) to HOLDS
  entries so forced/confirmations arrays display translated, exactly as
  they do for CIRCULATION. The log viewer moved to an ajax datatable in
  bug 29016, so this replaces the earlier json_found change to
  viewlog.pl/viewlog.tt.

misc/maintenance/migrate_action_logs_to_json.pl:
- Extended to handle legacy pre-Bug 40866 HOLDS CREATE logs where info
  is a bare hold_id, recovering branchcode / biblionumber / itemnumber
  from old_reserves / reserves.
- Uses the same keyset-pagination pattern as the circulation loop so
  rows are not skipped under --commit.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:42 -06:00
Martin Renvoize 92fb131877
Bug 41358: Log forced overrides for offline and ILL checkouts
This patch updates offline circulation and ILL checkouts to properly
record their nature in circulation logs as forced transactions.

Offline circulation (process_koc.pl):
- Offline transactions have already occurred without access to normal
  circulation checks
- Now marks these as forced with 'OFFLINE_CIRCULATION' override code
- This helps identify historical transactions that bypassed checks

ILL checkouts (Koha/ILL/Request.pm):
- ILL checkouts are system-initiated and may bypass certain restrictions
  by design
- Now marks these as forced with 'ILL_CHECKOUT' override code
- This helps track and report on ILL circulation activity separately

Display updates (action-logs.inc):
- Added translation strings for both new override codes:
  - OFFLINE_CIRCULATION: "Offline circulation checkout"
  - ILL_CHECKOUT: "ILL system checkout"
- These will now display properly in Tools > Log viewer

This ensures these special checkout types are properly identified in
reporting and analysis, making it easier to understand the context
of historical transactions.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:41 -06:00
Martin Renvoize 314df725d5
Bug 41358: Add migration script for legacy circulation logs
This patch adds an out-of-band migration script to convert legacy
CIRCULATION ISSUE action logs from the old itemnumber-only format
to the new consistent JSON format.

The script:
- Finds all CIRCULATION ISSUE logs where info is just an itemnumber
- Converts them to JSON format with the standard structure
- Attempts to recover issue_id and branchcode from old_issues/issues
- Runs in dry-run mode by default (--commit flag to actually update)
- Processes in batches to handle large databases safely
- Includes verbose mode for detailed output

Usage:
  perl misc/maintenance/migrate_action_logs_to_json.pl --help
  perl misc/maintenance/migrate_action_logs_to_json.pl --verbose
  perl misc/maintenance/migrate_action_logs_to_json.pl --commit

This is designed as an out-of-band maintenance script rather than
an atomic update because:
1. May be expensive on large databases with many log entries
2. Migration is optional - display code handles both formats
3. Can be run during off-peak hours at admin's discretion

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:41 -06:00
Martin Renvoize 63e4f77edd
Bug 41358: Always store circulation logs in consistent JSON format
Previously, CIRCULATION ISSUE logs would store just the itemnumber when
there were no confirmations or forced overrides, but would store a full
JSON structure when overrides were present. This inconsistency makes
reporting difficult as queries need to handle both formats.

This patch changes AddIssue to always store logs in JSON format,
matching the pattern already used in AddRenewal. The JSON structure
now consistently includes:
- issue: issue_id
- branchcode: branchcode
- itemnumber: itemnumber
- confirmations: array of confirmation codes (empty array if none)
- forced: array of forced override codes (empty array if none)

Benefits:
- Consistent data format simplifies reporting and analysis
- Matches existing RENEWAL logging pattern
- Display code (tools/viewlog.pl) already handles both formats,
  so backward compatibility is maintained

Test plan:
1. prove t/db_dependent/Circulation.t
2. Perform a normal checkout - verify action log contains JSON
3. Perform a checkout with overrides - verify confirmations/forced
   are populated in the JSON
4. Check Tools > Log viewer - both types should display correctly

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:40 -06:00
Baptiste Wojtkowski abd436ecf6
Bug 39208: Remove xhtml2pdf from printoverdues.sh
Note: this requires a command line access to the test machine

TEST PLAN:
1 - Anywhere on the computer running koha, run following command:
mkdir tmp
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><p>Тетрис — это классика!</p></body></html>' > tmp/tmp.html
echo '* { color: red };' > tmp.css
./misc/cronjobs/printoverdues.sh tmp tmp.css
2 - Open the pdf in tmp/tmp.pdf, notice it is broken
3 - Apply patch
4 - Rerun ./misc/cronjobs/printoverdues.sh tmp tmp.css
5 - Open the pdf in tmp/tmp.pdf, notice it is now displaying properly, notice the css property still applies

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:40 -06:00
Martin Renvoize 76e719964c
Bug 43033: Sort unallocated task indices before compacting the retry matrix
The previous two commits on this bug fixed two ordering assumptions in
the RETRY loop of _allocateWithTransportCostMatrix(): resetting $inf at
the start of each attempt, and splicing unallocated task indices out of
@requests in descending order (removing a lower index first would shift
the position of any higher index still waiting to be removed).

This is a third instance of the same underlying problem, a few lines
earlier in the loop that compacts the working cost matrix $m before
that splice runs. @unallocated is built by iterating over agents (item
rows), pushing the column index Munkres assigned to each row that
turned out infeasible - and Munkres' row-to-column assignment is a
permutation, not a monotonic function of the row index, so
@unallocated is no more reliably sorted here than it was for the
splice.

That compaction loop scans matrix columns in ascending order and
advances a single pointer into @unallocated, expecting to meet each
entry in that same ascending order. When @unallocated arrives out of
order, the pointer never catches up: columns that should have been
dropped are left in place, and the columns after them are never
shifted left to fill the gap. From that point on, $m no longer lines
up with @requests, which *is* correctly reduced by the splice - so on
the next retry pass a hold's transport cost gets evaluated using a
different hold's matrix row. Depending on the exact misalignment, this
can silently drop a fillable hold from the queue while a genuinely
available item sits unused, or fill a hold via the wrong-cost item.

It didn't show up in the "many holds" regression test added earlier on
this bug, because that test's transport cost matrix is uniform (every
disabled pair costs the same); this defect only surfaces once the
columns competing for a scarce item have distinct costs.

Sort @unallocated once, ascending, and use that copy for the
compaction pass; reverse it for the splice, which needs descending
order.

Test plan:

1. Run the new t/db_dependent/HoldsQueue/RetryAllocationOrdering.t -
   it calls _allocateWithTransportCostMatrix() directly with a small
   hand-built scarce transport cost matrix and a pinned srand() (the
   specific item-to-branch permutation needed to trigger the bug
   depends on the shuffle() inside the routine). Reverting just this
   patch makes 3 of its 6 subtests fail: the L2 pickup hold is dropped
   even though an available item could have filled it.
2. Run t/db_dependent/HoldsQueue/TransportCostOptimizations.t to
   confirm no regression in the existing retry-loop coverage.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:39 -06:00
Andreas Jonsson 4d4bc5b18f
Bug 43033: retry allocation with valid items
* The representation of the value "infinity" must be reset to -1 at the
  start of each allocation attempt.
* Unallocated items must be spliced out from the candidate arrays in
  reverse index order.

Test plan:

1. Run the unit tests in
   t/db_dependent/HoldsQueue/TransportCostOptimizations.t

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>
2026-08-10 14:56:39 -06:00
Nick Clemens e30f16750b
Bug 43033: Test with a lot of holds
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>
2026-08-10 14:56:38 -06:00
Martin Renvoize ed0a3ebd82
Bug 42430: (QA follow-up) Fix nested plugin package corruption on reload
InstallPlugins forces every discovered plugin class to be deleted and
re-required from disk on every call, so plugin_methods reflects the
.pm file as it currently exists rather than whatever was already
loaded into a long-lived Plack worker. That deletion used
Symbol::delete_package, which clears the target package's whole
symbol table -- including any already-loaded packages nested under it
(e.g. a REST API controller a plugin bundles alongside itself, such as
Koha/Plugin/Foo/Controller.pm). Those nested classes keep their own
%INC entry, so they are never re-required afterwards and are left
permanently blanked out for the rest of the worker's life.

This was caught when running
`prove -r t/db_dependent/Koha/Plugins/ t/db_dependent/Koha/REST/Plugin/`
on top of the original patch. Tests fail several files that have nothing
to do with the bug being fixed (Biblio_and_Items_plugin_hooks.t,
Holds_hooks.t, TemplateIncludePathHook.t, REST/Plugin/PluginRoutes.t),
because Koha::Plugin::Test::Controller (a bundled test-fixture
controller living in t/lib/plugins/Koha/Plugin/Test/Controller.pm)
gets its symbol table wiped as collateral damage every time
Koha::Plugin::Test is reloaded.

_forget_plugin_class() replaces the delete_package call with the same
technique used by the CPAN module Class::Unload (not added as a
dependency here, just the technique): only clear the target class's
own symbols and reset its @ISA, explicitly leaving any nested package
entries in its symbol table alone. Module::Pluggable discovers bundled
nested classes as their own entries in the plugin list regardless, so
they still get reloaded correctly on their own turn.

I also annotate the no strict 'refs' block added by the previous commit
with a '## no critic' justification, as flagged by koha-qa.pl.

Test plan:
1. Confirm the bug exists without this patch:
   a) git checkout <previous commit> (drop this commit, keeping the
      first 3 commits of the bug applied)
   b) Inside KTD:
      prove -r t/db_dependent/Koha/Plugins/ t/db_dependent/Koha/REST/Plugin/
   c) Observe failing files unrelated to this bug's own tests:
      Biblio_and_Items_plugin_hooks.t, Holds_hooks.t,
      TemplateIncludePathHook.t and REST/Plugin/PluginRoutes.t.
2. Confirm the fix:
   a) Re-apply this commit
   b) Re-run the same command from 1(b) -- all tests now pass
3. Confirm the original bug this branch set out to fix is still fixed,
   both via the manual UI steps from the first commit's test plan
   (install Batch Permission Modifier Plugin 1.3.1, upload 1.4.1 over
   it via Tools > Plugins, confirm the renamed method takes effect
   without restarting Plack) and via:
   prove t/db_dependent/Koha/Plugins/Plugins.t
   (see the 'InstallPlugins() reloads stale symbol tables tests'
   subtest)
4. Confirm QA tooling is clean:
   koha-qa.pl -c 4
   => PASS for Koha/Plugins.pm and all touched test files, no critic
      or tidiness failures.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:38 -06:00
Nick Clemens 9f45afff03
Bug 42430: (follow-up) Add missing POD
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:37 -06:00
Kyle M Hall 7e42b9f73d
Bug 42430: Add unit tests
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>
2026-08-10 14:56:37 -06:00
Kyle M Hall c912369c0a
Bug 42430: Fix issue with stale plugin methods after plugin upgrade
When a plugin is upgraded in place (by uploading a new version over
the old one), the list of methods Koha knows about for that plugin
can get out of date. For example, if a plugin used to have a "tool"
method and the new version renames it to "admin", the staff interface
can still show the old "Run tool" link, and clicking it produces an
error saying the plugin has no "tool" method.

This happens because the plugin upload runs inside a long-lived
Plack worker that has already loaded the old version of the plugin
in memory. When Koha scans the plugin to record its methods, Perl
has no reason to re-read the file from disk, so the scan sees the
old method list and stores that in the plugin_methods table. The
plugin code on disk is new, but the record of what methods it has
is stale.

This patch forces Koha to throw away the in-memory copy of each
plugin and reload it from disk before scanning, so the recorded
list of methods matches the plugin that is actually installed.

Test Plan:
1) Install the Batch Permission Modifier Plugin version 1.3.1
(this version exposes a "tool" method)
2) Go to Tools > Plugins and confirm the plugin row shows
"Run tool" in its actions menu
3) Upload Batch Permission Modifier Plugin version 1.4.1 over
the top of 1.3.1 (this version exposes "admin" instead of
"tool")
4) Reload the Plugins page
5) Note the plugin still shows "Run tool" in its actions menu
6) Click "Run tool"
7) Note the error: the plugin has no "tool" method!
8) Apply this patch
9) Restart all the things!
10) Uninstall the plugin
11) Repeat steps 1-4
12) Note that "Run tool" is gone and "Run admin tool" appears
in its place
13) Click "Run admin tool" and confirm it loads without error!

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
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>
2026-08-10 14:56:36 -06:00
Laura_Escamilla 734e384912
Bug 42339: Make Record display customization cancel return to record display list
1. Go to the staff interface.
2. Go to Tools > Additional contents.
3. Select the Record display customizations tab/category.
4. Add a new Record display customization, or edit an existing one.
5. Click Cancel.
6. Confirm you are returned to:
   /cgi-bin/koha/tools/additional-contents.pl?category=record_display
7. Confirm you are not returned to the HTML customizations category.
8. Repeat with another Additional content category, such as Pages, and
   confirm its Cancel behavior is unchanged.
9. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:36 -06:00
Lari Taskula 4a366b4d5e
Bug 41796: "Forgot your password" link is not visible if OpacResetPassword is enabled but OpacPasswordChange is disabled
Before applying this patch, to reproduce the issue:
1. Set system preference OpacResetPassword to 'not allowed'
2. Set system preference OpacPasswordChange to 'Don't allow'
3. Set preference opacuserlogin to 'Allow'
4. Navigate to OPAC
5. Observe no "Forgot your password?" link on the OPAC main page
6. Set system preference OpacResetPassword to 'allowed'
7. Observe no "Forgot your password?" link on the OPAC main page
8. Set system preference OpacPasswordChange 'Allow'
9. Observe "Forgot your password?" link is present on the OPAC main page.

Instead, it should be already present when OpacResetPassword is allowed.

To test this patch:
1. Apply this patch
2. Set system preference OpacResetPassword to 'not allowed'
3. Set system preference OpacPasswordChange to 'Don't allow'
4. Set preference opacuserlogin to 'Allow'
5. Navigate to OPAC
6. Observe no "Forgot your password?" link on the OPAC main page
7. Set system preference OpacResetPassword to 'allowed'
8. Observe "Forgot your password?" link is present

Signed-off-by: Bo Gustavsson <bosse@gustavsson.one>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:35 -06:00
Caroline Cyr La Rose 8df3159ecf
Bug 42885: FineNoRenewals description mentions fines rather than charges
This patch fixes the terminology in the FineNoRenewals system preference
description to use the term 'charges' instead of 'fines'.

To test:

1. Apply patch
2. Go to Administration > System preferences
3. Search for FineNoRenewals
4. Make sure the description is gramatically correct and doesn't have
   contain typos

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>
2026-08-10 14:56:35 -06:00
Owen Leonard 38e16776a0
Bug 42618: Incorrect sidebar menu link to MARC order accounts
The administration sidebar menu has an incorrect link for MARC ordering
accounts. Clicking the link takes you to EDI accounts. This patch fixes
it.

To test, apply the patch and run 'yarn build'

- Go to Administration -> Libraries (for example)
- In the sidebar menu, click the MARC ordering accounts link.
- You should be taken to the MARC ordering accounts page.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:34 -06:00
Chris Mathevet db3ca9f330
Bug 42579: Added "no-export" class to ratings div
"Title" column of Checkout history export contains too much
informations, making the export file hard to read and understand.

Test plan :
1 - Connect on OPAC
2 - Go to "Your account"
3 - Go to "Checkout history"
4 - Click on "CSV"
5 - Open file
6 - Notice that the "title" column is too long and contains ratings
    informations
7 - Go back on OPAC
8 - Click on "Copy"
9 - Copy to a .txt file
10 - Notice the same problem as the CSV export
11 - Apply patch
12 - Repeat previous instructions
13 - Notice that the "title" column contains only the title

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:34 -06:00
Kyle M Hall be571d1b50
Bug 42571: Sending EDI order results in variable not available warnings
The warnings look like:

[2026/05/08 15:02:42] [32918] [WARN] Variable "@messages" is not available at /usr/share/koha/intranet/cgi-bin/acqui/basket.pl line 698.
[2026/05/08 15:02:42] [32918] [WARN] Variable "$op" is not available at /usr/share/koha/intranet/cgi-bin/acqui/basket.pl line 704.

Note in the fix, we are switching to the "our" keyword. When we declare
things with "our", and we run this under plack, those variables and
values persist across multiple requests! Due to this behavior we always
need to reinitialize anything that uses "our" unless we specifically
want that behavior ( like caching ).

Test plan:
1) Send an EDI order, note the warnings in the logs
2) Apply this patch
3) Restart all the things!
4) Send another EDI order, note the warnings are gone!

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>
2026-08-10 14:56:33 -06:00
Jonathan Druart 270136ea70
Bug 42551: Fix QA failures
[FAIL] t/db_dependent/Koha/Plugins/C3.t
   FAIL   file_permissions
                File must have the exec flag
   FAIL   test_no_warnings
                'use Test::NoWarnings' must be present in this test file.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:33 -06:00
Kyle M Hall 1648db7a84
Bug 42551: Skip preload when running perl with -c
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>
2026-08-10 14:56:32 -06:00
Kyle M Hall 8ef30ca3fb
Bug 42551: Add unit test
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>
2026-08-10 14:56:32 -06:00
Jan Kissig 7d354f3716
Bug 42912: Fix Uncaught TypeError on opac-readingrecord when no Checkout history
This prevents an 'Uncaught TypeError: can't access property "DataTable", table is undefined' when a patron has no items checked out yet and is visiting opac-readingrecord.
The code now checks if table is defined before accessing its properties.

Testplan:
a) log in in opac, open dev console
b) go to /cgi-bin/koha/opac-readingrecord.pl
c) without patch applied the error above occurs
d) apply patch and reload opac-readingrecord.pl, there should not be any error now
e) check out an item in staff to that patron and check that the table on opac-readingrecord.pl is diplayed

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:31 -06:00
Paul Derscheid bb16f1dd29
Bug 41946: Allow superlibrarians to set protected flag on patron creation
Since bug 36085, the protected flag in a patron account was removed from
the patron creation form, regardless of if the user is a superlibrarian.

- Hoist $logged_in_user lookup before the edit_form block
- Move superlibrarian capability checks after the block so they apply
  to all operations including patron creation

To test:
1. Sign in as a superlibrarian
2. Go to Patrons > New patron > choose any category
3. Without this patch: the "Protected" yes/no field is missing
   from the "Library management" section
4. Apply this patch
5. Repeat step 2
   - The "Protected" yes/no field is now visible
6. Create the patron with Protected set to "Yes"
7. Confirm the patron is saved as protected
8. Sign in as a non-superlibrarian with edit_borrowers permission
9. Repeat step 2
   - The "Protected" field should still be hidden
10. Edit an existing patron as superlibrarian
    - The "Protected" field is visible and functional
11. Edit an existing patron as non-superlibrarian
    - The "Protected" field is hidden
12. Sign off

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 14:56:12 -06:00
Lucas Gass 6b18b0500f
Update release notes for the 26.05.02 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 9ad474c994b7e7b9f90821da35e030be98b8d58d)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:16 -06:00
Lucas Gass b341f0b6da
Increment version for the 26.05.02 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 0974071966a2e2ad52931a7f12d7440d6a7deb5c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:15 -06:00
Martin Renvoize e99c6d3d62
Bug 42471: (follow-up) prevent XSS in suggestion status
The custom/authorized-value status branch concatenated row.status
directly into a class="..." attribute string. escape_str() only
escapes &, <, > (not quotes), so a status code containing a double
quote could still break out of the attribute.

Build the status <span> via DOM APIs instead, the same way the
reason field was fixed: fixed statuses get a hardcoded class, and
the free-form status code goes into a data-status attribute via
dataset, which the DOM escapes safely regardless of content.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 7ec92d492576215296bbee42669bcf6ed81c7626)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:14 -06:00
David Cook b28831147d
Bug 42471: (follow-up) prevent XSS in suggestion reason
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5e8c9e1529dcf76d065970451270878b4b473b27)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:14 -06:00
Jonathan Druart d12862594f
Bug 42471: Prevent XSS in suggestion.tt
escape_str all the things

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 522c932b2c3376b8c2f91d04dd83d7eabf067055)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:13 -06:00
Martin Renvoize d059db421b
Bug 42746: (QA follow-up) Sanitize agefield and substitution field at the sink too
The original fix only whitelisted agefield, condition_field, and
substitution_field at the point they're saved in
tools/automatic_item_modification_by_age.pl, leaving the actual
consumer of the stored syspref, C4::Items::ToggleNewStatus, trusting
agefield and substitution field unchecked. agefield is spliced
directly into raw SQL (TO_DAYS($agefield)) and so can't be
parameterized like an ordinary value; substitution field reaches a
dynamic method call ($item->$field($value)). Any future or
alternate writer of the automatic_item_modification_by_age_configuration
syspref would have reopened both holes without touching the form at
all.

This adds the same allow-list validation directly in ToggleNewStatus
(falling back to the safe default agefield, or skipping the
substitution/condition, exactly as before for missing values), shares
the age-field allow-list between the two files via a new
GetAutomaticItemModificationAgeFields() in C4::Items so it can't drift
between them, and logs rejected values via Koha::Logger instead of
silently discarding them so tampering leaves an audit trail.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 9d6f6571f6c43baff87cfde43304dbe98f642535)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:12 -06:00
Jonathan Druart 59135cbb2e
Bug 42746: Sanitize condition and substitution fields
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Amended-by: Jonathan Druart:
Replace , with ;

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 666767550ac75dbf56ec8b9843b43ecc4a7424ec)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:11 -06:00
Jonathan Druart 659eb8e474
Bug 42746: Sanitize age_field
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 9d3742b7d29cfed9b08327b5a36b190a575e0b6a)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:11 -06:00
Martin Renvoize 3f91a7a0b0
Bug 42747: (QA follow-up) Parameterize query, dedupe, and log rejections
The original fix only whitelisted image_name at the point it's saved
in edit-layout.pl, leaving the actual injection sink in create-pdf.pl
unparameterized. This adds defense-in-depth by fixing the query itself,
avoids a redundant duplicate call to get_all_image_names() (already
fetched earlier in the script as $image_names), and logs rejected
image_name values instead of silently discarding them so tampering
attempts leave an audit trail.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 5b9e06dafc78c7097ce4090b52fc63390df490fc)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:10 -06:00
Jonathan Druart facf9f7c8b
Bug 42747: Prevent SQL injection via image_name in edit-layout
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit afb01e704459f7adae7a780656b1eef5f9a011d7)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:09 -06:00
Jonathan Druart baedef6e14
Bug 42866: Prevent SQL injection in Koha::AddtionalContents->search_for_display
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 9ed242f2e3ccdb944bac87f9033a695eac187f5c)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:08 -06:00
Jonathan Druart 9c982058fc
Bug 42749: Prevent SQL injection from GetInvoices (order_by)
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit f2c2314baa29ef39b2a0743d7838eb439de08c17)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:08 -06:00
Martin Renvoize 6dfe5f4415
Bug 42847: (QA follow-up) Clear session state on all login_core exit paths
login_core only cleared leftover session state on the failed-auth
branch. The earlier 'SIP access not authorized' return (unknown
terminal id) left any account/ils/institution/policy/credentials from
a prior connection on a reused prefork worker in place.

Move the reset to the top of login_core so every non-success exit path
leaves clean state, and only a successful api_auth re-establishes the
session. This keeps the no-authenticated-state-until-auth invariant in
a single place rather than duplicating the key list per branch.

Also extend the unit test to cover the unknown-terminal path on a
reused worker.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit c375a5cd34e88b0a1d2d0109a523199ef9a6dc06)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:07 -06:00
Kyle M Hall e00a821a26
Bug 42847: Prevent previously authenticated user
After a SIP terminal logs in successfully, a later connection handled by the
same worker could bypass authentication entirely. login_core() stores the account
in the session before calling api_auth(), and left it in place when authentication
failed. The check in raw_transport() that is meant to drop unauthenticated connections
only looks at $self->{account}->{id}, which is now populated straight from the SIP config,
so the connection is treated as logged in.

Test Plan:
1) Create a SIP user with the username and password "term1"
2) Restart the SIP server
3) Apply the first patch
4) prove t/db_dependent/SIP/MsgType.t
5) Note the new test fails
6) Telnet to the SIP server port ( 6001 for ktd )
5) Send a successful login: 9300CNterm1|COterm1
6) Note you get a 941 back
7) Disconnect, then telnet to localhost 6001 again
8) Send a bad login: 9300CNterm1|COterm2
9) Note you get a 940 back, but the connection stays open
10) Send a patron information request, using a real patron cardnumber:
    6300120260615    110310          AOCPL|AA23529001000463
11) Note you get a 64 patron information response with the patron's data back,
    even though the login failed!
12) Apply the second patch
13) prove t/db_dependent/SIP/MsgType.t
14) Note the subtest now passes!
15) Restart all the things!
16) Repeat steps 4 to 10
17) Note the the patron info request returns no patron data and the connection is dropped!

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 b1f8256ab97913678aacd80c7d53deec72868876)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:06 -06:00
Kyle M Hall 5d742fa95c
Bug 42847: Add unit tests
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 d12f0bb06300b194376d70d91d76e76876b3f739)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:05 -06:00
Laura_Escamilla b7045022df
Bug 43019: Prevent access to pages belonging to another library (staff side)
To test:
1. Create two patron accounts:
    * One patron whose home library matches the library assigned to the page.
    * One patron whose home library is different from the library assigned to the page.
2. Create a new page with the following settings:
    * Display location = OPAC
    * Library = a specific library
    * Publication date = Today
    * Title and Content = minimally necessary content
3. Save the page.
4. Right-click the OPAC link and copy the link address.
5. Open the link in an incognito or private browser window without signing in.
    * Confirm that the library-specific page is not displayed.
    * Confirm that a 404 page is shown.
6. In the incognito window, sign in using the patron account whose home library matches the page library.
7. Open the copied OPAC link again.
    * Confirm that the page is displayed.
8. Sign out and sign in using the patron account whose home library does not match the page library.
9. Open the copied OPAC link again.
    * Confirm that the page is not displayed.
    * Confirm that a 404 page is shown.
10. Edit the page and change Library to All libraries.
11. Open the OPAC link in an incognito or private browser window without signing in.
    * Confirm that the page is displayed.
12. While signed in as each patron account, open the OPAC link again.
    * Confirm that the page is displayed for patrons from both libraries.
13. Create or edit a page with the following settings:
    * Display location = Staff interface
    * Library = a specific library
    * Publication date = Today
    * Title and Content = minimally necessary content
14. Sign in to the staff interface using a staff account whose library matches the page library.
15. Open the page from the staff interface.
    * Confirm that the page is displayed.
16. Sign out and sign in using a staff account whose library does not match the page library.
17. Open the staff page link directly.
    * Confirm that the page is not displayed.
    * Confirm that a 404 page is shown.
18. Edit the staff page and change Library to All libraries.
19. Open the page while signed in with staff accounts from both libraries.
    * Confirm that the page is displayed for both accounts.
20. Repeat the OPAC and staff interface checks using Display location = Staff interface and OPAC.
    * Confirm that the page is displayed in both interfaces for users from the matching library.
    * Confirm that users from a different library receive a 404 page.
    * Confirm that pages assigned to All libraries are available in both interfaces.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit bf5c5952105b5f881a1519b4f236c3d6d1b3d5ac)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:05 -06:00
David Cook bc5cd3a47c
Bug 43019: Fix branch comparison
This change fixes the branch comparison between the Page
and the user. Previously, it was doing a numeric "!=" and now it
does the string comparison "ne". Also, if no environmental variable
is set for the default branch, it defaults to an empty string
for anonymous (ie non-authenticated) users.

Test plan:
0. Apply the patch
1. restart_all
2. Log into the staff interface
3. Go to Tools
4. Click on "Pages"
5. Create a Page for All Libraries for the OPAC
6. Copy the "OPAC" link in the "Page URL" table cell and open it
in a private window or a separate browser
7. Note that the page appears
8. Now change the "Page" to a specific library
9. Revisit the URL in the private window
10. Note that it now gives a 404 error
11. Log into the OPAC with a user whose library matches the libary
of the Page
12. Note that now you can view the URL again
13. Change the library of the user or the Page
14. Note that you cannot see the Page now

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit dcbb779d8f39d4fe403e0661335c4abaa8e06a28)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:04 -06:00
Martin Renvoize a0059d4407
Bug 30233: (follow-up) Fix POD formatting codes in _apply_substitution
The angle-bracket delimiter notation in the POD prose (e.g.
PATTERN<DELIM>) caused the POD parser to treat the trailing letter of
each word as an unknown formatting code (N<>, T<>). Replace with
C<s/PATTERN/REPLACEMENT/FLAGS> and a parenthetical note that any
non-space delimiter is accepted.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 2a733f8093181c2aa880f8e56626de83277b64e8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:03 -06:00
Martin Renvoize 74258d36d4
Bug 30233: (follow-up) Add tests for _apply_substitution and combined modifiers
- t/C4/ClassSplitRoutine/RegEx.t (new): exercises _apply_substitution via
  the public split_callnumber interface — basic substitution, alternate
  delimiters, \n/$n backrefs, /gi combined flag, /e rejection, and malformed
  or uncompilable rules (including the new logger warn path).
- t/Koha/Regex/Replacement.t: extend the subst() helper to handle the ig/gi
  combined modifier and add a subtest that confirms case-insensitive global
  replacement and backref case preservation.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit a4511cf84303aea7ffff09cbd5345b777b9623bd)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:02 -06:00
Martin Renvoize 6daa66b589
Bug 30233: (follow-up) Log a warning when _apply_substitution discards a bad pattern
When eval { qr/$pattern/ } fails (e.g. the admin-configured s/// contains
an invalid regex), _apply_substitution previously returned silently with no
indication of why the rule was skipped. Add a Koha::Logger warn so the
failure appears in the log and is diagnosable without a debugger.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 79f23d017a5d6f611f37308cf6cca609a3e1b366)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:02 -06:00
David Cook da4ae85d61
Bug 30233: (follow-up) fix some errors reported by QA tools
Note: While the methodology in Koha::Regex::Replacement may seem
bespoke, it's similar to how Python's re.sub(pattern, repl, string)
regex substitution works.

Unit tests:
prove -v t/Koha/Regex/Replacement.t \
    t/SimpleMARC.t \
    t/db_dependent/MarcModificationTemplates.t \
    t/Labels_split_Regex.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit b8c554175f40b9cb1a31bff6d69c9eef1459df20)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:01 -06:00
Sanjar Tulkinov Anvar o'g'li 0d3185d28a
Bug 30233: Stop running call number split regexes as code
C4::ClassSplitRoutine::RegEx::split_callnumber ran the admin-configured rule
through eval "\$cn_item =~ $regex", executing arbitrary Perl. Parse the
rule as s<DELIM>PATTERN<DELIM>REPLACEMENT<DELIM>FLAGS, reject the /e flag,
compile the pattern with qr// and expand the replacement as data via
Koha::Regex::Replacement::expand_template, so the rule is applied without
eval.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit c76e2dcc3360e2c4a7d3123f5625c3426dfe207e)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:00 -06:00
Sanjar Tulkinov Anvar o'g'li e55fd0407f
Bug 30233: Stop running MARC modification replacements as code
Koha::SimpleMARC::_modify_values and Koha::Items::apply_regex wrapped the
user replacement in a string and ran it through s///ee, so an interpolation
block such as \@{[ system(...) ]} executed arbitrary Perl. Expand the
replacement as data via Koha::Regex::Replacement::expand_template under a
single /e instead, which keeps capture-group replacements working but never
runs the input as code.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 992a2642ca0cfe14a5fc0b13c828ce0658901ae8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:18:00 -06:00
Sanjar Tulkinov Anvar o'g'li e283e72131
Bug 30233: Add Koha::Regex::Replacement to expand replacements as data
Add a small single-pass helper, Koha::Regex::Replacement::expand_template,
that expands a user-supplied regex replacement string as data: numbered
backreferences ($1..$n, ${n}, the legacy \1..\9 form), named captures
($+{name}, ${name}) and the \n \r \t escapes. Every other character is
copied through literally, so \@{...}/${...} interpolation blocks end up as
text rather than being run. This is used to replace the s///ee double-eval.

The routine is fully qualified at the call sites rather than exported, and
case folding is left out to keep it small.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit ac067c0dbb743a0d7e0918e9dc419194620245e8)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-08-10 08:17:49 -06:00
Lisette Scheer 64bea0833a
Bug 41889: Update API to allow for null patron_id to be returned when listing checkouts
If a patron is deleted and there's a checkout associated with them, an api call to {url}/api/v1/checkouts?checked_in=true
will result in an error. This patch updates the api to allow for a null value to be returned for patron_id

To test:
Before applying the patch:

1. In Administration->System preference enable the RESTBasicAuth system preference.
2. Checkout an item to a patron (I used item barcode: 39999000000252 and patron barcode 23529000121682)
3. Check the item back in
4. In your brower go to {sandbox/ktdurl}/api/v1/checkouts?checked_in=1
5. Observe there is a patron ID in the JSON response
6. Delete the patron
7. Repeat step 4
8. Observe an error because a "null" value was returned
9. Apply patch
   Sandbox:
       In sandbox management Actions->Apply patches
       Once complete Actions->Build CSS and JS
       Actions->Restart services
   KTD:
       ktd --shell
       git bz apply 41889
       yarn build
       restart_all

10. Repeat step 4
11. Observe the checkout loads, but now has a null patron id.
12. Sign off
On KTD can also run the script: qa
to run tests.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:36 -06:00
Owen Leonard 3e8f6a1cd2
Bug 40610: Update OPAC templates to improve ease of translation
This patch updates the markup structure of some OPAC include files in
order to make the translation process easier.

To test, apply the patch and test that the correct strings are
translatable. In this example I'm testing fr-FR:

- Update a translation:

  > gulp po:update --lang fr-FR
  > perl misc/translator/translate update fr-FR

- Open the corresponding .po file for the strings pulled from
  the include file and locate the updated strings.

  - misc/translator/po/fr-FR-messages.po
    - opac-tmpl/bootstrap/en/includes/biblio-title.inc lines 4 & 5
    - opac-tmpl/bootstrap/en/includes/date-format.inc lines 4, 6, 8
    - opac-tmpl/bootstrap/en/includes/holds-table.inc lines 132 & 135
    - opac-tmpl/bootstrap/en/includes/masthead.inc line 498

  - misc/translator/po/fr-FR/fr-FR-opac-bootstrap.po
    - opac-tmpl/bootstrap/en/includes/biblio-title-head.inc
    - opac-tmpl/bootstrap/en/includes/holds-table.inc line 129 & 141

- Edit the "msgstr" string however you want (it's just for testing).
- Remove the "fuzzy, " text if it's there.
- Install the updated translation:

  > perl mis/translator/translate install fr-FR

- Switch to your translated language in the OPAC.
- Confirm that the strings you translated show up correctly on these
  pages:
  - The page title when viewing a bibliographic record detail page for
    a record without a title.
  - On a user summary page, under the hold tab:
    - A hold that is waiting
    - A hold that is in transit
- In Administration -> Identity providers define a new identity provider
  (it doesn't have to be functional).
  - In the OPAC, test that the "Log in with [identity provider]" link is
    correct in each of the three login forms:
    - The modal triggered from the link in the header
    - The form in the right-hand sidebar
    - The form you see if you navigate directly to a user-related page
      e.g. /cgi-bin/koha/opac-user.pl
- The changes to date-format.inc are only visible if JS is turned off
  because Flatpickr hides the format hint when it loads.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:35 -06:00
Lucas Gass cae44451ce
Bug 42909: Use createdRow to add back suspend class
To test:
0/ APPLY PATCH
1/ View a record with some holds on it
2/ Look at the holds table ( reserve/request.pl?biblionumber=32 )
3/ Suspend at least one hold and notice the 'suspend' class on the <tr>
4/ Unsuspend the hold and notice the class is removed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:35 -06:00
David Nind 94c785596a
Bug 42721: Update OPACAuthorIdentifiersAndInformation description and identifier labels
The patch updates the OPACAuthorIdentifiersAndInformation system preference
description so that identifiers are listed in alphabetical order.

It also updates the identifier labels in the system preference
description and what is shown in the OPAC:
- ScopusID --> Scopus ID
- WIKIDATA ID --> Wikidata ID

Test plan:
1. For the OPACAuthorIdentifiersAndInformation system preference, select
   "Identifiers (024$2$a)".
2. Note that in the system preference description the identifiers shown
   in the note are not in alphabetical order.
3. Add identifiers to an authority record in the 024$2 (the code for the
   identifier) and 024$a (the value for the identifier) subfields:
   - Example authority record for authority #483, Wall, Larry:
     . ISNI: isni, 0000000110763894
     . Scopus ID: scopus, 57221714177 (note: not his real Scopus ID)
     . VIAF ID: viaf, 91402770
     . Wikidata ID: wikidata, Q92597
4. View the OPAC authority record details page for an authority record
   with 024$2 and $024a information:
   - Example authority record for authority #483, Wall, Larry
     /cgi-bin/koha/opac-authoritiesdetail.pl?authid=483
5. In another tab view the author information tab for an OPAC
   bibliographic record details page where the author is linked to an
   authority record with 024$2 and $024a information:
   - Example bibliographic record details page for Programming Perl:
     /cgi-bin/koha/opac-detail.pl?biblionumber=262
6. Note for both steps 4 and 5 that:
   - Scopus ID is shown as ScopusID
   - Wikidata ID is shown as WIKIDATA ID
7. Apply the patch.
8. Refresh the OPAC authority record and bibliographic record pages.
9. Note that the identifier labels are now shown as Scopus ID and
   Wikdiata ID.
10. View the OPACAuthorIdentifiersAndInformation system preference
    description and note that the identifiers in the note are now
    listed in alphabetical order.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Olivia Reynolds <olivia.reynolds@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:34 -06:00
Kyle M Hall c05cc73acd
Bug 42664: Unit tests
Test Plan:
1) prove t/db_dependent/SIP/SIPServer.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:34 -06:00
Kyle M Hall 04c933be4c
Bug 42664: Update SIP2 config timestamp after storing the change
Koha::SIP2::Object::store bumps the timestamp before SUPER::store,
opening a race condition where a worker reloading during db update
transaction caches a config that is missing the new row.

Bump the timestamp after the row has been written so it is not advanced
ahead of the data it represents.

Test Plan:
1) prove t/db_dependent/Koha/SIP2/SIP2ModuleMigration.t
2) prove t/db_dependent/api/v1/sip2_accounts.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:33 -06:00
Kyle M Hall 9c67fde9bf
Bug 42664: Reload SIP2 configuration in every worker process
SIP2 accounts/institutions are stored in the database and the SIP server is supposed to reload its configuration when they change.

In practice, after adding or editing a SIP2 account, most SIP worker processes keep serving the old configuration (e.g. new accounts get "SIP access not authorized") until the SIP server is restarted ( I tested multiple times over 30 minutes ).

There seem to be two issues:
* L1 cache flushed too late. process_request() calls _config_up_to_date() before Koha::Caches->flush_L1_caches(). The SIP parent reads sip2_resource_last_modified into its own L1 cache at startup and never flushes it. Every worker inherits that stale L1, so a worker's first connection compares against the startup-era value instead of the current shared value.

* sip2_config_read_timestamp is stored in the shared (memcached) cache, but the configuration it tracks is per-process. When one worker reloads the config from the database, it bumps the shared timestamp, so every other worker then evaluates as "up to date" and never reloads its own stale in-memory config.

Test Plan:
1) Configure the SIP server with several worker processes (e.g. set
   min_servers and max_servers to 5) and confirm an existing SIP2
   account works
2) Add a new SIP2 account in the staff interface
3) Without restarting the SIP server, test the new account with misc/sip_cli_emulator.pl
4) Note the login fails
5) Apply this patch
6) Restart all the things!
7) Add another new SIP2 account
8) Without restarting the SIP server test this new account with sip_cli_emulator.pl
9) Note that the new account works!
10) prove t/db_dependent/SIP/SIPServer.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:33 -06:00
Jonathan Druart ea0d44d5f0
Bug 42654: Split on several lines for readability
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:32 -06:00
Martin Renvoize 29ac3a0b5e
Bug 42654: Restore section in OPAC course reserve detail title, breadcrumb and heading
When a course reserve has a section defined, the OPAC course detail page
should display it alongside the course name in the page title, breadcrumb,
and main heading. A previous change inadvertently removed this, leaving only
the course name in all three places.

This patch restores the conditional logic so that:
- If a section is set, the format "Course reserves for 'Course name' - Section"
  is used in the title, breadcrumb and <h1>.
- If no section is set, the existing "Course reserves for 'Course name'"
  format is preserved.

Test plan:
1. Enable the CourseReserves system preference.
2. In the staff interface, go to Course Reserves and create (or edit) a course
   that has a 'Section' value filled in (e.g. "Section A").
3. Add at least one item to that course reserve.
4. In the OPAC, browse to Course Reserves and click through to the detail
   page for that course.
5. Confirm that the browser tab title, the breadcrumb trail, and the page
   heading all show "Course reserves for '<course name>' - <section>".
6. Edit the course and clear the Section field.
7. Revisit the detail page and confirm that all three locations now show
   only "Course reserves for '<course name>'" with no trailing dash or
   section fragment.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:32 -06:00
Martin Renvoize 7385ac8eae
Bug 42645: Add tests for FallbackToSMSIfNoEmail with alternate email fields
Verify that the SMS fallback in ModSuggestion() respects EmailFieldPrimary
and EmailFieldPrecedence: a patron with smsalertnumber and an emailpro
address (but no borrowers.email) must receive the notice by email, not SMS.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:31 -06:00
Martin Renvoize 1d9237f6b6
Bug 42645: Use notice_email_address in ModSuggestion SMS fallback check
The FallbackToSMSIfNoEmail transport selection in ModSuggestion() was
checking $patron->email (the raw borrowers.email column) rather than
$patron->notice_email_address, which respects the EmailFieldPrimary and
EmailFieldPrecedence system preferences. Patrons with only emailpro or
B_email set were incorrectly receiving suggestion status-change notices
via SMS even when a valid email address was available.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:31 -06:00
Marcel de Rooy 59b9b74fc3
Bug 42321: Improve handling of empty Z3950/SRU search results
Skips the empty results; only reports the 'real failures'.
Includes adding a grand total of hits to the form.
Some preliminary work to remove the hardcoded 20 results per target
in the code.
This is not yet a complete solution, but an improvement. The code in
C4/Breeding needs a larger overhaul. (And there might be an issue on
the Folio server side at LOC.)

Test plan:
Without this patch:
Use the LOC SRU server (since if frequently generates errcode 61).
Try the same search several times in a row (with Go). You probably will
come across the "LOC SRU record x: 0" messages. (Random behavior.)
Apply this patch.
Repeat the above. Notice that you will sometimes see the error 61 (and the
tip to refresh) but not the code 0 messages.
Run t/db../Breeding.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:30 -06:00
Jonathan Druart a508813f8b
Bug 42453: Remove warning
[WARN] Use of uninitialized value $index_count in numeric ne (!=) at /kohadevbox/koha/about.pl line 899.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:30 -06:00
Jonathan Druart fc66bac9b0
Bug 42453: Prevent 500 on the about page if elastic is not runing
If the elastic countainer is stopped, the about page crashes with a 500
"No nodes are available"

Test plan:
Startup ktd with `--search-engine es8`
Go to the about page
Notice that the "Elasticsearch" line contains the version and "running"
Stop the es container: `docker start koha-es-1`
Refresh the about page
=> Without this patch you get the 500
=> With this patch applied the page is rendered with:
"Version: Unkown" and "Status: not running"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:29 -06:00
Martin Renvoize ec19d35ca9
Bug 41709: Fix EDIfact special character escaping in GIR segments and other fields
Apply encode_text function to all text fields in EDIfact Order messages
to properly escape special characters (?, ', :, +) that could break
message parsing. Previously only bibliographic description fields were
escaped, but GIR segments containing collection codes, branch codes,
item types, call numbers, and other data were vulnerable.

Test plan:
- Verify all existing tests pass
- Added comprehensive tests for special character escaping in GIR segments
- Confirmed that fields containing ?, ', :, + are properly escaped

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-13 16:10:21 -06:00
Janusz Kaczmarek b8a81cf73c
Bug 42159: Unit tests
Unit tests

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:52 -06:00
Janusz Kaczmarek 45981401e6
Bug 42159: With OPACAuthorIdentifiersAndInformation information lacking from field 110/111
When the OPACAuthorIdentifiersAndInformation system preference is
enabled, selected information about authors is displayed. However, if
the main author is a corporate body or a meeting/conference (MARC 21
fields 110/111), the information is not shown.

Test plan:
==========
1. Enable the display of identifiers in
   OPACAuthorIdentifiersAndInformation.
2. Add a 024 field with a VIAF ID to any corporate authority record,
   e.g.:
   024 7# $a 1234 $2 viaf
3. Use this authority heading as the main entry (110 or 111) in any
   bibliographic record.
4. In the OPAC view of that bibliographic record, verify that the Author
   information tab does not display information from the main entry.
5. Use the same heading as an added entry (710 or 711). In the OPAC
   view, verify that the Author information tab now displays information
   from the added entry, but still not from the main entry.
6. Apply the patch ; restart_all.
7. Verify that OPAC now displays information from the 110/111
   heading fields in the bibliographic record.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:52 -06:00
Adolfo Rodríguez 012d42f6a4
Bug 42825: ERM Local Title - Start date (started_on) not saved for package resources
The started_on field inside relationshipFields was missing indexRequired: true,
which is required to generate a unique DOM id per row and correctly bind the
flatpickr input to the Vue data model.

The date_to componentProp also needs indexRequired: true so it references the
correct indexed id of the ended_on field.

Additionally, add a null check in calendar.inc before accessing ._flatpickr
on the end date element to prevent a TypeError when the linked element is
not found.

To reproduce:
1. Go to ERM > eHoldings > Local > Titles
2. Create or edit a local title
3. Link it to a package and enter a Start date, End date and Proxy
4. Save
5. Reopen the title and click on the linked package row
=> Start date is empty; End date and Proxy are saved correctly

Apply patch, then:
1. Go to ERM > eHoldings > Local > Titles
2. Create or edit a local title
3. Link it to a package and enter a Start date, End date and Proxy
4. Save
5. Reopen the title and click on the linked package row
=> Start date is now saved and displayed correctly
=> No TypeError in the browser console when selecting a start date

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:51 -06:00
Fridolin Somers 2739a741fa
Bug 42640: Script search_for_data_inconsistencies.pl use binmode UTF-8
Like Bug 32012 for runreport.pl the script search_for_data_inconsistencies.pl should use binmode UTF-8 to correctly display datas.

Test plan:
1) Edit an authorised value to add some diacritics in code, in "LOC" for example
2) Edit an item to use this code
3) Delete this authorised value
4) Run misc/maintenance/search_for_data_inconsistencies.pl
=> Check you have a correct message with diacritics

Signed-off-by: Chris Mathevet <chris.mathevet@inLibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:51 -06:00
Jan Kissig 48e8411266
Bug 42357: Show patron details on holds table
This patch reintroduces patron details in 'Existing holds' table by using $patron_to_html.

test plan:
a) get a nice fresh ktd and navigate to /cgi-bin/koha/reserve/request.pl?biblionumber=262
b) place a hold for a patron f.e. 23529000035676
c) take a look at the Existing holds tables patron column which only shows the patrons card number.
d) apply patch and run 'yarn js:build'
e) refresh /cgi-bin/koha/reserve/request.pl?biblionumber=262
f) the patron column now should show patrons name
g) set system preference  HidePatronName  to 'Don't show'
h) go to /cgi-bin/koha/reserve/request.pl?biblionumber=262
f) the patron column now should only show the patrons card number.

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:50 -06:00
Paul Derscheid 93d9e0abe8
Bug 42231: (QA follow-up) Show item title in note and suspend-hold modals
The static 'hidden' attribute on #addNoteTitle and #suspendHoldTitle was never
removed, so the item title was populated but stayed invisible.

- Toggle 'hidden' off when a modal is populated with a title
- Toggle 'hidden' on when a modal is closed/cleared

Test plan:
- Enable AllowCheckoutNotes
- OPAC > Summary > Checked out: click 'Add note', confirm the item title shows
- OPAC > Summary > Holds: click 'Suspend', confirm the item title shows
- Reopen each modal on a different item, confirm the title updates
- Run an accessibility checker, confirm no empty-heading errors return

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:50 -06:00
Laura_Escamilla 74e3c9f987
Bug 42231: Fix accessibility issues in OPAC summary table
The OPAC summary page contains several accessibility issues including an unlabeled renew checkbox, unlabeled textarea in the add note modal, and empty headings rendered before JavaScript population.

Test plan:
1. Have a user with checkouts
2. Log into the OPAC and go to "Summary"
3. Notice the renew checkbox does not have a corresponding label
4. Apply the patch
5. Repeat steps 1-2
6. Confirm the renew checkbox now has a corresponding label

Additional accessibility fixes:
7. Open the "Report a problem" modal
8. Confirm the note textarea has a corresponding label
9. Confirm addNoteTitle and suspendHoldTitle are not exposed as empty headings
10. Run an accessibility checker and confirm the related errors are resolved
11. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:49 -06:00
Pedro Amorim b1834a75fd
Bug 42666: (RM follow-up): Tidy datatables.js
prove xt/js_tidy.t

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:48 -06:00
Jonathan Druart 0b3946c713
Bug 42666: OPAC counterpart
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
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>
2026-07-09 13:06:47 -06:00
Jonathan Druart a10ad06a93
Bug 42666: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
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>
2026-07-09 13:06:47 -06:00
Chris Mathevet 63611e5b58
Bug 42666: Parse kohaTable pageLength setting to int
Test plan :
01 - Clear your browser's local storage
02 - Go to Administration > Item types
03- You need to have more than 10 item types, add some if needed
04 - Observe "Showing 1 to 010 of XX" on top of the table
05 - Click on "next" button at the bottom of the table
06 - Nothing happens
07 - Go to Administration > Patron categories
08 - You need to have more than 20 patron categories, add some if needed
09 - Observe "Showing 1 to 020 of XX" on top of the table
10 - Click on "next" button at the bottom of the table
11 - Nothing happens
12 - Apply patch
13 - Clear your browser's local storage
14 - Repeat 2-5 and 7-10, observe that the leading 0 is gone, and that the
"next" button works

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>
2026-07-09 13:06:46 -06:00
Jonathan Druart 1fc43a783c
Bug 42611: Add missing raw filter
[FAIL] koha-tmpl/intranet-tmpl/prog/en/modules/members/readingrec.tt
   FAIL   filters
                asset_must_be_raw at line 172 (    [% Asset.js("js/members/readingrec.js") %])

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:45 -06:00
David Cook 97ea38692f
Bug 42611: Fix Javascript error when no Circulation history
This patch checks that "table" is defined before trying to access
the DataTable object for it.

This patch also moves the Javascript from the .tt file into a separate .js
file since it doesn't need any Template::Toolkit itself.

Test plan:
0. Apply the patch (and reset_all)
1. Log into the staff interface
2. Go to http://localhost:8081/cgi-bin/koha/members/readingrec.pl?borrowernumber=51
3. Open the console and see there is no longer a Javascript error
4. Check out an item like 39999000001310 to this user
5. Review the "Circulation history" and note that the DataTable appears
and there are no Javascript errors

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>
2026-07-09 13:06:44 -06:00
Jan Kissig 70820a2a3e
Bug 42582: Fix Uncaught TypeError on 'Place request with partner libraries'
This patch fixes an Uncaught TypeError on Place request with partner libraries (op=generic_confirm) when at least one partner library is available (primary email is set) and system preference ILLCheckAvailability is enabled.
Reason for this is an undefined object services.

Test plan:
a) start a ktd instance
 ktd --proxy --name main up -d
b) enable system preference ILLModule and ILLCheckAvailability
 http://main-intra.localhost/cgi-bin/koha/admin/preferences.pl?tab=interlibrary_loans
 ILLModule: 'enable'
 ILLCheckAvailability: 'check'
c) edit an ILL patron and set an Primary email address:
 http://main-intra.localhost/cgi-bin/koha/members/moremember.pl?borrowernumber=16 (Walker)
d) create an ill request (Interlibrary loans -> (Requests) -> New ILL request) and fill out mandatory data
e) open dev tools (F12 on Firefox) and go to 'Place requests with partners'
f) notice console error: Uncaught TypeError: can't access property "enabled", services[0] is undefined
g) apply patch and reload page 'Place requests with partners' -> no error

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>
2026-07-09 13:06:43 -06:00
Jan Kissig a866e3d380
Bug 42141: Fix Save and show/list/edit button on Vue SIP2 config
This patch fixes the behavior of the Save (Save and list, Save and continue editing, Save and show) button which is controlled via useBaseResource.navigationOnFormSave on
Accounts, Institutions and System preference overrides in Administration -> Self-service circulation (SIP2)

To test:
a) go to Administration -> Self-service circulation (SIP2)
b) create one of the given resources (institution, account, system preference override) and fill out mandatory fields
c) click either Save or Save and continue editing or Save and show
d) check that you always end up on the list.
e) apply patch and run 'yarn js:build' , reload your browser
f) repeat b) and c)
g) check that the desired view is now shown
  - show the edited/created resource
  - list all created resources
  - continue editing the resource

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:43 -06:00
Eric Phetteplace 3aa58e05a0
Bug 42116: e.preventDefault is a function, needs parentheses
This change is unnoticeable in the UI, so the patch does not need before/after testing, but assurance that everything works. The affected code is used in 3 places for holds (cancelling a hold from reserve/request.pl, circ/circulation.pl, and members/moremember.pl) and 2 for cataloging (two different functions on cataloguing/additem.pl).

For holds:
1. Create three holds on different bibs for a patron
2. On the same page where you create the last hold (reserve/request.pl), delete it
3. Circulation > Check out (circ/circulation.pl) > Check out a different title to patron
4. Switch to patron's Holds tab, delete a hold
5. Go to Patrons account (members/moremember.pl) > Holds tab, delete a hold
6. All holds should delete successfully

For cataloging:
1. Go to any bib > New > New item
2. Select "Add Multiple Copies of this Item", add a note
3. Confirm multiple items are created with the note
4. Enter a new note, then select "Save as template"
5. Confirm the item template exists & create an item using it

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>
2026-07-09 13:06:42 -06:00
Jonathan Druart cb65f74206
Bug 42803: Only perform the lookups when !$filter is true
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:41 -06:00
David Cook c60f67e089
Bug 42803: Fix case where filter is not supplied
This change fixes the case where "Any library" and "Any item type"
weren't validating.

Test plan:
0) Apply the patch
1) Set strict_sql_modes to 0 in koha-conf.xml and restart plack
2) Go to Reports > Items with no checkouts:
<staff_url>/cgi-bin/koha/reports/catalogue_out.pl
3) Leave Library set to "Any library"
4) Pick a specific item type that you know has items (e.g. "BK")
5) Note that you get results

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>
2026-07-09 13:06:40 -06:00
Lisette Scheer fc845e1093
Bug 42734: (QA follow-up) Remove integer class
The integer class appears to only lmit the with of the text box to 5em, making it difficult to see the full preference.
I removed the class for better readability.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:39 -06:00
Martin Renvoize 6b29d29cae
Bug 42734: Add autoMemberNumValue to patrons sysprefs UI with caution note
This patch adds autoMemberNumValue to the Patrons system preferences
page immediately below autoMemberNum, so superlibrarians can view and
correct a runaway counter without needing vendor support.

The entry carries a caution note explaining:
- the value is managed automatically and should not normally be changed
- Koha generates the next cardnumber from max(counter, live_db_max),
  so lowering the counter only has effect once the patron responsible
  for the inflated number has been deleted or had their cardnumber
  corrected first
- the risk of setting it too low: potential re-issuance of a previously
  used cardnumber, which may grant a new patron access to third-party
  services linked to the original holder

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:39 -06:00
Pedro Amorim 15783cbc0e
Bug 42002: Make 'replace' in screen_msg_regex not required
Default to empty string if null to ensure that leaving it blank produces the required outcome: the replace value is an empty string

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:38 -06:00
Fridolin Somers c7d8dbf473
Bug 42846: Koha::Database::DataInconsistency fix typo for biblioitemnumber in MARC
Check of biblioitemnumber in MARC uses $biblionumber instead of $biblioitemnumber

It returns for example :
Biblionumber 542620 has biblioitemnumber '542620' but should be '542620' in 090$a

Test plan (for MARC21 only) :
1) Choose a biblio record id=X
2) Change with SQL :
update biblio_metadata set metadata = UpdateXML( metadata , '//datafield[@tag="999"]/subfield[@code="d"]' , CONCAT('<subfield code="d">1111</subfield>') ) where biblionumber = X
3) Run audit on this record
4) Check message is correct

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>
2026-07-09 13:06:37 -06:00
Jonathan Druart 29f3b80e3a
Bug 42535: Remove .news_delete JS from the main page
The .news_delete elements have been removed on
  commit c7b576ad7e
  Bug 34478: Remove 'Delete' link for news on the homepage
- | <a class="news_delete" href="/cgi-bin/koha/tools/additional-contents.pl?op=delete_confirmed&amp;ids=[% koha_new.additional_content_id | html %]&amp;csrf_token=[% csrf_token | uri %]">Delete</a>

We can remove this code

Test plan:
Confirm that there is no "Delete" links to delete news on the main page

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-09 13:06:28 -06:00
Laura_Escamilla 406a79026a
Bug 42078: Allow vendor ID searches on serial subscription vendor search page
The serials vendor search currently only searches vendors by name.
This patch allows the search field to also match vendor IDs so
staff can locate vendors using either name or ID.

Test plan:
1. Go to Serials → New subscription.
2. Click to search for a vendor.
3. Search using part of a vendor name and confirm results appear.
4. Search using an exact vendor ID and confirm the correct vendor is returned.
5. Sign off and have a wonderful day! :D

Signed-off-by: Angela Berrett <angela.berrett@familysearch.org>
Signed-off-by: Angela <angela.berrett@familysearch.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:52 -06:00
Owen Leonard 430bfcd76b
Bug 42641: JavaScript errors on patron attribute types admin page
This patch fixes some JavaScript errors on the patron attributes page.
Vanilla JS doesn't automatically handle null variables like jQuery does.

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

- If necessary, add multiple attribute types.
- The table listing all attributes should be displayed as DataTable and
  there should be no errors in the console.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:51 -06:00
Jonathan Druart 1640dc10c7
Bug 42550: Remove JS error from report list if none exist
If there are no saved SQL reports, the "rtable" variable is empty and
triggers an error when called later.
Uncaught TypeError: can't access property "DataTable", rtable is
undefined

Test plan:
/cgi-bin/koha/reports/guided_reports.pl?op=list
Without reports
=> Without this patch there is a JS error in the console
=> With the patch the error is gone

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:51 -06:00
Laura_Escamilla 18f1e259e2
Bug 42531: Allow repeatable field values to wrap
Test plan
1. Go to Administration → MARC bibliographic frameworks.
2. Edit Books, Booklets, Workbooks (BKS) framework.
3. In tag 508, ensure subfield $a is visible in the editor.
4. Open a bibliographic record (e.g. /cgi-bin/koha/catalogue/detail.pl?biblionumber=90).
5. Edit the record and add a long block of text to 508$a (e.g. extended production credits with many names and separators).
6. Save the record and view it in the staff interface.
    * Before patch: The Production Credits field does not wrap and overflows off the page.
7. Apply the patch and run yarn build.
8. Refresh the record detail page.
    * After patch: The Production Credits field wraps correctly within the page.
9. Confirm that shorter repeatable fields still display inline with separators (|) as expected.
10. Sign off and have a great day!

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:50 -06:00
Lucas Gass a6c727a565
Bug 42397: DBRev 26.05.01.001
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:50 -06:00
Pedro Amorim 6530cf6564
Bug 42397: Atomicupdate
1) Enable ILLModule and IllLog system preferences.
2) Install a metadata enrichment plugin that supports ILL batches (restart plack if needed), e.g.:
  https://github.com/openfifth/koha-plugin-api-pubmed/releases
3) Visit ILL module:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl
4) Click the 'New ILL requests batch' button
5) Enter any name, cardnumber '42' and any library. Click 'Continue'
6) Add a pubmedid (e.g. '123') and click process identifiers.
7) Click 'Add items to batch' and 'Finish and view batch'.
8) Visit the ILL request we just created:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?op=illview&illrequest_id=1
9) Confirm it explodes with a nasty 500 error
10) Apply patches, updatedatabase and restart plack.
11) Repeat 8). Confirm it no longer explodes
12) Click' ILL request log'. Confirm it shows the correct log(s) relative to this ILL request
13) Visit the log viewer and query for 'Interlibrary loans':
http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?src=&user=&modules=ILL&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
14) Confirm only ILL requests are shown, do the same for ILL batches:
http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?do_it=1&src=&user=&modules=ILL_BATCHES&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
15) Confirm only ILL batches are shown
16) Run all ILL batch relevant tests:
prove t/db_dependent/Koha/ILL/Batch*

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:49 -06:00
Pedro Amorim daacc3ec26
Bug 42397: Add and update ILL batch related tests
Add new tests to assert the usage of the new ILL_BATCHES action_logs modulename.
Update existing tests to utilize real log_something instead of mocking it.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:49 -06:00
Pedro Amorim 5b77e44b3c
Bug 42397: Add 'ILL_BATCHES' action_logs modulename
Update ILL/Batch.pm and ILL/Batch/Status.pm to utilize this new modulename instead

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:48 -06:00
David Cook 15a30e13d0
Bug 42202: Unfilter branches for OPAC search, news, and most popular
This change unfilters the branches for OPAC search, news, and most popular.
This is most noticeable when using the IndependentBranches system preference.
This change aligns the UX for both logged in and not logged in users

__SETUP__
- Do not apply the patch yet
- Log into Koha
- Go to Administration > System preferences
- Search for OpacAddMastheadLibraryPulldown system preference
- Change it to "Add" and save the preference
- Search for IndependentBranches system preference
- Set it to "Yes" and save the preference
- Search for OpacTopissue system preference
- Set it to "Allow" and save the preference
- Search for OpacNewsLibrarySelect system preference
- Set it to "Show" and save the preference
- Go to Patrons
- Create or modify a patron with a username/password but no superlibrarian
permission

__TESTS__
- Go to the OPAC (and logout if you're already logged in)
- Note that the library pull down saying "All libraries" is present
in the masthead
- Log in as your unprivileged user
- Note that the library pull down saying "All libraries" is now hidden
- Go to Advanced search and note there's no "Location and availability" option
- Go to "Most popular" and note there's no "From:" library filter
- Go to the home page and note it does not show a library selector for news

- Apply the patch

- Refresh the page
- Note that the library pull down saying "All libraries" is present
in the masthead
- Note that the "From:" library filter now appears
- Go to Advanced search and note "Location and availability" now appears
- Go to the home page and note it now shows a library selector for news

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:48 -06:00
Owen Leonard b955afac62
Bug 42128: Remove event attribute from admin-icon-selection.inc
This patch removes the "onmousedown" event attribute from
admin-icon-selection.inc, an include used in itemtypes.tt and
authorised_values.tt. I took this opportunity to create separate JS
files for both of those templates and added the event handler to the JS.

To test, apply the patch and go to Administration.

- Go to Item types and add or edit an item type.
  - In the icon selection section, select one of the built-in icons.
  - Switch to the "Remove image" tab.
  - Click the text input. The corresponding radio button should be
    selected.
- Go to Authorized values and perform the same test.

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>
2026-07-07 16:07:47 -06:00
Kyle M Hall 27e34e630c
Bug 36988: Update unit tests
These highlight why we may have made this choice. With only 'edit_borrowers' you can make
changes, but you cannot view your changes as you aren't allowed to see other borrowers.

Test now pass, but not sure if this answers the question of why we did it this way, or if there is more
work here. Should you be able to get back the fields that you updated at the least, to confirm your changes? Or
should we send a success with a message that you can't see the data?

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:47 -06:00
Kyle M Hall 9348047f10
Bug 36988: Change permission
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:46 -06:00
Owen Leonard 577f312f89
Bug 41803: Improvements to consistency in basket groups template
This patch makes some updates to the basket groups template:
- Buttons in a table's "actions" column should have the class "btn
  btn-default btn-xs"
- Adding icons to action buttons
- Group export options (CSV and PDF) under button dropdown
- Use class instead of ID as hook for closing and deleting basket
  groups. This prevents duplicate element IDs on the page.
- When there is no EDIFACT configuration, disable the "Generate EDIFACT
  order" button and put the error message in a tooltip.
- Style warning message as Bootstrap alert

To test, apply the patch and go to Acquisitions and locate a vendor.

- View the vendor details.
- Click "Basket groups" in the sidebar.
- Create multiple basket groups if necessary.
- Test various operations in the list of open groups:
  - Close and export PDF
  - Edit
  - Delete
- Test the actions for a closed basket group:
  - Reopen
  - Export -> CSV and Export -> PDF
- Enable the EDIFACT system preference but do not add any EDI accounts.
  - Under the "Closed" basket group tab the "Generate EDIFACT order"
    buttons should be disabled, and hovering over each one should
    trigger a popup warning that there is no EDIFACT configuration for
    the vendor.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:46 -06:00
Lucas Gass 5c4df77a79
Bug 41070: (QA follow-up) Tidy
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:45 -06:00
Baptiste 956b039907
Bug 41070: Fix selector in addorderiso validator
Steps to reproduce, do not apply patch
1 - Go to any record and click save -> marc (Unicode/utf8)
2 - Go to any vendor and add a basket
3 - In your acquisition grid, make sure "barcode" is mandatory
4 - In this basket, create a new order from file, use the generated file and wait for the end of the import. Click on add staged files to basket.
5 - Select the record, make sure no Fund or barcode is filled, neither in first or third tab.
6 - Validate: There will be an error due to the missing barcode. If
there is another error, fill the missing part of the form.
7 - Apply patch, repeat 6, the error will be due to the missing fund
8 - Select a fund in the first panel -> the error will be due to the
missing barcode
9 - Select a fund in the third panel -> the error will be due to the
missing barcode

Note: the point about the barcode are not relevant for the bug, they are
just here to prevent the submission of the form. And avoid repeating
steps 4-5.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:45 -06:00
Martin Renvoize 539e160383
Bug 40225: Send stockrotation report even when all items are stationary
The stockrotation cronjob was only sending email reports when there were
actionable items (items that needed to be moved between branches). This
meant that libraries would not receive any notification when all items
in their rotation were stationary, even when the --send-all flag was
passed to the script.

This patch modifies the report logic to also send reports when there are
only stationary items, ensuring libraries have visibility into the complete
status of their stock rotation items.

Test plan:
1. Set up a stock rotation rota with items that are all stationary
   (items that should stay at their current branch)
2. Run the stockrotation cronjob: perl misc/cronjobs/stockrotation.pl
3. Without this patch: No email report is generated
4. Apply this patch
5. Run the cronjob again
6. Verify that an email report is now generated showing the stationary items
7. Test with mixed scenarios (some actionable, some stationary items)
8. Confirm reports are sent in all cases where items exist in the rotation

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:44 -06:00
Olivia Reynolds 7edaba25d9
Bug 12757: Set float type on OpenDocument cells
Test plan:
1. Navigate to Reports > Create from SQL.
2. Create a new report with a name and the following SQL query:

SELECT 'test', 1, '2'

3. Run the report.
4. Under the Download dropdown, select "Open Document Spreadsheet".
5. Verify a ".ods" file is downloaded.
6. Open the downloaded spreadsheet.
7. Verify the two number-like columns are handled as such. This depends
   on your choice of spreadsheet software:
   - In Excel, the numbers should not have a leading single quote in the
     edit-bar when selected.
   - In Google Sheets, the numbers should be right-aligned, and
     selecting both of them should display their sum in the bottom
     toolbar.
8. Run `prove t/Koha/Util/OpenDocument.t` and verify all tests were
   successful.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:44 -06:00
Jonathan Druart 63bef24e1d
Bug 42802: Update kohastructure.sql
Test plan:
root@kohadevbox:koha$ perl /kohadevbox/misc4dev/run_tests.pl --run-db-compare-only --compare-with v24.05.00
should pass with this patch

Without this patch you get:
Running [diff /tmp/current_db.sql /tmp/upgraded_db.sql && diff -q /tmp/current_db.sql /tmp/upgraded_db.sql || { echo "ERROR - DB structures are not identical" && exit 1; }]...
7207c7207
< /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:43 -06:00
Tomás Cohen Arazi cf825d3829
Bug 42739: Add CSRF-TOKEN header to OPAC ratings.js fetch call
The ratings.js file uses raw fetch() to POST to the REST API without
including the CSRF-TOKEN header. This will fail once CSRF enforcement
is enabled on the REST API.

Test plan:
1. Apply patch
2. Enable star ratings in OPAC (StarRatings syspref)
3. Log in to the OPAC
4. Rate a biblio using the star widget
=> SUCCESS: Rating is saved without errors
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: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 16:07:36 -06:00
Nick Clemens d829ac3219
Bug 42659: Use biblio-title.inc for displaying title at checkin
Currently at checking we display the number and parts all grouped,this is different than
other places in Koha, we should use the same include here

To test:
1 - Edit a record and set the title like:
    245 1 4 ‡aThe passion according to G.H. ‡bsubtitle ‡nnumber1 ‡ppart1 ‡nnumber2 ‡ppart2 /‡cClarice Lispector ; translation by Ronald W. Sousa.
2 - Enable syspref 'ShowAllCheckins'
3 - Checkin an item from that record, display is like:
    The passion according to G.H. subtitle number1 number2 part1 part2 /
4 - Apply patch
5 - Check in again
6 - Display is now:
    The passion according to G.H. subtitle number1 part1 number2 part2 /
7 - Sign off!

Signed-off-by: Eric Garcia <cubingguy714@gmail.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 12:58:59 -06:00
Owen Leonard 2ab181a7af
Bug 42707: Malformed Bootstrap dropdown in reports output
This patch updates the SQL report results markup in order to make sure
Bootstrap dropdowns are formatted correctly.

To test, apply the patch and locate or create a report which returns
patron card number, e.g.

SELECT cardnumber, borrowernumber FROM borrowers LIMIT 10

In the results, test the menus triggered when you click a card number or
a borrowernumber. The menu in the cardnumber column should be
formatted like the menu for borrowernumber.

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>
2026-07-07 12:58:58 -06:00
Andrii Nugged 187c4fbb2c
Bug 42702: Fix malformed cancel links on parcel.pl
The pending orders table builds the two cancel order links with the
link text concatenated immediately after the invoice_id value. This
leaves the href attribute unclosed and makes the browser absorb the
link text and following escaped HTML into the href.

Add the missing separator after invoice_id for both cancel link builders.

Test plan:
1. Create a closed acquisition basket with a pending order.
2. Open an invoice receive page for the vendor.
3. Inspect the Cancel order link in the pending orders table.
4. Confirm the href ends after invoiceid and the link text is outside the href.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 12:58:51 -06:00
Lucas Gass dacce7d3fc
Update release notes for 26.05.01 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:54 -06:00
Lucas Gass 8f5136e243
Increment version for the 26.05.01 release
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:54 -06:00
Lucas Gass bacea9025b
Bug 42795: Do not try to update ES mappings if not using ES patch for main
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:53 -06:00
Kyle M Hall 753bc3c0da
Bug 42862: Use current-relative dates in bookingsModalDatePicker_spec.ts
The booking date picker tests fake "today" with cy.clock and create bookings on
hardcoded June 2026 dates. The modal fetches existing bookings from from
the api and that query uses the real current time, not the fake one in the browser.
Once the real date passes the hardcoded dates, the server stops returning those bookings (
they are now in the past ), so the modal sees no conflicts and assertions such as "the date
should have the leadDisable class" fail.

This patch anchors each test's "today" to the start of next month based on the current
date so the bookings always stay in the future and are returned no
matter when the test runs.

The 'any item' test also sets cy.clock before its second page reload
because the freeze does not survive the reload.

Test Plan:
1) On main run:
   yarn cypress run --spec t/cypress/integration/Circulation/bookingsModalDatePicker_spec.ts
2) Note the test fails
3) Apply this patch
4) Run the command again
5) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:52 -06:00
Jonathan Druart e00ba8d1aa
Bug 42735: Fix SQL injection
This change fixes an SQL injection in the circulation stats.

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/issues_stats.pl
3. Try out a bunch of options
4. Try to break it
5. Note that you just get output and no errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:51 -06:00
Jonathan Druart e54a0d3d41
Bug 42369: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:51 -06:00
David Cook b2f42dda9e
Bug 42369: Fix SQL injection
This change fixes SQL injection in bor issues top

Test plan:
0. Apply patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/bor_issues_top.pl
3. Try out all the different options
4. Try to break it
5. Note that you get output and no errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:50 -06:00
Jonathan Druart 76a32366c5
Bug 42368: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:49 -06:00
David Cook 4b9ea0c3a9
Bug 42368: Fix SQL injection in issue avg stats
This change fixes SQL injection in issues avg stats

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/issues_avg_stats.pl
3. Try out all the different options
4. Try to break the report
5. Note that you always get output instead of errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:49 -06:00
Jonathan Druart ddfb6349d7
Bug 42363: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:48 -06:00
David Cook 4df08e5129
Bug 42363: Fix SQL injection
This change cixes an SQL injection in cat stats

Test plan:
0. Apply patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/catalogue_stats.pl
3. Try out different values
4. Try to break the report
5. Note that you just get output and not errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:47 -06:00
Jonathan Druart a9f2bec7a4
Bug 42360: Move separator validation to C4::Context::csv_delimiter
So we protect other calls

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:46 -06:00
David Cook cd8608ab4e
Bug 42360: Also validate sep param
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:46 -06:00
David Cook d2e7b1e8e7
Bug 42360: Fix SQL injection
This change fixes an SQL injection in the acq stats.

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/acquisitions_stats.pl
3. Try out a bunch of options
4. Try to break it
5. Note that you just get output and no errors
NOTE: In a fresh KTD you likely don't have any actual statistics

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-07-07 07:47:39 -06:00
Jonathan Druart 67ff112866
Bug 42708: (bug 41566 follow-up) Restore display of itemtype images
> - [% UNLESS noItemTypeImages %]
> + if (prefs.noItemTypeImages){

This is wrong!

Test plan:
Go to /cgi-bin/koha/catalogue/detail.pl?biblionumber=4
Notice that with this patch the "Book" icon is displayed
Turn off noItemTypeImages and reload
=> It's now hidden!

Without this patch the behavior is reversed

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>
2026-06-15 18:12:25 -06:00
Julian Maurice 10a3768991
Bug 42741: Fix plural form in lists
Test plan:
1. Go to Lists
2. Create three lists called:
   - Test 0 items
   - Test 1 item
   - Test 2 items
3. Add one item in the "Test 1 item" list and add two items in the "Test
   2 items" list
4. Go to lists and view the list of lists
   --> In the Contents column, "Test 0 items" should be "0 items"
   --> "Test 1 item" should be "1 item"
   --> "Test 2 items" should be "2 items"

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:24 -06:00
Artur Mickiewicz a202ff3d33
Bug 41987: Remove duplicate searchresult-breeding id
The searchresult-breeding id was present on both the wrapper div and
the "no results" alert element, causing duplicate id usage in the DOM.

This patch ensures the id is only used once on the parent container
and keeps the anchor link functional.

Signed-off-by: Artur Mickiewicz <davno_pora@mail.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:24 -06:00
Laura_Escamilla 946ce25c52
Bug 42234: Add text to checkout history cover image table header
1. Enable at least one cover image service, such as GoogleJackets or
   OPACAmazonCoverImages.
2. Log in to the OPAC as a patron with checkout history.
3. Go to your account > Checkout history.
4. Inspect the checkout history table header.
5. Confirm the cover image column header contains "Cover image".
6. Confirm the table still displays correctly.
7. Confirm sorting/export behavior is unchanged for that column.
8. Sign off and have a wonderful day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:23 -06:00
Pedro Amorim 21dd3412f6
Bug 42485: (RM follow-up): Tidy
prove xt/perltidy.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:23 -06:00
Martin Renvoize 40b9e8abd5
Bug 42485: (QA follow-up) Add explicit JSON::PP import and test coverage
Add explicit `use JSON::PP ()` to Elasticsearch.pm and the test file
to make the dependency on JSON::PP::false visible rather than relying
on YAML::XS loading it as a side effect.

Extend the get_elasticsearch_mappings() test subtest to assert that
date_detection and numeric_detection are disabled at the mapping root.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:22 -06:00
Tomás Cohen Arazi 78850b4a15
Bug 42485: Disable date and numeric detection on ES mappings
When ElasticsearchMARCFormat is set to ARRAY, the marc_data_array
field uses dynamic:true mapping. Elasticsearch auto-detects field
types based on the first document indexed. If a MARC subfield value
looks like a date (e.g. 2024-01-15 in 952$e), ES locks that
subfield to type date. Subsequent records with non-date values in
the same subfield fail to index with a document_parsing_exception.

This patch sets date_detection and numeric_detection to false at
the mapping root level, so ES treats all dynamically mapped fields
as text instead of guessing types.

Test plan:
1. $ ktd --search-engine es8 --name test-es --proxy up -d
   $ ktd --name test-es --wait-ready 180
   $ ktd --name test-es --shell
2. k$ koha-mysql kohadev -e "UPDATE systempreferences
     SET value='ARRAY'
     WHERE variable='ElasticsearchMARCFormat';"
   k$ echo 'flush_all' | nc -q1 memcached 11211
   k$ koha-mysql kohadev -e "DELETE FROM biblio_metadata;
     DELETE FROM items; DELETE FROM biblioitems;
     DELETE FROM biblio;"
   k$ rebuild_elasticsearch.pl -d -r --biblios
3. Add a record with date-like 952$e and index it:
   k$ koha-shell kohadev -p -c 'perl -MC4::Biblio
     -MMARC::Record -MMARC::Field -MKoha::Item -e "
     my \$r = MARC::Record->new();
     \$r->append_fields(
       MARC::Field->new(q{245},q{1},q{0},a=>q{Record one}),
       MARC::Field->new(q{942},q{ },q{ },c=>q{BK}));
     my (\$bn) = C4::Biblio::AddBiblio(\$r, q{});
     Koha::Item->new({biblionumber=>\$bn,
       biblioitemnumber=>\$bn, homebranch=>q{CPL},
       holdingbranch=>q{CPL}, itype=>q{BK},
       booksellerid=>q{2024-01-15}})->store;
     print qq{Added biblio \$bn\n};"'
   k$ rebuild_elasticsearch.pl --biblios -bn <biblionumber>
4. Check the mapping for 952$e:
   k$ curl -s es:9200/koha_kohadev_biblios/_mapping/field/\
     marc_data_array.fields.952.subfields.e
=> FAIL: type is "date" (should be "text")
5. Add a record with non-date 952$e and index it:
   k$ koha-shell kohadev -p -c 'perl -MC4::Biblio
     -MMARC::Record -MMARC::Field -MKoha::Item -e "
     my \$r = MARC::Record->new();
     \$r->append_fields(
       MARC::Field->new(q{245},q{1},q{0},a=>q{Record two}),
       MARC::Field->new(q{942},q{ },q{ },c=>q{BK}));
     my (\$bn) = C4::Biblio::AddBiblio(\$r, q{});
     Koha::Item->new({biblionumber=>\$bn,
       biblioitemnumber=>\$bn, homebranch=>q{CPL},
       holdingbranch=>q{CPL}, itype=>q{BK},
       booksellerid=>q{My Vendor Name}})->store;
     print qq{Added biblio \$bn\n};"'
   k$ rebuild_elasticsearch.pl --biblios -bn <biblionumber> -v -v -v
=> FAIL: document_parsing_exception - failed to parse date field
   [My Vendor Name]
6. Apply this patch, repeat from step 2
7. Repeat step 4:
=> SUCCESS: type is "text"
8. Repeat step 5:
=> SUCCESS: No indexing errors
9. Sign off :-D

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:22 -06:00
David Cook 0a44bf4e8e
Bug 42612: Change button colours in manage-marc-import.pl
This patches changes the button colours in manage-marc-import.pl
so that "Import this batch into the catalog" is more prominent,
and "Apply different matching rules" is less prominent.

Test plan:
0. Apply the patch
1. Go to a bib record like
http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=29
2. Click "Save" and choose "MARCXML"
3. Go to http://localhost:8081/cgi-bin/koha/tools/stage-marc-import.pl
4. Click "Upload file"
5. Choose "MARCXML" for the "Format:"
6. Change "Check for embedded item record data" to "No"
7. Click "Stage for import"
8. Wait for the staging to complete. This will be show when the status
bar hits "100% Finished" and a "View batch" link appears.
9. Click "View batch"
10. Note that "Import this batch into the catalog" is a prominent yellow
button with black text
11. Note that "Apply different matching rules" is a less prominent dark grey
button with white text

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:21 -06:00
Owen Leonard ea10c84e80
Bug 42684: Capitalization: No Barcode
This patch corrects the capitalization of the phrase "No Barcode" and
several other instances of incorrect capitalization.

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>
2026-06-15 18:12:21 -06:00
Jonathan Druart 91f2e47f49
Bug 41992: Do not restore pagination offset
When "Save configuration state on page change" is enabled, the page
number is kept in localStorage and restored.

To prevent that we can set the attribute "start" of the state to 0.

Test plan:
Go on a page with several pages, go to another page than the first one
Go the another view and get back
Without this patch the last visited page was preserved when returning to the previous view
With this patch applied the view now defaults to the first page

Note that this does not affect shareable links.

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:20 -06:00
Owen Leonard efff7c7046
Bug 42229: (follow-up) Restore card number <label> when necessary
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:20 -06:00
Owen Leonard b9bff3256a
Bug 42229: (follow-up) Markup correction
The "Street type" fields had double "name" attributes.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:19 -06:00
Laura_Escamilla c416062ef0
Bug 42229: Avoid using form labels for static values in opac-memberentry.tt
The OPAC personal details page uses <label> elements in some places
where no visible form control is present. In these cases, the content
is display-only and the label is not associated with an interactive
field.

Replace those labels with non-form markup for static values while
preserving existing labels for actual form controls.

Test plan:
1. Log in to the OPAC and go to the "Personal details" page
2. Inspect display-only values such as Username and Expiration date
3. Confirm <label> elements are used for content that is not part of a
   form control
4. Apply the patch
5. Log in to the OPAC and go to the "Personal details" page
6. Confirm display-only values such as Username and Expiration date no
   longer use <label> elements
7. Confirm other static display-only values updated by the patch also no
   longer use <label> elements
8. Confirm actual form controls, such as text inputs and selects, still
   have proper associated labels
9. Run an accessibility checker and confirm the related label misuse
   issues are resolved
10. Sign off and have a great day!

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:19 -06:00
Laura_Escamilla a8e7087ce8
Bug 42232: Add missing legend to action fieldset in OPAC
To test:
1. Log on to the OPAC and go to "Messaging" page.
2. The fieldset that contains the Save/Cancel button is missing a legend.
3. Apply the patch and refresh the "Messaging" page.
4. Notice that the fieldset now contains an accessible legend.
5. Sign off and have a great day!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:18 -06:00
Laura_Escamilla e07b92e48a
Bug 42233: Add accessible text to empty purchase suggestions table header
The first header cell in the OPAC purchase suggestions table contains
only an icon, leaving the <th> without accessible text. This causes
the column header to be announced unclearly by assistive technology
and can be flagged by accessibility testing as an empty table header.

Add visually hidden text to the header so the column has a meaningful
accessible name while preserving the current visual display.

Test plan:
1. Log in to the OPAC
2. Make a purchase suggestion
3. In the suggestions table, the first <th> contains no text
4. Apply the patch
5. Repeat steps 1-2
6. Inspect the suggestions table header
7. Confirm the first <th> now contains visually hidden text describing
   the checkbox selection column
8. Confirm the info icon still appears and its tooltip still works
9. Run an accessibility checker or inspect the DOM and confirm the
   empty table header issue is resolved

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:18 -06:00
Nick Clemens 183d1cf211
Bug 42245: Prevent preselecting sort values for guarantor search
av-build-dropbox.inc takes a 'default' param

When building the patron we are setting this to the value of sort1, then sort2
This seems to remain cached, and when we build the guarantor search the sort2 value is used as the default
This patch ensures we pass a blank default to the modal

To recreate:
1 - Define 2 values for 'BSort1' in Administration -> Authorised values
2 - Define 2 values for 'BSort2' in Administration -> Authorised values
3 - Find a borrower who is eligible to have a guarantor
4 - Edit patron and assign values to sort 1 and 2 for this borrower and save
5 - Edit patron again and click 'Add guarantor'
6 - Note that sort 2 is populated
7 - Apply patch
8 - Reload borrower
9 - Confirm sort1 and 2 are empty

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:17 -06:00
Laura_Escamilla fda196565d
Bug 42299: Add accessible label to star rating select in OPAC
To test:
1. Set  OpacStarRatings so it shows on the OPAC detail page
2. Notice star ratings has no associated label element
3. Apply the patch and refresh the OPAC detail page you were viewing.
4. Notice that the star ratings now have an accessible label.
5. Sign off and have a great day. :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:17 -06:00
Johanna Räisä 07325de908
Bug 42395: Fix missing translations on load_patron_holds_table
This patch adds missing translations for the load_patron_holds_table function.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:16 -06:00
Saiful Amin 10fc3f3c64
Bug 29587: Test mail option in SMTP servers
Edit: Added CSRF protection.

This patch adds a "Send test email" action to the SMTP servers
administration page. It sends a test message to the address in
the KohaAdminEmailAddress system preference and reports success
or failure to the user.

Test plan:
1. Add a valid SMTP server in Administration → SMTP servers.
2. Set KohaAdminEmailAddress to a valid email address.
3. Apply the patch.
4. Click "Send test email" in the Actions column.
5. Confirm that Koha reports "Test email sent successfully".
6. Edit the SMTP server and change the domain to an invalid one.
7. Click "Send test email" again.
8. Confirm that Koha reports failure.
9. Sign off.

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:16 -06:00
Matt Blenkinsop b9d6d16f8e
Bug 41605: Fix incorrect default key
Test plan:
No behavioural change but it is an inconsistency in the code
1) Look in base-resource.js at the newResource computed method
2) Confirm that the code in the first conditional block is looking for 'hasOwnProperty("defaultValue")'
3) Check the new patch and confirm that the code has been changed from 'value: false' to 'defaultValue: false' to match this

There is no behaviour change as boolean fields default to false within the framework but we should remove this inconsistency

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>
2026-06-15 18:12:15 -06:00
Emmi Takkinen c28b9b86ca
Bug 41829: Set correct id to copied Tag editor button
When one copies MARC field, Tag editor button on fields
containing value builder plugin have same id as original
MARC field. This patch sets correct id to these buttons.

To reproduce:
1. Find MARC framework which has value builder plugin
   attached to one of its fields.
2. Navigate to Cataloging -> New record.
3. Navigate to MARC field with plugin attached to it.
4. Inspect fields Tag editor button with browsers developer
   tools.
   -> Check the id of Tag editor button.
5. Copy the field and check id of copied Tag editor button.
   -> Notice that it has the same id as in original field.
6. Apply this patch.
7. Referesh the page, navigate back to field and copy it.
8. Inspect Tag editors id.
   -> Id should now be same as in other copied fields.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:15 -06:00
Jonathan Druart bba0146b3d
Bug 42781: Do not modify window.name
https://developer.mozilla.org/en-US/docs/Web/API/Window/name

Test plan:
0. Do not apply this patch (you can apply the first one)
1. Go to /cgi-bin/koha/members/members-home.pl
2. Open the console
3. type "window.name", it's ""
4. Run a patron search, "d"
5. window.name is now "Duffy, Joann name"
6. Apply this patch, try again
7. window.name is still an empty string

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:14 -06:00
Magnus Enger 7b02715f4f
Bug 42781: Declare 'name' before using it in patron-format.js
The code in koha-tmpl/intranet-tmpl/prog/js/patron-format.js uses
a variable called 'name' without declaring it first. The declaration
was removed in bug 32176. No deterioration of functionality has been
discovered in connection with this, but the declaration should be
restored.

To test:
- Apply the patch.
- HidePatronName = Show
- Look at the staff detail view for a record that has at least one
  item on loan.
- Verify the name of the patron is displayed as expected.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:14 -06:00
Jan Kissig a26800691f
Bug 42844: Fix broken subscription search when using additional fields as seach criteria
This fixes an error when searching for a subscription with an additional field when no result is found by changing the call of SearchSubscriptions to expect an array instead of an hash ref.
Serials.pm, when using filter_by_additional_fields may return an empty array, but the calling function only checked for an hash ref which lead to an error when no result was found.

Testplan:
a) create an additional field for subscriptions: More -> Serials -> Manage subscription fields
   f.e. use an AV CCODE as additional field
   make sure "searchable" is checked
   No subscription should use this additional field by now.
b) go to More -> Serials
c1) if bug 40201 is pushed you can search for that additional field on serials-home
   -> An error stack trace will appear.
c2) otherwise perform an empty search on serials home to end on serials-search. In the search form on the left select that additional field as search criteria.
   -> An error stack trace will appear.
d) add a subscription using that additional field
  -> More -> Serials -> New subscription.
  On step two there is a section for Additional fields. Select f.e. CCODE Fiction
e) redo c1 or c2 using that additonal field value as search criteria.
f) Check that the search result shows your subscription.
g) apply patch and redo c1 or c2 for an additional field value that is not used (f.e. Non-fiction)
h) The search should work now, returning 0 results instead of an error
i) check that searching for a used additional field (f.e. Fiction) still works

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-15 18:12:05 -06:00
Pedro Amorim 838fa2f78b
Bug 42783: Fix ManageMarcImport_spec.ts
If import_batch_profiles is slow to respond, it'll trigger select.change() in stage-marc-import.tt and change the format to ISO2709 before the form is submitted, causing the error.

cypress run --spec t/cypress/integration/Tools/ManageMarcImport_spec.ts

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-12 09:28:29 -06:00
Lucas Gass b8fab422f4
Bug 42764: (RMaint follow-up) Fix DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 12:56:17 -06:00
Pedro Amorim 2f03758634
Bug 42764: (QA Follow-up): Fix DB update mismatch
perl /kohadevbox/misc4dev/run_tests.pl --run-db-compare-only --compare-with v24.05.00

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:41 -06:00
Lucas Gass 2cd11ac987
Bug 42764: DBRev 26.05.00.001
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:41 -06:00
Lucas Gass 606450fd56
Bug 42764: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:40 -06:00
Pedro Amorim c5d078eccf
Bug 42764: (QA follow-up): Update DB schema
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:39 -06:00
Pedro Amorim 919b5e9e93
Bug 42764: (QA follow-up): Atomicupate: Fix typo in SQL table comment
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:38 -06:00
Martin Renvoize 9f3cdb665c
Bug 42764: Use inline codespell:ignore for SAN-Ouest Provence in templates
Rather than excluding the entire about.tt files from codespell scanning,
annotate only the specific lines containing the proper noun "Provence"
(SAN-Ouest Provence, a French library co-operative and Koha sponsor) with
an inline TT comment that codespell recognises as a per-line ignore.

This lets codespell continue to check the rest of each file for genuine
spelling mistakes, while still catching accidental "Provence" for
"Province" in any other template not carrying this annotation.

Replaces the file-level exceptions added to Koha::Devel::Files in
favour of the more surgical inline approach.

Test plan:
1. Apply patch
2. Verify "Provence" on the annotated lines produces no codespell output:
   codespell -d --ignore-words .codespell-ignore \
     koha-tmpl/intranet-tmpl/prog/en/modules/about.tt
   => no output
3. Verify the rest of about.tt is still checked by introducing a
   deliberate typo elsewhere in the file and confirming codespell flags it
4. prove xt/author/codespell.t
=> All tests pass

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:37 -06:00
Martin Renvoize 1c0de7e971
Bug 42764: Fix false positives caused by mixed filetypes in CI failure store
The ci-results repo stores all failures from a single test run as one
flat JSON array, regardless of whether they came from the pl, tt, or js
filetype pass. On the next incremental run, get_files_to_test('pl') reads
that entire list -- including any .tt failures -- and calls
remove_exceptions with the 'pl' context. Since .tt files only appear in
the tt exception list, they survive the filter and get codespell'd in the
pl pass where nothing is configured to exclude them.

The fix filters stored failures by filetype before applying exceptions,
so each pass only re-checks failures that belong to its own type.

Test plan:
1. Apply all patches
2. Add a file to the tt codespell exceptions in Koha::Devel::Files that
   contains a word codespell would flag
3. Introduce a deliberate failure in another file so the ci-results repo
   records the excepted .tt file as a previous failure
4. On the next incremental run, confirm the excepted .tt file is not
   re-tested in the pl or js pass
5. prove xt/author/codespell.t
=> Tests pass; no false positives from cross-filetype re-checks

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:37 -06:00
Martin Renvoize 952e60e221
Bug 42764: Include filename in codespell test output
Without a test comment, failures appear as bare 'not ok N' lines with
no indication of which file failed or what codespell found.

Passing the filename as the third argument to is() means:
- Every passing test shows: ok N - path/to/file
- Every failing test shows: not ok N - path/to/file

Test plan:
1. Apply patch
2. Introduce a deliberate spelling error in any .pl file
3. prove xt/author/codespell.t
=> Failing line shows the filename and the codespell finding inline,
   without needing to dig into full console logs

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:36 -06:00
Martin Renvoize e7dc04a178
Bug 42764: Fix false positives caused by POD letter-code formatting
Two patterns in the codebase produce spurious codespell hits because
codespell tokenises word fragments out of formatted letter codes:

- B<P>rovider in C4/Auth_with_shibboleth.pm produces 'rovider'
- T(ransit), W(aiting), F(inished), P(rocessing) in C4/Reserves.pm
  produce 'ransit', 'aiting', 'inished', 'rocessing'

For the shibboleth POD line the bold-letter markup cannot be simplified
without losing the formatting intent, so a per-line # codespell:ignore
comment is the right suppression.

For the Reserves.pm status table the fix is made at source: the notation
is changed from X(word) to X (Word) so codespell sees the full English
word and no fragment is produced.

Because the per-line comment on the shibboleth line now suppresses
'rovider' directly, the global .codespell-ignore entry for that fragment
is removed as redundant.

Test plan:
1. Apply patch
2. prove xt/author/codespell.t
=> No new codespell failures

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:35 -06:00
Lucas Gass f6deb07bdf
Bug 42764: Fix spelling errors across the codebase
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:34 -06:00
Martin Renvoize bb7b3aa9a0
Bug 42764: Update .codespell-ignore for valid technical terms
Add words to .codespell-ignore that codespell 2.4.x flags but are
valid technical terms, domain vocabulary, or proper names:

- checkin: core library circulation term (check-in as noun/verb)
- plack: Perl PSGI middleware framework (proper software name)
- requestor: COUNTER 5 standard term used in ERM usage statistics
- laf: EDIFACT 3-letter segment code
- vai/lamba: Unicode script and language names in character set data
- aas: acronym used in Cypress test support files
- carmel: contributor name in about.tt
- prfile: legacy variable abbreviation in C4/Creators/PDF.pm

Test plan:
1. Apply patch
2. prove xt/author/codespell.t
=> Test passes (or only genuine spelling errors remain)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-11 09:53:26 -06:00
Owen Leonard f4c0819644
Bug 42518: Capitalization: Refund Payout Receipt
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>
2026-06-10 12:35:45 -06:00
David Nind d2df80f20d
Bug 42695: (follow-up) Format paragraph
Moves the explanation sentence into its own paragraph.

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>
2026-06-10 12:35:44 -06:00
Owen Leonard 6fa6228f73
Bug 42695: Minor language and markup corrections to reports error messages
This patch makes a vew minor corrections to guided_reports_start.tt.

To test apply the patch and go to Reports.

- Try to create an SQL report which uses UPDATE, e.g.
  UPDATE items SET barcode = 1 WHERE itemnumber = 1
- Confirm that the text of the error message reads well and is formatted
  well.

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>
2026-06-10 12:35:44 -06:00
Owen Leonard 19bc16654a
Bug 42692: Typo: regarless
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>
2026-06-10 12:35:43 -06:00
Owen Leonard 889ab52a33
Bug 42691: Typo: automaticaly
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>
2026-06-10 12:35:37 -06:00
Pedro Amorim 64e4501129
Bug 42705: Fix SCO spec WebBasedSelfCheck setup
cy.login() in beforeEach broke the test in two ways: the intranet staff session cookie bled into the OPAC request (HTTP cookies have no port scope in
browsers), and navigating from intranet→OPAC mid-test hit a cross-origin block in Cypress. The afterEach suffered the same race condition - the syspref POST fired before the login redirect completed.

Replace with cy.request(), which makes HTTP calls at the runner level without touching the browser's current origin. cy.visitOpac() is then the first
browser navigation, so no cross-origin issue arises. A three-step flow satisfies Koha's CSRF middleware:
1) GET the login page to obtain a session and its token
2) POST credentials to authenticate
3) POST to the svc endpoint.

This fixes the #barcode and #patronlogin not found issues

The CSRF token also requires encodeURIComponent() — base64 uses +, which a
URL-encoded form body decodes as a space, silently corrupting the token and causing the login to fail.

This resolves the 403 Forbidden issue

Everything must pass:
$ cypress run --spec t/cypress/integration/OPAC/SCO_spec.ts

as well as npm run cypress open (cypress ui)

ktd selenium:
$ perl /kohadevbox/misc4dev/run_tests.pl --run-only t/cypress/integration/OPAC/SCO_spec.ts

squash

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-09 15:42:26 -06:00
Pedro Amorim 5baf1ce6e0
Bug 42705: Add HttpClient_spec.ts
This fails without the fix patch in this set:
cypress run --spec t/cypress/integration/HttpClient_spec.ts

It passes with the fix

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-09 15:42:26 -06:00
Pedro Amorim 4e87782636
Bug 42705: Make http-client.js handle svc endpoint error
The afterEach calls cy.login() then immediately posts to the syspref endpoint. On CI, the machine is slow enough that the POST fires before the login
redirect completes — no valid session yet — so the endpoint returns a C4::Service auth error in this format:

{ "type": "auth", "message": "expired" }

http-client.js only knew how to parse modern REST API errors (json.error or json.errors[]). Neither field exists here, so json.errors.map(...) crashed with a TypeError instead of surfacing the actual error message.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-09 15:42:25 -06:00
Lucas Gass 87a552d8a7
Bug 34973: Update yarn.lock
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-09 15:42:19 -06:00
David Cook bcbe93fbea
Bug 42361: Fix SQL injection in catalogue_out.pl
This change removes an SQL injection vulnerability from catalogue_out.pl

Test plan:
0. Apply the patch
1. Go to koha-conf.xml and change the following:
<strict_sql_modes>1</strict_sql_modes>
to:
<strict_sql_modes>0</strict_sql_modes>
2. sudo koha-plack --restart kohadev
3. Go to /cgi-bin/koha/reports/catalogue_out.pl
4. Try out the various options and note no errors occur

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-09 08:18:59 -06:00
Lucas Gass 9122ad9dc6
Bug 42757: Use default suggestor or fallback to librarian
To test:
1. Find a patron
2. From the patron account click the "Purchase suggestions" button
3. Make a suggestion
4. That suggestion is not associated with the patron from step 1
5. APPLY PATCH and restart_all
6. Repeart steps 1 -3. The purchase suggestion suggesedby should be the patron from step 1
7. Go  Acquisitions -> Suggestions -> Add suggestion
8. Make sure when you do it this way the suggestedby is set to the logged-in-user by default

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>
2026-06-08 13:53:26 -06:00
Lucas Gass eca5b83960
Bug 42750: Add redirect params back and exit after redirect
To test:
1. Find a patron
2. From the patron account click the "Purchase suggestions" button
3. Make a suggestion
4. Your are redirected to suggestion/suggestion.pl
5. APPLY PATCH, restart_all
6. Try again, this time you should be properly redirected back to the patron account: ( members/purchase-suggestions.pl?borrowernumber=X )

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>
2026-06-08 13:53:25 -06:00
Lucas Gass 885577073c
Bug 42740: Fix suggestion status when editing
To test:
1. Make a purchase suggestion.
2. Set the status to something, anything.
3. Edit that suggestion, notice that the "Status:" is set to 'No Status'
4. Now when you save, they actual status is lost.
5. APPLY PATCH, restart_all
6. Try steps 1 - 3. This time at step 3 the suggestion status should be properly set.
7. Save the suggestion, make sure it saves with the correct status.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:25 -06:00
Eric Phetteplace 33a8dc5d6f
Bug 42723: Purchase suggestion 500 when EmailPurchaseSuggestions is set to BranchEmailAddress
Testing plan:
- Set EmailPurchaseSuggestions to BranchEmailAddress
- Go to create suggestion on the staff side /suggestion/suggestion.pl?op=add_form
- Create a suggestion with Library: Any
- 500 error: inbound_email_address is not a method of undef
- Apply patch & repeat
- Suggestion created

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:24 -06:00
Lucas Gass 2f08c5b8ae
Bug 42710: Set library to logged in library for new suggestions
To test:
1.  Acquisitions -> Suggestions ->Add suggestion
2.  Notice the under Acquisition information the library is set to Any
3.  APPLY PATCH
4.  Try again, library should be set to the logged in library.
5.  Make a suggestion and set it to some branch other then the logged in branch
6   Edit the suggestion from step 5, ensure that the "Library:" is properly set

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:23 -06:00
Emily Lamancusa 35e670c886
Bug 42701: Fix editable class on additem.pl
When editing the items on a biblio record, you should be able to select
an item to edit by clicking anywhere on the item row. This functionality
was lost because the "editable" class was not getting added correctly to
the table row. This patch fixes that issue.

To test:
1. Find a biblio record that has at least one item
2. Click on Edit > Manage items
3. In the table that lists editable items, click on any cell of any item
   (other than the Actions button)
--> Nothing happens; the buttons to "Edit item" and "Delete item" do not
    appear as expected
4. Apply patch and refresh the page
5. Repeat step 3
--> The buttons to "Edit item" and "Delete item" appear

Signed-off-by: Barbara Johnson <barbara.johnson@bedfordtx.gov>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:22 -06:00
Janusz Kaczmarek dbd51e342c
Bug 42555: (Bug 25314 follow-up) Destroyed labels for customized facets
After applying Bug 25314, customized facets generated from
mappings.yaml are displayed with the technical type_id instead of the
proper label.

Test plan:
==========
1. In KTD with Elasticsearch enabled, add the following to
   admin/searchengine/elasticsearch/mappings.yaml

  su-gen:
    facet_order: 33
    label: Genre/Form
    mappings:
    - facet: 1
      marc_field: 655a
      marc_type: marc21
      sort: 1
      suggestible: ''
    opac: 1
    staff_client: 1
    type: ''

   Reindex with: koha-elasticsearch --rebuild -r -d -b kohadev
2. In the OPAC, at the bottom of the facets area, you should see a
   facet labeled su-gen_id instead of Genre/Form.
3. Apply the patch ; restart_all.
4. The facet should now display the correct label: Genre/Form.

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>
2026-06-08 13:53:21 -06:00
Owen Leonard c64f707691
Bug 42498: Fix invalid aria attribute in OPAC search results pagination
This patch corrects the aria-current attribute in OPAC search results
pagination. There should be no aria-current attribute on the list item,
and the aria-current value on the link should be "page."

To test, apply the patch and perform a catalog search in the OPAC which
will return multiple pages of results.

View the page source or use the developer console to inspect the HTML of
the active pagination link. It should have an aria-current label with
the value "page." The containing list item should not have an
aria-current label.

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>
2026-06-08 13:53:21 -06:00
David Nind f545db2179
Bug 42454: Terminology: Use "and" instead of "&" for curbside pickups tab
The "Staged & ready (0)" tab for curbside pickups (Circulation > Holds
and bookings > Curbside pickup) should use "and" instead of "&".

See terminology guidelines for "& (ampersand)" - Only use "&" when part
of a proper noun or a common abbreviation,... Otherwise, use "and":
https://wiki.koha-community.org/wiki/Terminology#characters

Test plan:
1. Enable the CurbsidePickup system preference.
2. Configure curbside pickup for Centerville:
   - Go to Administration > Patrons and circulation > Curbside pickup
   - Set values:
     . Enable: selected
     . Pickup interval: 5
     . Maximum patrons per interval: 1
     . Patron-scheduled pickup: selected
     . Enable for waiting holds only: selected
     . Curbside pickup hours: add from and to hours for some days
     . Click Save configuration
3. Go to Circulation > Holds and bookings > Curbside pickup.
4. Note that there is an '&' in the "Staged & ready (0)" tab title.
5. Apply the patch.
6. Refresh the page.
7. Note that and is spelt in full "Stage and ready (0)".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:20 -06:00
Julian Maurice 39a090affe
Bug 42194: Fix redirect after sharing a report to Mana
Bug 34478 removed a '&' by mistake in the redirect URL

Test plan:
1. Setup a local instance of Mana
   (https://gitlab.com/koha-community/koha-mana) with disabled email
   validation (skip_email_validation in config.yml)
2. Connect Koha to this local instance (mana_config in $KOHA_CONF)
3. From Koha Admin interface, create a Mana security token
4. Share a report to Mana
5. You should be redirected to the list of reports with a message saying
   the report was saved successfully

Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:19 -06:00
Maryse Simard 4112dde941
Bug 42193: Fix the "Suspend Hold" modal in the OPAC
In the OPAC, the "Suspend Hold" modal sometimes shows up as (and acts like) a "Resume Hold" modal. The reason for this is a left over "HOLD.suspend" in the template. "HOLD" should not be used in this template and is set as a side effect of the processing of "holds-table.inc".

Since there is a separate form that exist for the "Resume" action and the modal is only needed for suspension, this patch simply removes the inacurate conditional in the modal definition.

To test :
1. Have at least 2 holds.
2. In the holds table in the OPAC, make sure all holds are active and have a "Suspend" link.
3. Click the "Suspend" link on the newest hold (with the highest reserve_id).
    => The modal is titled "Suspend hold" and submitting it correctly suspends the hold.
4. Click the "Suspend" link on any of the other hold.
    => The modal is titled "Resume hold" and submitting it doesn't suspends the hold.
5. Apply patch
6. Repeat step 4.
    => The modal is titled "Suspend hold" and submitting it correctly suspends the hold.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:18 -06:00
Kevin Carnes e6d6a608c3
Bug 42178: Fix close button for remove from bundle
The close button for remove from bundle has not type, so it submits the form.

This patch adds a button type.

Test plan:
1. Apply bug 42177 or add a local use preference for bundlesEnabled set to 1.
2. Edit a record.
3. Modify the leader so that it has a collection bibliographic level.
4. Ensure that is has at least one item.
5. Click Manage bundle next to the item.
6. Click Remove from bundle.
7. Click the Close button.
8. Observe that the form is submitted.
9. Apply the patch.
10. Repeat steps 5-7.
11. Observe that the form is not submitted.
12. Sign off.

Sponsored-by: Lund University Library
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>
2026-06-08 13:53:17 -06:00
Jonathan Druart 5dcec8b803
Bug 42130: ERM - Index biblio when local title is created/updated
The indexation step has been removed by
  commit d2820f57d6
  Bug 34788: (QA follow-up)

Koha/ERM/EHoldings/Title.pm
-            C4::Biblio::ModBiblio( $record, $self->biblio_id, '' );
+            C4::Biblio::ModBiblio( $record, $self->biblio_id, '', { skip_record_index => 1 } );

From the commit message:
2) The background job now uses skip_record_index to avoid queuing indexing jobs for every new biblio and instead queues one job at the end

it's then missing when we add or edit a local title.

Test plan:
1 - Enable ERMModule
2 - Go to ERM
3 - eHoldings->Titles
4 - New title
5 - Populate the form
6 - Check the create bibliographic record box
7 - Save
=> Confirm that the biblio is now indexed
8. Edit the title, modify a value, tick the box, save
=> Confirm that the biblio is now indexed with the new value

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:17 -06:00
Baptiste Wojtkowski adf7b69858
Bug 41998: Fix redirect_with_params
Test plan (main only):
1 - Create a suggestion
2 - Delete the suggestion -> error
3 - Apply patch
4 - Repeat 1&2

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:16 -06:00
Martin Renvoize dd99a66a4b
Bug 32938: Add ORDRSP message display to basket page
This patch extends the EDIFACT message display functionality on the basket page
to include ORDRSP (Order Response) messages alongside existing QUOTE and ORDERS
messages.

Features added:
- ORDRSP message collection and filtering
- Smart button display logic:
  * Single button for one message type
  * Dropdown for multiple message types (now includes ORDRSP)
- ORDRSP-specific UI elements:
  * Reply icon (fa-reply) indicating response nature
  * "View order response" labeling
  * Integration with existing modal and highlighting system

The ORDRSP messages correctly link to baskets since they are vendor responses
to the original order, following the standard EDI workflow:
ORDERS → ORDRSP → INVOICE

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:15 -06:00
Martin Renvoize a0611d2809
Bug 32938: Fix ORDRSP message linking to point to baskets instead of invoices
ORDRSP (Order Response) messages are responses to ORDERS messages and should
link to the related basket, not to invoices. This follows the standard EDI
workflow:
1. ORDERS message sent to vendor
2. ORDRSP message received from vendor (order response/confirmation)
3. INVOICE message received later when items shipped

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:14 -06:00
Owen Leonard 71559d0909
Bug 42441: Remove event attributes from authority merge template
This patch moves a couple of "onclick" event attributes from the
authority merge template, moving the event handling to the JS block.

This patch also fixes the behavior that I assume the click handlers are
meant to have, changing the framework selection. A mismatch of IDs in
the code meant that clicking the radio buttons didn't do anything.

To test, apply the patch and go to Authorities.

- Perform a search for authority records which will return multiple
  results, including records with different heading types (e.g. Personal
  Name, Topical Term, etc.)
- In the "Actions" menu for one of the search results, click "Merge"
- Do the same with another result which has a different heading type.
- On the "Merging records" page you should see a frameowrk dropdown.
- When you click the radio buttons next to your two records to merge the
  framework selection should change to match your selection.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Amended-by: Jonathan Druart
Tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:13 -06:00
Owen Leonard d27639707f
Bug 42154: Bug 38714 hid the "New match check" link in record matching rules
This patch corrects the logic used to determine whether to show the "New
match check" link. I mistakenly changed it to check for the matchpoints
variable instead of the matchchecks variable.

To test, apply the patch and go to Administration -> Record matching
rules.

- Click "New record matching rule" in the toolbar.
- Scroll down to the "Required match checks" section.
- You should see a "New match check" link alongside a "Remove this match
  check" link.
- Go back to the record matching rules main page.
- If necessary, create a rule which doesn't have any match checks.
- In the "Required match checks" section of the edit form you should see
  a "New match check" link, previously hidden by Bug 388714.

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>
2026-06-08 13:53:13 -06:00
Marcel de Rooy 1022ad4c13
Bug 42084: (QA follow-up) Simplify
Since this is an intranet svc script (called in intranet js), there
is no need to check the session.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:12 -06:00
Hammat Wele 78d4c42a61
Bug 42084: Incorrect interface shown in log viewer for system preference changes
The log viewer displays the OPAC interface for system preference changes. Since system preferences cannot be modified from the OPAC, the interface displayed in the logs appears to be incorrect.

To reproduce:

1- Disables plack
    1- sudo koha-plack --disable kohadev
    2- sudo koha-plack --stop kohadev
    3- sudo service apache2 restart
2- Change a system preference from the staff interface.
    1- Go to Administration -> System preferences.
    2- Search for a preference such as AcqCreateItem (any system preference will reproduce the issue).
    3- Change the value of the preference.
    4- Click Save all Acquisitions preferences (or save the preference).
3- Go to the log viewer:
    1- Navigate to Tools -> Log viewer.
    2- Fill the form as follows:
        Librarian: leave empty
        Modules: uncheck everything except System preferences
        Actions: keep All actions selected
        Object: leave empty
        Info: leave empty
        Interface: keep All interfaces
        Display from: choose yesterday
        Display to: leave empty
    3- Click Submit.
        ==>The log entry shows the OPAC interface.
4- Apply the patch
5- Repeat step 1,2,3
    ==>The log entry shows the Staff interface.

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>
2026-06-08 13:53:11 -06:00
Pedro Amorim 125416a92d
Bug 42733: Add tests
Apply tests patch only, run:
yarn cypress run --spec t/cypress/integration/Tools/ManageMarcImport_spec.ts

Confirm it fails with the same error as Jenkins:
Timed out retrying after 10000ms: `cy.wait()` timed out waiting `10000ms` for the 2nd request to the route: `jobPoll`. No request ever occurred.

Apply fix patch. Run again.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:10 -06:00
Pedro Amorim 74cf164d32
Bug 42733: Job progress fix
waitForJobFinished previously had no handling for failed jobs or
non-200 responses — if the job API returned anything unexpected,
the recursive cy.wait() would silently stall waiting for a 2nd
poll that never came, producing a confusing timeout. Adds an
explicit "failed" status check and updates the intercept
to reproduce the exact CI failure (Apache returning 503 during
reset_all).

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:10 -06:00
Martin Renvoize f0bb03d6e9
Bug 42726: Add 26.11 release team
Add the 26.11 release team as voted at the development meeting.

Pedro Amorim as Release Manager, with Tomás Cohen Arazi and Martin
Renvoize as RM Assistants. Lisette Scheer as QA Manager with a
19-person QA team. David Cook as Security Release Manager.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:09 -06:00
Martin Renvoize 5d12a83751
Bug 42726: Fix 25.11 QA team list to match dashboard
Update the 25.11 QA team to reflect the definitive list from the
dashboard. Remove Aleisha Amohia and Julian Maurice who did not
appear in the final count, and add John Vinke, Matt Blenkinsop,
Alex Carver and Victor Grousset who did. Reorder by descending
QA count.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-08 13:53:01 -06:00
Jonathan Druart 7c0880a9ec
Bug 42133: Restore plugins when editing items in batch
It has been broken then fixed then broken again, several times...

This patch fixes 2 problems:
1. The CSRF fix (I think it's coming from there) didn't adjust the 'if show' test and the JS code from plugins was not injected into the template
2. The selector to call the plugin didn't include this view, only the
   add/edit items

Test plan:
Confirm that you can now use cataloging plugins when editing items in
batch

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:10 -06:00
Owen Leonard caa63ce8e6
Bug 34973: (follow-up) Remove old Font Awesome asset references from SCO and SCI
The self-checkout and self-checkin templates no longer need explicit
inclusion of Font Awesome CSS because it's rolled into _common.scss and
thus sco.scss.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:10 -06:00
Owen Leonard df42d81dbe
Bug 34973: (follow-up) Use lib/fontawesome for font location
It makes more sense to use lib/fontawesome/webfonts as the source
directory for Font Awesome font files rather than bootstrap/css/fonts.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:09 -06:00
Jonathan Druart 9ee36cea5a
Bug 34973: Adjust opac-curbside-pickups.tt
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:09 -06:00
Owen Leonard 4c9604d1a7
Bug 34973: Upgrade Font Awesome in the OPAC to 7.2
We are using Font Awesome 6.3 in the OPAC. The current version is 7.2.
Upgrading will eliminate the repeated warnings in the browser console
about "Glyph bbox was incorrect."

The patch adds Font Awesome to the list of packages in node_modules and
moves the Font Awesome SCSS into _common.scss so that it can be bundled
along with Bootstrap and other 3rd party assets.

Despite the fact that _common.scss grabs FA SCSS files directly from
node_modules I've added the FA font files to css/fonts. I was unable to
see how to reference the webfonts directory in
node_modules/@fortawesome.

The many changed .inc and .tt files are a result of a missed aspect of
our upgrade to Bootstrap 5. The "sr-only" class was removed in BS5 but
the Font Awesome CSS included its own "sr-only" class. This made up for
the fact that we missed the required upgrade to BS5's "visually-hidden."
As soon as Font Awesome's "sr-only" was gone all these mistakes were
made visible.

To test, apply the patch and run 'yarn install.'
- Rebuild the OPAC SCSS.
- Clear your browser cache.
- Test many pages in the OPAC to confirm that no icons are missing.
- Check changes to the templates to confirm that "sr-only" has been
  replaced with "visually-hidden."
  - Places where a broken "sr-only" class would be obvious: Star ratings
    on the user summary page, <legend>s on the search history page.

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>
2026-06-04 15:09:08 -06:00
Martin Renvoize 855e7669d4
Bug 41705: (follow-up) Fix for new kohaTable requirements
This bug sat in the queue too long and thus there's coding standard
changes to resolve.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:07 -06:00
Martin Renvoize 6b45fda621
Bug 41705: (follow-up) Hide receipt page buttons when printing
The Print receipt and Close buttons added in the previous patch use
class="no-print", but printreceiptinvoice.css had no @media print rule
for that class — so they appeared in the printed output (e.g. when
saving as PDF or auto-printing in browsers that block popups).

Add a @media print rule so the buttons remain visible on screen but
disappear from the printed receipt.

Test plan:
1. Disable browser popups (Chrome: "Don't allow sites to send pop-ups
   or use redirects").
2. Enable FinePaymentAutoPopup.
3. Complete a POS sale.
4. On the receipt page, choose Print to PDF (or trigger auto-print).
5. Verify the printed/PDF output contains only the receipt body — no
   Print receipt or Close buttons.
6. Verify the buttons remain visible on screen.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:07 -06:00
Martin Renvoize a5a1eeaa5b
Bug 41705: Use PRG pattern for POS receipt printing to avoid popup blockers
The previous approach triggered popup blockers because the receipt window
was opened via JavaScript after a page reload — browsers treat this as a
non-user-initiated action. This patch reverses the flow: when
FinePaymentAutoPopup is enabled, the payment form submits directly to a
new tab via target="_blank" (a user-initiated action that browsers allow),
the server processes the payment and redirects (303) to the receipt page,
which auto-prints and closes itself, returning focus to the POS tab.

When FinePaymentAutoPopup is disabled, the server redirects back to the
POS page with payment details in the URL (standard PRG), displaying the
existing success banner with print/email receipt buttons.

Changes:
- pos/pay.pl: Implement PRG redirect for both popup and non-popup flows,
  URI-encode redirect parameters, remove session-based state management
- pos/pay.tt: Add target="_blank" when FinePaymentAutoPopup is enabled,
  reset form state after submission via same-scope setTimeout
- pos/printreceipt.pl: Add error handling for missing receipts, pass
  autoprint parameter to template
- pos/printreceipt.tt: Add conditional auto-print, manual print/close
  buttons, error display, and IntranetSlipPrinterJS preference support
- slip-print.inc: Modernize jQuery to vanilla JS, remove obsolete
  Chrome setTimeout hack

Test plan:
1. Enable FinePaymentAutoPopup system preference
2. Go to Point of Sale, add items, and complete a sale
3. Verify receipt opens in a new tab without popup blocker warnings
4. Verify receipt auto-prints and tab closes after printing
5. Verify original POS tab form is reset for next transaction
6. Disable FinePaymentAutoPopup
7. Complete another sale
8. Verify POS page shows success with Print/Email receipt buttons
9. Test receipt printing from other pages (circulation, fees) to
   confirm slip-print.inc change causes no regressions

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:06 -06:00
Owen Leonard e2f5afe100
Bug 42475: (follow-up) Fix a couple more instances of 'opac'
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:09:06 -06:00
Jonathan Druart b860c04197
Bug 42475: Fix opac vs OPAC
Correct "opac" with "OPAC" in the pref file

Test plan:
git grep opac **/*.pref
and confirm that there is no relevant occurrences in the output (i.e.
no strings displayed on the UI)

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-06-04 15:08:59 -06:00
439 changed files with 11870 additions and 2552 deletions

View File

@ -1,7 +1,6 @@
ser ser
thes thes
nam nam
rovider
fo fo
isnt isnt
wither wither
@ -18,3 +17,12 @@ theses
rouge rouge
connexion connexion
falsy falsy
aas
carmel
checkin
laf
lamba
plack
prfile
requestor
vai

View File

@ -28,7 +28,6 @@ BEGIN {
} }
use C4::Context; use C4::Context;
use C4::Stats;
use C4::Members; use C4::Members;
use Koha::Account; use Koha::Account;
use Koha::Account::Lines; use Koha::Account::Lines;

View File

@ -2432,7 +2432,8 @@ sub GetInvoices {
NULL NULL
) )
) AS is_linked_to_subscriptions, ) AS is_linked_to_subscriptions,
SUM(aqorders.quantityreceived) AS receiveditems SUM(aqorders.quantityreceived) AS receiveditems,
SUM(aqorders.quantity) AS itemsexpected
FROM aqinvoices FROM aqinvoices
LEFT JOIN aqbooksellers ON aqbooksellers.id = aqinvoices.booksellerid LEFT JOIN aqbooksellers ON aqbooksellers.id = aqinvoices.booksellerid
LEFT JOIN aqorders ON aqorders.invoiceid = aqinvoices.invoiceid LEFT JOIN aqorders ON aqorders.invoiceid = aqinvoices.invoiceid
@ -2530,7 +2531,7 @@ sub GetInvoices {
if ( $args{order_by} ) { if ( $args{order_by} ) {
my ( $column, $direction ) = split / /, $args{order_by}; my ( $column, $direction ) = split / /, $args{order_by};
if ( grep { $_ eq $column } @columns ) { if ( grep { $_ eq $column } @columns ) {
$direction ||= 'ASC'; $direction = ( uc( $direction // '' ) eq 'DESC' ) ? 'DESC' : 'ASC';
$query .= " ORDER BY $column $direction"; $query .= " ORDER BY $column $direction";
} }
} }

View File

@ -275,15 +275,18 @@ sub get_template_and_user {
} }
if ($kick_out) { if ($kick_out) {
$session = get_session("");
$template = C4::Templates::gettemplate( $template = C4::Templates::gettemplate(
'opac-auth.tt', 'opac', 'opac-auth.tt', 'opac',
$in->{query} $in->{query}
); );
$template->param( sessionID => $session->id );
$cookie = $cookie_mgr->replace_in_list( $cookie = $cookie_mgr->replace_in_list(
$cookie, $cookie,
$in->{query}->cookie( $in->{query}->cookie(
-name => 'CGISESSID', -name => 'CGISESSID',
-value => '', -value => $session->id,
-HttpOnly => 1, -HttpOnly => 1,
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
-sameSite => 'Lax', -sameSite => 'Lax',

View File

@ -312,7 +312,7 @@ Create some metadata for your koha instance (if you're in a single instance setu
=item 2. =item 2.
Swap metadata with your Identidy Provider (IdP) Swap metadata with your Identify Provider (IdP)
=item 3. =item 3.
@ -365,7 +365,7 @@ Note: The minimum you need here is a <matchpoint> block, containing a valid colu
It should be as simple as that; you should now be able to login via shibboleth in the opac. It should be as simple as that; you should now be able to login via shibboleth in the opac.
If you need more help configuring your B<S>ervice B<P>rovider to authenticate against a chosen B<Id>entity B<P>rovider then it might be worth taking a look at the community wiki L<page|https://wiki.koha-community.org/wiki/Shibboleth_Configuration> If you need more help configuring your B<S>ervice B<P>rovider to authenticate against a chosen B<Id>entity B<P>rovider then it might be worth taking a look at the community wiki L<page|https://wiki.koha-community.org/wiki/Shibboleth_Configuration> # codespell:ignore
=head1 FUNCTIONS =head1 FUNCTIONS

View File

@ -155,7 +155,9 @@ sub Z3950Search {
} }
my $xslh = Koha::XSLT::Base->new; my $xslh = Koha::XSLT::Base->new;
my $nremaining = $s; my $nremaining = $s;
my $hits_per_page = 20; #TODO make configurable
my $total_hits = 0;
while ( $nremaining-- ) { while ( $nremaining-- ) {
my $k; my $k;
my $event; my $event;
@ -169,18 +171,22 @@ sub Z3950Search {
my ($error) = $oConnection[$k]->error_x(); #ignores errmsg, addinfo, diagset my ($error) = $oConnection[$k]->error_x(); #ignores errmsg, addinfo, diagset
if ($error) { if ($error) {
if ( $error =~ m/^(10000|10007)$/ ) { if ( $error =~ m/^(10000|10007)$/ ) {
push( @errconn, { server => $servers[$k]->{host}, error => $error } ); push( @errconn, { server => $servers[$k]->{host}, errcode => $error } );
} }
} else { } else {
my $numresults = $oResult[$k]->size(); my $numresults = $oResult[$k]->size();
$total_hits += $numresults;
my $i; my $i;
my $res; my $res;
if ( $numresults > 0 and $numresults >= ( ( $page - 1 ) * 20 ) ) { if ( $numresults > 0 and $numresults >= ( ( $page - 1 ) * $hits_per_page ) ) {
$show_next = 1 if $numresults >= ( $page * 20 ); $show_next = 1 if $numresults >= ( $page * $hits_per_page );
$total_pages = int( $numresults / 20 ) + 1 if $total_pages < ( $numresults / 20 ); $total_pages = int( $numresults / $hits_per_page ) + 1
if $total_pages < ( $numresults / $hits_per_page );
for ( for (
$i = ( $page - 1 ) * 20 ; $i = ( $page - 1 ) * $hits_per_page ;
$i < ( ( $numresults < ( $page * 20 ) ) ? $numresults : ( $page * 20 ) ) ; $i < (
( $numresults < ( $page * $hits_per_page ) ) ? $numresults : ( $page * $hits_per_page )
) ;
$i++ $i++
) )
{ {
@ -191,14 +197,17 @@ sub Z3950Search {
$xslh $xslh
); #ignores error in sequence numbering ); #ignores error in sequence numbering
push @breeding_loop, $res if $res; push @breeding_loop, $res if $res;
push @errconn, { server => $servers[$k]->{servername}, error => $error, seq => $i + 1 } push @errconn, { server => $servers[$k]->{servername}, errcode => $error, seq => $i + 1 }
if $error; if $error;
} else { } else {
my @diags = $oConnection[$k]->error_x(); # errcode, errmsg, addinfo, diagset
next unless $diags[0]; # Just silently ignore empty results
push @errconn, push @errconn,
{ {
'server' => $servers[$k]->{servername}, server => $servers[$k]->{servername},
error => ( ( $oConnection[$k]->error_x() )[0] ), errcode => $diags[0],
error_msg => ( ( $oConnection[$k]->error_x() )[1] ), seq => $i + 1 errmsg => $diags[1],
seq => $i + 1,
}; };
} }
} }
@ -212,6 +221,7 @@ sub Z3950Search {
total_pages => $total_pages, total_pages => $total_pages,
show_nextbutton => $show_next ? 1 : 0, show_nextbutton => $show_next ? 1 : 0,
show_prevbutton => $page != 1, show_prevbutton => $page != 1,
total_hits => $total_hits,
); );
} # while nremaining } # while nremaining

View File

@ -83,7 +83,6 @@ use Try::Tiny;
use JSON; use JSON;
use C4::Context; use C4::Context;
use C4::Stats qw( UpdateStats );
use C4::Reserves use C4::Reserves
qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority IsAvailableForItemLevelRequest ); qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority IsAvailableForItemLevelRequest );
use C4::Biblio qw( UpdateTotalIssues ); use C4::Biblio qw( UpdateTotalIssues );
@ -123,6 +122,7 @@ use Koha::SearchEngine::Indexer;
use Koha::Exceptions::Checkout; use Koha::Exceptions::Checkout;
use Koha::Plugins; use Koha::Plugins;
use Koha::Recalls; use Koha::Recalls;
use Koha::Statistics;
use Koha::Library::Hours; use Koha::Library::Hours;
use Koha::Library::FloatLimits; use Koha::Library::FloatLimits;
use Carp qw( carp ); use Carp qw( carp );
@ -885,7 +885,7 @@ sub CanBookBeIssued {
if ( $patron->category->category_type eq 'X' && ( $item_object->barcode ) ) { if ( $patron->category->category_type eq 'X' && ( $item_object->barcode ) ) {
# stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 . # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 .
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => C4::Context->userenv->{'branch'}, branch => C4::Context->userenv->{'branch'},
type => 'localuse', type => 'localuse',
@ -897,7 +897,7 @@ sub CanBookBeIssued {
location => $item_object->location, location => $item_object->location,
interface => C4::Context->interface, interface => C4::Context->interface,
} }
); )->store();
my $block_lost_return = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0; my $block_lost_return = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0;
ModDateLastSeen( $item_object->itemnumber, $block_lost_return ); # FIXME Move to Koha::Item ModDateLastSeen( $item_object->itemnumber, $block_lost_return ); # FIXME Move to Koha::Item
return ( { STATS => 1, }, {} ); return ( { STATS => 1, }, {} );
@ -1985,7 +1985,7 @@ sub AddIssue {
} }
# Record the fact that this book was issued. # Record the fact that this book was issued.
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => C4::Context->userenv->{'branch'}, branch => C4::Context->userenv->{'branch'},
type => ( $onsite_checkout ? 'onsite_checkout' : 'issue' ), type => ( $onsite_checkout ? 'onsite_checkout' : 'issue' ),
@ -1998,7 +1998,7 @@ sub AddIssue {
ccode => $item_object->ccode, ccode => $item_object->ccode,
categorycode => $patron->categorycode categorycode => $patron->categorycode
} }
); )->store();
# Send a checkout slip. # Send a checkout slip.
my $circulation_alert = 'C4::ItemCirculationAlertPreference'; my $circulation_alert = 'C4::ItemCirculationAlertPreference';
@ -2021,19 +2021,16 @@ sub AddIssue {
# Log the checkout # Log the checkout
if ( C4::Context->preference('IssueLog') ) { if ( C4::Context->preference('IssueLog') ) {
my $info = $item_object->itemnumber; my $info = to_json(
if ( defined($confirmations) || defined($forced) ) { {
$info = to_json( issue => $issue->issue_id,
{ branchcode => $issue->branchcode,
issue => $issue->issue_id, itemnumber => $item_object->itemnumber,
branchcode => $issue->branchcode, confirmations => $confirmations || [],
itemnumber => $item_object->itemnumber, forced => $forced || []
confirmations => $confirmations, },
forced => $forced { pretty => 1, canonical => 1 }
}, );
{ pretty => 1, canonical => 1 }
);
}
logaction( logaction(
"CIRCULATION", "ISSUE", "CIRCULATION", "ISSUE",
$patron->borrowernumber, $patron->borrowernumber,
@ -2720,7 +2717,7 @@ sub AddReturn {
# Record the fact that this book was returned. # Record the fact that this book was returned.
my $categorycode = $patron_unblessed ? $patron_unblessed->{categorycode} : undef; my $categorycode = $patron_unblessed ? $patron_unblessed->{categorycode} : undef;
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => $branch, branch => $branch,
type => $stat_type, type => $stat_type,
@ -2732,7 +2729,8 @@ sub AddReturn {
categorycode => $categorycode, categorycode => $categorycode,
interface => C4::Context->interface, interface => C4::Context->interface,
} }
) unless ( $skip_localuse && !$issue ); )->store()
unless ( $skip_localuse && !$issue );
# Send a check-in slip. # NOTE: borrower may be undef. Do not try to send messages then. # Send a check-in slip. # NOTE: borrower may be undef. Do not try to send messages then.
if ($patron) { if ($patron) {
@ -3497,17 +3495,13 @@ sub CanBookBeRenewed {
# If its cron, tell it it's too soon for a an auto renewal # If its cron, tell it it's too soon for a an auto renewal
return ( 0, $auto_renew, { soonest_renew_date => $soonest } ) if $cron; return ( 0, $auto_renew, { soonest_renew_date => $soonest } ) if $cron;
# Check if it's too soon for a manual renewal
my $soonestManual = GetSoonestRenewDate( $patron, $issue );
if ( $soonestManual > dt_from_string() ) {
return ( 0, "too_soon", { soonest_renew_date => $soonestManual } ) unless $override_limit;
}
} }
$soonest = GetSoonestRenewDate( $patron, $issue ); unless ($cron) {
if ( $soonest > dt_from_string() ) { $soonest = GetSoonestRenewDate( $patron, $issue );
return ( 0, "too_soon", { soonest_renew_date => $soonest } ) unless $override_limit; if ( $soonest > dt_from_string() ) {
return ( 0, "too_soon", { soonest_renew_date => $soonest } ) unless $override_limit;
}
} }
my $auto_renew_code = my $auto_renew_code =
@ -3750,7 +3744,7 @@ sub AddRenewal {
)->store(); )->store();
# Add the renewal to stats # Add the renewal to stats
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => $item_object->renewal_branchcode( { branch => $branch } ), branch => $item_object->renewal_branchcode( { branch => $branch } ),
type => 'renew', type => 'renew',
@ -3763,7 +3757,7 @@ sub AddRenewal {
categorycode => $patron->categorycode, categorycode => $patron->categorycode,
interface => C4::Context->interface, interface => C4::Context->interface,
} }
); )->store();
#Update borrowers.lastseen #Update borrowers.lastseen
try { try {

View File

@ -19,6 +19,9 @@ package C4::ClassSplitRoutine::RegEx;
use Modern::Perl; use Modern::Perl;
use Koha::Logger;
use Koha::Regex::Replacement;
=head1 NAME =head1 NAME
C4::ClassSplitRoutine::RegEx - regex call number sorting key routine C4::ClassSplitRoutine::RegEx - regex call number sorting key routine
@ -41,13 +44,62 @@ sub split_callnumber {
my ( $cn_item, $regexs ) = @_; my ( $cn_item, $regexs ) = @_;
for my $regex (@$regexs) { for my $regex (@$regexs) {
eval "\$cn_item =~ $regex"; ## no critic (StringyEval) _apply_substitution( \$cn_item, $regex );
} }
my @lines = split "\n", $cn_item; my @lines = split "\n", $cn_item;
return @lines; return @lines;
} }
=head2 _apply_substitution
_apply_substitution( \$string, $regex );
Applies a single admin-configured s/// substitution to $string in place,
without ever evaluating the input as code. The rule is parsed as
C<s/PATTERN/REPLACEMENT/FLAGS> (any non-space delimiter is accepted); the pattern is compiled with
qr// (so embedded-code assertions are treated as a runtime pattern and blocked
by Perl), and the replacement is expanded as data via
Koha::Regex::Replacement::expand_template. The
/e flag is rejected and anything that is not a plain substitution is ignored.
This replaces the previous C<eval "\$cn_item =~ $regex"> stringy eval, which
executed arbitrary Perl.
=cut
sub _apply_substitution {
my ( $ref, $regex ) = @_;
return unless defined $regex;
# Parse s<DELIM>PATTERN<DELIM>REPLACEMENT<DELIM>FLAGS, allowing any
# non-space delimiter and backslash-escaped delimiters inside the pattern
# and replacement. Anything that is not a well-formed plain substitution is
# ignored rather than executed.
return unless $regex =~ m{^s(\S)((?:\\.|(?!\1).)*)\1((?:\\.|(?!\1).)*)\1([a-z]*)\z}s;
my ( $pattern, $replacement, $flags ) = ( $2, $3, $4 );
return if $flags =~ /e/; # never evaluate the replacement as code
my $global = $flags =~ /g/ ? 1 : 0;
my $ci = $flags =~ /i/ ? '(?i)' : '';
my $compiled = eval { qr/$ci$pattern/ };
unless ( defined $compiled ) {
Koha::Logger->get->warn("C4::ClassSplitRoutine::RegEx: failed to compile pattern [$pattern]: $@");
return;
}
my $expand = sub { Koha::Regex::Replacement::expand_template( $replacement, [ @{^CAPTURE} ], {%+} ) };
if ($global) {
${$ref} =~ s/$compiled/$expand->()/ge;
} else {
${$ref} =~ s/$compiled/$expand->()/e;
}
return;
}
1; 1;
=head1 AUTHOR =head1 AUTHOR

View File

@ -52,6 +52,10 @@ use Koha::Config::SysPrefs;
use Koha::Config; use Koha::Config;
use Koha::Database; use Koha::Database;
# NOTE: The below list should match the corresponding options in the .pref file
# for CSVDelimiter
use constant ALLOWED_CSV_DELIMITERS => ( ';', 'tabulation', ',', '/', '\\', '#' );
=head1 NAME =head1 NAME
C4::Context - Maintain and manipulate the context of a Koha script C4::Context - Maintain and manipulate the context of a Koha script
@ -485,6 +489,13 @@ sub delete_preference {
sub csv_delimiter { sub csv_delimiter {
my ( $self, $value ) = @_; my ( $self, $value ) = @_;
# Validate input against allowed delimiters
if ($value) {
my %allowed = map { $_ => 1 } ALLOWED_CSV_DELIMITERS;
$value = undef unless $allowed{$value};
}
my $delimiter = $value || $self->preference('CSVDelimiter') || ','; my $delimiter = $value || $self->preference('CSVDelimiter') || ',';
$delimiter = "\t" if $delimiter eq 'tabulation'; $delimiter = "\t" if $delimiter eq 'tabulation';
return $delimiter; return $delimiter;

View File

@ -272,7 +272,7 @@ ADD_LAYOUTS:
This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged
to the Apache log. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separated to the Apache log. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separated
fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valud SQL 'WHERE' filter. fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.' NOTE: Do not pass in the keyword 'WHERE.'
@ -320,7 +320,7 @@ sub get_all_image_names {
This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count
for each batch upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log. for each batch upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter. One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.' NOTE: Do not pass in the keyword 'WHERE.'
@ -352,7 +352,7 @@ sub get_batch_summary {
This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count
for each label upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log. for each label upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter. One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.' NOTE: Do not pass in the keyword 'WHERE.'
@ -405,7 +405,7 @@ sub get_label_summary {
This function returns an arrayref whose elements are hashes containing the label_ids of current cards along with the item count This function returns an arrayref whose elements are hashes containing the label_ids of current cards along with the item count
for each card upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log. for each card upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter. One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.' NOTE: Do not pass in the keyword 'WHERE.'

View File

@ -476,7 +476,6 @@ sub _allocateWithTransportCostMatrix {
my @m = map { [ (undef) x $num_tasks ] } ( 1 .. $num_agents ); my @m = map { [ (undef) x $num_tasks ] } ( 1 .. $num_agents );
my $inf = -1; # Initially represent infinity with a negative value.
my $max = 0; my $max = 0;
# If some candidate holds requests cannot be filled and there are # If some candidate holds requests cannot be filled and there are
@ -493,6 +492,7 @@ sub _allocateWithTransportCostMatrix {
RETRY: RETRY:
while (1) { while (1) {
my $inf = -1; # Initially represent infinity with a negative value.
return [] if $num_agents == 0 || $num_tasks + scalar(@remaining) == 0; return [] if $num_agents == 0 || $num_tasks + scalar(@remaining) == 0;
if ( $num_tasks < $num_agents && @remaining ) { if ( $num_tasks < $num_agents && @remaining ) {
@ -668,20 +668,32 @@ RETRY:
if ( $retries-- > 0 && @unallocated && @remaining ) { if ( $retries-- > 0 && @unallocated && @remaining ) {
# @unallocated is built by iterating agents (rows), so its
# entries are not necessarily in column order: Munkres'
# row-to-column assignment is a permutation, not a monotonic
# mapping. The matrix compaction below scans columns in
# ascending order and expects to consume @unallocated in that
# same order, so it must be sorted first.
my @unallocated_sorted = sort { $a <=> $b } @unallocated;
# Remove the transport cost of unfilled holds and compact the matrix. # Remove the transport cost of unfilled holds and compact the matrix.
# Also remove the hold request from the array. # Also remove the hold request from the array.
for ( my $i = 0 ; $i < $num_agents ; $i++ ) { for ( my $i = 0 ; $i < $num_agents ; $i++ ) {
my $u = 0; my $u = 0;
for ( my $j = 0 ; $j < $num_tasks ; $j++ ) { for ( my $j = 0 ; $j < $num_tasks ; $j++ ) {
if ( $u < scalar(@unallocated) && $unallocated[$u] == $j ) { if ( $u < scalar(@unallocated_sorted) && $unallocated_sorted[$u] == $j ) {
$u++; $u++;
} elsif ( $u > 0 ) { } elsif ( $u > 0 ) {
$m[$i][ $j - $u ] = $m[$i][$j]; $m[$i][ $j - $u ] = $m[$i][$j];
} }
} }
} }
for ( my $u = 0 ; $u < scalar(@unallocated) ; $u++ ) {
splice @requests, $unallocated[$u], 1; # Indices must be spliced out in reverse order, otherwise removing
# a lower index shifts the position of any higher index still to
# be removed.
for my $u ( reverse @unallocated_sorted ) {
splice @requests, $u, 1;
} }
$num_tasks = scalar(@requests); $num_tasks = scalar(@requests);

View File

@ -38,6 +38,7 @@ BEGIN {
SearchItems SearchItems
PrepareItemrecordDisplay PrepareItemrecordDisplay
ToggleNewStatus ToggleNewStatus
GetAutomaticItemModificationAgeFields
); );
} }
@ -58,6 +59,7 @@ use Try::Tiny qw( catch try );
use Koha::AuthorisedValues; use Koha::AuthorisedValues;
use Koha::DateUtils qw( dt_from_string ); use Koha::DateUtils qw( dt_from_string );
use Koha::Database; use Koha::Database;
use Koha::Logger;
use Koha::Biblios; use Koha::Biblios;
use Koha::Biblioitems; use Koha::Biblioitems;
@ -1655,6 +1657,25 @@ sub PrepareItemrecordDisplay {
}; };
} }
=head2 GetAutomaticItemModificationAgeFields
my @fields = GetAutomaticItemModificationAgeFields();
Returns the fixed, C<items.>-prefixed list of date columns that are valid
"age field" values for automatic item modification rules (see
ToggleNewStatus below). This is the single source of truth for that
allow-list; it is shared with F<tools/automatic_item_modification_by_age.pl>
so the two cannot drift apart.
=cut
sub GetAutomaticItemModificationAgeFields {
return (
'items.dateaccessioned', 'items.replacementpricedate', 'items.datelastborrowed', 'items.datelastseen',
'items.damaged_on', 'items.itemlost_on', 'items.withdrawn_on'
);
}
=head2 ToggleNewStatus =head2 ToggleNewStatus
Missing POD for ToggleNewStatus. Missing POD for ToggleNewStatus.
@ -1671,17 +1692,32 @@ sub ToggleNewStatus {
my @item_columns = map { "items.$_" } Koha::Items->columns; my @item_columns = map { "items.$_" } Koha::Items->columns;
my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns; my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
my @biblio_columns = map { "biblio.$_" } Koha::Biblios->columns; my @biblio_columns = map { "biblio.$_" } Koha::Biblios->columns;
my @age_fields = GetAutomaticItemModificationAgeFields();
my $report; my $report;
for my $rule (@rules) { for my $rule (@rules) {
my $age = $rule->{age}; my $age = $rule->{age};
# Default to using items.dateaccessioned if there's an old item modification rule # Default to using items.dateaccessioned if there's an old item modification rule
# missing an agefield value # missing an agefield value, or if the stored value is not (or is no longer) a
my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned'; # recognised age field - values are only trusted from a fixed allow-list since
# this is spliced directly into the SQL below and cannot be bound as a placeholder.
my $agefield = $rule->{agefield} // '';
unless ( grep { $_ eq $agefield } @age_fields ) {
Koha::Logger->get->warn(qq{Rejecting invalid agefield "$agefield" in automatic item modification rule})
if length $agefield;
$agefield = 'items.dateaccessioned';
}
my $conditions = $rule->{conditions}; my $conditions = $rule->{conditions};
my $substitutions = $rule->{substitutions}; my $substitutions = $rule->{substitutions};
foreach (@$substitutions) { foreach my $substitution (@$substitutions) {
( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ ); my $field = $substitution->{field} // '';
if ( grep { $_ eq $field } @item_columns ) {
( $substitution->{item_field} ) = ( $field =~ /^items\.(.*)/ );
} else {
Koha::Logger->get->warn(
qq{Rejecting invalid substitution field "$field" in automatic item modification rule})
if length $field;
}
} }
my @params; my @params;
@ -1706,6 +1742,9 @@ sub ToggleNewStatus {
$query .= qq| AND $condition->{field} = ?|; $query .= qq| AND $condition->{field} = ?|;
push @params, $condition->{value}; push @params, $condition->{value};
} }
} else {
Koha::Logger->get->warn(
qq{Rejecting invalid condition field "$condition->{field}" in automatic item modification rule});
} }
} }
if ( defined $age ) { if ( defined $age ) {
@ -1722,6 +1761,7 @@ sub ToggleNewStatus {
my $field = $substitution->{item_field}; my $field = $substitution->{item_field};
my $value = $substitution->{value}; my $value = $substitution->{value};
next unless $substitution->{field}; next unless $substitution->{field};
next unless defined $field;
next next
if ( defined $values->{ $substitution->{item_field} } if ( defined $values->{ $substitution->{item_field} }
and $values->{ $substitution->{item_field} } eq $substitution->{value} ); and $values->{ $substitution->{item_field} } eq $substitution->{value} );

View File

@ -742,13 +742,28 @@ sub _normalize_match_point {
sub _isbn_cleanup { sub _isbn_cleanup {
my ($isbn) = @_; my ($isbn) = @_;
return NormalizeISBN(
{ return unless $isbn;
isbn => $isbn,
format => 'ISBN-10', if ( $isbn =~ /^979/ ) {
strip_hyphens => 1,
} # ISBNs starting with 979 cannot be converted to ISBN-10 format, but all other valid ISBNs can
) if $isbn; return NormalizeISBN(
{
isbn => $isbn,
format => 'ISBN-13',
strip_hyphens => 1,
}
);
} else {
return NormalizeISBN(
{
isbn => $isbn,
format => 'ISBN-10',
strip_hyphens => 1,
}
);
}
} }
=head2 NormalizeISBN =head2 NormalizeISBN

View File

@ -51,8 +51,8 @@ This will return a list of all the available delimiters.
sub GetDelimiterChoices { sub GetDelimiterChoices {
my $default = C4::Context->preference('CSVDelimiter'); my $default = C4::Context->preference('CSVDelimiter');
# NOTE: The below list should match the corresponding options in the .pref file # Use the centralized list of allowed delimiters from C4::Context
my @dels = ( ';', 'tabulation', ',', '/', '\\', '#' ); my @dels = C4::Context::ALLOWED_CSV_DELIMITERS;
return { return {
values => \@dels, values => \@dels,

View File

@ -1,7 +1,7 @@
package C4::Reserves; package C4::Reserves;
# Copyright 2000-2002 Katipo Communications # Copyright 2000-2002 Katipo Communications
# 2006 SAN Ouest Provence # 2006 SAN Ouest Provence # codespell:ignore
# 2007-2010 BibLibre Paul POULAIN # 2007-2010 BibLibre Paul POULAIN
# 2011 Catalyst IT # 2011 Catalyst IT
# #
@ -110,10 +110,10 @@ This modules provides some functions to deal with reservations.
- priority >0 : then the reserve is at 1st stage, and not yet affected to any item. - priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
=0 : then the reserve is being dealt =0 : then the reserve is being dealt
- found : NULL : means the patron requested the 1st available, and we haven't chosen the item - found : NULL : means the patron requested the 1st available, and we haven't chosen the item
T(ransit) : the reserve is linked to an item but is in transit to the pickup branch T (Transit) : the reserve is linked to an item but is in transit to the pickup branch
W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf W (Waiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
F(inished) : the reserve has been completed, and is done F (Finished) : the reserve has been completed, and is done
P(rocessing) : reserved item has been returned using self-check machine and reserve needs to be confirmed P (Processing) : reserved item has been returned using self-check machine and reserve needs to be confirmed
by librarian before notice is send and status changed to waiting. by librarian before notice is send and status changed to waiting.
Applicable only if HoldsNeedProcessingSIP system preference is set. Applicable only if HoldsNeedProcessingSIP system preference is set.
- itemnumber : empty : the reserve is still unaffected to an item - itemnumber : empty : the reserve is still unaffected to an item
@ -271,7 +271,6 @@ sub AddReserve {
# Log the hold creation # Log the hold creation
if ( C4::Context->preference('HoldsLog') ) { if ( C4::Context->preference('HoldsLog') ) {
my $info = to_json( my $info = to_json(
{ {
hold => $hold->id, hold => $hold->id,
@ -289,12 +288,11 @@ sub AddReserve {
patron_expiration_date => $hold->patron_expiration_date, patron_expiration_date => $hold->patron_expiration_date,
priority => $hold->priority, priority => $hold->priority,
reservenotes => $hold->reservenotes, reservenotes => $hold->reservenotes,
confirmations => $confirmations, confirmations => $confirmations || [],
forced => $forced forced => $forced || []
}, },
{ pretty => 1, canonical => 1 } { pretty => 1, canonical => 1 }
); );
logaction( 'HOLDS', 'CREATE', $hold->id, $info ); logaction( 'HOLDS', 'CREATE', $hold->id, $info );
} }

View File

@ -55,6 +55,10 @@ my %transports = (
# Read configuration # Read configuration
# #
my $config = C4::SIP::Sip::Configuration->new( $ARGV[0] ); my $config = C4::SIP::Sip::Configuration->new( $ARGV[0] );
# Per-process record of when this worker loaded $config. Not shared since $config is per-process.
our $sip2_config_read_timestamp = Koha::Caches->get_instance->get_from_cache('sip2_resource_last_modified');
my @params; my @params;
# #
@ -234,13 +238,15 @@ sub _config_up_to_date {
my $cache = Koha::Caches->get_instance(); my $cache = Koha::Caches->get_instance();
my $sip2_resource_last_modified = $cache->get_from_cache("sip2_resource_last_modified"); my $sip2_resource_last_modified = $cache->get_from_cache("sip2_resource_last_modified");
my $sip2_config_read_timestamp = $cache->get_from_cache("sip2_config_read_timestamp");
unless ($sip2_resource_last_modified) { unless ($sip2_resource_last_modified) {
siplog( "LOG_WARNING", "Couldn't find sip2_resource_last_modified, considering configuration not up to date" ); siplog( "LOG_INFO", "Couldn't find sip2_resource_last_modified, considering configuration not up to date" );
return 0; return 0;
} }
return $sip2_config_read_timestamp >= $sip2_resource_last_modified;
return 0 unless defined $sip2_config_read_timestamp;
return $sip2_config_read_timestamp >= $sip2_resource_last_modified ? 1 : 0;
} }
# #
@ -260,15 +266,17 @@ sub process_request {
my ( $sockaddr, $port, $proto ); my ( $sockaddr, $port, $proto );
my $transport; my $transport;
# Flush L1 before the freshness check so it sees the current shared value, not a stale one inherited at fork.
Koha::Caches->flush_L1_caches();
$self->{config} = $config; $self->{config} = $config;
unless ( $self->_config_up_to_date() ) { unless ( $self->_config_up_to_date() ) {
$self->{config} = C4::SIP::Sip::Configuration->get_configuration( undef, $self->{config} ); $self->{config} = C4::SIP::Sip::Configuration->get_configuration( undef, $self->{config} );
$sip2_config_read_timestamp = Koha::Caches->get_instance->get_from_cache('sip2_resource_last_modified');
} }
# Flushing L1 to make sure the request will be processed using the correct data
Koha::Caches->flush_L1_caches();
$self->{account} = undef; # Clear out the account from the last request, it may be different $self->{account} = undef; # Clear out the account from the last request, it may be different
$self->{ils} = undef; # And the ILS handle, so a reused worker can't reuse a prior session's connection
$self->{logger} = set_logger( Koha::Logger->get( { interface => 'sip' } ) ); $self->{logger} = set_logger( Koha::Logger->get( { interface => 'sip' } ) );
# Flush previous MDCs to prevent accidentally leaking incorrect MDC-entries # Flush previous MDCs to prevent accidentally leaking incorrect MDC-entries
@ -468,12 +476,12 @@ sub telnet_transport {
$pwd = get_clean_string($pwd); $pwd = get_clean_string($pwd);
siplog( "LOG_DEBUG", "telnet_transport 2: uid length %s, pwd length %s", length($uid), length($pwd) ); siplog( "LOG_DEBUG", "telnet_transport 2: uid length %s, pwd length %s", length($uid), length($pwd) );
# Check if user is authorized for SIP access, then authenticate via login_core # Only log the terminal in once login_core actually authenticates it
if ( exists( $config->{accounts}->{$uid} ) ) { if ( exists( $config->{accounts}->{$uid} )
&& C4::SIP::Sip::MsgType::login_core( $self, $uid, $pwd ) )
{
$account = $config->{accounts}->{$uid}; $account = $config->{accounts}->{$uid};
if ( C4::SIP::Sip::MsgType::login_core( $self, $uid, $pwd ) ) { last;
last;
}
} }
siplog( "LOG_WARNING", "Invalid login attempt: '%s'", ( $uid || '' ) ); siplog( "LOG_WARNING", "Invalid login attempt: '%s'", ( $uid || '' ) );
print("Invalid login$CRLF"); print("Invalid login$CRLF");

View File

@ -41,17 +41,13 @@ my $parser = XML::Simple->new(
sub get_configuration { sub get_configuration {
my ( $class, $config_file, $current_config ) = @_; my ( $class, $config_file, $current_config ) = @_;
my $cache = Koha::Caches->get_instance(); my $cache = Koha::Caches->get_instance();
my $sip2_resource_last_modified = $cache->get_from_cache("sip2_resource_last_modified");
if ($sip2_resource_last_modified) { unless ( $cache->get_from_cache("sip2_resource_last_modified") ) {
$cache->set_in_cache( 'sip2_config_read_timestamp', $sip2_resource_last_modified );
} else {
# Set sip2_resource_last_modified so config won't be read from db on each request # Seed sip2_resource_last_modified so the configuration is not re-read
my $now = dt_from_string()->epoch; # from the database on every request while the value is missing.
$cache->set_in_cache( 'sip2_resource_last_modified', $now ); $cache->set_in_cache( 'sip2_resource_last_modified', dt_from_string()->epoch );
$cache->set_in_cache( 'sip2_config_read_timestamp', $now );
} }
my $cfg; my $cfg;

View File

@ -932,19 +932,19 @@ sub login_core {
my $pwd = shift; my $pwd = shift;
my $status = 1; # Assume it all works my $status = 1; # Assume it all works
# Clear any session state up front, including anything left over from a previous connection
# on this preforked worker, so no login_core exit path can leave the caller acting as an
# authenticated terminal. Only a successful api_auth below re-establishes the session.
delete $server->{$_} foreach qw( account ils institution policy sip_username sip_password );
# Check if this userid is authorized for SIP access # Check if this userid is authorized for SIP access
if ( !exists( $server->{config}->{accounts}->{$uid} ) ) { if ( !exists( $server->{config}->{accounts}->{$uid} ) ) {
siplog( "LOG_WARNING", "MsgType::login_core: SIP access not authorized for user '$uid'" ); siplog( "LOG_WARNING", "MsgType::login_core: SIP access not authorized for user '$uid'" );
return 0; return 0;
} }
# Store the active account configuration # Authenticate before storing the account on the session
$server->{account} = $server->{config}->{accounts}->{$uid}; my $inst = $server->{config}->{accounts}->{$uid}->{institution};
my $inst = $server->{account}->{institution};
$server->{institution} = $server->{config}->{institutions}->{$inst};
$server->{policy} = $server->{institution}->{policy};
$server->{sip_username} = $uid;
$server->{sip_password} = $pwd;
# Authenticate using Koha's internal authentication (checks hashed password in borrowers table) # Authenticate using Koha's internal authentication (checks hashed password in borrowers table)
my $auth_status = api_auth( $uid, $pwd, $inst ); my $auth_status = api_auth( $uid, $pwd, $inst );
@ -956,6 +956,13 @@ sub login_core {
return 0; return 0;
} }
# Store the active account configuration now user has authenticated
$server->{account} = $server->{config}->{accounts}->{$uid};
$server->{institution} = $server->{config}->{institutions}->{$inst};
$server->{policy} = $server->{institution}->{policy};
$server->{sip_username} = $uid;
$server->{sip_password} = $pwd;
siplog( "LOG_INFO", "Successful login/auth for '%s' of '%s'", $server->{account}->{id}, $inst ); siplog( "LOG_INFO", "Successful login/auth for '%s' of '%s'", $server->{account}->{id}, $inst );
# #

View File

@ -110,7 +110,7 @@ sub update_data {
$i++; $i++;
} }
} }
say "$i data insered or updated"; say "$i data inserted or updated";
} }
=head2 get_report =head2 get_report

View File

@ -1,66 +0,0 @@
package C4::Stats;
# Copyright 2000-2002 Katipo Communications
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use base 'Exporter';
BEGIN {
our @EXPORT_OK = qw(
UpdateStats
);
}
use Koha::Statistics;
=head1 NAME
C4::Stats - Update Koha statistics (log)
=head1 SYNOPSIS
use C4::Stats;
=head1 DESCRIPTION
The functions of this module deals with statistics table of Koha database.
=head1 FUNCTIONS
=head2 UpdateStats
C4::Stats::UpdateStats($params);
This is a (legacy) alias for Koha::Statistic->new($params)->store.
Please see Koha::Statistic module.
=cut
sub UpdateStats {
my $params = shift;
Koha::Statistic->new($params)->store;
}
1;
__END__
=head1 AUTHOR
Koha Development Team <https://koha-community.org/>
=cut

View File

@ -108,7 +108,7 @@ sub ModSuggestion {
my $transport = my $transport =
( C4::Context->preference("FallbackToSMSIfNoEmail") ) ( C4::Context->preference("FallbackToSMSIfNoEmail") )
&& ( $patron->smsalertnumber ) && ( $patron->smsalertnumber )
&& ( !$patron->email ) ? 'sms' : 'email'; && ( !$patron->notice_email_address ) ? 'sms' : 'email';
if ( if (
my $letter = C4::Letters::GetPreparedLetter( my $letter = C4::Letters::GetPreparedLetter(

View File

@ -241,6 +241,7 @@ sub _shared_preferences {
AmazonCoverImages AmazonCoverImages
OPACAmazonCoverImages OPACAmazonCoverImages
Babeltheque Babeltheque
EnableZotero
GoogleJackets GoogleJackets
HTML5MediaEnabled HTML5MediaEnabled
LibraryThingForLibrariesEnabled LibraryThingForLibrariesEnabled

View File

@ -16,7 +16,7 @@ sub search {
my $start = $params->{start}; my $start = $params->{start};
my $length = $params->{length}; my $length = $params->{length};
# If not logged in user, be carreful and set the borrowernumber to 0 # If not logged in user, be careful and set the borrowernumber to 0
# to prevent private lists lack # to prevent private lists lack
my $loggedinuser = C4::Context->userenv->{'number'} || 0; my $loggedinuser = C4::Context->userenv->{'number'} || 0;

View File

@ -29,7 +29,7 @@ use vars qw{ $VERSION };
# - #4 : the developer version. The 4th number is the database subversion. # - #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 # used by developers when the database changes. updatedatabase take care of the changes itself
# and is automatically called by Auth.pm when needed. # and is automatically called by Auth.pm when needed.
$VERSION = "26.05.00.000"; $VERSION = "26.05.03.000";
sub version { sub version {
return $VERSION; return $VERSION;

View File

@ -25,7 +25,6 @@ use Try::Tiny qw( catch try );
use C4::Letters; use C4::Letters;
use C4::Log qw( logaction ); use C4::Log qw( logaction );
use C4::Stats qw( UpdateStats );
use C4::Overdues qw(GetFine); use C4::Overdues qw(GetFine);
use C4::Context; use C4::Context;
@ -38,6 +37,7 @@ use Koha::Account::DebitTypes;
use Koha::Exceptions; use Koha::Exceptions;
use Koha::Exceptions::Account; use Koha::Exceptions::Account;
use Koha::Plugins; use Koha::Plugins;
use Koha::Statistics;
=head1 NAME =head1 NAME
@ -286,7 +286,7 @@ sub add_credit {
} }
)->store(); )->store();
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => $library_id, branch => $library_id,
type => lc($credit_type), type => lc($credit_type),
@ -294,7 +294,8 @@ sub add_credit {
borrowernumber => $self->{patron_id}, borrowernumber => $self->{patron_id},
interface => $interface, interface => $interface,
} }
) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' ); )->store()
if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
Koha::Plugins->call( Koha::Plugins->call(
'after_account_action', 'after_account_action',

View File

@ -19,12 +19,14 @@ package Koha::ActionLog;
use Modern::Perl; use Modern::Perl;
use JSON qw( decode_json );
use C4::Context; use C4::Context;
# Avoid `use Koha::Patrons` here: Koha::ActionLog is loaded transitively by # Avoid `use Koha::Patrons` here: Koha::ActionLog is loaded transitively by
# C4::Log very early in the dependency chain, before Koha::Patron has been # C4::Log very early in the dependency chain, before Koha::Patron has been
# fully defined. The accessors below reach for Koha::Patron at call time, # fully defined. The accessors below reach for Koha::Patron (and Koha::Items)
# by which point it is reliably available. # at call time, by which point they are reliably available.
use base qw(Koha::Object); use base qw(Koha::Object);
@ -70,6 +72,42 @@ sub patron {
return Koha::Patron->_new_from_dbic($rs); return Koha::Patron->_new_from_dbic($rs);
} }
=head3 item
my $item = $log->item;
Returns the related I<Koha::Item> for a CIRCULATION log entry, or I<undef>.
The itemnumber is not stored in a dedicated column; it lives inside the
C<info> field, which for circulation is either a JSON payload carrying an
C<itemnumber> key (ISSUE, RENEWAL) or a bare itemnumber string (RETURN).
Returns I<undef> for non-circulation rows, when no itemnumber can be
derived, or when the item has since been deleted.
=cut
sub item {
my ($self) = @_;
return unless ( $self->module // q{} ) eq 'CIRCULATION';
my $info = $self->info;
return unless defined $info;
my $itemnumber;
my $decoded = eval { decode_json($info) };
if ( ref($decoded) eq 'HASH' && $decoded->{itemnumber} ) {
$itemnumber = $decoded->{itemnumber};
} elsif ( $info =~ /^\s*(\d+)\s*$/ ) {
$itemnumber = $1;
}
return unless $itemnumber;
require Koha::Items;
return Koha::Items->find($itemnumber);
}
=head2 Internal methods =head2 Internal methods
=head3 _type =head3 _type

View File

@ -95,19 +95,31 @@ sub search_for_display {
my ( $self, $params ) = @_; my ( $self, $params ) = @_;
my $lang = $params->{lang} || q{}; my $lang = $params->{lang} || q{};
# If lang is not default, we will search for entries matching $lang but fallback to default if $lang is not found
# Then we need a subquery count in where clause; DBIx::Class/SQL::Abstract does not support it, fallback to literal SQL
my $subquery =
qq|(SELECT COUNT(*) FROM additional_contents_localizations WHERE lang='$lang' AND additional_content_id=me.additional_content_id)=0|;
my $search_params = get_public_query_search_params($params); my $search_params = get_public_query_search_params($params);
$search_params->{lang} = 'default' if !$lang || $lang eq 'default'; $search_params->{lang} = 'default' if !$lang || $lang eq 'default';
$search_params->{-or} = [ { 'lang' => $lang }, '-and' => [ 'lang', 'default', \$subquery ] ]
if !$search_params->{lang}; unless ( $search_params->{lang} ) {
$search_params->{-or} = [
{ lang => $lang },
{
-and => [
{ lang => 'default' },
\[
'(SELECT COUNT(*) FROM additional_contents_localizations WHERE lang = ? AND additional_content_id = me.additional_content_id) = 0',
$lang
]
]
}
];
}
my $attribs = { my $attribs = {
prefetch => 'additional_content', prefetch => 'additional_content',
order_by => { -desc => 'additional_content.published_on' } order_by => [
\['ISNULL(additional_content.number)'],
{ -asc => 'additional_content.number' },
{ -desc => 'additional_content.published_on' },
],
}; };
return Koha::AdditionalContentsLocalizations->search( $search_params, $attribs ); return Koha::AdditionalContentsLocalizations->search( $search_params, $attribs );
} }

View File

@ -104,7 +104,7 @@ sub process {
}; };
try { try {
my ($results) = Koha::Items->search( { itemnumber => \@record_ids } )->batch_update( my ($results) = Koha::Items->search( { itemnumber => { -in => \@record_ids } } )->batch_update(
{ {
regex_mod => $regex_mod, regex_mod => $regex_mod,
new_values => $new_values, new_values => $new_values,

View File

@ -18,6 +18,7 @@ package Koha::BackgroundJob::PseudonymizeStatistic;
use Modern::Perl; use Modern::Perl;
use Koha::PseudonymizedTransactions; use Koha::PseudonymizedTransactions;
use Koha::Statistics;
use base 'Koha::BackgroundJob'; use base 'Koha::BackgroundJob';

View File

@ -1624,7 +1624,9 @@ sub get_marc_authors {
if ( $marcflavour eq "UNIMARC" ) { if ( $marcflavour eq "UNIMARC" ) {
$fields_filter = '200'; $fields_filter = '200';
} else { # marc21/normarc } else { # marc21/normarc
$fields_filter = '100'; $fields_filter = '1..';
$mintag = '100';
$maxtag = '111';
} }
my @first_authors = @{ my @first_authors = @{

View File

@ -23,7 +23,7 @@ use base qw(Koha::Object);
=head1 NAME =head1 NAME
Koha::ClassSortRule Koha Classfication Sorting Rule Object class Koha::ClassSortRule Koha Classification Sorting Rule Object class
=head1 API =head1 API

View File

@ -26,7 +26,7 @@ my $cache = Koha::Caches->get_instance();
=head1 NAME =head1 NAME
Koha::ClassSource - Koha Classfication Source Object class Koha::ClassSource - Koha Classification Source Object class
=head1 API =head1 API

View File

@ -25,7 +25,7 @@ use base qw(Koha::Object);
=head1 NAME =head1 NAME
Koha::ClassSplitRule Koha Classfication Splitting Rule Object class Koha::ClassSplitRule Koha Classification Splitting Rule Object class
=head1 API =head1 API

View File

@ -68,7 +68,8 @@ sub invalid_item_library {
=head2 ids =head2 ids
Build a hashref of IDs from a Koha::Object Koha::Objects-based object. Build a set of biblionumbers suitable for an IN search from a Koha::Object or Koha::Objects-based object.
Returns a subquery for a Koha::Objects set and an arrayref for a single Koha::Object.
=cut =cut
@ -76,8 +77,8 @@ sub ids {
my ( $self, $object ) = @_; my ( $self, $object ) = @_;
if ( $object->can('_resultset') ) { if ( $object->can('_resultset') ) {
# It's a Koha::Objects # It's a Koha::Objects, don't materialize the ids, let the database resolve the set
return [ $object->get_column('biblionumber') ]; return $object->_resultset->get_column('biblionumber')->as_query;
} else { } else {
# It's a single Koha::Object # It's a single Koha::Object
@ -98,7 +99,7 @@ sub no_item_type {
if ( C4::Context->preference('item-level_itypes') ) { if ( C4::Context->preference('item-level_itypes') ) {
my $items_without_itype = Koha::Items->search( my $items_without_itype = Koha::Items->search(
{ {
biblionumber => $ids, biblionumber => { -in => $ids },
-or => [ itype => undef, itype => '' ] -or => [ itype => undef, itype => '' ]
} }
); );
@ -122,7 +123,7 @@ sub no_item_type {
} else { } else {
my $biblioitems_without_itemtype = Koha::Biblioitems->search( my $biblioitems_without_itemtype = Koha::Biblioitems->search(
{ {
biblionumber => $ids, biblionumber => { -in => $ids },
-or => [ itemtype => undef, itemtype => '' ] -or => [ itemtype => undef, itemtype => '' ]
} }
); );
@ -152,7 +153,7 @@ sub invalid_item_type {
if ( C4::Context->preference('item-level_itypes') ) { if ( C4::Context->preference('item-level_itypes') ) {
my $items_with_invalid_itype = Koha::Items->search( my $items_with_invalid_itype = Koha::Items->search(
{ {
biblionumber => $ids, biblionumber => { -in => $ids },
-and => [ itype => { not_in => \@itemtypes }, itype => { '!=' => '' } ] -and => [ itype => { not_in => \@itemtypes }, itype => { '!=' => '' } ]
} }
); );
@ -169,7 +170,7 @@ sub invalid_item_type {
} else { } else {
my $biblioitems_with_invalid_itemtype = Koha::Biblioitems->search( my $biblioitems_with_invalid_itemtype = Koha::Biblioitems->search(
{ {
biblionumber => $ids, biblionumber => { -in => $ids },
-and => [ itemtype => { not_in => \@itemtypes }, itemtype => { '!=' => '' } ] -and => [ itemtype => { not_in => \@itemtypes }, itemtype => { '!=' => '' } ]
} }
); );
@ -200,7 +201,7 @@ sub errors_in_marc {
my ( $item_tag, $item_subfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber"); my ( $item_tag, $item_subfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber");
my $search_string = q{ExtractValue(metadata,'count(//datafield[@tag="} . $item_tag . q{"])')>0}; my $search_string = q{ExtractValue(metadata,'count(//datafield[@tag="} . $item_tag . q{"])')>0};
my $biblio_metadatas_with_item_fields = my $biblio_metadatas_with_item_fields =
Koha::Biblio::Metadatas->search( { biblionumber => $ids } )->search( \$search_string ); Koha::Biblio::Metadatas->search( { biblionumber => { -in => $ids } } )->search( \$search_string );
if ( $biblio_metadatas_with_item_fields->count ) { if ( $biblio_metadatas_with_item_fields->count ) {
while ( my $biblio_metadata_with_item_fields = $biblio_metadatas_with_item_fields->next ) { while ( my $biblio_metadata_with_item_fields = $biblio_metadatas_with_item_fields->next ) {
push @item_fields_in_marc, push @item_fields_in_marc,
@ -212,7 +213,7 @@ sub errors_in_marc {
my ( $biblio_tag, $biblio_subfield ) = C4::Biblio::GetMarcFromKohaField("biblio.biblionumber"); my ( $biblio_tag, $biblio_subfield ) = C4::Biblio::GetMarcFromKohaField("biblio.biblionumber");
my ( $biblioitem_tag, $biblioitem_subfield ) = C4::Biblio::GetMarcFromKohaField("biblioitems.biblioitemnumber"); my ( $biblioitem_tag, $biblioitem_subfield ) = C4::Biblio::GetMarcFromKohaField("biblioitems.biblioitemnumber");
$biblios = Koha::Biblios->search( { biblionumber => $ids } ); $biblios = Koha::Biblios->search( { biblionumber => { -in => $ids } } );
while ( my $biblio = $biblios->next ) { while ( my $biblio = $biblios->next ) {
my $record = eval { $biblio->metadata->record; }; my $record = eval { $biblio->metadata->record; };
if ($@) { if ($@) {
@ -248,7 +249,7 @@ sub errors_in_marc {
__x( __x(
q{Biblionumber {biblionumber} has biblioitemnumber '{biblioitemnumber_in_marc}' but should be '{biblioitemnumber}' in {biblioitem_tag}${biblioitem_subfield}}, q{Biblionumber {biblionumber} has biblioitemnumber '{biblioitemnumber_in_marc}' but should be '{biblioitemnumber}' in {biblioitem_tag}${biblioitem_subfield}},
biblionumber => $biblio->biblionumber, biblionumber => $biblio->biblionumber,
biblioitemnumber_in_marc => $biblionumber, biblioitemnumber_in_marc => $biblioitemnumber,
biblioitemnumber => $biblio->biblioitem->biblioitemnumber, biblioitemnumber => $biblio->biblioitem->biblioitemnumber,
biblioitem_tag => $biblioitem_tag, biblioitem_tag => $biblioitem_tag,
biblioitem_subfield => $biblioitem_subfield, biblioitem_subfield => $biblioitem_subfield,
@ -316,7 +317,7 @@ sub nonexistent_AV {
# We are only checking biblios with items # We are only checking biblios with items
my $items = Koha::Items->search( my $items = Koha::Items->search(
{ {
'me.biblionumber' => $ids, 'me.biblionumber' => { -in => $ids },
$tmp_kohafield => { $tmp_kohafield => {
-not_in => [ $avs->get_column('authorised_value'), '' ], -not_in => [ $avs->get_column('authorised_value'), '' ],
'!=' => undef, '!=' => undef,
@ -371,7 +372,7 @@ sub empty_title {
my @errors; my @errors;
$biblios = Koha::Biblios->search( $biblios = Koha::Biblios->search(
{ {
biblionumber => $ids, biblionumber => { -in => $ids },
-or => [ -or => [
title => '', title => '',
title => undef, title => undef,

View File

@ -140,6 +140,7 @@ sub get_files_to_test {
@koha_commit_history; @koha_commit_history;
if ($last_build_commit) { if ($last_build_commit) {
@files = @{ from_json( read_file("$self->{git_repo_dir}/$self->{test_dir}/$last_build_commit") ) }; @files = @{ from_json( read_file("$self->{git_repo_dir}/$self->{test_dir}/$last_build_commit") ) };
@files = grep { $dev_files->get_filetype($_) eq $filetype } @files;
@files = $dev_files->remove_exceptions( \@files, $filetype ); @files = $dev_files->remove_exceptions( \@files, $filetype );
push @files, $dev_files->ls_files( $filetype, "$last_build_commit HEAD" ); push @files, $dev_files->ls_files( $filetype, "$last_build_commit HEAD" );
} else { } else {

View File

@ -1603,7 +1603,7 @@ Koha::EDI
Returns 1 on success undef on error Returns 1 on success undef on error
Most usual cause of error is a line with no or incorrect budget codes Most usual cause of error is a line with no or incorrect budget codes
which woild cause order creation to abort which would cause order creation to abort
If other correct lines exist these are processed and the erroneous line os logged If other correct lines exist these are processed and the erroneous line os logged
=head2 title_level_class =head2 title_level_class

View File

@ -414,7 +414,7 @@ Edifact - Edifact message handler
=head2 service_string_advice =head2 service_string_advice
Examines the Service String Advice returns 1 if the default separartors are in use Examines the Service String Advice returns 1 if the default separators are in use
undef otherwise undef otherwise
=head2 segmentize =head2 segmentize

View File

@ -149,7 +149,7 @@ sub interchange_header {
$hdr .= $self->interchange_control_reference(); $hdr .= $self->interchange_control_reference();
$hdr .= $separator; $hdr .= $separator;
# Recipents reference password not usually used in edifact # Recipients reference password not usually used in edifact
$hdr .= q{+ORDERS}; # application reference $hdr .= q{+ORDERS}; # application reference
#Edifact does not usually include the following #Edifact does not usually include the following
@ -468,7 +468,7 @@ sub order_line {
# FTX free text for current orderline # FTX free text for current orderline
# Pass vendor note in FTX free text segment # Pass vendor note in FTX free text segment
if ( $orderline->order_vendornote ) { if ( $orderline->order_vendornote ) {
my $vendornote = $orderline->order_vendornote; my $vendornote = encode_text( $orderline->order_vendornote );
chomp $vendornote; chomp $vendornote;
my $ftx = 'FTX+LIN+++'; my $ftx = 'FTX+LIN+++';
$ftx .= $vendornote; $ftx .= $vendornote;
@ -492,7 +492,7 @@ sub order_line {
# RFF : suppliers unique quotation reference number # RFF : suppliers unique quotation reference number
if ( $orderline->suppliers_reference_number ) { if ( $orderline->suppliers_reference_number ) {
$rff = join q{}, 'RFF+', $orderline->suppliers_reference_qualifier, $rff = join q{}, 'RFF+', $orderline->suppliers_reference_qualifier,
':', $orderline->suppliers_reference_number, $seg_terminator; ':', encode_text( $orderline->suppliers_reference_number ), $seg_terminator;
$self->add_seg($rff); $self->add_seg($rff);
} }
@ -579,7 +579,7 @@ sub gir_segments {
my $orderfields = $params->{ol_fields}; my $orderfields = $params->{ol_fields};
my @onorderitems = @{ $params->{items} }; my @onorderitems = @{ $params->{items} };
my $budget_code = $orderfields->{budget_code}; my $budget_code = encode_text( $orderfields->{budget_code} );
my @segments; my @segments;
my $sequence_no = 1; my $sequence_no = 1;
my $lsq_field = C4::Context->preference('EdifactLSQ'); my $lsq_field = C4::Context->preference('EdifactLSQ');
@ -593,27 +593,27 @@ sub gir_segments {
} }
if ( $item->{branchcode} ) { if ( $item->{branchcode} ) {
push @gir_elements, push @gir_elements,
{ identity_number => 'LLO', data => $item->{branchcode} }; { identity_number => 'LLO', data => encode_text( $item->{branchcode} ) };
} }
if ( $item->{itype} ) { if ( $item->{itype} ) {
push @gir_elements, push @gir_elements,
{ identity_number => 'LST', data => $item->{itype} }; { identity_number => 'LST', data => encode_text( $item->{itype} ) };
} }
# Handle LSQ mapping # Handle LSQ mapping
if ( $lsq_field && $item->{$lsq_field} ) { if ( $lsq_field && $item->{$lsq_field} ) {
push @gir_elements, push @gir_elements,
{ identity_number => 'LSQ', data => $item->{$lsq_field} }; { identity_number => 'LSQ', data => encode_text( $item->{$lsq_field} ) };
} }
# Handle LSL mapping # Handle LSL mapping
if ( $lsl_field && $item->{$lsl_field} ) { if ( $lsl_field && $item->{$lsl_field} ) {
push @gir_elements, push @gir_elements,
{ identity_number => 'LSL', data => $item->{$lsl_field} }; { identity_number => 'LSL', data => encode_text( $item->{$lsl_field} ) };
} }
if ( $item->{itemcallnumber} ) { if ( $item->{itemcallnumber} ) {
push @gir_elements, push @gir_elements,
{ identity_number => 'LSM', data => $item->{itemcallnumber} }; { identity_number => 'LSM', data => encode_text( $item->{itemcallnumber} ) };
} }
# itemcallnumber -> shelfmark # itemcallnumber -> shelfmark
@ -621,7 +621,7 @@ sub gir_segments {
push @gir_elements, push @gir_elements,
{ {
identity_number => 'LVT', identity_number => 'LVT',
data => $orderfields->{servicing_instruction} data => encode_text( $orderfields->{servicing_instruction} )
}; };
} }
my $e_cnt = 0; # count number of elements so we dont exceed 5 per segment my $e_cnt = 0; # count number of elements so we dont exceed 5 per segment
@ -666,7 +666,7 @@ sub lin_segment {
my ( $line_number, $item_number_id ) = @_; my ( $line_number, $item_number_id ) = @_;
if ($item_number_id) { if ($item_number_id) {
$item_number_id = "++${item_number_id}:EN"; $item_number_id = "++" . encode_text($item_number_id) . ":EN";
} else { } else {
$item_number_id = q||; $item_number_id = q||;
} }
@ -691,7 +691,7 @@ sub additional_product_id {
} }
# function id set to 5 states this is the main product id # function id set to 5 states this is the main product id
return "PIA+5+$product_id:$product_code$seg_terminator"; return "PIA+5+" . encode_text($product_id) . ":$product_code$seg_terminator";
} }
sub message_date_segment { sub message_date_segment {

View File

@ -21,7 +21,7 @@ use Exception::Class (
}, },
'Koha::Exceptions::Patron::FailedDeleteAnonymousPatron' => { 'Koha::Exceptions::Patron::FailedDeleteAnonymousPatron' => {
isa => 'Koha::Exceptions::Patron', isa => 'Koha::Exceptions::Patron',
description => "Deleting patron failed, AnonymousPatron is not deleteable" description => "Deleting patron failed, AnonymousPatron is not deletable"
}, },
'Koha::Exceptions::Patron::FailedDeleteProtectedPatron' => { 'Koha::Exceptions::Patron::FailedDeleteProtectedPatron' => {
isa => 'Koha::Exceptions::Patron', isa => 'Koha::Exceptions::Patron',

View File

@ -244,6 +244,45 @@ sub _is_connected {
die "Subclass must implement _is_connected"; die "Subclass must implement _is_connected";
} }
=head3 current_directory
my $path = $transport->current_directory();
Returns the transport's current remote working directory as a plain string,
without side effects. Automatically establishes a connection if needed.
B<Note:> This is a read-only query. It must never navigate the connection -
unlike some underlying libraries' own C<cwd()>/C<pwd()> methods, which differ
in meaning between backends (see subclass implementations).
B<Returns:> The current directory path on success, undef on failure. Check
object_messages() for details.
=cut
sub current_directory {
my ($self) = @_;
return unless $self->_ensure_connected();
return $self->_current_directory();
}
=head3 _current_directory
my $path = $transport->_current_directory();
Internal method that returns the protocol-specific current working
directory. Must be implemented by subclasses. Called by current_directory()
after connection verification.
=cut
sub _current_directory {
my ($self) = @_;
die "Subclass must implement _current_directory";
}
=head3 _auto_change_directory =head3 _auto_change_directory
$transport->_auto_change_directory($dir_type, $custom_path); $transport->_auto_change_directory($dir_type, $custom_path);

View File

@ -17,6 +17,7 @@ package Koha::File::Transport::FTP;
use Modern::Perl; use Modern::Perl;
use Net::FTP; use Net::FTP;
use Time::Local qw( timegm );
use Try::Tiny; use Try::Tiny;
use base qw(Koha::File::Transport); use base qw(Koha::File::Transport);
@ -122,6 +123,26 @@ sub _download_file {
return 1; return 1;
} }
=head3 _current_directory
my $path = $self->_current_directory;
Internal method that returns the FTP connection's current working directory.
Net::FTP's own C<cwd()> method is a mutator: called with no argument it
defaults to "/" and issues a real C<CWD /> command, changing the connection's
directory as a side effect and returning a boolean rather than a path. The
read-only equivalent is C<pwd()>, which sends PWD and parses the path out of
the response without navigating anywhere.
=cut
sub _current_directory {
my ($self) = @_;
return $self->{connection}->pwd;
}
=head3 _change_directory =head3 _change_directory
my $success = $server->_change_directory($directory); my $success = $server->_change_directory($directory);
@ -144,7 +165,7 @@ sub _change_directory {
type => 'success', type => 'success',
payload => { payload => {
directory => $remote_directory, directory => $remote_directory,
pwd => $self->{connection}->pwd pwd => $self->_current_directory
} }
} }
); );
@ -156,7 +177,22 @@ sub _change_directory {
Internal method that performs the FTP-specific file listing operation. Internal method that performs the FTP-specific file listing operation.
Returns an array reference of hashrefs with file information. Returns an array reference of hashrefs with file information.
Each hashref contains: filename, longname, size, perms. Each hashref contains: filename, longname, size, perms, mtime, type.
The listing is obtained with MLSD (RFC 3659), whose machine-readable output
is locale- and platform-independent and gives us the filename unambiguously
(the pathname is the entire text after the "facts" and a single space, so
filenames containing spaces are handled correctly). Servers that do not
support MLSD fall back to NLST, which returns bare filenames only.
This mirrors what Net::FTP does internally for rmdir(). Net::FTP 3.x exposes
no public MLSD method, so we drive it through its _list_cmd() helper - the
same private method that backs the public ls()/dir() and that Net::FTP's own
rmdir() uses for MLSD. That interface has been stable for many releases.
The hashref shape is kept consistent with the SFTP and Local transports so
that consumers of list_files() (including out-of-tree ones) can rely on the
same structure regardless of transport type.
=cut =cut
@ -164,26 +200,63 @@ sub _list_files {
my ($self) = @_; my ($self) = @_;
my $operation = "list"; my $operation = "list";
# Get detailed listing using dir() for consistency with SFTP format
my $detailed_list = $self->{connection}->dir or return $self->_abort_operation($operation);
# Convert to hash format consistent with SFTP
my @file_list; my @file_list;
foreach my $line ( @{$detailed_list} ) {
# Parse FTP dir output (similar to ls -l format) # Prefer MLSD: machine-readable, no locale/format guessing. Net::FTP has
# Example: "-rw-r--r-- 1 user group 1234 Jan 01 12:00 filename.txt" # no public MLSD method, so use its internal _list_cmd() (as its own
if ( $line =~ /^([d\-rwx]+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(.+?)\s+(.+)$/ ) { # rmdir() does). Returns an arrayref of "facts filename" lines, or undef.
my ( $perms, $size, $date_part, $filename ) = ( $1, $2, $3, $4 ); my $mlsd = $self->{connection}->_list_cmd("MLSD");
# Skip directories (start with 'd') if ( $mlsd && @{$mlsd} ) {
next if $perms =~ /^d/; foreach my $line ( @{$mlsd} ) {
# Each line is "fact1=val;fact2=val;...; pathname"; the pathname is
# everything after the facts and the single separating space.
next unless $line =~ /^(.*?;)\s(.+)$/;
my ( $facts_str, $filename ) = ( $1, $2 );
my %fact =
map { my ( $k, $v ) = split /=/, $_, 2; ( lc($k) => $v ) }
grep { length } split /;/, $facts_str;
# Skip only the current/parent directory pseudo-entries; real
# subdirectories (type=dir) are now included like Local and SFTP.
next if ( $fact{type} // q{} ) =~ /^(?:cdir|pdir)$/i;
my $type =
( $fact{type} // q{} ) eq 'file' ? 'file'
: ( $fact{type} // q{} ) eq 'dir' ? 'directory'
: 'other';
my $perms = $self->_perms_from_mlsd_facts( \%fact, $type );
push @file_list, { push @file_list, {
filename => $filename, filename => $filename,
longname => $line, longname => $line,
size => $fact{size},
perms => $perms,
mtime => $self->_mtime_from_mlsd_fact( $fact{modify} ),
type => $type,
};
}
} else {
# Fallback for servers without MLSD: NLST returns bare filenames
# only, so probe each entry with SIZE/MDTM (both plain Net::FTP
# methods) to recover size, mtime and a best-effort type. A
# permission-denied file would be misclassified as a directory here
# (SIZE failing is the only signal available) - a rare, accepted
# limitation of this legacy fallback path. perms stays undef: there
# is no way to get Unix permission bits without MLSD/UNIX.mode.
my $names = $self->{connection}->ls or return $self->_abort_operation($operation);
foreach my $name ( grep { !/^[.]{1,2}$/ } @{$names} ) {
my $size = $self->{connection}->size($name);
push @file_list, {
filename => $name,
longname => undef,
size => $size, size => $size,
perms => $perms perms => undef,
mtime => $self->{connection}->mdtm($name),
type => defined $size ? 'file' : 'directory',
}; };
} }
} }
@ -194,7 +267,7 @@ sub _list_files {
type => 'success', type => 'success',
payload => { payload => {
count => scalar @file_list, count => scalar @file_list,
pwd => $self->{connection}->pwd pwd => $self->_current_directory
} }
} }
); );
@ -202,6 +275,60 @@ sub _list_files {
return \@file_list; return \@file_list;
} }
=head3 _perms_from_mlsd_facts
my $perms = $self->_perms_from_mlsd_facts( \%fact, $type );
Converts MLSD facts into the same C<"%04o"> Unix permission-bits string used
by the Local and SFTP transports.
Prefers the C<UNIX.mode> vendor-extension fact (exact Unix octal
permissions) when the server provides it. Otherwise approximates from the
RFC 3659 C<perm> fact, which describes only what the connected user is
allowed to do (append/create/delete/rename/list/mkdir/read/write/etc.), not
a real owner/group/other split - the resulting triad is mirrored across
owner/group/other since that distinction isn't available. Returns undef if
neither fact is present.
=cut
sub _perms_from_mlsd_facts {
my ( $self, $fact, $type ) = @_;
if ( defined $fact->{'unix.mode'} ) {
return sprintf( "%04o", oct( $fact->{'unix.mode'} ) & oct('07777') );
}
my $perm = $fact->{perm};
return unless defined $perm;
my $bits = 0;
$bits |= 4 if $perm =~ /r/;
$bits |= 2 if $perm =~ /[wa]/;
$bits |= 1 if $type eq 'directory' && $perm =~ /e/;
return sprintf( "%04o", ( $bits << 6 ) | ( $bits << 3 ) | $bits );
}
=head3 _mtime_from_mlsd_fact
my $mtime = $self->_mtime_from_mlsd_fact( $fact->{modify} );
Converts an MLSD C<modify> fact (RFC 3659 C<YYYYMMDDHHMMSS[.sss]> format,
always UTC) into a Unix epoch timestamp. Returns undef if the fact is
missing or doesn't match the expected format.
=cut
sub _mtime_from_mlsd_fact {
my ( $self, $modify ) = @_;
return unless defined $modify;
return unless $modify =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/;
return eval { timegm( $6, $5, $4, $3, $2 - 1, $1 ) };
}
=head3 _rename_file =head3 _rename_file
Internal method that performs the FTP-specific file rename operation. Internal method that performs the FTP-specific file rename operation.
@ -255,9 +382,19 @@ Internal method to check if transport is currently connected.
sub _is_connected { sub _is_connected {
my ($self) = @_; my ($self) = @_;
return $self->{connection} && $self->{connection}->pwd(); return $self->{connection} && $self->_current_directory();
} }
=head3 _abort_operation
return $self->_abort_operation($operation);
Records an error message for the named operation, aborts any in-progress
transfer on the current connection and returns nothing, so callers can
C<return $self-E<gt>_abort_operation($operation)> on failure.
=cut
sub _abort_operation { sub _abort_operation {
my ( $self, $operation ) = @_; my ( $self, $operation ) = @_;

View File

@ -19,6 +19,7 @@ use Modern::Perl;
use File::Copy qw( copy move ); use File::Copy qw( copy move );
use File::Spec; use File::Spec;
use IO::Dir; use IO::Dir;
use Fcntl qw( S_ISDIR S_ISREG );
use base qw(Koha::File::Transport); use base qw(Koha::File::Transport);
@ -253,11 +254,27 @@ sub _change_directory {
return 1; return 1;
} }
=head3 _current_directory
my $path = $self->_current_directory;
Internal method that returns the local transport's current working
directory: the directory most recently set via change_directory(), falling
back to the configured download_directory, then to '.'.
=cut
sub _current_directory {
my ($self) = @_;
return $self->{current_directory} || $self->download_directory || '.';
}
=head3 _list_files =head3 _list_files
Internal method that performs the local file system file listing operation. Internal method that performs the local file system file listing operation.
Returns an array reference of hashrefs with file information. Returns an array reference of hashrefs with file information.
Each hashref contains: filename, longname, size, perms, mtime. Each hashref contains: filename, longname, size, perms, mtime, type.
=cut =cut
@ -300,14 +317,20 @@ sub _list_files {
while ( defined( my $file = $dir_handle->read ) ) { while ( defined( my $file = $dir_handle->read ) ) {
next if $file =~ /^\.\.?$/; # Skip . and .. next if $file =~ /^\.\.?$/; # Skip . and ..
my $full_path = File::Spec->catfile( $directory, $file ); my $full_path = File::Spec->catfile( $directory, $file );
next unless -f $full_path; # Only files
# Get file stats for consistency with SFTP format # Get file stats for consistency with the SFTP/FTP format
my @stat = stat($full_path); my @stat = stat($full_path);
next unless @stat; # Skip entries we can't stat (e.g. broken symlinks)
my $size = $stat[7] || 0; my $size = $stat[7] || 0;
my $mtime = $stat[9] || 0; my $mtime = $stat[9] || 0;
my $mode = $stat[2] || 0; my $mode = $stat[2] || 0;
my $type =
S_ISDIR($mode) ? 'directory'
: S_ISREG($mode) ? 'file'
: 'other';
# Create permissions string (simplified) # Create permissions string (simplified)
my $perms = sprintf( "%04o", $mode & oct('07777') ); my $perms = sprintf( "%04o", $mode & oct('07777') );
@ -316,7 +339,8 @@ sub _list_files {
longname => sprintf( "%s %8d %s %s", $perms, $size, scalar( localtime($mtime) ), $file ), longname => sprintf( "%s %8d %s %s", $perms, $size, scalar( localtime($mtime) ), $file ),
size => $size, size => $size,
perms => $perms, perms => $perms,
mtime => $mtime mtime => $mtime,
type => $type,
}; };
} }
$dir_handle->close; $dir_handle->close;

View File

@ -23,7 +23,8 @@ use File::Spec;
use IO::File; use IO::File;
use Net::SFTP::Foreign; use Net::SFTP::Foreign;
use Try::Tiny; use Try::Tiny;
use JSON qw( decode_json encode_json ); use JSON qw( decode_json encode_json );
use Fcntl qw( S_ISDIR S_ISREG );
use base qw(Koha::File::Transport); use base qw(Koha::File::Transport);
@ -137,6 +138,24 @@ sub _download_file {
return 1; return 1;
} }
=head3 _current_directory
my $path = $self->_current_directory;
Internal method that returns the SFTP connection's current working directory.
Net::SFTP::Foreign's C<cwd()> is a pure read-only accessor - it reports the
current remote directory without navigating - unlike FTP's C<cwd()> (see
Koha::File::Transport::FTP::_current_directory).
=cut
sub _current_directory {
my ($self) = @_;
return $self->{connection}->cwd;
}
=head3 _change_directory =head3 _change_directory
my $success = $server->_change_directory($directory); my $success = $server->_change_directory($directory);
@ -172,7 +191,16 @@ sub _change_directory {
Internal method that performs the SFTP-specific file listing operation. Internal method that performs the SFTP-specific file listing operation.
Returns an array reference of hashrefs with file information. Returns an array reference of hashrefs with file information.
Each hashref contains: filename, longname, a (attributes). Each hashref contains: filename, longname, size, perms, mtime, type.
Net::SFTP::Foreign's ls() natively returns the size/perms/mtime nested inside
a Net::SFTP::Foreign::Attributes object (the 'a' key). Those fields are
flattened out here so the returned shape matches the FTP and Local transports.
Net::SFTP::Foreign::ls() does not filter '.' and '..' out of its
SSH_FXP_READDIR response itself - whether those pseudo-entries appear is
purely down to the remote server implementation. They are skipped here so
behaviour is consistent with the FTP and Local transports.
=cut =cut
@ -182,6 +210,22 @@ sub _list_files {
my $file_list = $self->{connection}->ls or return $self->_abort_operation($operation); my $file_list = $self->{connection}->ls or return $self->_abort_operation($operation);
my @files = map {
my $attrs = $_->{a};
my $mode = $attrs ? $attrs->perm : undef;
{
filename => $_->{filename},
longname => $_->{longname},
size => $attrs ? $attrs->size : undef,
perms => $attrs && defined $mode ? sprintf( "%04o", $mode & oct('07777') ) : undef,
mtime => $attrs ? $attrs->mtime : undef,
type => !defined $mode ? 'other'
: S_ISDIR($mode) ? 'directory'
: S_ISREG($mode) ? 'file'
: 'other',
}
} grep { $_->{filename} !~ /^\.\.?$/ } @{$file_list};
$self->add_message( $self->add_message(
{ {
message => $operation, message => $operation,
@ -194,7 +238,7 @@ sub _list_files {
} }
); );
return $file_list; return \@files;
} }
=head3 _rename_file =head3 _rename_file

View File

@ -56,7 +56,7 @@ Koha::FrameworkPlugin - Facilitate use of plugins in MARC/items editor
This object support two code styles: This object support two code styles:
- In the new style, the plugin returns a hashref with a builder and a - In the new style, the plugin returns a hashref with a builder and a
launcher key pointing to two anynomous subroutines. launcher key pointing to two anonymous subroutines.
- In the old style, the builder is subroutine plugin_javascript and the - In the old style, the builder is subroutine plugin_javascript and the
launcher is subroutine plugin. For each plugin the routines are launcher is subroutine plugin. For each plugin the routines are
redefined. redefined.

View File

@ -214,14 +214,32 @@ sub move_hold {
$new_itemnumber = $item->itemnumber; $new_itemnumber = $item->itemnumber;
$new_biblionumber = $item->biblionumber; $new_biblionumber = $item->biblionumber;
# need to make sure the item is being moved to a valid pickup location
return { success => 0, error => 'invalidPickupBranch' } unless $self->_pickup_valid_for_item( $item, $patron );
$canReserve = C4::Reserves::CanItemBeReserved( $patron, $item, $self->branchcode, { ignore_hold_counts => 1 } ); $canReserve = C4::Reserves::CanItemBeReserved( $patron, $item, $self->branchcode, { ignore_hold_counts => 1 } );
} elsif ( $args->{new_biblionumber} ) { } elsif ( $args->{new_biblionumber} ) {
$new_biblionumber = $args->{new_biblionumber}; $new_biblionumber = $args->{new_biblionumber};
# for record level holds we must check to make sure there is at least 1 item on the record that can fulfill the new hold
my @items = Koha::Items->search( { biblionumber => $new_biblionumber } )->as_list;
return { success => 0, error => 'noItemsOnBib' } unless @items;
my $valid_pickup_item = 0;
foreach my $candidate_item (@items) {
if ( $self->_pickup_valid_for_item( $candidate_item, $patron ) ) {
$valid_pickup_item = 1;
last;
}
}
return { success => 0, error => 'invalidPickupBranch' } unless $valid_pickup_item;
$canReserve = C4::Reserves::CanBookBeReserved( $canReserve = C4::Reserves::CanBookBeReserved(
$patron->borrowernumber, $new_biblionumber, $self->branchcode, $patron->borrowernumber, $new_biblionumber, $self->branchcode,
{ ignore_hold_counts => 1 } { ignore_hold_counts => 1 }
); );
} else { } else {
return { success => 0, error => 'Missing itemnumber or biblionumber' }; return { success => 0, error => 'Missing itemnumber or biblionumber' };
} }
@ -1697,6 +1715,30 @@ sub strings_map {
return $strings; return $strings;
} }
=head3 _pickup_valid_for_item
Helper method to determine if the pickup location for an item is valid when a hold is moved.
Returns true if the pickup location is valid.
=cut
sub _pickup_valid_for_item {
my ( $self, $item, $patron ) = @_;
my $branchitemrule = C4::Circulation::GetBranchItemRule(
Koha::Policy::Holds->holds_control_library( $item, $patron ),
$item->effective_itemtype
);
my $policy = $branchitemrule->{hold_fulfillment_policy};
return 0
if ( $policy eq 'homebranch' || $policy eq 'holdingbranch' )
&& $self->branchcode ne $item->$policy;
return 1;
}
=head2 Internal methods =head2 Internal methods
=head3 _type =head3 _type

View File

@ -161,11 +161,18 @@ sub get_items_that_can_fill {
collapse => 1, collapse => 1,
} }
)->get_column('itemnumber'); )->get_column('itemnumber');
my @pending_recalls = Koha::Recalls->search(
{ 'status' => [ 'waiting', 'in_transit' ] },
{
columns => ['item_id'],
collapse => 1,
}
)->get_column('item_id');
return Koha::Items->search( return Koha::Items->search(
{ {
-or => \@bibs_or_items, -or => \@bibs_or_items,
itemnumber => { -not_in => [ @branchtransfers, @waiting_holds ] }, itemnumber => { -not_in => [ @branchtransfers, @waiting_holds, @pending_recalls ] },
onloan => undef, onloan => undef,
notforloan => 0, notforloan => 0,
} }

View File

@ -108,7 +108,7 @@ sub create_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_create', actionname => 'batch_create',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( {} ) infos => to_json( {} )
@ -144,7 +144,7 @@ sub update_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_update', actionname => 'batch_update',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( infos => to_json(
@ -172,7 +172,7 @@ sub delete_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_delete', actionname => 'batch_delete',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( {} ) infos => to_json( {} )

View File

@ -63,7 +63,7 @@ sub create_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_status_create', actionname => 'batch_status_create',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( {} ) infos => to_json( {} )
@ -94,7 +94,7 @@ sub update_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_status_update', actionname => 'batch_status_update',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( infos => to_json(
@ -131,7 +131,7 @@ sub delete_and_log {
$logger->log_something( $logger->log_something(
{ {
modulename => 'ILL', modulename => 'ILL_BATCHES',
actionname => 'batch_status_delete', actionname => 'batch_status_delete',
objectnumber => $self->id, objectnumber => $self->id,
infos => to_json( {} ) infos => to_json( {} )

View File

@ -45,6 +45,7 @@ use Koha::Items;
use Koha::Libraries; use Koha::Libraries;
use Koha::AdditionalContents; use Koha::AdditionalContents;
use Koha::Patron; use Koha::Patron;
use Koha::Statistics;
use C4::Circulation qw( CanBookBeIssued AddIssue ); use C4::Circulation qw( CanBookBeIssued AddIssue );
@ -1725,6 +1726,12 @@ sub check_out {
} }
# We can allegedly check out, so make it so # We can allegedly check out, so make it so
# Mark as ILL checkout in the logs to track system-initiated checkouts.
# AddIssue signature is ( $patron, $barcode, $datedue, $cancelreserve,
# $issuedate, $sipmode, $params ) - pad to position 7 so the params
# hashref is not mistaken for $issuedate or $sipmode.
push @issue_args, undef while @issue_args < 6;
push @issue_args, { forced => ['ILL_CHECKOUT'] };
my $issue = C4::Circulation::AddIssue(@issue_args); my $issue = C4::Circulation::AddIssue(@issue_args);
if ($issue) { if ($issue) {
@ -2269,7 +2276,7 @@ Actions to be done after the request has been completed
sub after_completed { sub after_completed {
my ($self) = @_; my ($self) = @_;
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
borrowernumber => $self->borrowernumber // undef, borrowernumber => $self->borrowernumber // undef,
branch => $self->branchcode, branch => $self->branchcode,
@ -2281,7 +2288,7 @@ sub after_completed {
location => undef, location => undef,
type => 'illreq_comp', type => 'illreq_comp',
} }
); )->store();
} }
=head3 after_created =head3 after_created
@ -2295,7 +2302,7 @@ Actions to be done after the request has been fully created
sub after_created { sub after_created {
my ($self) = @_; my ($self) = @_;
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
borrowernumber => $self->borrowernumber // undef, borrowernumber => $self->borrowernumber // undef,
branch => $self->branchcode, branch => $self->branchcode,
@ -2307,7 +2314,7 @@ sub after_created {
location => undef, location => undef,
type => 'illreq_created', type => 'illreq_created',
} }
); )->store();
} }
=head3 requested_partners =head3 requested_partners

View File

@ -102,7 +102,7 @@ sub new_from_file {
); );
} }
if ($errors) { if ( scalar @$errors ) {
Koha::Logger->get->warn( 'The following error(s) occurred during ' Koha::Logger->get->warn( 'The following error(s) occurred during '
. ( defined $format ? ( $format . " " ) : "" ) . ( defined $format ? ( $format . " " ) : "" )
. "record import:\nERROR: " . "record import:\nERROR: "

View File

@ -58,6 +58,7 @@ use Koha::StockRotationItem;
use Koha::StockRotationRotas; use Koha::StockRotationRotas;
use Koha::TrackedLinks; use Koha::TrackedLinks;
use Koha::Policy::Holds; use Koha::Policy::Holds;
use Koha::Statistics;
use base qw(Koha::Object); use base qw(Koha::Object);
@ -268,7 +269,7 @@ Record a statistics entry for this item (e.g. on store or delete).
sub _add_statistic { sub _add_statistic {
my ( $self, $type ) = @_; my ( $self, $type ) = @_;
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
borrowernumber => undef, borrowernumber => undef,
branch => C4::Context->userenv ? C4::Context->userenv->{branch} : undef, branch => C4::Context->userenv ? C4::Context->userenv->{branch} : undef,
@ -279,7 +280,7 @@ sub _add_statistic {
location => $self->location, location => $self->location,
type => $type, type => $type,
} }
); )->store();
} }
=head3 delete =head3 delete

View File

@ -31,7 +31,7 @@ Koha::Item::Templates - Koha Item Template Object set class
=head3 get_available =head3 get_available
Returns a hashref with keys 'owned' and 'shared' pointing to Koha::Item::Templats objects Returns a hashref with keys 'owned' and 'shared' pointing to Koha::Item::Templates objects
representing templates owned by the user or shared to the user respectivetly. representing templates owned by the user or shared to the user respectivetly.
=cut =cut

View File

@ -32,6 +32,7 @@ use Koha::SearchEngine::Indexer;
use Koha::Item::Attributes; use Koha::Item::Attributes;
use Koha::Item; use Koha::Item;
use Koha::CirculationRules; use Koha::CirculationRules;
use Koha::Regex::Replacement;
use base qw(Koha::Objects); use base qw(Koha::Objects);
@ -311,9 +312,9 @@ sub filter_by_has_recalls {
$params //= {}; $params //= {};
my $recalls = Koha::Recalls->search( my $recalls = Koha::Recalls->search(
{ %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], 'me.item_level' => 1, }, { %$params, 'me.item_id' => [ $self->get_column('itemnumber') ], 'me.item_level' => 1, },
{ {
columns => ['itemnumber'], columns => ['item_id'],
distinct => 1 distinct => 1
} }
)->_resultset->as_query; )->_resultset->as_query;
@ -599,7 +600,8 @@ sub batch_update {
if (@modified_itemnumbers) { if (@modified_itemnumbers) {
my @biblionumbers = my @biblionumbers =
uniq( Koha::Items->search( { itemnumber => \@modified_itemnumbers } )->get_column('biblionumber') ); uniq(
Koha::Items->search( { itemnumber => { -in => \@modified_itemnumbers } } )->get_column('biblionumber') );
if (@biblionumbers) { if (@biblionumbers) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
@ -633,22 +635,24 @@ sub apply_regex {
my $modifiers = $params->{modifiers} || q{}; my $modifiers = $params->{modifiers} || q{};
my $value = $params->{value}; my $value = $params->{value};
$replace =~ s/"/\\"/g; # Protection from embedded code
$replace = '"' . $replace . '"'; # Put in a string for /ee
my @available_modifiers = qw( i g ); my @available_modifiers = qw( i g );
my $retained_modifiers = q||; my $retained_modifiers = q||;
for my $modifier ( split //, $modifiers ) { for my $modifier ( split //, $modifiers ) {
$retained_modifiers .= $modifier $retained_modifiers .= $modifier
if grep { /$modifier/ } @available_modifiers; if grep { /$modifier/ } @available_modifiers;
} }
# The replacement is expanded as DATA (capture-group backreferences and the
# \n \r \t escapes) and never evaluated as code, so @{...}/${...}
# interpolation blocks are inert text, not Perl.
if ( $retained_modifiers =~ m/^(ig|gi)$/ ) { if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
$value =~ s/$search/$replace/igee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige;
} elsif ( $retained_modifiers eq 'i' ) { } elsif ( $retained_modifiers eq 'i' ) {
$value =~ s/$search/$replace/iee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie;
} elsif ( $retained_modifiers eq 'g' ) { } elsif ( $retained_modifiers eq 'g' ) {
$value =~ s/$search/$replace/gee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge;
} else { } else {
$value =~ s/$search/$replace/ee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e;
} }
return $value; return $value;

View File

@ -374,7 +374,7 @@ sub empty {
Koha::Objects->reset(); Koha::Objects->reset();
resets iteration so the next call to next() will start agein resets iteration so the next call to next() will start again
with the first object in a set. with the first object in a set.
=cut =cut

View File

@ -1038,7 +1038,7 @@ sub is_going_to_expire {
return 0 unless $self->dateexpiry; return 0 unless $self->dateexpiry;
return 0 if $self->dateexpiry =~ '^9999'; return 0 if $self->dateexpiry =~ '^9999';
return 1 return 1
if dt_from_string( $self->dateexpiry, undef, 'floating' )->subtract( days => $delay ) < if dt_from_string( $self->dateexpiry, undef, 'floating' )->subtract( days => $delay ) <=
dt_from_string( undef, undef, 'floating' )->truncate( to => 'day' ); dt_from_string( undef, undef, 'floating' )->truncate( to => 'day' );
return 0; return 0;
} }

View File

@ -55,7 +55,7 @@ sub delete {
my $default = Koha::Patron::Restriction::Types->find( { is_default => 1 } )->code; my $default = Koha::Patron::Restriction::Types->find( { is_default => 1 } )->code;
# We can't use Koha objects here because Koha::Patron::Debarments # We can't use Koha objects here because Koha::Patron::Debarments
# is not a Koha object. So we'll do it old skool # is not a Koha object. So we'll do it old school
my $rows = C4::Context->dbh->do( my $rows = C4::Context->dbh->do(
"UPDATE borrower_debarments SET type = ? WHERE type = ?", "UPDATE borrower_debarments SET type = ? WHERE type = ?",
undef, ( $default, $self->code ) undef, ( $default, $self->code )

View File

@ -716,7 +716,7 @@ sub filter_by_safe_to_delete {
push @ids, $patron->borrowernumber push @ids, $patron->borrowernumber
if $patron->safe_to_delete; if $patron->safe_to_delete;
} }
return Koha::Patrons->search( { borrowernumber => \@ids } ); return Koha::Patrons->search( { borrowernumber => { -in => \@ids } } );
} }
=head3 extended_attributes_config =head3 extended_attributes_config
@ -749,6 +749,10 @@ sub check_for_existing_matches {
$match_conditions->{$field} = $new_patron_data->{$field} if $new_patron_data->{$field}; $match_conditions->{$field} = $new_patron_data->{$field} if $new_patron_data->{$field};
} }
# Without any usable conditions, Koha::Patrons->search would run
# unfiltered and match every patron in the system.
return { 'duplicate_found' => 0 } unless $match_conditions && %$match_conditions;
my $patrons = Koha::Patrons->search($match_conditions); my $patrons = Koha::Patrons->search($match_conditions);
if ( $patrons->count > 0 ) { if ( $patrons->count > 0 ) {
return { return {

View File

@ -594,7 +594,7 @@ LINE: while ( my $borrowerline = <$handle> ) {
my @csvcolumns = $self->prepare_columns({headerrow => $borrowerline, keycol => \%csvkeycol, errors => \@errors, }); my @csvcolumns = $self->prepare_columns({headerrow => $borrowerline, keycol => \%csvkeycol, errors => \@errors, });
Returns an array of all column key and populates a hash of colunm key positions. Returns an array of all column key and populates a hash of column key positions.
=cut =cut

View File

@ -47,9 +47,10 @@ BEGIN {
pop @INC if $INC[-1] eq '.'; pop @INC if $INC[-1] eq '.';
# Load plugins early to ensure they're available before any transactions begin # Load plugins early to ensure they're available before any transactions begin
# Skip under "perl -c" to avoid C3 error when syntax checking plugins
my $enable_plugins = C4::Context->config("enable_plugins") // 0; my $enable_plugins = C4::Context->config("enable_plugins") // 0;
if ($enable_plugins) { if ( $enable_plugins && !$^C ) {
require Koha::Plugins::Loader; require Koha::Plugins::Loader;
Koha::Plugins::Loader->get_enabled_plugins(); Koha::Plugins::Loader->get_enabled_plugins();
} }
@ -132,6 +133,12 @@ sub get_enabled_plugins {
return Koha::Plugins::Loader->get_enabled_plugins(); return Koha::Plugins::Loader->get_enabled_plugins();
} }
=head3 _verbose
Helper function to reduce verbosity in tests
=cut
sub _verbose { sub _verbose {
my $class = shift; my $class = shift;
@ -321,6 +328,14 @@ sub InstallPlugins {
} }
foreach my $plugin_class (@plugin_classes) { foreach my $plugin_class (@plugin_classes) {
# Force a fresh load from disk. Without this, a long-lived Plack
# worker that already has the plugin loaded would rescan a stale
# symbol table during an upgrade, so plugin_methods ends up out of
# sync with the installed .pm (e.g. missing newly-added methods or
# retaining methods that were renamed or removed).
$self->_forget_plugin_class($plugin_class);
if ( can_load( modules => { $plugin_class => undef }, verbose => $verbose, nocache => 1 ) ) { if ( can_load( modules => { $plugin_class => undef }, verbose => $verbose, nocache => 1 ) ) {
next unless $plugin_class->isa('Koha::Plugins::Base'); next unless $plugin_class->isa('Koha::Plugins::Base');
@ -358,6 +373,43 @@ sub InstallPlugins {
return @plugins; return @plugins;
} }
=head3 _forget_plugin_class
Force Perl to forget an in-memory plugin class, so a subsequent re-require
picks up the .pm file as it currently exists on disk, rather than whatever
was loaded into this worker previously.
We deliberately do not use Symbol::delete_package here: it clears the
whole symbol table, including any already-loaded packages nested under
it (e.g. Koha::Plugin::Foo::Controller, bundled by the plugin in its own
Koha/Plugin/Foo/Controller.pm). Since those keep their own %INC entry,
they are never re-required afterwards and are left permanently blanked
out. Module::Pluggable discovers them as their own entries in the plugin
list regardless, so they get reloaded on their own turn; this helper
only ever clears the target class's own symbols, leaving any nested
packages alone.
=cut
sub _forget_plugin_class {
my ( $self, $plugin_class ) = @_;
return unless Class::Inspector->loaded($plugin_class);
no strict 'refs'; ## no critic (ProhibitNoStrict)
@{"${plugin_class}::ISA"} = ();
my $stash = \%{"${plugin_class}::"};
for my $symbol ( keys %$stash ) {
next if $symbol =~ /::\z/; # leave nested packages alone
delete $stash->{$symbol};
}
( my $plugin_inc_key = $plugin_class ) =~ s{::}{/}g;
delete $INC{"$plugin_inc_key.pm"};
return;
}
=head2 RemovePlugins =head2 RemovePlugins
Koha::Plugins->RemovePlugins( { Koha::Plugins->RemovePlugins( {
@ -401,6 +453,13 @@ sub RemovePlugins {
$class->_restart_after_change(); $class->_restart_after_change();
} }
=head3 _restart_after_change
Helper function to reload plack after a plugin change if the
'plugins_restart' flag is enabled in the koha-conf.xml
=cut
sub _restart_after_change { sub _restart_after_change {
my ( $class, $params ) = @_; my ( $class, $params ) = @_;

View File

@ -22,6 +22,7 @@ use Mojo::Base 'Mojolicious::Controller';
use Koha::ERM::EHoldings::Titles; use Koha::ERM::EHoldings::Titles;
use Koha::BackgroundJob::CreateEHoldingsFromBiblios; use Koha::BackgroundJob::CreateEHoldingsFromBiblios;
use Koha::BackgroundJob::ImportKBARTFile; use Koha::BackgroundJob::ImportKBARTFile;
use Koha::SearchEngine::Indexer;
use Scalar::Util qw( blessed ); use Scalar::Util qw( blessed );
use Try::Tiny qw( catch try ); use Try::Tiny qw( catch try );
@ -95,6 +96,11 @@ sub add {
my $title = Koha::ERM::EHoldings::Title->new_from_api($body) my $title = Koha::ERM::EHoldings::Title->new_from_api($body)
->store( { create_linked_biblio => $create_linked_biblio } ); ->store( { create_linked_biblio => $create_linked_biblio } );
if ($create_linked_biblio) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( $title->biblio_id, "specialUpdate", "biblioserver" );
}
$title->resources($resources); $title->resources($resources);
my @extended_attributes = my @extended_attributes =
@ -161,6 +167,11 @@ sub update {
$title->set_from_api($body)->store( { create_linked_biblio => $create_linked_biblio } ); $title->set_from_api($body)->store( { create_linked_biblio => $create_linked_biblio } );
if ($create_linked_biblio) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( $title->biblio_id, "specialUpdate", "biblioserver" );
}
$title->resources($resources); $title->resources($resources);
my @extended_attributes = my @extended_attributes =
map { { 'id' => $_->{field_id}, 'value' => $_->{value} } } @{$extended_attributes}; map { { 'id' => $_->{field_id}, 'value' => $_->{value} } } @{$extended_attributes};

View File

@ -19,6 +19,7 @@ use Modern::Perl;
use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Controller';
use C4::Context;
use Koha::Patrons; use Koha::Patrons;
use Try::Tiny qw( catch try ); use Try::Tiny qw( catch try );
@ -31,6 +32,26 @@ Koha::REST::V1::ILL
=head2 Class methods =head2 Class methods
=head3 config
Return the configuration options needed for the ILL Vue app
=cut
sub config {
my $c = shift->openapi->valid_input or return;
return $c->render(
status => 200,
openapi => {
settings => {
ILLModule => C4::Context->preference('ILLModule'),
ILLPartnerCode => C4::Context->preference('ILLPartnerCode'),
},
},
);
}
=head3 list_users =head3 list_users
Return the list of possible ILL users Return the list of possible ILL users

View File

@ -66,10 +66,11 @@ sub login {
} }
my $current_session; my $current_session;
my $session_id;
my $current_session_cookie = $c->req->cookie('CGISESSID'); my $current_session_cookie = $c->req->cookie('CGISESSID');
if ($current_session_cookie) { if ($current_session_cookie) {
my $current_session_id = $current_session_cookie->value; $session_id = $current_session_cookie->value;
$current_session = Koha::Session->get_session( { sessionID => $current_session_id } ); $current_session = Koha::Session->get_session( { sessionID => $session_id } );
} }
unless ( $provider_config && $provider_config->{authorize_url} ) { unless ( $provider_config && $provider_config->{authorize_url} ) {
@ -91,10 +92,15 @@ sub login {
if ($is_callback) { if ($is_callback) {
# callback, check CSRF token # callback, check CSRF token
unless ($session_id) {
my $error = "wrong_csrf_token";
return $c->redirect_to( $uri . "?auth_error=$error" );
}
unless ( unless (
Koha::Token->new->check_csrf( Koha::Token->new->check_csrf(
{ {
session_id => $c->req->cookie('CGISESSID')->value, session_id => $session_id,
token => $c->param('state'), token => $c->param('state'),
} }
) )
@ -105,8 +111,14 @@ sub login {
} }
} else { } else {
# initial request, generate CSRF token # initial request — create session if needed (IdP-initiated flow)
$state = Koha::Token->new->generate_csrf( { session_id => $c->req->cookie('CGISESSID')->value } ); unless ($session_id) {
$current_session = Koha::Session->get_session( {} );
$session_id = $current_session->id;
$c->cookie( CGISESSID => $session_id, { path => '/' } );
}
$state = Koha::Token->new->generate_csrf( { session_id => $session_id } );
Koha::Auth::Identity::Referer->store_referer( Koha::Auth::Identity::Referer->store_referer(
{ {

View File

@ -24,8 +24,7 @@ use Koha::Database;
use Koha::Recall; use Koha::Recall;
use Koha::DateUtils qw( dt_from_string ); use Koha::DateUtils qw( dt_from_string );
use Koha::Plugins; use Koha::Plugins;
use Koha::Statistics;
use C4::Stats qw( UpdateStats );
use base qw(Koha::Objects); use base qw(Koha::Objects);
@ -191,7 +190,7 @@ sub add_recall {
$item = Koha::Items->find($itemnumber); $item = Koha::Items->find($itemnumber);
# add to statistics table # add to statistics table
C4::Stats::UpdateStats( Koha::Statistic->new(
{ {
branch => C4::Context->userenv->{'branch'}, branch => C4::Context->userenv->{'branch'},
type => 'recall', type => 'recall',
@ -202,7 +201,7 @@ sub add_recall {
categorycode => $checkout->patron->categorycode, categorycode => $checkout->patron->categorycode,
interface => $interface, interface => $interface,
} }
); )->store();
Koha::Plugins->call( Koha::Plugins->call(
'after_recall_action', 'after_recall_action',

199
Koha/Regex/Replacement.pm Normal file
View File

@ -0,0 +1,199 @@
package Koha::Regex::Replacement;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
=head1 NAME
Koha::Regex::Replacement - expand a regular-expression replacement string as data
=head1 SYNOPSIS
use Koha::Regex::Replacement;
if ( $value =~ /$search/ ) {
my $text = Koha::Regex::Replacement::expand_template(
$replacement, # the user-supplied replacement string
[ @{^CAPTURE} ], # numbered capture groups from the match ($1 is element 0)
{ %+ }, # named capture groups from the match
);
}
=head1 DESCRIPTION
Koha lets staff run regular-expression find and replace over MARC fields, item
fields and call numbers. The replacement half of those substitutions used to be
run through Perl's C<s///ee>, which evaluated the replacement as Perl code and
so allowed arbitrary code execution (see bug 30233).
Note: It was necessary to use C<s///ee> because Perl does not provide a native
way of processing backreferences and backslash escapes in the replacement string
at runtime. While this doesn't matter when running hard-coded regular expressions,
it does make it difficult to support replacement strings provided by end users.
Note: While the "expand_template" may seem bespoke, it works similarly to
how Python's own template expansion works in Python's re.sub() regular
expression substitution function:
https://github.com/python/cpython/blob/3.15/Modules/_sre/sre.c#L3022
This module expands the replacement string as B<data> instead of evaluating it.
It walks the string once, from left to right, and only acts on the handful of
placeholders a replacement legitimately contains:
=over
=item * numbered backreferences: C<$1> .. C<$n>, C<${n}>, and the older
C<\1> .. C<\9> form
=item * named captures: C<$+{name}> and C<${name}>
=item * the escapes C<\n>, C<\r> and C<\t>, plus C<\\> and C<\$> for a literal
backslash or dollar sign
=back
Every other character is copied through unchanged. Because the string is never
evaluated, interpolation blocks such as C<@{[ ... ]}> and C<${ ... }> end up in
the output as ordinary text rather than being run as Perl.
Case-folding escapes (C<\U>, C<\L>, C<\u>, C<\l>, C<\E>) are deliberately left
out. They are a display nicety rather than a requirement, and omitting them
keeps this routine small and easy to follow. They can be added later if a real
call-number or MARC workflow turns out to need them.
=head1 FUNCTIONS
=head2 expand_template
my $text = Koha::Regex::Replacement::expand_template( $replacement, \@captures, \%named_captures );
Returns C<$replacement> with the placeholders listed above resolved. Numbered
backreferences are looked up in C<\@captures> (element 0 is group 1, matching
C<@{^CAPTURE}>) and named captures in C<\%named_captures> (matching C<%+>). A
reference to a group that did not match, or to a name that is not present,
expands to the empty string. The input is treated purely as data and is never
run as code.
=cut
sub expand_template {
my ( $replacement, $captures, $named_captures ) = @_;
$captures //= [];
$named_captures //= {};
my $output = '';
my @characters = split //, $replacement;
my $position = 0;
while ( $position < @characters ) {
my $character = $characters[$position];
# Backslash escapes: \n \r \t, the legacy \1 .. \9 backreference, and
# \\ or \$ for a literal backslash or dollar. Any other \X is emitted as
# a literal X, so a stray backslash can never introduce executable syntax.
if ( $character eq '\\' && $position + 1 < @characters ) {
my $escaped = $characters[ $position + 1 ];
if ( $escaped eq 'n' ) { $output .= "\n"; }
elsif ( $escaped eq 'r' ) { $output .= "\r"; }
elsif ( $escaped eq 't' ) { $output .= "\t"; }
elsif ( $escaped =~ /[1-9]/ ) { # legacy \1 .. \9 backreference
my $group_value = $captures->[ $escaped - 1 ];
$output .= defined $group_value ? $group_value : '';
} else { # \\ -> \, \$ -> $, any other \X -> X
$output .= $escaped;
}
$position += 2;
next;
}
if ( $character eq '$' ) {
# ${name} or ${number}: read up to the closing brace, then treat a
# number as a positional group and anything else as a named group.
if ( $position + 1 < @characters && $characters[ $position + 1 ] eq '{' ) {
my $scan = $position + 2;
my $name = '';
$name .= $characters[ $scan++ ] while $scan < @characters && $characters[$scan] ne '}';
if ( $scan < @characters ) { # found the closing brace
if ( $name =~ /^[0-9]+\z/ ) {
my $group_value = $name >= 1 ? $captures->[ $name - 1 ] : undef;
$output .= defined $group_value ? $group_value : '';
} else {
my $named_value = $named_captures->{$name};
$output .= defined $named_value ? $named_value : '';
}
$position = $scan + 1;
next;
}
# An unterminated ${ : emit the dollar literally and carry on.
$output .= '$';
$position += 1;
next;
}
# $+{name}: a named capture written with the %+ syntax.
if ( $position + 2 < @characters
&& $characters[ $position + 1 ] eq '+'
&& $characters[ $position + 2 ] eq '{' )
{
my $scan = $position + 3;
my $name = '';
$name .= $characters[ $scan++ ] while $scan < @characters && $characters[$scan] ne '}';
if ( $scan < @characters ) {
my $named_value = $named_captures->{$name};
$output .= defined $named_value ? $named_value : '';
$position = $scan + 1;
next;
}
$output .= '$';
$position += 1;
next;
}
# $1 .. $n: a plain numbered backreference (a run of digits after $).
if ( $position + 1 < @characters && $characters[ $position + 1 ] =~ /[0-9]/ ) {
my $scan = $position + 1;
my $group_number = '';
$group_number .= $characters[ $scan++ ] while $scan < @characters && $characters[$scan] =~ /[0-9]/;
my $group_value = $group_number >= 1 ? $captures->[ $group_number - 1 ] : undef;
$output .= defined $group_value ? $group_value : '';
$position = $scan;
next;
}
# A lone $ that is not a backreference is a literal dollar sign.
$output .= '$';
$position += 1;
next;
}
# Anything that is not a recognised placeholder is copied verbatim.
$output .= $character;
$position += 1;
}
return $output;
}
1;

View File

@ -46,8 +46,9 @@ This is the base class for SIP2 objects in the Koha library system.
sub store { sub store {
my ($self) = @_; my ($self) = @_;
my $result = $self->SUPER::store;
_update_config_timestamp(); _update_config_timestamp();
return $self->SUPER::store; return $result;
} }
=head3 delete =head3 delete
@ -59,8 +60,9 @@ sub store {
sub delete { sub delete {
my ($self) = @_; my ($self) = @_;
my $result = $self->SUPER::delete;
_update_config_timestamp(); _update_config_timestamp();
return $self->SUPER::delete; return $result;
} }
=head3 _update_config_timestamp =head3 _update_config_timestamp

View File

@ -75,7 +75,7 @@ SQL snippet for us in reports
is_nullable: 1 is_nullable: 1
size: 6 size: 6
Koha module this definition is for Circulation, Catalog, Patrons, Acquistions, Accounts) Koha module this definition is for (Circulation, Catalog, Patrons, Acquisitions, Accounts)
=cut =cut
@ -117,8 +117,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("id"); __PACKAGE__->set_primary_key("id");
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-01-21 13:39:29 # Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-06-11 18:54:31
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7r8vJ2yWD3yQCyri+uNSRw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:NEd9fYq1gJ8G+SvtDIF8Ng
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View File

@ -148,6 +148,7 @@ Foreign key to sip_institutions.sip_institution_id
=head2 holds_get_captured =head2 holds_get_captured
data_type: 'tinyint' data_type: 'tinyint'
default_value: 1
is_nullable: 1 is_nullable: 1
=head2 inhouse_item_types =head2 inhouse_item_types
@ -297,7 +298,7 @@ __PACKAGE__->add_columns(
"holds_block_checkin", "holds_block_checkin",
{ data_type => "tinyint", is_nullable => 1 }, { data_type => "tinyint", is_nullable => 1 },
"holds_get_captured", "holds_get_captured",
{ data_type => "tinyint", is_nullable => 1 }, { data_type => "tinyint", default_value => 1, is_nullable => 1 },
"inhouse_item_types", "inhouse_item_types",
{ data_type => "varchar", is_nullable => 1, size => 255 }, { data_type => "varchar", is_nullable => 1, size => 255 },
"inhouse_patron_categories", "inhouse_patron_categories",
@ -506,8 +507,8 @@ __PACKAGE__->belongs_to(
); );
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-04-24 16:44:37 # Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-08-11 14:14:39
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nr/MwKeH6rrt/ekP3Zeqcw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:X188nmexWCwsij/WO44ljA
__PACKAGE__->add_columns( __PACKAGE__->add_columns(

View File

@ -58,6 +58,7 @@ use Search::Elasticsearch::Role::Cxn;
} }
use Try::Tiny qw( catch try ); use Try::Tiny qw( catch try );
use JSON::PP ();
use YAML::XS; use YAML::XS;
use List::Util qw( sum0 ); use List::Util qw( sum0 );
@ -272,6 +273,15 @@ sub get_elasticsearch_mappings {
$mappings->{properties}{'match-heading'} = _get_elasticsearch_field_config( 'search', 'text' ); $mappings->{properties}{'match-heading'} = _get_elasticsearch_field_config( 'search', 'text' );
$mappings->{properties}{'subject-heading-thesaurus'} = _get_elasticsearch_field_config( 'search', 'text' ); $mappings->{properties}{'subject-heading-thesaurus'} = _get_elasticsearch_field_config( 'search', 'text' );
} }
# Disable date and numeric detection to prevent Elasticsearch from
# auto-detecting field types in marc_data_array. Without this, a
# subfield value that looks like a date (e.g. "2024-01-15") causes
# ES to lock that subfield to type "date", and subsequent non-date
# values fail to index. (Bug 42485)
$mappings->{date_detection} = JSON::PP::false;
$mappings->{numeric_detection} = JSON::PP::false;
$all_mappings{ $self->index } = $mappings; $all_mappings{ $self->index } = $mappings;
} }
$self->sort_fields( \%{ $sort_fields{ $self->index } } ); $self->sort_fields( \%{ $sort_fields{ $self->index } } );

View File

@ -609,7 +609,7 @@ our $koha_to_index_name = {
}; };
# Note that sears and aat map to 008/11 values here # Note that sears and aat map to 008/11 values here
# but don't appear in C4/Headin/MARC21 thesaurus # but don't appear in C4/Heading/MARC21 thesaurus
# because they don't have values in controlled field indicators # because they don't have values in controlled field indicators
# https://www.loc.gov/marc/authority/ad008.html # https://www.loc.gov/marc/authority/ad008.html
our $thesaurus_to_value = { our $thesaurus_to_value = {

View File

@ -18,6 +18,9 @@ package Koha::SimpleMARC;
use Modern::Perl; use Modern::Perl;
use base 'Exporter'; use base 'Exporter';
use Koha::Exceptions;
use Koha::Regex::Replacement;
BEGIN { BEGIN {
our @EXPORT_OK = qw( our @EXPORT_OK = qw(
read_field read_field
@ -717,8 +720,6 @@ sub _modify_values {
if ( $regex and $regex->{search} ) { if ( $regex and $regex->{search} ) {
my $replace = $regex->{replace}; my $replace = $regex->{replace};
$replace =~ s/"/\\"/g; # Protection from embedded code
$replace = '"' . $replace . '"'; # Put in a string for /ee
$regex->{modifiers} //= q||; $regex->{modifiers} //= q||;
my @available_modifiers = qw( i g ); my @available_modifiers = qw( i g );
my $modifiers = q||; my $modifiers = q||;
@ -726,15 +727,20 @@ sub _modify_values {
$modifiers .= $modifier $modifiers .= $modifier
if grep { /$modifier/ } @available_modifiers; if grep { /$modifier/ } @available_modifiers;
} }
my $search = $regex->{search};
foreach my $value (@$values) { foreach my $value (@$values) {
# The replacement is expanded as DATA (capture-group backreferences
# and the \n \r \t escapes) and never evaluated as code, so
# @{...}/${...} interpolation blocks are inert text, not Perl.
if ( $modifiers =~ m/^(ig|gi)$/ ) { if ( $modifiers =~ m/^(ig|gi)$/ ) {
$value =~ s/$regex->{search}/$replace/igee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige;
} elsif ( $modifiers eq 'i' ) { } elsif ( $modifiers eq 'i' ) {
$value =~ s/$regex->{search}/$replace/iee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie;
} elsif ( $modifiers eq 'g' ) { } elsif ( $modifiers eq 'g' ) {
$value =~ s/$regex->{search}/$replace/gee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge;
} else { } else {
$value =~ s/$regex->{search}/$replace/ee; $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e;
} }
} }
} }

View File

@ -103,8 +103,12 @@ sub store {
my $toaddress; my $toaddress;
if ( $emailpurchasesuggestions eq "BranchEmailAddress" ) { if ( $emailpurchasesuggestions eq "BranchEmailAddress" ) {
# can be undef for branch=any suggestions
my $library = $result->library; my $library = $result->library;
$toaddress = $library->inbound_email_address; if ( $library && $library->inbound_email_address ) {
$toaddress = $library->inbound_email_address;
}
} elsif ( $emailpurchasesuggestions eq "KohaAdminEmailAddress" ) { } elsif ( $emailpurchasesuggestions eq "KohaAdminEmailAddress" ) {
$toaddress = C4::Context->preference('ReplytoDefault') $toaddress = C4::Context->preference('ReplytoDefault')
|| C4::Context->preference('KohaAdminEmailAddress'); || C4::Context->preference('KohaAdminEmailAddress');

View File

@ -21,6 +21,7 @@ use Modern::Perl;
use File::Basename qw( dirname ); use File::Basename qw( dirname );
use OpenOffice::OODoc qw( odfDocument odfWorkingDirectory ); use OpenOffice::OODoc qw( odfDocument odfWorkingDirectory );
use Scalar::Util qw(looks_like_number);
use parent qw( Exporter ); use parent qw( Exporter );
@ -70,6 +71,7 @@ sub generate_ods {
for ( my $i = 0 ; $i < $nb_rows ; $i++ ) { for ( my $i = 0 ; $i < $nb_rows ; $i++ ) {
for ( my $j = 0 ; $j < $nb_cols ; $j++ ) { for ( my $j = 0 ; $j < $nb_cols ; $j++ ) {
my $cellval = $input_rows[$i][$j]; my $cellval = $input_rows[$i][$j];
$odf_doc->cellValueType( $odf_rows[$i], $j, 'float' ) if looks_like_number($cellval);
$odf_doc->cellValue( $odf_rows[$i], $j, $cellval ); $odf_doc->cellValue( $odf_rows[$i], $j, $cellval );
} }
} }

View File

@ -393,23 +393,24 @@ my $target_map = {
'./virtualshelves' => 'INTRANET_CGI_DIR', './virtualshelves' => 'INTRANET_CGI_DIR',
# ignore files and directories created by the install itself # ignore files and directories created by the install itself
'./pm_to_blib' => 'NONE', './pm_to_blib' => 'NONE',
'./blib' => 'NONE', './blib' => 'NONE',
'.git-blame-ignore-revs' => 'NONE', '.git-blame-ignore-revs' => 'NONE',
'./.github' => 'NONE', './.github' => 'NONE',
'.prettierrc.js' => 'NONE', '.prettierrc.js' => 'NONE',
'.sass-lint.yml' => 'NONE', '.sass-lint.yml' => 'NONE',
'.scss-lint.yml' => 'NONE', '.scss-lint.yml' => 'NONE',
'gulpfile.js' => 'NONE', 'gulpfile.js' => 'NONE',
'package.json' => 'NONE', 'package.json' => 'NONE',
'yarn.lock' => 'NONE', 'yarn.lock' => 'NONE',
'cypress.config.ts' => 'NONE', 'cypress.config.ts' => 'NONE',
'tsconfig.json' => 'NONE', 'tsconfig.json' => 'NONE',
'webpack.config.js' => 'NONE', 'webpack.config.js' => 'NONE',
'rspack.config.js' => 'NONE', 'rspack.config.js' => 'NONE',
'eslint.config.mjs' => 'NONE', 'eslint.config.mjs' => 'NONE',
'.codespell-ignore' => 'NONE', '.codespell-ignore' => 'NONE',
'./koha-tmpl/intranet-tmpl/prog/js/vue/dist' => 'NONE', './koha-tmpl/intranet-tmpl/prog/js/vue/dist' => 'NONE',
'./koha-tmpl/opac-tmpl/bootstrap/js/vue/dist' => 'NONE',
}; };
=head1 CONFIGURATION OPTIONS =head1 CONFIGURATION OPTIONS
@ -675,11 +676,26 @@ get_file_map( $target_map, $dirtree, $file_map );
#NOTE: List files generated by build-resources.PL, so that they're copied into blib for installation #NOTE: List files generated by build-resources.PL, so that they're copied into blib for installation
#INTRANET_TMPL_DIR #INTRANET_TMPL_DIR
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/bookings.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/bookings.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/bookings-rtl.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/bookings-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/calendar.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/calendar.css'; $file_map->{'koha-tmpl/intranet-tmpl/prog/css/calendar.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/calendar.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/calendar-rtl.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/calendar-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/edifact_interchange.css'} =
'blib/INTRANET_TMPL_DIR/prog/css/edifact_interchange.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/edifact_interchange-rtl.css'} =
'blib/INTRANET_TMPL_DIR/prog/css/edifact_interchange-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/holds.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/holds.css'; $file_map->{'koha-tmpl/intranet-tmpl/prog/css/holds.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/holds.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/installer.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/installer.css'; $file_map->{'koha-tmpl/intranet-tmpl/prog/css/holds-rtl.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/holds-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/mainpage.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/mainpage.css'; $file_map->{'koha-tmpl/intranet-tmpl/prog/css/ill-requests.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/ill-requests.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/staff-global.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/staff-global.css'; $file_map->{'koha-tmpl/intranet-tmpl/prog/css/ill-requests-rtl.css'} =
'blib/INTRANET_TMPL_DIR/prog/css/ill-requests-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/installer.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/installer.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/installer-rtl.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/installer-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/mainpage.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/mainpage.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/mainpage-rtl.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/mainpage-rtl.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/staff-global.css'} = 'blib/INTRANET_TMPL_DIR/prog/css/staff-global.css';
$file_map->{'koha-tmpl/intranet-tmpl/prog/css/staff-global-rtl.css'} =
'blib/INTRANET_TMPL_DIR/prog/css/staff-global-rtl.css';
#NOTE: The 'koha-tmpl/intranet-tmpl/prog/js/vue/dist' directory is now handled in the "top_targets" function #NOTE: The 'koha-tmpl/intranet-tmpl/prog/js/vue/dist' directory is now handled in the "top_targets" function
@ -1926,7 +1942,11 @@ sub top_targets {
#NOTE: Make move_compiled_js depend on pm_to_blib, so we have access to the blib directory #NOTE: Make move_compiled_js depend on pm_to_blib, so we have access to the blib directory
#NOTE: Copy the generated vue dist files into the relevant blib #NOTE: Copy the generated vue dist files into the relevant blib
$compile_step .= "move_compiled_js: compile_css_js pm_to_blib\n"; $compile_step .= "move_compiled_js: compile_css_js pm_to_blib\n";
$compile_step .= "\t" . 'mkdir -p blib/INTRANET_TMPL_DIR/prog/js/vue/' . "\n";
$compile_step .= "\t" . 'cp -r koha-tmpl/intranet-tmpl/prog/js/vue/dist blib/INTRANET_TMPL_DIR/prog/js/vue/' . "\n"; $compile_step .= "\t" . 'cp -r koha-tmpl/intranet-tmpl/prog/js/vue/dist blib/INTRANET_TMPL_DIR/prog/js/vue/' . "\n";
$compile_step .= "\t" . 'mkdir -p blib/OPAC_TMPL_DIR/bootstrap/js/vue/' . "\n";
$compile_step .=
"\t" . 'cp -r koha-tmpl/opac-tmpl/bootstrap/js/vue/dist blib/OPAC_TMPL_DIR/bootstrap/js/vue/' . "\n";
$compile_step .= "\n"; $compile_step .= "\n";
my $top_targets = $compile_step . "\n" . $inherited; my $top_targets = $compile_step . "\n" . $inherited;

View File

@ -871,10 +871,9 @@ sub elasticsearch_check {
# fetch the list of available indexes (e.g. plugins, etc) # fetch the list of available indexes (e.g. plugins, etc)
$es_status->{nodes} = $es_conf->{nodes}; $es_status->{nodes} = $es_conf->{nodes};
my $es = Search::Elasticsearch->new($es_conf); my $es = Search::Elasticsearch->new($es_conf);
my $es_status->{version} = $es->info->{version}->{number};
foreach my $index (@indexes) { foreach my $index (@indexes) {
my $index_count; my $index_count = 0;
try { try {
$index_count = $es->indices->stats( index => $index )->{_all}{primaries}{docs}{count}; $index_count = $es->indices->stats( index => $index )->{_all}{primaries}{docs}{count};
} catch { } catch {
@ -910,6 +909,9 @@ sub elasticsearch_check {
}; };
} }
$es_status->{running} = $es_running; $es_status->{running} = $es_running;
if ($es_running) {
$es_status->{version} = $es->info->{version}->{number};
}
$template->param( $template->param(
elasticsearch_status => $es_status, elasticsearch_status => $es_status,

View File

@ -176,7 +176,7 @@ if ( $op eq "" ) {
#import selected lines #import selected lines
$template->param( 'basketno' => $cgiparams->{'basketno'} ); $template->param( 'basketno' => $cgiparams->{'basketno'} );
# Budget_id is mandatory for adding an order, we just add a default, the user needs to modify this aftewards # Budget_id is mandatory for adding an order, we just add a default, the user needs to modify this afterwards
my $budgets = GetBudgets(); my $budgets = GetBudgets();
if ( scalar @$budgets == 0 ) { if ( scalar @$budgets == 0 ) {
die "No budgets defined, can't continue"; die "No budgets defined, can't continue";

View File

@ -235,12 +235,12 @@ unless ( CanUserManageBasket( $loggedinuser, $basket, $userflags ) ) {
# FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
# if no booksellerid in parameter, get it from basket # if no booksellerid in parameter, get it from basket
# warn "=>".$basket->{booksellerid}; # warn "=>".$basket->{booksellerid};
my $op = $query->param('op') // 'list'; our $op = $query->param('op') // 'list';
our $confirm_pref = C4::Context->preference("BasketConfirmations") || '1'; our $confirm_pref = C4::Context->preference("BasketConfirmations") || '1';
$template->param( skip_confirm_reopen => 1 ) if $confirm_pref eq '2'; $template->param( skip_confirm_reopen => 1 ) if $confirm_pref eq '2';
my @messages; our @messages = ();
if ( $op eq 'cud-delete-order' ) { if ( $op eq 'cud-delete-order' ) {
output_and_exit( $query, $cookie, $template, 'insufficient_permission' ) output_and_exit( $query, $cookie, $template, 'insufficient_permission' )

View File

@ -56,46 +56,60 @@ my ($unitprice_field) = C4::Budgets->FieldsForCalculatingFundValues();
my $query = <<EOQ; my $query = <<EOQ;
SELECT SELECT
aqorders.biblionumber, aqorders.basketno, aqorders.ordernumber, biblionumber,
quantity-quantityreceived AS tleft, basketno,
budgetdate, entrydate, ordernumber,
aqbasket.booksellerid, tleft,
GROUP_CONCAT(DISTINCT itype SEPARATOR '|') as itypes, budgetdate,
entrydate,
booksellerid,
itypes,
title, title,
aqorders.invoiceid, invoiceid,
aqinvoices.invoicenumber, invoicenumber,
quantityreceived, quantityreceived,
$unitprice_field, $unitprice_field,
datereceived, datereceived,
aqbooksellers.name as vendorname aqbooksellers.name as vendorname
FROM (aqorders, aqbasket) FROM
LEFT JOIN biblio ON (
biblio.biblionumber=aqorders.biblionumber SELECT
LEFT JOIN aqorders_items ON aqorders.biblionumber, aqorders.basketno, aqorders.ordernumber,
aqorders.ordernumber = aqorders_items.ordernumber quantity-quantityreceived AS tleft,
LEFT JOIN items ON budgetdate, entrydate,
aqorders_items.itemnumber = items.itemnumber aqbasket.booksellerid,
LEFT JOIN aqinvoices ON GROUP_CONCAT(DISTINCT itype SEPARATOR '|') as itypes,
aqorders.invoiceid = aqinvoices.invoiceid title,
LEFT JOIN aqbooksellers ON aqorders.invoiceid,
aqbasket.booksellerid = aqbooksellers.id aqinvoices.invoicenumber,
WHERE quantityreceived,
aqorders.basketno=aqbasket.basketno AND $unitprice_field,
budget_id=? AND datereceived
datecancellationprinted IS NULL AND FROM aqorders
datereceived IS NOT NULL JOIN aqbasket ON aqorders.basketno=aqbasket.basketno
GROUP BY aqorders.biblionumber, aqorders.basketno, aqorders.ordernumber, LEFT JOIN biblio ON
tleft, biblio.biblionumber=aqorders.biblionumber
budgetdate, entrydate, LEFT JOIN aqorders_items ON
aqbasket.booksellerid, aqorders.ordernumber = aqorders_items.ordernumber
title, LEFT JOIN items ON
aqorders.invoiceid, aqorders_items.itemnumber = items.itemnumber
aqinvoices.invoicenumber, LEFT JOIN aqinvoices ON
quantityreceived, aqorders.invoiceid = aqinvoices.invoiceid
$unitprice_field, WHERE
datereceived, budget_id=? AND
aqbooksellers.name datecancellationprinted IS NULL AND
datereceived IS NOT NULL
GROUP BY aqorders.biblionumber, aqorders.basketno, aqorders.ordernumber,
tleft,
budgetdate, entrydate,
aqbasket.booksellerid,
title,
aqorders.invoiceid,
aqinvoices.invoicenumber,
quantityreceived,
$unitprice_field,
datereceived
) orders LEFT JOIN aqbooksellers ON orders.booksellerid = aqbooksellers.id
EOQ EOQ
my $sth = $dbh->prepare($query); my $sth = $dbh->prepare($query);
$sth->execute($bookfund); $sth->execute($bookfund);

View File

@ -1,7 +1,7 @@
#!/usr/bin/perl #!/usr/bin/perl
# Copyright 2008 BibLibre, BibLibre, Paul POULAIN # Copyright 2008 BibLibre, BibLibre, Paul POULAIN
# SAN Ouest Provence # SAN Ouest Provence # codespell:ignore
# #
# This file is part of Koha. # This file is part of Koha.
# #

View File

@ -91,7 +91,7 @@ else {
# FIXME - we really need a better way to do this consistently # FIXME - we really need a better way to do this consistently
# and across the board, be it bigints, Math::FixedPoint, a # and across the board, be it bigints, Math::FixedPoint, a
# modernized version of Math::Curency that isn't tied to the system # modernized version of Math::Currency that isn't tied to the system
# locale, or something else. # locale, or something else.
$total = sprintf( "%.2f", $total ); $total = sprintf( "%.2f", $total );
$sub_unalloc = sprintf( "%.2f", $sub_unalloc ); $sub_unalloc = sprintf( "%.2f", $sub_unalloc );

View File

@ -107,6 +107,51 @@ if ( $op eq 'cud-add' ) {
reason => 'invalid_id' reason => 'invalid_id'
}; };
} }
} elsif ( $op eq 'cud-test_email' ) {
my $smtp_server_id = $input->param('smtp_server_id');
my $smtp_server;
$smtp_server = Koha::SMTP::Servers->find($smtp_server_id)
unless !$smtp_server_id;
if ($smtp_server) {
my $test_email_address = C4::Context->preference('KohaAdminEmailAddress');
my $smtp_transport = $smtp_server->transport;
my $params = {
to => $test_email_address,
from => $test_email_address,
sender => $test_email_address,
subject => "Koha SMTP Test",
text_body => "Email is working."
};
my $email = Koha::Email->create($params);
try {
$email->send_or_die( { transport => $smtp_transport } );
push @messages,
{
type => 'message',
code => 'success_on_test'
};
} catch {
push @messages,
{
type => 'alert',
code => 'error_on_test'
};
};
} else {
push @messages,
{
type => 'alert',
code => 'error_on_test',
reason => 'invalid_id'
};
}
$op = 'list';
} elsif ( $op eq 'cud-edit_save' ) { } elsif ( $op eq 'cud-edit_save' ) {
my $smtp_server_id = $input->param('smtp_server_id'); my $smtp_server_id = $input->param('smtp_server_id');

View File

@ -67,4 +67,12 @@ properties:
Patron object joined on `object` (only meaningful for MEMBERS, Patron object joined on `object` (only meaningful for MEMBERS,
CIRCULATION, FINES and APIKEYS rows; only present if requested CIRCULATION, FINES and APIKEYS rows; only present if requested
via x-koha-embed) via x-koha-embed)
item:
type:
- object
- "null"
description: |
Item object derived from the itemnumber carried in `info` (only
meaningful for CIRCULATION rows; only present if requested via
x-koha-embed)
additionalProperties: false additionalProperties: false

View File

@ -5,7 +5,9 @@ properties:
type: integer type: integer
description: internally assigned checkout identifier description: internally assigned checkout identifier
patron_id: patron_id:
type: integer type:
- integer
- "null"
description: Internal patron identifier description: Internal patron identifier
item_id: item_id:
type: type:

View File

@ -0,0 +1,7 @@
---
type: object
properties:
settings:
type: object
description: List of sysprefs used for the ILL module
additionalProperties: false

View File

@ -67,6 +67,7 @@
enum: enum:
- librarian - librarian
- patron - patron
- item
collectionFormat: csv collectionFormat: csv
responses: responses:
"200": "200":

View File

@ -0,0 +1,38 @@
---
/ill/config:
get:
x-mojo-to: ILL#config
operationId: getILLconfig
description: This resource returns a list of options needed for the ILL Vue app. EXPERIMENTAL - DO NOT RELY on this, it is subject to change!
summary: get the ILL config
tags:
- ill_config
produces:
- application/json
responses:
200:
description: The ILL config
schema:
$ref: "../swagger.yaml#/definitions/ill_config"
400:
description: Bad request
schema:
$ref: "../swagger.yaml#/definitions/error"
403:
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
500:
description: |
Internal server error. Possible `error_code` attribute values:
* `internal_server_error`
schema:
$ref: "../swagger.yaml#/definitions/error"
503:
description: Under maintenance
schema:
$ref: "../swagger.yaml#/definitions/error"
x-koha-authorization:
permissions:
ill: 1

View File

@ -615,7 +615,7 @@
$ref: "../swagger.yaml#/definitions/error" $ref: "../swagger.yaml#/definitions/error"
x-koha-authorization: x-koha-authorization:
permissions: permissions:
borrowers: "1" borrowers: edit_borrowers
delete: delete:
x-mojo-to: Patrons#delete x-mojo-to: Patrons#delete
operationId: deletePatron operationId: deletePatron

View File

@ -108,6 +108,8 @@ definitions:
$ref: ./definitions/ill_backend.yaml $ref: ./definitions/ill_backend.yaml
ill_backends: ill_backends:
$ref: ./definitions/ill_backends.yaml $ref: ./definitions/ill_backends.yaml
ill_config:
$ref: ./definitions/ill_config.yaml
ill_status: ill_status:
$ref: ./definitions/ill_status.yaml $ref: ./definitions/ill_status.yaml
ill_request: ill_request:
@ -471,6 +473,8 @@ paths:
$ref: "./paths/holds.yaml#/~1holds~1{hold_id}~1suspension" $ref: "./paths/holds.yaml#/~1holds~1{hold_id}~1suspension"
"/holds/{hold_id}/lowest_priority": "/holds/{hold_id}/lowest_priority":
$ref: "./paths/holds.yaml#/~1holds~1{hold_id}~1lowest_priority" $ref: "./paths/holds.yaml#/~1holds~1{hold_id}~1lowest_priority"
/ill/config:
$ref: ./paths/ill_config.yaml#/~1ill~1config
/ill/backends: /ill/backends:
$ref: ./paths/ill_backends.yaml#/~1ill~1backends $ref: ./paths/ill_backends.yaml#/~1ill~1backends
"/ill/backends/{ill_backend_id}": "/ill/backends/{ill_backend_id}":
@ -1403,6 +1407,9 @@ tags:
- description: "Manage ILL module batch statuses\n" - description: "Manage ILL module batch statuses\n"
name: ill_batchstatuses name: ill_batchstatuses
x-displayName: ILL batch statuses x-displayName: ILL batch statuses
- description: "Manage ILL configuration\n"
name: ill_config
x-displayName: ILL configuration
- description: "Manage ILL requests\n" - description: "Manage ILL requests\n"
name: ill_requests name: ill_requests
x-displayName: ILL requests x-displayName: ILL requests

View File

@ -556,7 +556,8 @@ if ( $@ || $error ) {
} }
my $hits = $results_hashref->{$server}->{"hits"} // 0; my $hits = $results_hashref->{$server}->{"hits"} // 0;
if ( $hits == 0 && $basic_search ) { if ( $hits == 0 && $basic_search && $operands[0] !~ /^\".*\"$/ ) {
my $og_query = $query_desc;
$operands[0] = '"' . $operands[0] . '"'; #quote it $operands[0] = '"' . $operands[0] . '"'; #quote it
## I. BUILD THE QUERY ## I. BUILD THE QUERY
( (
@ -585,6 +586,10 @@ if ( $hits == 0 && $basic_search ) {
}; };
my $quoted_hits = $quoted_results_hashref->{$server}->{"hits"} // 0; my $quoted_hits = $quoted_results_hashref->{$server}->{"hits"} // 0;
if ($quoted_hits) { if ($quoted_hits) {
$template->param(
quoted_hits => 1,
og_query => $og_query,
);
$results_hashref->{$server} = $quoted_results_hashref->{$server}; $results_hashref->{$server} = $quoted_results_hashref->{$server};
$hits = $quoted_hits; $hits = $quoted_hits;
} }

View File

@ -878,7 +878,7 @@ if ( $op eq "cud-addbiblio" ) {
if ( $biblio && C4::Context->preference('marcflavour') eq 'MARC21' ) { if ( $biblio && C4::Context->preference('marcflavour') eq 'MARC21' ) {
my $fixed_length_info = my $fixed_length_info =
Koha::Biblio::Metadata::Extractor::MARC->new( { biblio => $biblio } )->check_fixed_length; Koha::Biblio::Metadata::Extractor::MARC->new( { metadata => $record } )->check_fixed_length;
$template->param( marc21_fixlen => $fixed_length_info ) if @{ $fixed_length_info->{failed} }; $template->param( marc21_fixlen => $fixed_length_info ) if @{ $fixed_length_info->{failed} };
} }

View File

@ -23,7 +23,7 @@ use C4::Auth qw( get_template_and_user );
use C4::Output qw( output_html_with_http_headers ); use C4::Output qw( output_html_with_http_headers );
# Example of framework plugin new style. # Example of framework plugin new style.
# It should define and return at least one and normally two anynomous # It should define and return at least one and normally two anonymous
# subroutines in a hash ref. # subroutines in a hash ref.
# REQUEST: If you copy this code to construct a new plugin, please REMOVE # REQUEST: If you copy this code to construct a new plugin, please REMOVE
# all comments copied from this file. # all comments copied from this file.

View File

@ -98,14 +98,21 @@ if ( $op eq "cud-KillWaiting" ) {
$cancelled = 1; $cancelled = 1;
$reqmessage = 1; $reqmessage = 1;
} # FIXME else? } # FIXME else?
} elsif ( $op eq "cud-IgnoreHold" ) {
$ignoreRs = 1;
$settransit = 1;
$op = 'cud-transfer';
} elsif ( $op eq "cud-SetTransit" ) { } elsif ( $op eq "cud-SetTransit" ) {
my $item = $query->param('itemnumber'); my $item = $query->param('itemnumber');
my $reserve_id = $query->param('reserve_id'); my $reserve_id = $query->param('reserve_id');
ModReserveAffect( $item, $borrowernumber, 1, $reserve_id ); ModReserveAffect( $item, $borrowernumber, 1, $reserve_id );
my $hold = Koha::Holds->find( { reserve_id => $reserve_id } );
$ignoreRs = 1; $ignoreRs = 1;
$settransit = 1; $settransit = 1;
$reqmessage = 1; $reqmessage = 1;
$trigger = 'Reserve'; $trigger = 'Reserve';
$op = 'cud-transfer';
$tobranchcd = $hold->branchcode;
} elsif ( $op eq 'cud-KillReserved' ) { } elsif ( $op eq 'cud-KillReserved' ) {
my $biblionumber = $query->param('biblionumber'); my $biblionumber = $query->param('biblionumber');
my $reserve_id = $query->param('reserve_id'); my $reserve_id = $query->param('reserve_id');

View File

@ -352,7 +352,7 @@ if ($noreport) {
preferred_name => $data->{preferred_name}, preferred_name => $data->{preferred_name},
middle_name => $data->{middle_name}, middle_name => $data->{middle_name},
cardnumber => $data->{cardnumber}, cardnumber => $data->{cardnumber},
title => $data->{title} title => $data->{borrowertitle}
}, },
duedate => $data->{date_due}, duedate => $data->{date_due},
borrowernumber => $data->{borrowernumber}, borrowernumber => $data->{borrowernumber},

View File

@ -91,8 +91,8 @@ requires 'Net::Z3950::ZOOM', '1.16';
requires 'Number::Format', '1.52'; requires 'Number::Format', '1.52';
requires 'OpenOffice::OODoc', '2.125'; requires 'OpenOffice::OODoc', '2.125';
requires 'PDF::API2', '2'; requires 'PDF::API2', '2';
requires 'PDF::Reuse', '0.36'; requires 'PDF::Reuse', '0.43';
requires 'PDF::Reuse::Barcode', '0.05'; requires 'PDF::Reuse::Barcode', '0.09';
requires 'PDF::Table', 'v0.9.3'; requires 'PDF::Table', 'v0.9.3';
requires 'POSIX', '1.09'; requires 'POSIX', '1.09';
requires 'Plack::Middleware::LogWarn', '0.001002'; requires 'Plack::Middleware::LogWarn', '0.001002';

View File

@ -138,6 +138,7 @@ generate_config_file() {
-e "s#__TEMPLATE_CACHE_DIR__#$TEMPLATE_CACHE_DIR#g" \ -e "s#__TEMPLATE_CACHE_DIR__#$TEMPLATE_CACHE_DIR#g" \
-e "s#__TIMEZONE__#$TIMEZONE#g" \ -e "s#__TIMEZONE__#$TIMEZONE#g" \
-e "s#__BCRYPT_SETTINGS__#$BCRYPT_SETTINGS#g" \ -e "s#__BCRYPT_SETTINGS__#$BCRYPT_SETTINGS#g" \
-e "s#__ENCRYPTION_KEY__#$ENCRYPTION_KEY#g" \
-e "s#__UPLOAD_PATH__#$UPLOAD_PATH#g" \ -e "s#__UPLOAD_PATH__#$UPLOAD_PATH#g" \
-e "s#__TMP_PATH__#$TMP_PATH#g" \ -e "s#__TMP_PATH__#$TMP_PATH#g" \
-e "s/__LOG_DIR__/\/var\/log\/koha\/$name/g" \ -e "s/__LOG_DIR__/\/var\/log\/koha\/$name/g" \
@ -650,7 +651,7 @@ if [ "${CLO_ELASTICSEARCH_SERVER}" != "" ]; then
fi fi
BCRYPT_SETTINGS=$(htpasswd -bnBC 10 "" password | tr -d ':\n' | sed 's/$2y/$2a/'); BCRYPT_SETTINGS=$(htpasswd -bnBC 10 "" password | tr -d ':\n' | sed 's/$2y/$2a/');
ENCRYPTION_KEY="$(pwgen -s 32 1)"
if [ "$ENABLE_SRU" != "no" ]; then if [ "$ENABLE_SRU" != "no" ]; then
enable_sru_server enable_sru_server

View File

@ -367,6 +367,12 @@ set_action()
_do_instance() { _do_instance() {
local name=$1 local name=$1
#NOTE: It's very important that PERL5LIB and KOHA_HOME are local
# By being local, we keep the PERL5LIB and KOHA_HOME specific
# to the instance that we're working on right now.
# If you disagree, please look at BZ 25778 and BZ 43159.
local PERL5LIB=$PERL5LIB
local KOHA_HOME=$KOHA_HOME
adjust_paths_git_install $name adjust_paths_git_install $name
PERL5LIB=$PERL5LIB:$KOHA_HOME/installer:$KOHA_HOME/lib/installer PERL5LIB=$PERL5LIB:$KOHA_HOME/installer:$KOHA_HOME/lib/installer

View File

@ -1565,30 +1565,31 @@ team:
qa_manager: qa_manager:
name: Martin Renvoize name: Martin Renvoize
qa: qa:
- name: Jonathan Druart
- name: Andrew Fuerste-Henry
- name: Marcel de Rooy - name: Marcel de Rooy
- name: Martin Renvoize - name: Martin Renvoize
- name: Jonathan Druart - name: Kyle M Hall
- name: Paul Derscheid
- name: Laura Escamilla - name: Laura Escamilla
- name: Lucas Gass - name: Lucas Gass
- name: Tomás Cohen Arazi
- name: Lisette Scheer
- name: Nick Clemens - name: Nick Clemens
- name: Paul Derscheid
- name: Emily Lamancusa
- name: David Cook
- name: Matt Blenkinsop
- name: Andrew Fuerste-Henry
- name: Brendan Lawlor
- name: Pedro Amorim
- name: Kyle M Hall
- name: Aleisha Amohia
- name: David Nind
- name: Baptiste Wojtkowski
- name: Jan Kissig
- name: Katrin Fischer - name: Katrin Fischer
- name: Baptiste Wojtkowski
- name: David Cook
- name: Lisette Scheer
- name: Emily Lamancusa
- name: Jan Kissig
- name: Thomas Klausner - name: Thomas Klausner
- name: Julian Maurice - name: Brendan Lawlor
- name: David Nind
- name: Tomás Cohen Arazi
- name: Pedro Amorim
- name: Owen Leonard - name: Owen Leonard
- name: John Vinke
- name: Matt Blenkinsop
- name: Alex Carver
- name: Victor Grousset
documentation: documentation:
name: David Nind name: David Nind
documentation_team: documentation_team:
@ -1671,3 +1672,82 @@ team:
name: David Nind name: David Nind
newsletter: newsletter:
name: Michael Kuhn name: Michael Kuhn
26.11:
manager:
name: Pedro Amorim
manager_assistants:
- name: Tomás Cohen Arazi
- name: Martin Renvoize
maintainer:
- version: 26.05
name: Lucas Gass
- version: 25.11
name: Baptiste Wojtkowski
- version: 25.05
name: "Wainui Witika-Park (Catalyst IT)"
- version: 24.11
name: Fridolin Somers
maintainer_assistants:
- version: 26.05
name: Jacob O'Mara
- version: 25.05
name: Alex Buckley & Aleisha Amohia (Catalyst IT)
qa_manager:
name: Lisette Scheer
qa:
- name: Lucas Gass
- name: Laura Escamilla
- name: Kyle M Hall
- name: Baptiste Wojtkowski
- name: Victor Grousset
- name: David Cook
- name: Andrew Fuerste-Henry
- name: Brendan Lawlor
- name: Thomas Klausner
- name: Paul Derscheid
- name: Jan Kissig
- name: Jacob O'Mara
- name: Nick Clemens
- name: Tomás Cohen Arazi
- name: Marcel de Rooy
- name: Emily Lamancusa
- name: Aleisha Amohia
- name: Martin Renvoize
- name: David Nind
security_manager:
- name: David Cook
te:
- area: Elasticsearch/OpenSearch
name: Fridolin Somers
- area: SIP2
name: Kyle M Hall
- area: EDI
name: Kyle M Hall
- area: POS
name: Martin Renvoize
bugwrangler:
- name: Michaela Sieber
- name: Laura Escamilla
documentation:
name: Aude Charillon
documentation_team:
- name: Caroline Cyr La Rose
- name: David Nind
- name: Philip Orr
meeting_facilitator:
- name: Paul Derscheid
ktd:
- name: Tomás Cohen Arazi
- name: Jonathan Druart
wiki:
- name: Thomas Dukleth
- name: George Williams
website:
- name: Lawrence O'Regan-Lloyd
newsletter:
name: Michael Kuhn
social_media:
name: David Nind
accessibility_advocates:
- name: Laura Escamilla
- name: Brendan Lawlor

View File

@ -4,7 +4,7 @@ use Koha::Installer::Output qw(say_warning say_success say_info);
return { return {
bug_number => "33363", bug_number => "33363",
description => description =>
"Split suggestions_manage into three separate permissions for creating, updating, and deleting suggetions", "Split suggestions_manage into three separate permissions for creating, updating, and deleting suggestions",
up => sub { up => sub {
my ($args) = @_; my ($args) = @_;
my ( $dbh, $out ) = @$args{qw(dbh out)}; my ( $dbh, $out ) = @$args{qw(dbh out)};

View File

@ -28,9 +28,17 @@ return {
}, undef, $local_number_map_id }, undef, $local_number_map_id
); );
my $index_name = $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX; # We do want to update the mappings in the DB in case ES gets switched on
my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new( { index => $index_name } ); # but just skip the ES engine update if ES not enabled
$indexer->update_mappings(); my $searchengine =
$dbh->selectrow_array(q|SELECT value FROM systempreferences WHERE variable = 'SearchEngine'|);
if ( $searchengine eq 'Elasticsearch' ) {
my $index_name = $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX;
my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new( { index => $index_name } );
$indexer->update_mappings();
} else {
say $out "ES disabled, mappings not updated";
}
say $out "Updated ES mappings to make local-number sortable"; say $out "Updated ES mappings to make local-number sortable";
} elsif ( !defined $local_number_map_id ) { } elsif ( !defined $local_number_map_id ) {
say_warning( $out, "No mapping defined for local-number" ); say_warning( $out, "No mapping defined for local-number" );

View File

@ -0,0 +1,21 @@
use Modern::Perl;
use Koha::Installer::Output qw(say_warning say_success say_info);
return {
bug_number => "42764",
description => "Fix typo in reports_dictionary.report_area column comment",
up => sub {
my ($args) = @_;
my ( $dbh, $out ) = @$args{qw(dbh out)};
$dbh->do(
q{
ALTER TABLE `reports_dictionary`
MODIFY COLUMN `report_area` varchar(6) DEFAULT NULL
COMMENT 'Koha module this definition is for (Circulation, Catalog, Patrons, Acquisitions, Accounts)'
}
);
say_success( $out, "Fixed typo in reports_dictionary.report_area column comment" );
},
};

View File

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

Some files were not shown because too many files have changed in this diff Show More