Bug 17374: Make use of fields from syspref 'DefaultPatronSearchFields' in patron...
[koha.git] / C4 / Utils / DataTables / Members.pm
1 package C4::Utils::DataTables::Members;
2
3 use Modern::Perl;
4 use C4::Context;
5 use C4::Utils::DataTables;
6 use Koha::DateUtils;
7 use C4::Members::Attributes qw(SearchIdMatchingAttribute );
8
9 sub search {
10     my ( $params ) = @_;
11     my $searchmember = $params->{searchmember};
12     my $firstletter = $params->{firstletter};
13     my $categorycode = $params->{categorycode};
14     my $branchcode = $params->{branchcode};
15     my $searchtype = $params->{searchtype} || 'contain';
16     my $searchfieldstype = $params->{searchfieldstype} || 'standard';
17     my $dt_params = $params->{dt_params};
18
19     unless ( $searchmember ) {
20         $searchmember = $dt_params->{sSearch} // '';
21     }
22
23     # If branches are independent and user is not superlibrarian
24     # The search has to be only on the user branch
25     my $userenv = C4::Context->userenv;
26     my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
27     my @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
28
29     my ($sth, $query, $iTotalQuery, $iTotalRecords, $iTotalDisplayRecords);
30     my $dbh = C4::Context->dbh;
31     # Get the iTotalRecords DataTable variable
32     $query = $iTotalQuery = "SELECT COUNT(borrowers.borrowernumber) FROM borrowers";
33     if ( @restricted_branchcodes ) {
34         $iTotalQuery .= " WHERE borrowers.branchcode IN (" . join( ',', ('?') x @restricted_branchcodes ) . ")";
35     }
36     ($iTotalRecords) = $dbh->selectrow_array( $iTotalQuery, undef, @restricted_branchcodes );
37
38     # Do that after iTotalQuery!
39     if ( defined $branchcode and $branchcode ) {
40         @restricted_branchcodes = @restricted_branchcodes
41             ? grep { $_ eq $branchcode } @restricted_branchcodes
42                 ? ($branchcode)
43                 : (undef) # Do not return any results
44             : ($branchcode);
45     }
46
47     if ( $searchfieldstype eq 'dateofbirth' ) {
48         # Return an empty list if the date of birth is not correctly formatted
49         $searchmember = eval { output_pref( { str => $searchmember, dateformat => 'iso', dateonly => 1 } ); };
50         if ( $@ or not $searchmember ) {
51             return {
52                 iTotalRecords        => $iTotalRecords,
53                 iTotalDisplayRecords => 0,
54                 patrons              => [],
55             };
56         }
57     }
58
59     my $select = "SELECT
60         borrowers.borrowernumber, borrowers.surname, borrowers.firstname,
61         borrowers.streetnumber, borrowers.streettype, borrowers.address,
62         borrowers.address2, borrowers.city, borrowers.state, borrowers.zipcode,
63         borrowers.country, cardnumber, borrowers.dateexpiry,
64         borrowers.borrowernotes, borrowers.branchcode, borrowers.email,
65         borrowers.userid, borrowers.dateofbirth, borrowers.categorycode,
66         categories.description AS category_description, categories.category_type,
67         branches.branchname, borrowers.phone";
68     my $from = "FROM borrowers
69         LEFT JOIN branches ON borrowers.branchcode = branches.branchcode
70         LEFT JOIN categories ON borrowers.categorycode = categories.categorycode";
71     my @where_args;
72     my @where_strs;
73     if(defined $firstletter and $firstletter ne '') {
74         push @where_strs, "borrowers.surname LIKE ?";
75         push @where_args, "$firstletter%";
76     }
77     if(defined $categorycode and $categorycode ne '') {
78         push @where_strs, "borrowers.categorycode = ?";
79         push @where_args, $categorycode;
80     }
81     if(@restricted_branchcodes ) {
82         push @where_strs, "borrowers.branchcode IN (" . join( ',', ('?') x @restricted_branchcodes ) . ")";
83         push @where_args, @restricted_branchcodes;
84     }
85
86     my $searchfields = {
87         standard => C4::Context->preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid',
88         email => 'email,emailpro,B_email',
89         borrowernumber => 'borrowernumber',
90         phone => 'phone,phonepro,B_phone,altcontactphone,mobile',
91         address => 'streetnumber,streettype,address,address2,city,state,zipcode,country',
92     };
93
94     # * is replaced with % for sql
95     $searchmember =~ s/\*/%/g;
96
97     # split into search terms
98     my @terms;
99     # consider coma as space
100     $searchmember =~ s/,/ /g;
101     if ( $searchtype eq 'contain' ) {
102        @terms = split / /, $searchmember;
103     } else {
104        @terms = ($searchmember);
105     }
106
107     foreach my $term (@terms) {
108         next unless $term;
109
110         my $term_dt = eval { local $SIG{__WARN__} = {}; output_pref( { str => $term, dateonly => 1, dateformat => 'sql' } ); };
111
112         if ($term_dt) {
113             $term = $term_dt;
114         } else {
115             $term .= '%'    # end with anything
116               if $term !~ /%$/;
117             $term = "%$term"    # begin with anythin unless start_with
118               if $searchtype eq 'contain' && $term !~ /^%/;
119         }
120
121         my @where_strs_or;
122         if ( defined $searchfields->{$searchfieldstype} ) {
123             for my $searchfield ( split /,/, $searchfields->{$searchfieldstype} ) {
124                 push @where_strs_or, "borrowers." . $dbh->quote_identifier($searchfield) . " LIKE ?";
125                 push @where_args, $term;
126             }
127         } else {
128             push @where_strs_or, "borrowers." . $dbh->quote_identifier($searchfieldstype) . " LIKE ?";
129             push @where_args, $term;
130         }
131
132
133         if ( $searchfieldstype eq 'standard' and C4::Context->preference('ExtendedPatronAttributes') and $searchmember ) {
134             my $matching_borrowernumbers = C4::Members::Attributes::SearchIdMatchingAttribute($searchmember);
135
136             for my $borrowernumber ( @$matching_borrowernumbers ) {
137                 push @where_strs_or, "borrowers.borrowernumber = ?";
138                 push @where_args, $borrowernumber;
139             }
140         }
141
142         push @where_strs, '('. join (' OR ', @where_strs_or) . ')'
143             if @where_strs_or;
144     }
145
146     my $where;
147     $where = " WHERE " . join (" AND ", @where_strs) if @where_strs;
148     my $orderby = dt_build_orderby($dt_params);
149
150     my $limit;
151     # If iDisplayLength == -1, we want to display all patrons
152     if ( !$dt_params->{iDisplayLength} || $dt_params->{iDisplayLength} > -1 ) {
153         # In order to avoid sql injection
154         $dt_params->{iDisplayStart} =~ s/\D//g if defined($dt_params->{iDisplayStart});
155         $dt_params->{iDisplayLength} =~ s/\D//g if defined($dt_params->{iDisplayLength});
156         $dt_params->{iDisplayStart} //= 0;
157         $dt_params->{iDisplayLength} //= 20;
158         $limit = "LIMIT $dt_params->{iDisplayStart},$dt_params->{iDisplayLength}";
159     }
160
161     $query = join(
162         " ",
163         ($select ? $select : ""),
164         ($from ? $from : ""),
165         ($where ? $where : ""),
166         ($orderby ? $orderby : ""),
167         ($limit ? $limit : "")
168     );
169     $sth = $dbh->prepare($query);
170     $sth->execute(@where_args);
171     my $patrons = $sth->fetchall_arrayref({});
172
173     # Get the iTotalDisplayRecords DataTable variable
174     $query = "SELECT COUNT(borrowers.borrowernumber) " . $from . ($where ? $where : "");
175     $sth = $dbh->prepare($query);
176     $sth->execute(@where_args);
177     ($iTotalDisplayRecords) = $sth->fetchrow_array;
178
179     # Get some information on patrons
180     foreach my $patron (@$patrons) {
181         my $patron_object = Koha::Patrons->find( $patron->{borrowernumber} );
182         $patron->{overdues} = $patron_object->get_overdues->count;
183         $patron->{issues} = $patron_object->checkouts->count;
184         my $balance = $patron_object->account->balance;
185         # FIXME Should be formatted from the template
186         $patron->{fines} = sprintf("%.2f", $balance);
187
188         if( $patron->{dateexpiry} ) {
189             # FIXME We should not format the date here, do it in template-side instead
190             $patron->{dateexpiry} = output_pref( { dt => scalar dt_from_string( $patron->{dateexpiry}, 'iso'), dateonly => 1} );
191         } else {
192             $patron->{dateexpiry} = '';
193         }
194     }
195
196     return {
197         iTotalRecords => $iTotalRecords,
198         iTotalDisplayRecords => $iTotalDisplayRecords,
199         patrons => $patrons
200     }
201 }
202
203 1;
204 __END__
205
206 =head1 NAME
207
208 C4::Utils::DataTables::Members - module for using DataTables with patrons
209
210 =head1 SYNOPSIS
211
212 This module provides (one for the moment) routines used by the patrons search
213
214 =head2 FUNCTIONS
215
216 =head3 search
217
218     my $dt_infos = C4::Utils::DataTables::Members->search($params);
219
220 $params is a hashref with some keys:
221
222 =over 4
223
224 =item searchmember
225
226   String to search in the borrowers sql table
227
228 =item firstletter
229
230   Introduced to contain 1 letter but can contain more.
231   The search will done on the borrowers.surname field
232
233 =item categorycode
234
235   Search patrons with this categorycode
236
237 =item branchcode
238
239   Search patrons with this branchcode
240
241 =item searchtype
242
243   Can be 'start_with' or 'contain' (default value). Used for the searchmember parameter.
244
245 =item searchfieldstype
246
247   Can be 'standard' (default value), 'email', 'borrowernumber', 'phone', 'address' or 'dateofbirth', 'sort1', 'sort2'
248
249 =item dt_params
250
251   Is the reference of C4::Utils::DataTables::dt_get_params($input);
252
253 =cut
254
255 =back
256
257 =head1 LICENSE
258
259 This file is part of Koha.
260
261 Copyright 2013 BibLibre
262
263 Koha is free software; you can redistribute it and/or modify it
264 under the terms of the GNU General Public License as published by
265 the Free Software Foundation; either version 3 of the License, or
266 (at your option) any later version.
267
268 Koha is distributed in the hope that it will be useful, but
269 WITHOUT ANY WARRANTY; without even the implied warranty of
270 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
271 GNU General Public License for more details.
272
273 You should have received a copy of the GNU General Public License
274 along with Koha; if not, see <http://www.gnu.org/licenses>.