From 8e3e4dd9f4be3560474f041613a2af43226e0768 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Fri, 13 Feb 2026 13:25:20 +0000 Subject: [PATCH] Bug 26355: Pass a patron id rather than using the stashed patron This is to allow integration with external discovery systems that may use an API user rather than the specific patron account --- Koha/REST/V1/Patrons/SelfRenewal.pm | 8 +++--- api/v1/swagger/paths/public_patrons.yaml | 4 ++- api/v1/swagger/swagger.yaml | 4 +-- .../prog/js/fetch/patron-api-client.js | 8 +++--- .../PatronSelfRenewal/PatronSelfRenewal.vue | 7 ++++-- .../bootstrap/en/modules/opac-memberentry.tt | 6 ++++- .../bootstrap/en/modules/opac-user.tt | 2 +- t/cypress/integration/Islands/SelfRenewal.ts | 17 +++++++------ t/db_dependent/api/v1/patrons_self_renewal.t | 25 ++++++++++++------- 9 files changed, 50 insertions(+), 31 deletions(-) diff --git a/Koha/REST/V1/Patrons/SelfRenewal.pm b/Koha/REST/V1/Patrons/SelfRenewal.pm index 64efc143aca..10e8e3dba4e 100644 --- a/Koha/REST/V1/Patrons/SelfRenewal.pm +++ b/Koha/REST/V1/Patrons/SelfRenewal.pm @@ -42,10 +42,10 @@ Controller function that retrieves the metadata required to begin a patron self sub start { my $c = shift->openapi->valid_input or return; - my $patron = $c->stash('koha.user'); + my $patron = Koha::Patrons->find( $c->param('patron_id') ); return $c->render( status => 403, openapi => { error => "You are not eligible for self-renewal" } ) - if !$patron->is_eligible_for_self_renewal(); + if !$patron->is_eligible_for_self_renewal() || !$patron; return try { my $verification_checks = Koha::Patron::Attribute::Types->search_with_library_limits( @@ -88,7 +88,7 @@ Controller function that receives the renewal request and process the renewal sub submit { my $c = shift->openapi->valid_input or return; - my $patron = $c->stash('koha.user'); + my $patron = Koha::Patrons->find( $c->param('patron_id') ); return try { Koha::Database->new->schema->txn_do( @@ -96,7 +96,7 @@ sub submit { my $body = $c->req->json; return $c->render( status => 403, openapi => { error => "You are not eligible for self-renewal" } ) - if !$patron->is_eligible_for_self_renewal(); + if !$patron->is_eligible_for_self_renewal() || !$patron; my $OPACPatronDetails = C4::Context->preference("OPACPatronDetails"); if ($OPACPatronDetails) { diff --git a/api/v1/swagger/paths/public_patrons.yaml b/api/v1/swagger/paths/public_patrons.yaml index da0a7555a4b..2c56a95026e 100644 --- a/api/v1/swagger/paths/public_patrons.yaml +++ b/api/v1/swagger/paths/public_patrons.yaml @@ -344,7 +344,7 @@ description: Under maintenance schema: $ref: "../swagger.yaml#/definitions/error" -/public/patrons/self_renewal: +/public/patrons/{patron_id}/self_renewal: get: x-mojo-to: Patrons::SelfRenewal#start operationId: startSelfRenewal @@ -354,6 +354,7 @@ produces: - application/json parameters: + - $ref: "../swagger.yaml#/parameters/patron_id_pp" - $ref: "../swagger.yaml#/parameters/match" - $ref: "../swagger.yaml#/parameters/order_by" - $ref: "../swagger.yaml#/parameters/page" @@ -404,6 +405,7 @@ produces: - application/json parameters: + - $ref: "../swagger.yaml#/parameters/patron_id_pp" - description: A JSON object containing information about the self-renewal in: body name: body diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 1df3ad5a837..c9b003ac2ad 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -583,8 +583,8 @@ paths: $ref: "./paths/public_patrons.yaml#/~1public~1patrons~1{patron_id}~1ill~1requests" "/public/patrons/{patron_id}/password": $ref: "./paths/public_patrons.yaml#/~1public~1patrons~1{patron_id}~1password" - "/public/patrons/self_renewal": - $ref: "./paths/public_patrons.yaml#/~1public~1patrons~1self_renewal" + "/public/patrons/{patron_id}/self_renewal": + $ref: "./paths/public_patrons.yaml#/~1public~1patrons~1{patron_id}~1self_renewal" "/public/tickets": $ref: "./paths/tickets.yaml#/~1public~1tickets" /quotes: diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/patron-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/patron-api-client.js index 7e14b32d7ad..b12135b50b8 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/fetch/patron-api-client.js +++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/patron-api-client.js @@ -28,16 +28,16 @@ export class PatronAPIClient { get self_renewal() { return { - start: (query, params, headers) => + start: (id, query, params, headers) => this.httpClient.getAll({ - endpoint: "public/patrons/self_renewal", + endpoint: `public/patrons/${id}/self_renewal`, query, params, headers, }), - submit: renewal => + submit: (id, renewal) => this.httpClient.post({ - endpoint: "public/patrons/self_renewal", + endpoint: `public/patrons/${id}/self_renewal`, body: renewal, }), }; diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Islands/PatronSelfRenewal/PatronSelfRenewal.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Islands/PatronSelfRenewal/PatronSelfRenewal.vue index 0dd7a48e5c3..2a366380810 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Islands/PatronSelfRenewal/PatronSelfRenewal.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Islands/PatronSelfRenewal/PatronSelfRenewal.vue @@ -80,6 +80,9 @@ import { $__ } from "@koha-vue/i18n"; export default { components: { VerificationChecks }, + props: { + patron: String, + }, setup(props) { const verificationChecks = ref([]); const initialized = ref(false); @@ -92,7 +95,7 @@ export default { onBeforeMount(() => { const client = APIClient.patron; - client.self_renewal.start().then( + client.self_renewal.start(props.patron).then( response => { verificationChecks.value = response.verification_checks; renewalSettings.value = { @@ -126,7 +129,7 @@ export default { const submitRenewal = () => { const client = APIClient.patron; - client.self_renewal.submit({}).then( + client.self_renewal.submit(props.patron, {}).then( response => { let newLocation = "/cgi-bin/koha/opac-user.pl?self_renewal_success=" + diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt index 97fbbbe316f..4353703b9ca 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt @@ -244,7 +244,11 @@ > [% END %] -
+ [% INCLUDE 'csrf-token.inc' %] [% FOREACH field = ['streetnumber' 'streettype' 'cardnumber' 'branchcode' 'categorycode' 'title' 'surname' 'firstname' 'preferred_name' 'middle_name' 'dateofbirth' 'initials' 'pronouns' 'othernames' 'address' 'address2' 'city' 'state' 'zipcode' 'country' 'phone' 'phonepro' 'mobile' 'email' 'emailpro' 'fax' 'B_streettype' 'B_streetnumber' 'B_address' 'B_address2' 'B_city' 'B_state' 'B_zipcode' 'B_country' 'B_phone' 'B_email' 'contactnote' 'altcontactsurname' 'altcontactfirstname' 'altcontactaddress1' 'altcontactaddress2' 'altcontactaddress3' 'altcontactstate' 'altcontactzipcode' 'altcontactcountry' 'altcontactphone' 'password' 'lang' ] %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt index 136610fe439..552ef9a2c1d 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -1035,7 +1035,7 @@ [% IF ( self_renewal_available ) %] - + [% END %] [% SET islands = Asset.js("js/vue/dist/islands.esm.js").match('(src="([^"]+)")').1 %] diff --git a/t/cypress/integration/Islands/SelfRenewal.ts b/t/cypress/integration/Islands/SelfRenewal.ts index 1e091c85896..ee084e61c24 100644 --- a/t/cypress/integration/Islands/SelfRenewal.ts +++ b/t/cypress/integration/Islands/SelfRenewal.ts @@ -56,7 +56,7 @@ describe("Patron self-renewal", () => { cy.intercept( "GET", Cypress.env("opacBaseUrl") + - "/api/v1/public/patrons/self_renewal?_per_page=-1", + "/api/v1/public/patrons/*/self_renewal?_per_page=-1", { self_renewal_settings: { opac_patron_details: "0", @@ -78,7 +78,7 @@ describe("Patron self-renewal", () => { cy.intercept( "GET", Cypress.env("opacBaseUrl") + - "/api/v1/public/patrons/self_renewal?_per_page=-1", + "/api/v1/public/patrons/*/self_renewal?_per_page=-1", { self_renewal_settings: { opac_patron_details: "0", @@ -93,7 +93,8 @@ describe("Patron self-renewal", () => { cy.wait("@renewalConfig"); cy.intercept( "POST", - Cypress.env("opacBaseUrl") + "/api/v1/public/patrons/self_renewal", + Cypress.env("opacBaseUrl") + + "/api/v1/public/patrons/*/self_renewal", { statusCode: 201, body: { @@ -115,7 +116,7 @@ describe("Patron self-renewal", () => { cy.intercept( "GET", Cypress.env("opacBaseUrl") + - "/api/v1/public/patrons/self_renewal?_per_page=-1", + "/api/v1/public/patrons/*/self_renewal?_per_page=-1", { self_renewal_settings: { opac_patron_details: "0", @@ -133,7 +134,8 @@ describe("Patron self-renewal", () => { cy.wait("@renewalConfig"); cy.intercept( "POST", - Cypress.env("opacBaseUrl") + "/api/v1/public/patrons/self_renewal", + Cypress.env("opacBaseUrl") + + "/api/v1/public/patrons/*/self_renewal", { statusCode: 201, body: { @@ -170,7 +172,7 @@ describe("Patron self-renewal", () => { cy.intercept( "GET", Cypress.env("opacBaseUrl") + - "/api/v1/public/patrons/self_renewal?_per_page=-1", + "/api/v1/public/patrons/*/self_renewal?_per_page=-1", { self_renewal_settings: { opac_patron_details: "1", @@ -195,7 +197,8 @@ describe("Patron self-renewal", () => { ); cy.intercept( "POST", - Cypress.env("opacBaseUrl") + "/api/v1/public/patrons/self_renewal", + Cypress.env("opacBaseUrl") + + "/api/v1/public/patrons/*/self_renewal", { statusCode: 201, body: { diff --git a/t/db_dependent/api/v1/patrons_self_renewal.t b/t/db_dependent/api/v1/patrons_self_renewal.t index 7d1431df84b..3de8434b664 100755 --- a/t/db_dependent/api/v1/patrons_self_renewal.t +++ b/t/db_dependent/api/v1/patrons_self_renewal.t @@ -63,14 +63,17 @@ subtest 'start()' => sub { $patron->set_password( { password => $password, skip_validation => 1 } ); my $userid = $patron->userid; - $t->get_ok("//$userid:$password@/api/v1/public/patrons/self_renewal")->status_is( 403, 'REST3.2.2' ) - ->json_is( { error => "You are not eligible for self-renewal" } ); + my $borrowernumber = $patron->borrowernumber; + + $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") + ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } ); $category->self_renewal_enabled(1)->store(); t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); - $t->get_ok("//$userid:$password@/api/v1/public/patrons/self_renewal")->status_is( 200, 'REST3.2.2' )->json_is( + $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") + ->status_is( 200, 'REST3.2.2' )->json_is( { verification_checks => [], self_renewal_settings => { @@ -78,7 +81,7 @@ subtest 'start()' => sub { opac_patron_details => 1 } } - ); + ); my $attribute_type = $builder->build_object( { @@ -111,7 +114,8 @@ subtest 'start()' => sub { } } ); - $t->get_ok("//$userid:$password@/api/v1/public/patrons/self_renewal")->status_is( 200, 'REST3.2.2' )->json_is( + $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") + ->status_is( 200, 'REST3.2.2' )->json_is( { verification_checks => [ $attribute_type->unblessed ], self_renewal_settings => { @@ -119,7 +123,7 @@ subtest 'start()' => sub { opac_patron_details => 1 } } - ); + ); $schema->storage->txn_rollback; }; @@ -155,7 +159,9 @@ subtest 'submit()' => sub { $patron->set_password( { password => $password, skip_validation => 1 } ); my $userid = $patron->userid; - $t->post_ok( "//$userid:$password@/api/v1/public/patrons/self_renewal" => json => {} ) + my $borrowernumber = $patron->borrowernumber; + + $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} ) ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } ); t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); @@ -190,7 +196,7 @@ subtest 'submit()' => sub { ); my $counter = Koha::Notice::Messages->search( { borrowernumber => $patron->borrowernumber } )->count; - $t->post_ok( "//$userid:$password@/api/v1/public/patrons/self_renewal" => json => {} ) + $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} ) ->status_is( 201, 'REST3.2.2' ) ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } ); is( @@ -203,7 +209,8 @@ subtest 'submit()' => sub { my $modification_data = { firstname => 'Newname' }; $patron->dateexpiry( dt_from_string() )->store(); - $t->post_ok( "//$userid:$password@/api/v1/public/patrons/self_renewal" => json => $modification_data ) + $t->post_ok( + "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => $modification_data ) ->status_is( 201, 'REST3.2.2' ) ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } ); -- 2.50.1