Bug 30578: Remove circ/ysearch.pl in favor of the /patrons REST API route

This patch removes the circ/ysearch.pl script used by the jQuery autocomplete widget.
We can now use the /api/v1/patrons endpoint to retrieve the patrons and
generate the patron result list.

Prior to this patch the different occurrences were defining the style
and the list of patron's attributes to display for each option (name,
date of birth, age, address, etc.). Now they are all displaying the same
information.

To acchieve this we had to:
* Make js-date-format.inc and js-patron-get-age.inc available from js_includes.inc
and so available from everywhere, which is certainly a good move. We
could discuss why this code is in include file instead of JS files
however.
* Remove the .ajaxSetup call in tags-review.js to reduce its scope: an
underscore parameter was added to the REST API query (?)

A better solution would have been to extend the existing widget
(https://learn.jquery.com/jquery-ui/widget-factory/extending-widgets/)
but I didn't manage to do it, and I feel like there is a bug in jQuery
autocomplete. The "source" was not taken into account.
We could think about replacing the jQuery autocomplete with something
else, but that's outside the scope of this bug.

Test plan:
Search for patrons and confirm the autocomplete works and that the
"select" action works as before (either a redirect or select the
patrons) on the different views:
* Place a hold
* Search for tags (form on the left)
* In the header, "Check out" and "Search patrons"
* Add instructors to course reserves
* View logs (the "librarian" input)

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Jonathan Druart 2022-06-09 11:03:27 +02:00 committed by Tomas Cohen Arazi
parent e25819723d
commit 9309dedb53
No known key found for this signature in database
GPG Key ID: 0A272EA1B2F3C15F
15 changed files with 144 additions and 313 deletions

View File

@ -364,6 +364,11 @@ properties:
- number
- "null"
description: Balance of the patron's account
library:
type:
- object
- "null"
description: Library of the patron
additionalProperties: false
required:
- surname

View File

@ -359,6 +359,7 @@
- checkouts+count
- overdues+count
- account_balance
- library
collectionFormat: csv
responses:
"200":

View File

@ -1,103 +0,0 @@
#!/usr/bin/perl
# This software is placed under the gnu General Public License, v2 (http://www.gnu.org/licenses/gpl.html)
# Copyright 2007 Tamil s.a.r.l.
# Parts copyright 2010-2012 Athens County Public Libraries
#
# 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 <http://www.gnu.org/licenses>.
=head1 ysearch.pl
=cut
use Modern::Perl;
use CGI qw ( -utf8 );
use C4::Context;
use C4::Auth qw( check_cookie_auth );
use Koha::Patrons;
use Koha::DateUtils qw( format_sqldatetime );
use Koha::Libraries;
use JSON qw( to_json );
my $input = CGI->new;
my $query = $input->param('term');
binmode STDOUT, ":encoding(UTF-8)";
print $input->header( -type => 'text/plain', -charset => 'UTF-8' );
my ( $auth_status ) = check_cookie_auth( $input->cookie('CGISESSID'), { catalogue => '1' } );
if ( $auth_status ne "ok" ) {
exit 0;
}
my $limit_on_branch;
if ( C4::Context->preference("IndependentBranches")
&& C4::Context->userenv
&& !C4::Context->IsSuperLibrarian()
&& C4::Context->userenv->{'branch'} ) {
$limit_on_branch = 1;
}
my @parts = split( /,\s|\s/, $query );
my @params;
foreach my $p (@parts) {
push(
@params,
-or => [
surname => { -like => "%$p%" },
firstname => { -like => "%$p%" },
middle_name => { -like => "%$p%" },
cardnumber => { -like => "$p%" },
]
);
}
push( @params, { 'me.branchcode' => C4::Context->userenv->{branch} } ) if $limit_on_branch;
my $borrowers_rs = Koha::Patrons->search_limited(
{ -and => \@params },
{
# Get the first 10 results
page => 1,
rows => 10,
order_by => [ 'surname', 'firstname', 'middle_name' ],
prefetch => 'branchcode',
},
);
my @borrowers;
while ( my $b = $borrowers_rs->next ) {
push @borrowers,
{
borrowernumber => $b->borrowernumber,
surname => $b->surname // '',
firstname => $b->firstname // '',
middle_name => $b->middle_name // '',
cardnumber => $b->cardnumber // '',
dateofbirth => format_sqldatetime( $b->dateofbirth, undef, undef, 1 ) // '',
age => $b->get_age // '',
address => $b->address // '',
city => $b->city // '',
zipcode => $b->zipcode // '',
country => $b->country // '',
branchcode => $b->branchcode // '',
branchname => $b->library->branchname // '',
};
}
print to_json( \@borrowers );

View File

@ -5,7 +5,7 @@ function patron_autocomplete(params) {
var field_to_retrieve = params.field_to_retrieve || 'cardnumber';
$( input_autocomplete ).autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
source: "/api/v1/patrons",
minLength: 3,
select: function( event, ui ) {
var field = ui.item.cardnumber;

View File

@ -26,6 +26,9 @@
[% Asset.js("lib/jquery/plugins/jquery.validate.min.js") | $raw %]
<!-- koha core js -->
[% Asset.js("js/staff-global.js") | $raw %]
[% INCLUDE 'js-date-format.inc' %]
[% INCLUDE 'js-patron-get-age.inc' %]
[% Asset.js("js/patron-autocomplete.js") | $raw %]
[% INCLUDE 'validator-strings.inc' %]
[% IF ( IntranetUserJS ) %]
@ -65,112 +68,20 @@
</script>
[% END %]
[% IF ( CAN_user_circulate_circulate_remaining_permissions ) %]
[% IF ( PatronAutoComplete ) %]
<script>
// PatronAutoComplete && CAN_user_circulate_circulate_remaining_permissions
var loggedInLibrary = '[% Branches.GetLoggedInBranchcode | html %]';
var loggedInClass = "";
$(document).ready(function(){
var obj = $( "#findborrower" ).autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function( event, ui ) {
window.location.href = ui.item.link;
}
}).data( "ui-autocomplete" );
if( obj ) {
obj._renderItem = function( ul, item ) {
item.link = "/cgi-bin/koha/circ/circulation.pl?borrowernumber=" + item.borrowernumber;
var cardnumber = "";
if( item.cardnumber != "" ){
// Display card number in parentheses if it exists
cardnumber = " (" + item.cardnumber + ") ";
}
var itemString = "<a href=\"" + item.link + "\">" + ( item.surname ? item.surname.escapeHtml() : "" ) + ", "
+ ( item.firstname ? item.firstname.escapeHtml() : "" )
+ ( item.middle_name ? " " + item.middle_name.escapeHtml() : "" )
+ cardnumber.escapeHtml()
+ " <small>";
if( item.branchcode == loggedInLibrary ){
loggedInClass = "ac-currentlibrary";
} else {
loggedInClass = "";
}
if( item.dateofbirth ) {
itemString += ( item.dateofbirth ? item.dateofbirth.escapeHtml() : "" )
+ "<span class=\"age_years\"> (" + ( item.age ? item.age.escapeHtml() : "" ) + " " + _("years") + ")</span>, ";
}
itemString += ( item.address ? item.address.escapeHtml() : "" ) + " "
+ ( item.city ? item.city.escapeHtml() : "" ) + " "
+ ( item.zipcode ? item.zipcode.escapeHtml() : "" ) + " "
+ ( item.country ? item.country.escapeHtml() : "" )
+ "</small></a>";
[% UNLESS ( singleBranchMode ) %]
itemString += " <span class=\"ac-library\">" + item.branchname + "</span> " + "</a>";
[% END %]
return $( "<li></li>" )
.addClass( loggedInClass )
.data( "ui-autocomplete-item", item )
.append( itemString )
.appendTo( ul );
};
}
});
</script>
[% END %]
[% END %]
[% IF ( PatronAutoComplete ) %]
<script>
// PatronAutoComplete
var loggedInLibrary = '[% Branches.GetLoggedInBranchcode | html %]';
var singleBranchMode = '[% singleBranchMode | html %]';
var loggedInClass = "";
$(document).ready(function(){
var obj = $( "#searchmember" ).autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function( event, ui ) {
window.location.href = ui.item.link;
[% IF ( CAN_user_circulate_circulate_remaining_permissions ) %]
if ( $("#findborrower").length ) {
patron_autocomplete($("#findborrower"), { 'on-select': 'link-to-circ' });
}
}).data( "ui-autocomplete" );
if( obj ) {
obj._renderItem = function( ul, item ) {
item.link = "/cgi-bin/koha/members/moremember.pl?borrowernumber=" + item.borrowernumber;
var cardnumber = "";
if( item.cardnumber != "" ){
// Display card number in parentheses if it exists
cardnumber = " (" + item.cardnumber + ") ";
}
if( item.branchcode == loggedInLibrary ){
loggedInClass = "ac-currentlibrary";
} else {
loggedInClass = "";
}
return $( "<li></li>" )
.addClass( loggedInClass )
.data( "ui-autocomplete-item", item )
.append(
"<a href=\"" + item.link + "\">" + ( item.surname ? item.surname.escapeHtml() : "" ) + ", "
+ ( item.firstname ? item.firstname.escapeHtml() : "" )
+ ( item.middle_name ? " " + item.middle_name.escapeHtml() : "" )
+ cardnumber.escapeHtml()
+ " <small>"
+ ( item.dateofbirth ? item.dateofbirth.escapeHtml() : "" ) + " "
+ ( item.address ? item.address.escapeHtml() : "" ) + " "
+ ( item.city ? item.city.escapeHtml() : "" ) + " "
+ ( item.zipcode ? item.zipcode.escapeHtml() : "" ) + " "
+ ( item.country ? item.country.escapeHtml() : "" ) + " "
[% UNLESS ( singleBranchMode ) %]
+ "<span class=\"ac-library\">"
+ ( item.branchname ? item.branchname.escapeHtml() : "" )
+ "</span>"
[% END %]
+ "</small>"
+ "</a>" )
.appendTo( ul );
};
[% END %]
if ( $("#searchmember").length ) {
patron_autocomplete($("#searchmember"), { 'on-select': 'link-to-patron' });
}
});
</script>

View File

@ -236,7 +236,6 @@
</script>
[% INCLUDE 'datatables.inc' %]
[% INCLUDE 'js-date-format.inc' %]
[% INCLUDE 'js-patron-get-age.inc' %]
[% INCLUDE 'js-patron-format.inc' %]
[% INCLUDE 'js-patron-format-address.inc' %]

View File

@ -1029,7 +1029,6 @@
[% INCLUDE 'columns_settings.inc' %]
[% INCLUDE 'select2.inc' %]
[% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
[% INCLUDE 'js-date-format.inc' %]
<script>
/* Set some variable needed in circulation.js */
var logged_in_user_borrowernumber = "[% logged_in_user.borrowernumber | html %]";

View File

@ -174,36 +174,11 @@
[% MACRO jsinclude BLOCK %]
<script>
$(document).ready(function(){
$( "#find_instructor" ).autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function( event, ui ) {
AddInstructor( ui.item.surname + ", " + ui.item.firstname, ui.item.borrowernumber );
patron_autocomplete($("#find_instructor"), { 'on-select-callback': function( event, ui ) {
AddInstructor( ui.item.surname + ", " + ui.item.firstname, ui.item.patron_id );
return false;
}
})
.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
return $( "<li></li>" )
.data( "ui-autocomplete-item", item )
.append(
"<a>"
+ ( item.surname ? item.surname.escapeHtml() : "" )
+ ", "
+ ( item.firstname ? item.firstname.escapeHtml() : "" )
+ " (" + ( item.cardnumber ? item.cardnumber.escapeHtml() : "" ) + ")"
+ " "
+ "<small>"
+ ( item.address ? item.address.escapeHtml() : "" )
+ " "
+ ( item.city ? item.city.escapeHtml() : "" )
+ " "
+ ( item.zipcode ? item.zipcode.escapeHtml() : "" )
+ " "
+ ( item.country ? item.country.escapeHtml() : "" )
+ "</small>"
+ "</a>" )
.appendTo( ul );
};
});
if ( ! $('#instructors').html() ) {
$('#course_instructors').hide();

View File

@ -838,7 +838,6 @@
[% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
[% INCLUDE 'columns_settings.inc' %]
[% INCLUDE 'select2.inc' %]
[% INCLUDE 'js-date-format.inc' %]
<script>
/* Set some variable needed in circulation.js */
var logged_in_user_borrowernumber = "[% logged_in_user.borrowernumber | html %]";

View File

@ -335,7 +335,6 @@
[% INCLUDE 'format_price.inc' %]
[% Asset.js("js/cashup_modal.js") | $raw %]
[% INCLUDE 'calendar.inc' %]
[% INCLUDE 'js-date-format.inc' %]
<script>
var sales_table = $("#sales").dataTable($.extend(true, {}, dataTablesDefaults, {
orderFixed: [ 0, 'asc'],

View File

@ -1589,36 +1589,7 @@
[% UNLESS ( patron || patron.borrowernumber || borrowers || noitems || nobiblio ) %]
[% IF ( PatronAutoComplete ) %]
$( "#search_patron_filter" ).autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function( event, ui ) {
document.location.href = '/cgi-bin/koha/reserve/request.pl?[% url_biblio_params | url %]&borrowernumber=' + ui.item.borrowernumber;
}
})
.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
return $( "<li></li>" )
.data( "ui-autocomplete-item", item )
.append(
"<a>"
+ ( item.surname ? item.surname.escapeHtml() : "" )
+ ", "
+ ( item.firstname ? item.firstname.escapeHtml() : "" )
+ ( item.middle_name ? " " + item.middle_name.escapeHtml() : "" )
+ " (" + ( item.cardnumber ? item.cardnumber.escapeHtml() : "" ) + ")"
+ " "
+ "<small>"
+ ( item.address ? item.address.escapeHtml() : "" )
+ " "
+ ( item.city ? item.city.escapeHtml() : "" )
+ " "
+ ( item.zipcode ? item.zipcode.escapeHtml() : "" )
+ " "
+ ( item.country ? item.country.escapeHtml() : "" )
+ "</small>"
+ "</a>" )
.appendTo( ul );
};
patron_autocomplete($("#search_patron_filter"), { 'link-to': 'reserve', 'url-params': '[% url_biblio_params | url %]' });
[% END %]
[% END %]

View File

@ -149,7 +149,6 @@
[% MACRO jsinclude BLOCK %]
[% Asset.js("js/tools-menu.js") | $raw %]
[% INCLUDE 'js-date-format.inc' %]
[% INCLUDE 'datatables.inc' %]
<script>

View File

@ -1,8 +1,8 @@
$.ajaxSetup({
review_ajax_params = {
url: "/cgi-bin/koha/tags/review.pl",
type: "POST",
dataType: "script"
});
};
var ok_count = 0;
var nok_count = 0;
@ -102,12 +102,12 @@ $(document).ready(function() {
var gettitle;
// window.alert(_("Click detected on ") + event.target + ": " + $(event.target).html);
if ($(event.target).is('.ok')) {
$.ajax({
$.ajax(Object.assign({}, review_ajax_params, {
data: {
ok: $(event.target).attr("title")
},
success: count_approve // success_approve
});
}));
$(event.target).next(".rej").prop('disabled', false).css("color","#000");
$(event.target).next(".rej").html("<i class='fa fa-remove' aria-hidden='false'></i> " + __("Reject"));
$(event.target).prop('disabled', true).css("color","#666");
@ -123,12 +123,12 @@ $(document).ready(function() {
}
}
if ($(event.target).is('.rej')) {
$.ajax({
$.ajax(Object.assign({}, review_ajax_params, {
data: {
rej: $(event.target).attr("title")
},
success: count_reject // success_reject
});
}));
$(event.target).prev(".ok").prop('disabled', false).css("color","#000");
$(event.target).prev(".ok").html("<i class='fa fa-check' aria-hidden='false'></i> " + __("Approve"));
$(event.target).prop('disabled', true).css("color","#666");
@ -146,12 +146,12 @@ $(document).ready(function() {
}
if ($(event.target).is('#test_button')) {
$(event.target).text( __("Testing...") ).prop('disabled', true);
$.ajax({
$.ajax(Object.assign({}, review_ajax_params, {
data: {
test: $('#test').attr("value")
},
success: success_test_call // success_reject
});
}));
return false; // cancel submit
}
});
@ -159,19 +159,9 @@ $(document).ready(function() {
if ((alerted +=1) <= 1){ window.alert( __("AJAX error (%s alert)").format(alerted) ); }
});
var reviewerField = $("#approver");
reviewerField.autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function( event, ui ) {
reviewerField.val( ui.item.borrowernumber );
patron_autocomplete($("#approver"), { 'on-select-callback': function( event, ui ) {
$("#approver").val( ui.item.patron_id );
return false;
}
})
.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
return $( "<li></li>" )
.data( "ui-autocomplete-item", item )
.append( "<a>" + item.surname + ", " + item.firstname + " (" + item.cardnumber + ") <small>" + item.address + " " + item.city + " " + item.zipcode + " " + item.country + "</small></a>" )
.appendTo( ul );
};
});
});

View File

@ -0,0 +1,109 @@
function patron_autocomplete(node, options) {
let link_to;
let url_params;
let on_select_callback;
if ( options ) {
if ( options['link-to'] ) {
link_to = options['link-to'];
}
if ( options['url-params'] ) {
url_params = options['url-params'];
}
if ( options['on-select-callback'] ) {
on_select_callback = options['on-select-callback'];
}
}
return node.autocomplete({
source: function( request, response ) {
let params = {
'_page': 1,
'_per_page': 10,
'q': JSON.stringify([
{"me.firstname": {"like": "%"+request.term+"%"}},
{"me.surname": {"like": "%"+request.term+"%"}},
{"me.cardnumber": {"like": request.term+"%"}}
]),
'_order_by': '+me.surname,+me.firstname',
};
$.ajax({
data: params,
type: 'GET',
url: '/api/v1/patrons',
headers: {
"x-koha-embed": "library"
},
success: function(data) {
return response(data);
},
error: function() {
alert( _("An error occurred. Check the logs") );
return response();
}
});
},
minLength: 3,
select: function( event, ui ) {
if ( ui.item.link ) {
window.location.href = ui.item.link;
} else if ( on_select_callback ) {
return on_select_callback(event, ui);
}
},
})
.data( "ui-autocomplete" )
._renderItem = function( ul, item ) {
if ( link_to ) {
item.link = link_to == 'circ'
? "/cgi-bin/koha/circ/circulation.pl"
: link_to == 'reserve'
? "/cgi-bin/koha/reserve/request.pl"
: "/cgi-bin/koha/members/moremember.pl";
item.link += ( url_params ? '?' + url_params + '&' : "?" ) + 'borrowernumber=' + item.patron_id;
} else {
item.link = null;
}
var cardnumber = "";
if( item.cardnumber != "" ){
// Display card number in parentheses if it exists
cardnumber = " (" + item.cardnumber + ") ";
}
if( item.branchcode == loggedInLibrary ){
loggedInClass = "ac-currentlibrary";
} else {
loggedInClass = "";
}
return $( "<li></li>" )
.addClass( loggedInClass )
.data( "ui-autocomplete-item", item )
.append(
""
+ ( item.link ? "<a href=\"" + item.link + "\">" : "<a>" )
+ ( item.surname ? item.surname.escapeHtml() : "" ) + ", "
+ ( item.firstname ? item.firstname.escapeHtml() : "" )
+ cardnumber.escapeHtml()
+ " <small>"
+ ( item.date_of_birth
? $date(item.date_of_birth)
+ "<span class=\"age_years\"> ("
+ $get_age(item.date_of_birth)
+ " "
+ __("years")
+ ")</span>,"
: ""
) + " "
+ ( item.address ? item.address.escapeHtml() : "" ) + " "
+ ( item.city ? item.city.escapeHtml() : "" ) + " "
+ ( item.zipcode ? item.zipcode.escapeHtml() : "" ) + " "
+ ( item.country ? item.country.escapeHtml() : "" ) + " "
+ ( !singleBranchMode
?
"<span class=\"ac-library\">"
+ item.library.name.escapeHtml()
+ "</span>"
: "" )
+ "</small>"
+ "</a>" )
.appendTo( ul );
};
}

View File

@ -144,32 +144,9 @@ $(document).ready(function(){
e.preventDefault();
$(".compare:checked").prop("checked", false).change();
});
var obj = $("#user").autocomplete({
source: "/cgi-bin/koha/circ/ysearch.pl",
minLength: 3,
select: function ( event, ui ) {
$("#user").val( ui.item.borrowernumber );
patron_autocomplete($("#user"), { 'on-select-callback': function ( event, ui ) {
$("#user").val( ui.item.patron_id );
return false;
}
}).data("ui-autocomplete");
if (obj) {
obj._renderItem = function (ul, item) {
var cardnumber = "";
if (item.cardnumber != "") {
// Display card number in parentheses if it exists
cardnumber = " (" + item.cardnumber + ") ";
}
var itemString = "<a href=\"#\">" + (item.surname ? item.surname.escapeHtml() : "") + ", " + (item.firstname ? item.firstname.escapeHtml() : "") + cardnumber.escapeHtml() + " <small>";
itemString += (item.address ? item.address.escapeHtml() : "") + " "
+ (item.city ? item.city.escapeHtml() : "") + " "
+ (item.zipcode ? item.zipcode.escapeHtml() : "") + " "
+ (item.country ? item.country.escapeHtml() : "")
+ "</small></a>";
return $("<li></li>")
.data("ui-autocomplete-item", item)
.append(itemString)
.appendTo(ul);
};
}
});
});