From 361bac7bddc7a474ee3ecae63b51392233bc6bf2 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Thu, 25 Sep 2025 12:02:12 +0200 Subject: [PATCH] Bug 40876: Apply 'exact' to all attributes when column filtering If you select an option in a column filter, the "exact" behaviour will only apply to the first attribute mapped to this column eg. on the patron search we have "Libraries" mapped to library.name and library.library_id. If you select Centerville/CPL, the generated q parameter will be {name: CPL, id: {like: "%CPL%"}} This is caused by: if (value.match(/^\^(.*)\$$/)) { value = value.replace(/^\^/, "").replace(/\$$/, ""); On the second parameter we have lost the ^$ markers Moving it out of the loop fixes the problem (and does not impact the other behaviours: we search the same value for all the attributes Test plan: Create a library with code=CPLL and use if for a patron Go to http://localhost:8081/cgi-bin/koha/members/members-home.pl Click search Select "Centerville" in the "Libraries" column filter => Without this patch you get the patrons from CPL and CPLL => With this patch applied only patrons from CPL are listed Signed-off-by: Lucas Gass Signed-off-by: Paul Derscheid --- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 18 ++-- .../KohaTable/PatronSearch_spec.ts | 90 +++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 0e8a12b23f5..b03b44a9784 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -493,18 +493,20 @@ function _dt_default_ajax(params) { function build_query(col, value) { var parts = []; var attributes = col.data.split(":"); + + let criteria = options.criteria; + if (value.match(/^\^(.*)\$$/)) { + value = value.replace(/^\^/, "").replace(/\$$/, ""); + criteria = "exact"; + } else { + // escape SQL LIKE special characters % + value = value.replace(/(\%|\\)/g, "\\$1"); + } + for (var i = 0; i < attributes.length; i++) { var part = {}; var attr = attributes[i]; let default_build = true; - let criteria = options.criteria; - if (value.match(/^\^(.*)\$$/)) { - value = value.replace(/^\^/, "").replace(/\$$/, ""); - criteria = "exact"; - } else { - // escape SQL LIKE special characters % - value = value.replace(/(\%|\\)/g, "\\$1"); - } let built_value; if (col.type == "date") { diff --git a/t/cypress/integration/KohaTable/PatronSearch_spec.ts b/t/cypress/integration/KohaTable/PatronSearch_spec.ts index bffcd29580b..c6bff2545ac 100644 --- a/t/cypress/integration/KohaTable/PatronSearch_spec.ts +++ b/t/cypress/integration/KohaTable/PatronSearch_spec.ts @@ -180,4 +180,94 @@ describe("Filters", () => { ).should("have.value", null); }); }); + + describe("Exact search for all attributes", () => { + // In this case the library column is bind to library.name and library.library_id + it("From the form", () => { + cy.task("buildSampleObjects", { + object: "patron", + count: RESTdefaultPageSize, + values: {}, + }).then(patrons => { + // Needs more properties to not explode + // account_balace: balance_str.escapeHtml(...).format_price is not a function + patrons = patrons.map(p => ({ ...p, account_balance: 0 })); + + cy.intercept("GET", "/api/v1/patrons*", { + statusCode: 200, + body: patrons, + headers: { + "X-Base-Total-Count": baseTotalCount, + "X-Total-Count": baseTotalCount, + }, + }).as("searchPatrons"); + + cy.visit("/cgi-bin/koha/members/members-home.pl"); + + cy.window().then(win => { + win.categories_map = patrons.reduce((map, p) => { + map[p.category_id.toLowerCase()] = p.category_id; + return map; + }, {}); + }); + + cy.get("form.patron_search_form .branchcode_filter").select( + "CPL" + ); + cy.get("form.patron_search_form input[type='submit']").click(); + + cy.wait("@searchPatrons").then(interception => { + const q = interception.request.query.q; + expect(q).to.equal( + '[{"library.name":"CPL"},{"me.library_id":"CPL"}]' + ); + }); + }); + }); + + it("From the column filter", () => { + cy.task("buildSampleObjects", { + object: "patron", + count: RESTdefaultPageSize, + values: {}, + }).then(patrons => { + // Needs more properties to not explode + // account_balace: balance_str.escapeHtml(...).format_price is not a function + patrons = patrons.map(p => ({ ...p, account_balance: 0 })); + + cy.intercept("GET", "/api/v1/patrons*", { + statusCode: 200, + body: patrons, + headers: { + "X-Base-Total-Count": baseTotalCount, + "X-Total-Count": baseTotalCount, + }, + }).as("searchPatrons"); + + cy.visit("/cgi-bin/koha/members/members-home.pl"); + + cy.window().then(win => { + win.categories_map = patrons.reduce((map, p) => { + map[p.category_id.toLowerCase()] = p.category_id; + return map; + }, {}); + }); + + cy.get("form.patron_search_form input[type='submit']").click(); + + cy.wait("@searchPatrons"); + + cy.get( + `#${table_id} thead tr th[data-filter='libraries'] select` + ).select("^CPL$"); + + cy.wait("@searchPatrons").then(interception => { + const q = interception.request.query.q; + expect(q).to.equal( + '[{"library.name":"CPL"},{"me.library_id":"CPL"}]' + ); + }); + }); + }); + }); }); -- 2.39.5