Bugzilla – Attachment 193252 Details for
Bug 26355
Allow patrons to self-renew through the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 26355: Pass a patron id rather than using the stashed patron
Bug-26355-Pass-a-patron-id-rather-than-using-the-s.patch (text/plain), 18.12 KB, created by
Matt Blenkinsop
on 2026-02-16 14:26:02 UTC
(
hide
)
Description:
Bug 26355: Pass a patron id rather than using the stashed patron
Filename:
MIME Type:
Creator:
Matt Blenkinsop
Created:
2026-02-16 14:26:02 UTC
Size:
18.12 KB
patch
obsolete
>From 405342cc0ba016ab96088db8abd21cdf3b091816 Mon Sep 17 00:00:00 2001 >From: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk> >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 | 15 ++++++----- > .../definitions/patron_self_renewal.yaml | 9 +++++-- > 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 | 8 ++++-- > .../bootstrap/en/modules/opac-user.tt | 2 +- > t/cypress/integration/Islands/SelfRenewal.ts | 17 +++++++----- > t/db_dependent/api/v1/patrons_self_renewal.t | 27 ++++++++++++------- > 10 files changed, 63 insertions(+), 38 deletions(-) > >diff --git a/Koha/REST/V1/Patrons/SelfRenewal.pm b/Koha/REST/V1/Patrons/SelfRenewal.pm >index 64efc143aca..ce6ebb2c75c 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,15 +96,16 @@ 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) { >- my $extended_attributes = delete $body->{extended_attributes}; >+ my $patron_details = $body->{patron}; >+ my $extended_attributes = delete $patron_details->{extended_attributes}; > my $changed_fields = {}; > my $changes_detected; >- foreach my $key ( keys %$body ) { >- my $submitted_value = $body->{$key}; >+ foreach my $key ( keys %$patron_details ) { >+ my $submitted_value = $patron_details->{$key}; > my $original_value = $patron->$key; > > if ( $submitted_value ne $original_value ) { >diff --git a/api/v1/swagger/definitions/patron_self_renewal.yaml b/api/v1/swagger/definitions/patron_self_renewal.yaml >index a9291b72c83..d1209e9c743 100644 >--- a/api/v1/swagger/definitions/patron_self_renewal.yaml >+++ b/api/v1/swagger/definitions/patron_self_renewal.yaml >@@ -1,11 +1,11 @@ > --- > type: "object" > properties: >- verification_steps: >+ verification_checks: > type: > - array > - "null" >- description: Any additional verification steps set in patron attribute types >+ description: Any additional verification checks set in patron attribute types > self_renewal_settings: > type: > - object >@@ -21,3 +21,8 @@ properties: > - array > - "null" > description: The method(s) through which confirmation of renewal has been sent >+ patron: >+ type: >+ - object >+ description: The object representing the patron fields from the OPAC form >+additionalProperties: false >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..55dc4d65737 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 %] > >- <form method="post" [% IF (self_renewal) %]action="/api/v1/public/patrons/self_renewal" id="self-renewal-form"[% ELSE %]action="/cgi-bin/koha/opac-memberentry.pl" id="memberentry-form"[% END %] autocomplete="off"> >+ <form >+ method="post" >+ [% IF (self_renewal) %]action="/api/v1/public/patrons/[% logged_in_user.borrowernumber | uri %]/self_renewal" id="self-renewal-form"[% ELSE %]action="/cgi-bin/koha/opac-memberentry.pl" id="memberentry-form"[% END %] >+ autocomplete="off" >+ > > [% 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' ] %] >@@ -1461,7 +1465,7 @@ > url: $(form).attr('action'), > method: $(form).attr('method').toUpperCase(), > contentType: 'application/json', >- data: JSON.stringify(renewalBody) >+ data: JSON.stringify({patron: renewalBody}) > }; > $.ajax(options) > .then(function(result) { >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 @@ > <!-- /#main --> > > [% IF ( self_renewal_available ) %] >- <patron-self-renewal></patron-self-renewal> >+ <patron-self-renewal patron="[% borrowernumber | html %]"></patron-self-renewal> > [% 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..0371396c6d5 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( >@@ -200,10 +206,11 @@ subtest 'submit()' => sub { > > # Test that modifications are created correctly > t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); >- my $modification_data = { firstname => 'Newname' }; >+ my $modification_data = { patron => { 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
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 26355
:
191781
|
191782
|
191783
|
191784
|
191785
|
191786
|
191787
|
191788
|
191789
|
192440
|
192441
|
192442
|
192443
|
192444
|
192445
|
192446
|
192447
|
192448
|
193065
|
193066
|
193067
|
193068
|
193069
|
193070
|
193071
|
193072
|
193073
|
193074
|
193249
|
193250
|
193251
| 193252 |
193253