Dutch, Polish, Italian and German updates
[koha.git] / opac / opac-tags.pl
index 143488e..2b9dfd7 100755 (executable)
 
 TODO :: Description here
 
+C4::Scrubber is used to remove all markup content from the sumitted text.
+
 =cut
 
 use strict;
 use warnings;
-use C4::Auth;
+use CGI;
+use CGI::Cookie; # need to check cookies before having CGI parse the POST request
+
+use C4::Auth qw(:DEFAULT check_cookie_auth);
 use C4::Context;
 use C4::Debug;
-use C4::Output;
+use C4::Output 3.02 qw(:html :ajax pagination_bar);
 use C4::Dates qw(format_date);
-use CGI;
+use C4::Scrubber;
 use C4::Biblio;
-use C4::Tags qw(add_tag get_tags get_tag_rows remove_tag);
+use C4::Tags qw(add_tag get_approval_rows get_tag_rows remove_tag);
 
-my $query = new CGI;
 my %newtags = ();
 my @deltags = ();
 my %counts  = ();
 my @errors  = ();
 
+sub ajax_auth_cgi ($) {     # returns CGI object
+    my $needed_flags = shift;
+       my %cookies = fetch CGI::Cookie;
+       my $input = CGI->new;
+       my $sessid = $cookies{'CGISESSID'}->value || $input->param('CGISESSID');
+       my ($auth_status, $auth_sessid) = check_cookie_auth($sessid, $needed_flags);
+       $debug and
+       print STDERR "($auth_status, $auth_sessid) = check_cookie_auth($sessid," . Dumper($needed_flags) . ")\n";
+       if ($auth_status ne "ok") {
+               output_ajax_with_http_headers $input,
+               "window.alert('Your CGI session cookie ($sessid) is not current.  " .
+               "Please refresh the page and try again.');\n";
+               exit 0;
+       }
+       $debug and print STDERR "AJAX request: " . Dumper($input),
+               "\n(\$auth_status,\$auth_sessid) = ($auth_status,$auth_sessid)\n";
+       return $input;
+}
+
 # The trick here is to support multiple tags added to multiple bilbios in one POST.
+# The HTML might not use this, but it makes it more web-servicey from the start.
 # So the name of param has to have biblionumber built in.
 # For lack of anything more compelling, we just use "newtag[biblionumber]"
 # We split the value into tags at comma and semicolon
 
+my $is_ajax = is_ajax();
 my $openadds = C4::Context->preference('TagsModeration') ? 0 : 1;
