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