Bug 25709: Rename systempreference to NotesToHide
[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 $has_permission = $params->{has_permission};
18     my $dt_params = $params->{dt_params};
19
20     unless ( $searchmember ) {
21         $searchmember = $dt_params->{sSearch} // '';
22     }
23
24     # If branches are independent and user is not superlibrarian
25     # The search has to be only on the user branch
26     my $userenv = C4::Context->userenv;
27     my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
28     my @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
29
30     my ($sth, $query, $iTotalQuery, $iTotalRecords, $iTotalDisplayRecords);
31     my $dbh = C4::Context->dbh;
32
33     # Get the module_bit from a given permission code
34     if ( $has_permission ) {
35         ($has_permission->{module_bit}) = $dbh->selectrow_array(q|
36             SELECT bit FROM userflags WHERE flag=?
37         |, undef, $has_permission->{permission});
38     }
39
40     my (@where, @conditions);
41     # Get the iTotalRecords DataTable variable
42     $iTotalQuery = "SELECT COUNT(borrowers.borrowernumber) FROM borrowers";
43     if ( $has_permission ) {
44         $iTotalQuery .= ' LEFT JOIN user_permissions ON borrowers.borrowernumber=user_permissions.borrowernumber';
45         $iTotalQuery .= ' AND module_bit=? AND code=?';
46         push @conditions, $has_permission->{module_bit}, $has_permission->{subpermission};
47     }
48
49     if ( @restricted_branchcodes ) {
50         push @where, "borrowers.branchcode IN (" . join( ',', ('?') x @restricted_branchcodes ) . ")";
51         push @conditions, @restricted_branchcodes;
52     }
53     if ( $has_permission ) {
54         push @where, '( borrowers.flags = 1 OR borrowers.flags & (1 << ?) OR module_bit=? AND code=? )';
55         push @conditions, ($has_permission->{module_bit}) x 2, $has_permission->{subpermission};
56     }
57     $iTotalQuery .= ' WHERE ' . join ' AND ', @where if @where;
58     ($iTotalRecords) = $dbh->selectrow_array( $iTotalQuery, undef, @conditions );
59
60     # Do that after iTotalQuery!
61     if ( defined $branchcode and $branchcode ) {
62         @restricted_branchcodes = @restricted_branchcodes
63             ? ( grep { /^$branchcode$/ } @restricted_branchcodes )
64                 ? ($branchcode)
65                 : (undef) # Do not return any results
66             : ($branchcode);
67     }
68
69     if ( $searchfieldstype eq 'dateofbirth' ) {
70         # Return an empty list if the date of birth is not correctly formatted
71         $searchmember = eval { output_pref( { str => $searchmember, dateformat => 'iso', dateonly => 1 } ); };
72         if ( $@ or not $searchmember ) {
73             return {
74                 iTotalRecords        => $iTotalRecords,
75                 iTotalDisplayRecords => 0,
76                 patrons              => [],
77             };
78         }
79     }
80
81     my $select = "SELECT
82         borrowers.borrowernumber, borrowers.surname, borrowers.firstname,
83         borrowers.othernames,
84         borrowers.flags,
85         borrowers.streetnumber, borrowers.streettype, borrowers.address,
86         borrowers.address2, borrowers.city, borrowers.state, borrowers.zipcode,
87         borrowers.country, cardnumber, borrowers.dateexpiry,
88         borrowers.borrowernotes, borrowers.branchcode, borrowers.email,
89         borrowers.userid, borrowers.dateofbirth, borrowers.categorycode,
90         categories.description AS category_description, categories.category_type,
91         branches.branchname, borrowers.phone";
92     my $from = "FROM borrowers
93                 LEFT JOIN branches ON borrowers.branchcode = branches.branchcode
94                 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode";
95     my @where_args;
96     if ( $has_permission ) {
97         $from .= '
98                 LEFT JOIN user_permissions ON borrowers.borrowernumber=user_permissions.borrowernumber
99                 AND module_bit=? AND code=?';
100         push @where_args, $has_permission->{module_bit}, $has_permission->{subpermission};
101     }
102     my @where_strs;
103     if(defined $firstletter and $firstletter ne '') {
104         push @where_strs, "borrowers.surname LIKE ?";
105         push @where_args, "$firstletter%";
106     }
107     if(defined $categorycode and $categorycode ne '') {
108         push @where_strs, "borrowers.categorycode = ?";
109         push @where_args, $categorycode;
110     }
111     if(@restricted_branchcodes ) {
112         push @where_strs, "borrowers.branchcode IN (" . join( ',', ('?') x @restricted_branchcodes ) . ")";
113         push @where_args, @restricted_branchcodes;
114     }
115
116     my $searchfields = {
117         standard => C4::Context->preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid',
118         surname => 'surname',
119         email => 'email,emailpro,B_email',
120         borrowernumber => 'borrowernumber',
121         userid => 'userid',
122         phone => 'phone,phonepro,B_phone,altcontactphone,mobile',
123         address => 'streetnumber,streettype,address,address2,city,state,zipcode,country',
124         dateofbirth => 'dateofbirth',
125         sort1 => 'sort1',
126         sort2 => 'sort2',
127     };
128
129     # * is replaced with % for sql
130     $searchmember =~ s/\*/%/g;
131
132     # split into search terms
133     my @terms;
134     # consider coma as space
135     $searchmember =~ s/,/ /g;
136     if ( $searchtype eq 'contain' ) {
137        @terms = split / /, $searchmember;
138     } else {
139        @terms = ($searchmember);
140     }
141
142     foreach my $term (@terms) {
143         next unless $term;
144
145         my $term_dt = eval { local $SIG{__WARN__} = {}; output_pref( { str => $term, dateonly => 1, dateformat => 'sql' } ); };
146
147         if ($term_dt) {
148             $term = $term_dt;
149         } else {
150             $term .= '%'    # end with anything
151               if $term !~ /%$/;
152             $term = "%$term"    # begin with anythin unless start_with
153               if $searchtype eq 'contain' && $term !~ /^%/;
154         }
155
156         my @where_strs_or;
157         for my $searchfield ( split /,/, $searchfields->{$searchfieldstype} ) {
158             push @where_strs_or, "borrowers." . $dbh->quote_identifier($searchfield) . " LIKE ?";
159             push @where_args, $term;
160         }
161
162         if ( $searchfieldstype eq 'standard' and C4::Context->preference('ExtendedPatronAttributes') and $searchmember ) {
163             my $matching_borrowernumbers = C4::Members::Attributes::SearchIdMatchingAttribute($searchmember);
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>.