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