Bug 36520: Add tests
[koha.git] / C4 / Search / History.pm
1 package C4::Search::History;
2
3 use Modern::Perl;
4
5 use C4::Auth qw( get_session );
6 use C4::Context;
7 use Koha::DateUtils qw( dt_from_string output_pref );
8
9 use JSON qw( decode_json encode_json );
10 use URI::Escape qw( uri_escape uri_unescape );
11
12 sub add {
13     my ($params)   = @_;
14     my $userid     = $params->{userid};
15     my $sessionid  = $params->{sessionid};
16     my $query_desc = $params->{query_desc};
17     my $query_cgi  = $params->{query_cgi};
18     my $total      = $params->{total} // 0;
19     my $type       = $params->{type} || 'biblio';
20     my $time       = $params->{time};
21
22     my $dbh = C4::Context->dbh;
23
24     # Add the request the user just made
25     my $query = q{
26         INSERT INTO search_history(
27             userid, sessionid, query_desc, query_cgi, type, total} . ( $time ? ', time' : '' ) . q{
28         ) VALUES(
29             ?, ?, ?, ?, ?, ?} . ( $time ? ', ?' : '' ) . q{
30         )
31     };
32     my $sth = $dbh->prepare($query);
33     $sth->execute( $userid, $sessionid, $query_desc, $query_cgi, $type,
34         $total, ( $time ? $time : () ) );
35 }
36
37 sub add_to_session {
38     my ($params) = @_;
39     my $cgi = $params->{cgi};
40     my $query_desc = $params->{query_desc} || "unknown";
41     my $query_cgi  = $params->{query_cgi} || "unknown";
42     my $total      = $params->{total};
43     my $type       = $params->{type}                              || 'biblio';
44
45     # To a cookie (the user is not logged in)
46     my $now = dt_from_string;
47     my $id = $now->year . $now->month . $now->day . $now->hour . $now->minute . $now->second . int(rand(100));
48     my @recent_searches = get_from_session( { cgi => $cgi } );
49     push @recent_searches, {
50         query_desc => $query_desc,
51         query_cgi  => $query_cgi,
52         total      => "$total",
53         type       => $type,
54         # FIXME We shouldn't store the formatted date
55         time       => output_pref( { dt => $now, dateformat => 'iso', timeformat => '24hr' } ),
56         id         => $id,
57     };
58
59     shift @recent_searches if ( @recent_searches > 15 );
60     set_to_session( { cgi => $cgi, search_history => \@recent_searches } );
61 }
62
63 sub delete {
64     my ($params)  = @_;
65     my $id        = $params->{id};
66     my $userid    = $params->{userid};
67     my $sessionid = $params->{sessionid};
68     my $type      = $params->{type}     || q{};
69     my $previous  = $params->{previous} || 0;
70     my $interval  = $params->{interval} || 0;
71
72     unless ( ref( $id ) ) {
73         $id = $id ? [ $id ] : [];
74     }
75
76     unless ( $userid or @$id or $interval ) {
77         warn "ERROR: userid, id or interval is required for history deletion";
78         return;
79     }
80
81     my $dbh   = C4::Context->dbh;
82     my $query = q{
83         DELETE FROM search_history
84         WHERE 1
85     };
86
87     $query .= q{ AND id IN ( } . join( q{,}, (q{?}) x @$id ) . q{ )}
88         if @$id;
89
90     $query .= q{
91         AND userid = ?
92     } if $userid;
93
94     if ($sessionid) {
95         $query .=
96           $previous
97           ? q{ AND sessionid != ?}
98           : q{ AND sessionid = ?};
99     }
100
101     $query .= q{ AND type = ?}
102       if $type;
103
104     # FIXME DATE_SUB is a Mysql-ism. Postgres uses: datefield - INTERVAL '6 months'
105     $query .= q{ AND time < DATE_SUB( NOW(), INTERVAL ? DAY )}
106         if $interval;
107
108     $dbh->do(
109         $query, {},
110         ( @$id ? ( @$id ) : () ),
111         ( $userid ? $userid : () ),
112         ( $sessionid ? $sessionid : () ),
113         ( $type      ? $type      : () ),
114         ( $interval  ? $interval  : () ),
115     );
116 }
117
118 sub delete_from_cookie {
119     my ($params) = @_;
120     my $cookie   = $params->{cookie};
121     my $id       = $params->{id};
122
123     return unless $cookie;
124
125     unless ( ref( $id ) ) {
126         $id = $id ? [ $id ] : [];
127     }
128     return unless @$id;
129
130     my @searches;
131     if ( $cookie ){
132         $cookie = uri_unescape( $cookie );
133         if (decode_json( $cookie )) {
134             @searches = @{decode_json( $cookie )}
135         }
136     }
137
138     @searches = map {
139         my $search = $_;
140         ( grep { $_ != $search->{id} } @$id ) ? $search : ()
141     } @searches;
142
143     return uri_escape( encode_json( \@searches ) );
144
145 }
146
147 sub get {
148     my ($params)  = @_;
149     my $id        = $params->{id};
150     my $userid    = $params->{userid};
151     my $sessionid = $params->{sessionid};
152     my $type      = $params->{type};
153     my $previous  = $params->{previous};
154
155     unless ( ref( $id ) ) {
156         $id = $id ? [ $id ] : [];
157     }
158
159     unless ( $userid or @$id ) {
160         warn "ERROR: userid is required for history search";
161         return;
162     }
163
164     my $query = q{
165         SELECT *
166         FROM search_history
167         WHERE 1
168     };
169
170     $query .= q{ AND id IN ( } . join( q{,}, (q{?}) x @$id ) . q{ )}
171         if @$id;
172
173     $query .= q{
174         AND userid = ?
175     } if $userid;
176
177     if ($sessionid) {
178         $query .=
179           $previous
180           ? q{ AND sessionid != ?}
181           : q{ AND sessionid = ?};
182     }
183
184     $query .= q{ AND type = ?}
185       if $type;
186
187     my $dbh = C4::Context->dbh;
188     my $sth = $dbh->prepare($query);
189     $sth->execute(
190         ( @$id ? ( @$id ) : () ),
191         ( $userid ? $userid : () ),
192         ( $sessionid ? $sessionid : () ),
193         ( $type      ? $type      : () )
194     );
195     return $sth->fetchall_arrayref( {} );
196 }
197
198 sub get_from_session {
199     my ($params)  = @_;
200     my $cgi       = $params->{cgi};
201     my $sessionID = $cgi->cookie('CGISESSID');
202     return () unless $sessionID;
203     my $session = C4::Auth::get_session($sessionID);
204     return () unless $session and $session->param('search_history');
205     my $obj =
206       eval { decode_json( uri_unescape( $session->param('search_history') ) ) };
207     return () unless defined $obj;
208     return () unless ref $obj eq 'ARRAY';
209     return @{$obj};
210 }
211
212 sub set_to_session {
213     my ($params)       = @_;
214     my $cgi            = $params->{cgi};
215     my $search_history = $params->{search_history};
216     my $sessionID      = $cgi->cookie('CGISESSID');
217     return () unless $sessionID;
218     my $session = C4::Auth::get_session($sessionID);
219     return () unless $session;
220     $session->param( 'search_history',
221         uri_escape( encode_json($search_history) ) );
222     $session->flush;
223 }
224
225 1;
226
227 __END__
228
229 =pod
230
231 =head1 NAME
232
233 C4::Search::History - Manage search history
234
235 =head1 DESCRIPTION
236
237 This module provides some routines for the search history management.
238 It deals with session or database.
239
240 =head1 ROUTINES
241
242 =head2 add
243
244     C4::Search::History::add({
245         userid => $userid,
246         sessionid => $cgi->cookie("CGIESSID"),
247         query_desc => $query_desc,
248         query_cgi => $query_cgi,
249         total => $total,
250         type => $type,
251     });
252
253 type is "biblio" or "authority".
254
255 Add a new search to the user's history.
256
257 =head2 add_to_session
258
259     my $value = C4::Search::History::add_to_session({
260         cgi => $cgi,
261         query_desc => $query_desc,
262         query_cgi => $query_cgi,
263         total => $total,
264         type => $type,
265     });
266
267 Add a search to the session. The number of searches to keep is hardcoded to 15.
268
269 =head2 delete
270
271     C4::Search::History::delete({
272         userid => $loggedinuser,
273         sessionid => $sessionid,
274         type => $type,
275         previous => $previous
276     });
277
278 Delete searches in the database.
279 If the sessionid is missing all searches for all sessions will be deleted.
280 It is possible to delete searches for current session or all previous sessions using the previous flag.
281 If the type ("biblio" or "authority") is missing, all type will be deleted.
282 To delete *all* searches for a given userid, just pass a userid.
283
284 =head2 get
285
286     my $searches C4::Search::History::get({
287         userid => $userid,
288         sessionsid => $sessionid,
289         type => $type,
290         previous => $previous
291     });
292
293 Return a list of searches for a given userid.
294 If a sessionid is given, searches are limited to the matching session.
295 type and previous follow the same behavior as the delete routine.
296
297 =head2 get_from_session
298
299     my $searches = C4::Search::History::get_from_session({
300         cgi => $cgi
301     });
302
303 Return all searches present for the given session.
304
305 =head2 set_to_session
306
307     C4::Search::History::set_to_session({
308         cgi => $cgi,
309         search_history => $search_history
310     });
311
312 Store searches into the session.
313
314 =head1 AUTHORS
315
316 Jonathan Druart <jonathan.druart@biblibre.com>
317
318 =head1 LICENSE
319
320 This file is part of Koha.
321
322 Copyright 2013 BibLibre SARL
323
324 Koha is free software; you can redistribute it and/or modify it
325 under the terms of the GNU General Public License as published by
326 the Free Software Foundation; either version 3 of the License, or
327 (at your option) any later version.
328
329 Koha is distributed in the hope that it will be useful, but
330 WITHOUT ANY WARRANTY; without even the implied warranty of
331 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
332 GNU General Public License for more details.
333
334 You should have received a copy of the GNU General Public License
335 along with Koha; if not, see <http://www.gnu.org/licenses>.