Bug 11944: use CGI( -utf8 ) everywhere
[koha.git] / opac / opac-tags.pl
1 #!/usr/bin/perl
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
21 =head1 NAME
22
23 opac-tags.pl
24
25 =head1 DESCRIPTION
26
27 TODO :: Description here
28
29 C4::Scrubber is used to remove all markup content from the sumitted text.
30
31 =cut
32
33 use strict;
34 use warnings;
35 use CGI qw ( -utf8 );
36 use CGI::Cookie; # need to check cookies before having CGI parse the POST request
37
38 use C4::Auth qw(:DEFAULT check_cookie_auth);
39 use C4::Context;
40 use C4::Debug;
41 use C4::Output qw(:html :ajax pagination_bar);
42 use C4::Scrubber;
43 use C4::Biblio;
44 use C4::Tags qw(add_tag get_approval_rows get_tag_rows remove_tag stratify_tags);
45 use C4::XSLT;
46
47 use Data::Dumper;
48
49 my %newtags = ();
50 my @deltags = ();
51 my %counts  = ();
52 my @errors  = ();
53 my $perBibResults = {};
54
55 # Indexes of @errors that do not apply to a particular biblionumber.
56 my @globalErrorIndexes = ();
57
58 sub ajax_auth_cgi {     # returns CGI object
59         my $needed_flags = shift;
60         my %cookies = fetch CGI::Cookie;
61         my $input = CGI->new;
62     my $sessid = $cookies{'CGISESSID'}->value;
63         my ($auth_status, $auth_sessid) = check_cookie_auth($sessid, $needed_flags);
64         $debug and
65         print STDERR "($auth_status, $auth_sessid) = check_cookie_auth($sessid," . Dumper($needed_flags) . ")\n";
66         if ($auth_status ne "ok") {
67                 output_with_http_headers $input, undef,
68                 "window.alert('Your CGI session cookie ($sessid) is not current.  " .
69                 "Please refresh the page and try again.');\n", 'js';
70                 exit 0;
71         }
72         $debug and print STDERR "AJAX request: " . Dumper($input),
73                 "\n(\$auth_status,\$auth_sessid) = ($auth_status,$auth_sessid)\n";
74         return $input;
75 }
76
77 # The trick here is to support multiple tags added to multiple bilbios in one POST.
78 # The HTML might not use this, but it makes it more web-servicey from the start.
79 # So the name of param has to have biblionumber built in.
80 # For lack of anything more compelling, we just use "newtag[biblionumber]"
81 # We split the value into tags at comma and semicolon
82
83 my $is_ajax = is_ajax();
84 my $openadds = C4::Context->preference('TagsModeration') ? 0 : 1;
85 my $query = ($is_ajax) ? &ajax_auth_cgi({}) : CGI->new();
86 unless (C4::Context->preference('TagsEnabled')) {
87         push @errors, {+ tagsdisabled=>1 };
88     push @globalErrorIndexes, $#errors;
89 } else {
90         foreach ($query->param) {
91                 if (/^newtag(.*)/) {
92                         my $biblionumber = $1;
93                         unless ($biblionumber =~ /^\d+$/) {
94                                 $debug and warn "$_ references non numerical biblionumber '$biblionumber'";
95                                 push @errors, {+'badparam' => $_ };
96                 push @globalErrorIndexes, $#errors;
97                                 next;
98                         }
99                         $newtags{$biblionumber} = $query->param($_);
100                 } elsif (/^del(\d+)$/) {
101                         push @deltags, $1;
102                 }
103         }
104 }
105
106 my $add_op = (scalar(keys %newtags) + scalar(@deltags)) ? 1 : 0;
107 my ($template, $loggedinuser, $cookie);
108 if ($is_ajax) {
109         $loggedinuser = C4::Context->userenv->{'number'};  # must occur AFTER auth
110         $debug and print STDERR "op: $loggedinuser\n";
111 } else {
112         ($template, $loggedinuser, $cookie) = get_template_and_user({
113         template_name   => "opac-tags.tt",
114         query           => $query,
115         type            => "opac",
116         authnotrequired => ($add_op ? 0 : 1), # auth required to add tags
117         debug           => 1,
118         });
119 }
120
121 if ($add_op) {
122         unless ($loggedinuser) {
123                 push @errors, {+'login' => 1 };
124         push @globalErrorIndexes, $#errors;
125                 %newtags=();    # zero out any attempted additions
126                 @deltags=();    # zero out any attempted deletions
127         }
128 }
129
130 my $scrubber;
131 my @newtags_keys = (keys %newtags);
132 if (scalar @newtags_keys) {
133         $scrubber = C4::Scrubber->new();
134         foreach my $biblionumber (@newtags_keys) {
135         my $bibResults = {adds=>0, errors=>[]};
136                 my @values = split /[;,]/, $newtags{$biblionumber};
137                 foreach (@values) {
138                         s/^\s*(.+)\s*$/$1/;
139                         my $clean_tag = $scrubber->scrub($_);
140                         unless ($clean_tag eq $_) {
141                                 if ($clean_tag =~ /\S/) {
142                                         push @errors, {scrubbed=>$clean_tag};
143                                         push @{$bibResults->{errors}}, {scrubbed=>$clean_tag};
144                                 } else {
145                                         push @errors, {scrubbed_all_bad=>1};
146                                         push @{$bibResults->{errors}}, {scrubbed_all_bad=>1};
147                                         next;   # we don't add it if there's nothing left!
148                                 }
149                         }
150                         my $result = ($openadds) ?
151                                 add_tag($biblionumber,$clean_tag,$loggedinuser,$loggedinuser) : # pre-approved
152                                 add_tag($biblionumber,$clean_tag,$loggedinuser)   ;
153                         if ($result) {
154                                 $counts{$biblionumber}++;
155                 $bibResults->{adds}++;
156                         } else {
157                                 push @errors, {failed_add_tag=>$clean_tag};
158                                 push @{$bibResults->{errors}}, {failed_add_tag=>$clean_tag};
159                                 $debug and warn "add_tag($biblionumber,$clean_tag,$loggedinuser...) returned bad result (" . (defined $result ? $result : 'UNDEF') .")";
160                         }
161                 }
162         $perBibResults->{$biblionumber} = $bibResults;
163         }
164 }
165 my $dels = 0;
166 foreach (@deltags) {
167         if (remove_tag($_,$loggedinuser)) {
168                 $dels++;
169         } else {
170                 push @errors, {failed_delete=>$_};
171         }
172 }
173
174 if ($is_ajax) {
175         my $sum = 0;
176         foreach (values %counts) {$sum += $_;}
177         my $js_reply = sprintf("response = {\n\tadded: %d,\n\tdeleted: %d,\n\terrors: %d",$sum,$dels,scalar @errors);
178
179     # If no add attempts were made, flag global errors.
180     if (@globalErrorIndexes) {
181         $js_reply .= ",\n\tglobal_errors: [";
182         my $first = 1;
183         foreach (@globalErrorIndexes) {
184             $js_reply .= "," unless $first;
185             $first = 0;
186             $js_reply .= "\n\t\t$_";
187         }
188         $js_reply .= "\n\t]";
189     }
190     
191         my $err_string = '';
192         if (scalar @errors) {
193                 $err_string = ",\n\talerts: ["; # open response_function
194                 my $i = 1;
195                 foreach (@errors) {
196                         my $key = (keys %$_)[0];
197                         $err_string .= "\n\t\t KOHA.Tags.tag_message.$key(\"" . $_->{$key} . '")';
198                         if($i < scalar @errors){ $err_string .= ","; }
199                         $i++;
200                 }
201                 $err_string .= "\n\t]\n";       # close response_function
202         }
203
204     # Add per-biblionumber results for use on results page
205     my $js_perbib = "";
206     for my $bib (keys %$perBibResults) {
207         my $bibResult = $perBibResults->{$bib};
208         my $js_bibres = ",\n\t$bib: {\n\t\tadded: $bibResult->{adds}";
209         $js_bibres .= ",\n\t\terrors: [";
210         my $i = 0;
211         foreach (@{$bibResult->{errors}}) {
212             $js_bibres .= "," if ($i);
213                         my $key = (keys %$_)[0];
214                         $js_bibres .= "\n\t\t\t KOHA.Tags.tag_message.$key(\"" . $_->{$key} . '")';
215             $i++;
216         }
217         $js_bibres .= "\n\t\t]\n\t}";
218         $js_perbib .= $js_bibres;
219     }
220
221         output_with_http_headers($query, undef, "$js_reply\n$err_string\n$js_perbib\n};", 'js');
222         exit;
223 }
224
225 my $results = [];
226 my $my_tags = [];
227
228 if ($loggedinuser) {
229         $my_tags = get_tag_rows({borrowernumber=>$loggedinuser});
230         foreach (@$my_tags) {
231                 my $biblio = GetBiblioData($_->{biblionumber});
232         my $record = &GetMarcBiblio( $_->{biblionumber} );
233         $_->{subtitle} = GetRecordValue( 'subtitle', $record, GetFrameworkCode( $_->{biblionumber} ) );
234         $_->{title} = $biblio->{title};
235         $_->{author} = $biblio->{author};
236         if (C4::Context->preference("OPACXSLTResultsDisplay")) {
237             $_->{XSLTBloc} = XSLTParse4Display($_->{biblionumber}, $record, "OPACXSLTResultsDisplay");
238         }
239                 my $date = $_->{date_created} || '';
240                 $date =~ /\s+(\d{2}\:\d{2}\:\d{2})/;
241                 $_->{time_created_display} = $1;
242         }
243 }
244
245 $template->param(tagsview => 1);
246
247 if ($add_op) {
248         my $adds = 0;
249         for (values %counts) {$adds += $_;}
250         $template->param(
251                 add_op => 1,
252                 added_count => $adds,
253                 deleted_count => $dels,
254         );
255 } else {
256         my ($arg,$limit,$mine);
257         my $hardmax = 100;      # you might disagree what this value should be, but there definitely should be a max
258         $limit = $query->param('limit') || $hardmax;
259     $mine =  $query->param('mine') || 0; # set if the patron want to see only his own tags.
260         ($limit =~ /^\d+$/ and $limit <= $hardmax) or $limit = $hardmax;
261         $template->param(limit => $limit);
262         my $arghash = {approved=>1, limit=>$limit, 'sort'=>'-weight_total'};
263     $arghash->{'borrowernumber'} = $loggedinuser if $mine;
264         # ($openadds) or $arghash->{approved} = 1;
265         if ($arg = $query->param('tag')) {
266                 $arghash->{term} = $arg;
267         } elsif ($arg = $query->param('biblionumber')) {
268                 $arghash->{biblionumber} = $arg;
269         }
270         $results = get_approval_rows($arghash);
271     stratify_tags(10, $results); # work out the differents sizes for things
272         my $count = scalar @$results;
273         $template->param(TAGLOOP_COUNT => $count, mine => $mine);
274 }
275 (scalar @errors  ) and $template->param(ERRORS  => \@errors);
276 my @orderedresult = sort { uc($a->{'term'}) cmp uc($b->{'term'}) } @$results;
277 (scalar @$results) and $template->param(TAGLOOP => \@orderedresult );
278 (scalar @$my_tags) and $template->param(MY_TAGS => $my_tags);
279
280 output_html_with_http_headers $query, $cookie, $template->output;
281 __END__
282
283 =head1 EXAMPLE AJAX POST PARAMETERS
284
285 CGISESSID       7c6288263107beb320f70f78fd767f56
286 newtag396       fire,+<a+href="foobar.html">foobar</a>,+<img+src="foo.jpg"+/>
287
288 So this request is trying to add 3 tags to biblio #396.  The CGISESSID is the same as that the browser would
289 typically communicate using cookies.  If it is valid, the server will split the value of "newtag396" and 
290 process the components for addition.  In this case the intended tags are:
291         fire
292         <a+href="foobar.html">foobar</a>
293         <img src="foo.jpg" />
294
295 The first tag is acceptable.  The second will be scrubbed of markup, resulting in the tag "foobar".  
296 The third tag is all markup, and will be rejected.  
297
298 =head1 EXAMPLE AJAX JSON response
299
300 response = {
301         added: 2,
302         deleted: 0,
303         errors: 2,
304         alerts: [
305                  KOHA.Tags.tag_message.scrubbed("foobar"),
306                  KOHA.Tags.tag_message.scrubbed_all_bad("1"),
307         ],
308 };
309
310 =cut
311