ffzg/recall_notices.pl: added --interval and --dedup
[koha.git] / opac / opac-tags.pl
index 2b9dfd7..272d896 100755 (executable)
@@ -4,21 +4,25 @@
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 
-=head1 
+=head1 NAME
+
+opac-tags.pl
+
+=head1 DESCRIPTION
 
 TODO :: Description here
 
@@ -26,37 +30,46 @@ C4::Scrubber is used to remove all markup content from the sumitted text.
 
 =cut
 
-use strict;
-use warnings;
-use CGI;
+use Modern::Perl;
+
+use CGI qw ( -utf8 );
 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 3.02 qw(:html :ajax pagination_bar);
-use C4::Dates qw(format_date);
+use C4::Output qw(:html :ajax pagination_bar);
 use C4::Scrubber;
 use C4::Biblio;
-use C4::Tags qw(add_tag get_approval_rows get_tag_rows remove_tag);
+use C4::Items qw(GetItemsInfo GetHiddenItemnumbers);
+use C4::Tags qw(add_tag get_approval_rows get_tag_rows remove_tag stratify_tags);
+use C4::XSLT;
+
+use Data::Dumper;
+
+use Koha::Biblios;
 
 my %newtags = ();
 my @deltags = ();
 my %counts  = ();
 my @errors  = ();
+my $perBibResults = {};
 