-
+my $query = ($is_ajax) ? &ajax_auth_cgi({}) : CGI->new();
 unless (C4::Context->preference('TagsEnabled')) {
        push @errors, {+ tagsdisabled=>1 };
 } else {
@@ -67,47 +92,90 @@ unless (C4::Context->preference('TagsEnabled')) {
 }
 
 my $add_op = (scalar(keys %newtags) + scalar(@deltags)) ? 1 : 0;
-my ($template, $loggedinuser, $cookie) = get_template_and_user({
-       template_name   => "opac-tags.tmpl",
-       query           => $query,
-       type            => "opac",
-       authnotrequired => ($add_op ? 0 : 1),   # auth required to add tags
-       debug           => 1,
-});
+my ($template, $loggedinuser, $cookie);
+if ($is_ajax) {
+       $loggedinuser = C4::Context->userenv->{'number'};  # must occur AFTER auth
+       $debug and print STDERR "op: $loggedinuser\n";
+} else {
+       ($template, $loggedinuser, $cookie) = get_template_and_user({
+               template_name   => "opac-tags.tmpl",
+               query           => $query,
+               type            => "opac",
+               authnotrequired => ($add_op ? 0 : 1),   # auth required to add tags
+               debug           => 1,
+       });
+}
 
 if ($add_op) {
        unless ($loggedinuser) {
-               push @errors, {+'login' => $_ };
+               push @errors, {+'login' => 1 };
                %newtags=();    # zero out any attempted additions
                @deltags=();    # zero out any attempted deletions
        }
 }
-foreach my $biblionumber (keys %newtags) {
-       my @values = split /[;,]/, $newtags{$biblionumber};
-       foreach (@values) {
-               s/^\s*(.+)\s*$/$1/;
-               my $result;
-               if ($openadds) {
-                       $result = add_tag($biblionumber,$_,$loggedinuser,0); # pre-approved
-               } else {
-                       $result = add_tag($biblionumber,$_,$loggedinuser);
-               }
-               if ($result) {
-                       $counts{$biblionumber}++;
-               } else {
-                       warn "add_tag($biblionumber,$_,$loggedinuser...) returned $result";
+
+my $scrubber;
+my @newtags_keys = (keys %newtags);
+if (scalar @newtags_keys) {
+       $scrubber = C4::Scrubber->new();
+       foreach my $biblionumber (@newtags_keys) {
+               my @values = split /[;,]/, $newtags{$biblionumber};
+               foreach (@values) {
+                       s/^\s*(.+)\s*$/$1/;
+                       my $clean_tag = $scrubber->scrub($_);
+                       unless ($clean_tag eq $_) {
+                               if ($clean_tag =~ /\S/) {
+                                       push @errors, {scrubbed=>$clean_tag};
+                               } else {
+                                       push @errors, {scrubbed_all_bad=>1};
+                                       next;   # we don't add it if there's nothing left!
+                               }
+                       }
+                       my $result = ($openadds) ?
+                               add_tag($biblionumber,$clean_tag,$loggedinuser,$loggedinuser) : # pre-approved
+                               add_tag($biblionumber,$clean_tag,$loggedinuser)   ;
+                       if ($result) {
+                               $counts{$biblionumber}++;
+                       } else {
+                               push @errors, {failed_add_tag=>$clean_tag};
+                               $debug and warn "add_tag($biblionumber,$clean_tag,$loggedinuser...) returned bad result (" . (defined $result ? $result : 'UNDEF') .")";
+                       }
                }
        }
 }
 my $dels = 0;
 foreach (@deltags) {
-       remove_tag($_) and $dels++;
+       if (remove_tag($_,$loggedinuser)) {
+               $dels++;
+       } else {
+               push @errors, {failed_delete=>$_};
+       }
+}
+
+if ($is_ajax) {
+       my $sum = 0;
+       foreach (values %counts) {$sum += $_;}
+       my $js_reply = sprintf("response = {\n\tadded: %d,\n\tdeleted: %d,\n\terrors: %d",$sum,$dels,scalar @errors);
+       my $err_string = '';
+       if (scalar @errors) {
+               $err_string = ",\n\talerts: ["; # open response_function
+               my $i = 1;
+               foreach (@errors) {
+                       my $key = (keys %$_)[0];
+                       $err_string .= "\n\t\t KOHA.Tags.tag_message.$key(\"" . $_->{$key} . '")';
+                       if($i < scalar @errors){ $err_string .= ","; }
+                       $i++;
+               }
+               $err_string .= "\n\t]\n";       # close response_function
+       }
+       output_ajax_with_http_headers($query, "$js_reply\n$err_string};");
+       exit;
 }
 
 my $results = [];
 my $my_tags = [];
 
-if ($loggedinuser) {
+if ($loggedinuser and not $query->param('hidemytags')) {
        $my_tags = get_tag_rows({borrowernumber=>$loggedinuser});
        foreach (@$my_tags) {
                my $biblio = GetBiblioData($_->{biblionumber});
@@ -119,7 +187,10 @@ if ($loggedinuser) {
                $_->{date_created_display} = format_date($_->{date_created});
        }
 }
-$template->param(tagsview => 1,);
+
+$template->param(tagsview => 1,
+dateformat => C4::Context->preference("dateformat"));
+
 if ($add_op) {
        my $adds = 0;
        for (values %counts) {$adds += $_;}
@@ -129,26 +200,82 @@ if ($add_op) {
                deleted_count => $dels,
        );
 } else {
-       my ($arg,$limit,$tmpresults);
+       my ($arg,$limit);
        my $hardmax = 100;      # you might disagree what this value should be, but there definitely should be a max
        $limit = $query->param('limit') || $hardmax;
        ($limit =~ /^\d+$/ and $limit <= $hardmax) or $limit = $hardmax;
+       $template->param(limit => $limit);
+       my $arghash = {approved=>1, limit=>$limit, 'sort'=>'-weight_total'};
+       # ($openadds) or $arghash->{approved} = 1;
        if ($arg = $query->param('tag')) {
-               $tmpresults = get_tags({term => $arg, limit=>$limit, 'sort'=>'-weight'});
+               $arghash->{term} = $arg;
        } elsif ($arg = $query->param('biblionumber')) {
-               $tmpresults = get_tags({biblionumber => $arg, limit=>$limit, 'sort'=>'-weight'});
-       } else {
-               $tmpresults = get_tags({limit=>$limit, 'sort'=>'-weight'});
+               $arghash->{biblionumber} = $arg;
        }
-       my %uniq;
-       foreach (@$tmpresults) {
-               $uniq{$_->{term}}++ and next;
-               push @$results, $_;
+       $results = get_approval_rows($arghash);
+
+       my $count = scalar @$results;
+       $template->param(TAGLOOP_COUNT => $count);
+       # Here we make a halfhearted attempt to separate the tags into "strata" based on weight_total
+       # FIXME: code4lib probably has a better algorithm, iirc
+       # FIXME: when we get a better algorithm, move to C4
+       my $maxstrata = 5;
+       my $strata = 1;
+       my $previous = 0;
+       my $chunk = ($count/$maxstrata)/2;
+       my $total = 0;
+       my %cloud;
+       foreach (reverse @$results) {
+               my $current = $_->{weight_total};
+               $total++;
+               $cloud{$strata}++;
+               if ($current == $previous) {
+                       $_->{cloudweight} = $strata;
+                       next;
+               } 
+               if ($strata < $maxstrata and 
+                       ($cloud{$strata} > $chunk or 
+                       $count-$total <= $maxstrata-$strata)) {
+                       $strata++;
+               }
+               $_->{cloudweight} = $strata;
+               $previous = $current;
        }
 }
+$query->param('hidemytags') and $template->param(hidemytags => 1);
 (scalar @errors  ) and $template->param(ERRORS  => \@errors);
 (scalar @$results) and $template->param(TAGLOOP => $results);
 (scalar @$my_tags) and $template->param(MY_TAGS => $my_tags);
 
 output_html_with_http_headers $query, $cookie, $template->output;
+__END__
+
+=head1 EXAMPLE AJAX POST PARAMETERS
+
+CGISESSID      7c6288263107beb320f70f78fd767f56
+newtag396      fire,+<a+href="foobar.html">foobar</a>,+<img+src="foo.jpg"+/>
+
+So this request is trying to add 3 tags to biblio #396.  The CGISESSID is the same as that the browser would
+typically communicate using cookies.  If it is valid, the server will split the value of "newtag396" and 
+process the components for addition.  In this case the intended tags are:
+       fire
+       <a+href="foobar.html">foobar</a>
+       <img src="foo.jpg" />
+
+The first tag is acceptable.  The second will be scrubbed of markup, resulting in the tag "foobar".  
+The third tag is all markup, and will be rejected.  
+
+=head1 EXAMPLE AJAX JSON response
+
+response = {
+       added: 2,
+       deleted: 0,
+       errors: 2,
+       alerts: [
+                KOHA.Tags.tag_message.scrubbed("foobar"),
+                KOHA.Tags.tag_message.scrubbed_all_bad("1"),
+       ],
+};
+
+=cut