-sub ajax_auth_cgi ($) {     # returns CGI object
-    my $needed_flags = shift;
-       my %cookies = fetch CGI::Cookie;
+# Indexes of @errors that do not apply to a particular biblionumber.
+my @globalErrorIndexes = ();
+
+sub ajax_auth_cgi {     # returns CGI object
+       my $needed_flags = shift;
+    my %cookies = CGI::Cookie->fetch;
        my $input = CGI->new;
-       my $sessid = $cookies{'CGISESSID'}->value || $input->param('CGISESSID');
+    my $sessid = $cookies{'CGISESSID'}->value;
        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,
+               output_with_http_headers $input, undef,
                "window.alert('Your CGI session cookie ($sessid) is not current.  " .
-               "Please refresh the page and try again.');\n";
+               "Please refresh the page and try again.');\n", 'js';
                exit 0;
        }
        $debug and print STDERR "AJAX request: " . Dumper($input),
@@ -75,6 +88,7 @@ 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 };
+    push @globalErrorIndexes, $#errors;
 } else {
        foreach ($query->param) {
                if (/^newtag(.*)/) {
@@ -82,6 +96,7 @@ unless (C4::Context->preference('TagsEnabled')) {
                        unless ($biblionumber =~ /^\d+$/) {
                                $debug and warn "$_ references non numerical biblionumber '$biblionumber'";
                                push @errors, {+'badparam' => $_ };
+                push @globalErrorIndexes, $#errors;
                                next;
                        }
                        $newtags{$biblionumber} = $query->param($_);
@@ -98,17 +113,18 @@ if ($is_ajax) {
        $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,
+        template_name   => "opac-tags.tt",
+        query           => $query,
+        type            => "opac",
+        authnotrequired => ($add_op ? 0 : 1), # auth required to add tags
+        debug           => 1,
        });
 }
 
 if ($add_op) {
        unless ($loggedinuser) {
                push @errors, {+'login' => 1 };
+        push @globalErrorIndexes, $#errors;
                %newtags=();    # zero out any attempted additions
                @deltags=();    # zero out any attempted deletions
        }
@@ -119,6 +135,7 @@ my @newtags_keys = (keys %newtags);
 if (scalar @newtags_keys) {
        $scrubber = C4::Scrubber->new();
        foreach my $biblionumber (@newtags_keys) {
+        my $bibResults = {adds=>0, errors=>[]};
                my @values = split /[;,]/, $newtags{$biblionumber};
                foreach (@values) {
                        s/^\s*(.+)\s*$/$1/;
@@ -126,8 +143,10 @@ if (scalar @newtags_keys) {
                        unless ($clean_tag eq $_) {
                                if ($clean_tag =~ /\S/) {
                                        push @errors, {scrubbed=>$clean_tag};
+                                       push @{$bibResults->{errors}}, {scrubbed=>$clean_tag};
                                } else {
                                        push @errors, {scrubbed_all_bad=>1};
+                                       push @{$bibResults->{errors}}, {scrubbed_all_bad=>1};
                                        next;   # we don't add it if there's nothing left!
                                }
                        }
@@ -136,11 +155,14 @@ if (scalar @newtags_keys) {
                                add_tag($biblionumber,$clean_tag,$loggedinuser)   ;
                        if ($result) {
                                $counts{$biblionumber}++;
+                $bibResults->{adds}++;
                        } else {
                                push @errors, {failed_add_tag=>$clean_tag};
+                               push @{$bibResults->{errors}}, {failed_add_tag=>$clean_tag};
                                $debug and warn "add_tag($biblionumber,$clean_tag,$loggedinuser...) returned bad result (" . (defined $result ? $result : 'UNDEF') .")";
                        }
                }
+        $perBibResults->{$biblionumber} = $bibResults;
        }
 }
 my $dels = 0;
@@ -156,6 +178,19 @@ 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);
+
+    # If no add attempts were made, flag global errors.
+    if (@globalErrorIndexes) {
+        $js_reply .= ",\n\tglobal_errors: [";
+        my $first = 1;
+        foreach (@globalErrorIndexes) {
+            $js_reply .= "," unless $first;
+            $first = 0;
+            $js_reply .= "\n\t\t$_";
+        }
+        $js_reply .= "\n\t]";
+    }
+    
        my $err_string = '';
        if (scalar @errors) {
                $err_string = ",\n\talerts: ["; # open response_function
@@ -168,28 +203,83 @@ if ($is_ajax) {
                }
                $err_string .= "\n\t]\n";       # close response_function
        }
-       output_ajax_with_http_headers($query, "$js_reply\n$err_string};");
+
+    # Add per-biblionumber results for use on results page
+    my $js_perbib = "";
+    for my $bib (keys %$perBibResults) {
+        my $bibResult = $perBibResults->{$bib};
+        my $js_bibres = ",\n\t$bib: {\n\t\tadded: $bibResult->{adds}";
+        $js_bibres .= ",\n\t\terrors: [";
+        my $i = 0;
+        foreach (@{$bibResult->{errors}}) {
+            $js_bibres .= "," if ($i);
+                       my $key = (keys %$_)[0];
+                       $js_bibres .= "\n\t\t\t KOHA.Tags.tag_message.$key(\"" . $_->{$key} . '")';
+            $i++;
+        }
+        $js_bibres .= "\n\t\t]\n\t}";
+        $js_perbib .= $js_bibres;
+    }
+
+       output_with_http_headers($query, undef, "$js_reply\n$err_string\n$js_perbib\n};", 'js');
        exit;
 }
 
 my $results = [];
 my $my_tags = [];
+my $borcat  = q{};
 
-if ($loggedinuser and not $query->param('hidemytags')) {
-       $my_tags = get_tag_rows({borrowernumber=>$loggedinuser});
-       foreach (@$my_tags) {
-               my $biblio = GetBiblioData($_->{biblionumber});
-               $_->{bib_summary} = $biblio->{title}; 
-               ($biblio->{author}) and $_->{bib_summary} .= " by " . $biblio->{author};
-               my $date = $_->{date_created} || '';
-               $date =~ /\s+(\d{2}\:\d{2}\:\d{2})/;
-               $_->{time_created_display} = $1;
-               $_->{date_created_display} = format_date($_->{date_created});
-       }
+if ($loggedinuser) {
+    my $patron = Koha::Patrons->find( { borrowernumber => $loggedinuser } );
+    $borcat = $patron ? $patron->categorycode : $borcat;
+    my $should_hide = C4::Context->preference('OpacHiddenItems') // q{};
+    $should_hide = ( $should_hide =~ /\S/ ) ? 1 : 0;
+    $my_tags = get_tag_rows({borrowernumber=>$loggedinuser});
+    my $my_approved_tags = get_approval_rows({ approved => 1 });
+    foreach my $tag (@$my_tags) {
+        $tag->{visible} = 0;
+        my $biblio = Koha::Biblios->find( $tag->{biblionumber} );
+        my $record = &GetMarcBiblio({
+            biblionumber => $tag->{biblionumber},
+            embed_items  => 1,
+            opac         => 1,
+            borcat       => $borcat });
+        next unless $record;
+        my $hidden_items = undef;
+        my @hidden_itemnumbers;
+        my @all_items;
+        if ($should_hide) {
+            @all_items = GetItemsInfo( $tag->{biblionumber} );
+            @hidden_itemnumbers = GetHiddenItemnumbers({
+                items => \@all_items,
+                borcat => $borcat });
+            $hidden_items = \@hidden_itemnumbers;
+        }
+        next if ( $should_hide && scalar @all_items == scalar @hidden_itemnumbers );
+        $tag->{subtitle} = GetRecordValue( 'subtitle', $record, GetFrameworkCode( $tag->{biblionumber} ) );
+        $tag->{title} = $biblio->title;
+        $tag->{author} = $biblio->author;
+
+        my $xslfile = C4::Context->preference('OPACXSLTResultsDisplay');
+        my $lang   = $xslfile ? C4::Languages::getlanguage()  : undef;
+        my $sysxml = $xslfile ? C4::XSLT::get_xslt_sysprefs() : undef;
+
+        if ( $xslfile ) {
+            $tag->{XSLTBloc} = XSLTParse4Display(
+                    $tag->{ biblionumber }, $record, "OPACXSLTResultsDisplay",
+                    1, $hidden_items, $sysxml, $xslfile, $lang
+            );
+        }
+
+        my $date = $tag->{date_created} || '';
+        $date =~ /\s+(\d{2}\:\d{2}\:\d{2})/;
+        $tag->{time_created_display} = $1;
+        $tag->{approved} = ( grep { $_->{term} eq $tag->{term} and $_->{approved} } @$my_approved_tags );
+        $tag->{visible} = 1;
+    }
 }
 
-$template->param(tagsview => 1,
-dateformat => C4::Context->preference("dateformat"));
+$template->param(tagsview => 1);
 
 if ($add_op) {
        my $adds = 0;
@@ -200,12 +290,14 @@ if ($add_op) {
                deleted_count => $dels,
        );
 } else {
-       my ($arg,$limit);
+       my ($arg,$limit,$mine);
        my $hardmax = 100;      # you might disagree what this value should be, but there definitely should be a max
        $limit = $query->param('limit') || $hardmax;
+    $mine =  $query->param('mine') || 0; # set if the patron want to see only his own tags.
        ($limit =~ /^\d+$/ and $limit <= $hardmax) or $limit = $hardmax;
        $template->param(limit => $limit);
        my $arghash = {approved=>1, limit=>$limit, 'sort'=>'-weight_total'};
+    $arghash->{'borrowernumber'} = $loggedinuser if $mine;
        # ($openadds) or $arghash->{approved} = 1;
        if ($arg = $query->param('tag')) {
                $arghash->{term} = $arg;
@@ -213,38 +305,31 @@ if ($add_op) {
                $arghash->{biblionumber} = $arg;
        }
        $results = get_approval_rows($arghash);
-
+    my @filtered_results;
+    foreach my $my_tag (@$my_tags) {
+        if (grep { $_->{term} eq $my_tag->{term} } @$results) {
+            if (! $my_tag->{visible} ) {
+                my $check_biblio = GetMarcBiblio({
+                    biblionumber => $my_tag->{biblionumber},
+                    embed_items  => 1,
+                    opac         => 1,
+                    borcat       => $borcat });
+                if ($check_biblio) {
+                    push @filtered_results, $my_tag;
+                }
+            } else {
+                push @filtered_results, $my_tag;
+            }
+        }
+    }
+    $results = \@filtered_results;
+    stratify_tags(10, $results); # work out the differents sizes for things
        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;
-       }
+       $template->param(TAGLOOP_COUNT => $count, mine => $mine);
 }
-$query->param('hidemytags') and $template->param(hidemytags => 1);
 (scalar @errors  ) and $template->param(ERRORS  => \@errors);
-(scalar @$results) and $template->param(TAGLOOP => $results);
+my @orderedresult = sort { uc($a->{'term'}) cmp uc($b->{'term'}) } @$results;
+(scalar @$results) and $template->param(TAGLOOP => \@orderedresult );
 (scalar @$my_tags) and $template->param(MY_TAGS => $my_tags);
 
 output_html_with_http_headers $query, $cookie, $template->output;