Bug 14385: Extend OpacHiddenItems to allow specifying exempt borrower categories
authorChris Cormack <chrisc@catalyst.net.nz>
Wed, 5 Nov 2014 21:34:18 +0000 (10:34 +1300)
committerNick Clemens <nick@bywatersolutions.com>
Fri, 2 Nov 2018 10:33:09 +0000 (10:33 +0000)
Edit: Fixing merge conflicts in
 - t/db_dependent/Items.t
 - t/db_dependent/Search.t
 - C4/Search.pm

Changes the API for calling GetHiddenItems and all the places in the code that call it. This is to allow borrower categories to be passed in.
Adds an OpacHiddenItemsExceptions syspref to allow certain borrower categories to be able to see items, even if they are marked hidden by OpacHiddenItems

To test:

1) Make two borrowers, one in a category that should see everything (ie Adult), and another in a category that should only see certain things (ie Adult - exceptions)
2) Add the borrower that can see everything (the Adult) to OpacHiddenItemsExceptions
3) To the OpacHiddenItems syspref, add an item type (ensure that you have some records that fall under this type in your library).
4) Log in as the borrower that should only see certain things (Adult - exception)
5) Do a search, filtered to show records which are the item type that you specified in the OpacHiddenItems syspref. No records should show for this borrower as this item type is hidden to them.
6) Log in as the borrower that should see everything (Adult)
7) Do the same search. There should be results from this search, as this borrower category has been specified as an exception to the hidden items

Signed-off-by: Claire Gravely <c.gravely@arts.ac.uk>

Signed-off-by: Mark Tompsett <mtompset@hotmail.com>

Signed-off-by: Aleisha Amohia <aleishaamohia@hotmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

C4/Items.pm
C4/Search.pm
catalogue/search.pl
cataloguing/addbooks.pl
koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
opac/opac-ISBDdetail.pl
opac/opac-MARCdetail.pl
opac/opac-detail.pl
opac/opac-search.pl
t/db_dependent/Items.t
t/db_dependent/Search.t

index f81e0ca..4dd3bad 100644 (file)
@@ -1278,16 +1278,25 @@ sub get_hostitemnumbers_of {
 
 =head2 GetHiddenItemnumbers
 
-    my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
+    my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
 
 Given a list of items it checks which should be hidden from the OPAC given
 the current configuration. Returns a list of itemnumbers corresponding to
-those that should be hidden.
+those that should be hidden. Optionally takes a borcat parameter for certain borrower types
+to be excluded
 
 =cut
 
 sub GetHiddenItemnumbers {
-    my (@items) = @_;
+    my $params = shift;
+    my $items = $params->{items};
+    if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
+        foreach my $except (split(/\|/, $exceptions)){
+            if ($params->{'borcat'} eq $except){
+                return; # we dont hide anything for this borrower category
+            }
+        }
+    }
     my @resultitems;
 
     my $yaml = C4::Context->preference('OpacHiddenItems');
@@ -1304,7 +1313,7 @@ sub GetHiddenItemnumbers {
     my $dbh = C4::Context->dbh;
 
     # For each item
-    foreach my $item (@items) {
+    foreach my $item (@$items) {
 
         # We check each rule
         foreach my $field (keys %$hidingrules) {
index 46fafcd..4009c18 100644 (file)
@@ -1856,9 +1856,9 @@ sub searchResults {
 
     require C4::Items;
 
-    $search_context = 'opac' if !$search_context || $search_context ne 'intranet';
+    $search_context->{'interface'} = 'opac' if !$search_context->{'interface'} || $search_context->{'interface'} ne 'intranet';
     my ($is_opac, $hidelostitems);
-    if ($search_context eq 'opac') {
+    if ($search_context->{'interface'} eq 'opac') {
         $hidelostitems = C4::Context->preference('hidelostitems');
         $is_opac       = 1;
     }
@@ -1948,6 +1948,7 @@ sub searchResults {
         # add imageurl to itemtype if there is one
         $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
 
+        $oldbiblio->{'authorised_value_images'}  = ($search_context->{'interface'} eq 'opac' && C4::Context->preference('AuthorisedValueImages')) || ($search_context->{'interface'} eq 'intranet' && C4::Context->preference('StaffAuthorisedValueImages')) ? C4::Items::get_authorised_value_images( C4::Biblio::get_biblio_authorised_values( $oldbiblio->{'biblionumber'}, $marcrecord ) ) : [];
                $oldbiblio->{normalized_upc}  = GetNormalizedUPC(       $marcrecord,$marcflavour);
                $oldbiblio->{normalized_ean}  = GetNormalizedEAN(       $marcrecord,$marcflavour);
                $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
@@ -2079,7 +2080,7 @@ sub searchResults {
                     next;
                 }
                 # hidden based on OpacHiddenItems syspref
-                my @hi = C4::Items::GetHiddenItemnumbers($item);
+                my @hi = C4::Items::GetHiddenItemnumbers({ items=> [ $item ], borcat => $search_context->{category} });
                 if (scalar @hi) {
                     push @hiddenitems, @hi;
                     $hideatopac_count++;
@@ -2208,7 +2209,7 @@ sub searchResults {
                                        $other_items->{$key}->{count}++ if $item->{$hbranch};
                                        $other_items->{$key}->{location} = $shelflocations->{ $item->{location} };
                                        $other_items->{$key}->{description} = $item->{description};
-                                       $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
+                    $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype} }->{imageurl} );
                 }
                 # item is available
                 else {
@@ -2219,7 +2220,7 @@ sub searchResults {
                        $available_items->{$prefix}->{$_} = $item->{$_};
                                        }
                                        $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
-                                       $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
+                    $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype} }->{imageurl} );
                 }
             }
         }    # notforloan, item level and biblioitem level
@@ -2245,9 +2246,9 @@ sub searchResults {
 
         # XSLT processing of some stuff
         # we fetched the sysprefs already before the loop through all retrieved record!
+        my $interface = $search_context->{'interface'} eq 'opac' ? 'OPAC' : '';
         if (!$scan && $xslfile) {
             $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $xslsyspref, 1, \@hiddenitems, $sysxml, $xslfile, $lang);
-        # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs
         }
 
         # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either
index 842ada5..d9961a0 100755 (executable)
@@ -541,7 +541,7 @@ for (my $i=0;$i<@servers;$i++) {
     if ($server =~/biblioserver/) { # this is the local bibliographic server
         my $hits = $results_hashref->{$server}->{"hits"} // 0;
         my $page = $cgi->param('page') || 0;
-        my @newresults = searchResults('intranet', $query_desc, $hits, $results_per_page, $offset, $scan,
+        my @newresults = searchResults({ 'interface' => 'intranet' }, $query_desc, $hits, $results_per_page, $offset, $scan,
                                        $results_hashref->{$server}->{"RECORDS"});
         $total = $total + $hits;
 
index e374cd3..2082913 100755 (executable)
@@ -93,7 +93,10 @@ if ($query) {
     # format output
     # SimpleSearch() give the results per page we want, so 0 offet here
     my $total = @{$marcresults};
-    my @newresults = searchResults( 'intranet', $query, $total, $results_per_page, 0, 0, $marcresults );
+    my @newresults = searchResults( {'interface' => 'intranet'}, $query, $total, $results_per_page, 0, 0, $marcresults );
+    foreach my $line (@newresults) {
+        if ( not exists $line->{'size'} ) { $line->{'size'} = "" }
+    }
     $template->param(
         total          => $total_hits,
         query          => $query,
index a974f0c..29773e5 100644 (file)
@@ -590,6 +590,9 @@ OPAC:
               class: code
             - Define custom rules to hide specific items from search and view on the OPAC. How to write these rules is documented on the <a href="http://wiki.koha-community.org/wiki/OpacHiddenItems" target="_blank">Koha wiki</a>.
         -
+            - pref: OpacHiddenItemsExceptions
+            - List of borrower categories, separated by |, that can see items otherwise hidden by <tt>OpacHiddenItems</tt>
+        -
             - pref: OpacAllowPublicListCreation
               default: 1
               choices:
index fe69564..d6aba31 100755 (executable)
@@ -85,7 +85,15 @@ my $marcflavour      = C4::Context->preference("marcflavour");
 
 my @items = GetItemsInfo($biblionumber);
 if (scalar @items >= 1) {
-    my @hiddenitems = GetHiddenItemnumbers(@items);
+    my $borcat;
+    if ( C4::Context->preference('OpacHiddenItemsExceptions') ) {
+
+        # we need to fetch the borrower info here, so we can pass the category
+        my $borrower = GetMember( borrowernumber => $borrowernumber );
+        $borcat = $borrower->{categorycode};
+    }
+
+    my @hiddenitems = GetHiddenItemnumbers( { items => \@items, borcat => $borcat });
 
     if (scalar @hiddenitems == scalar @items ) {
         print $query->redirect("/cgi-bin/koha/errors/404.pl"); # escape early
index d438036..533c9a5 100755 (executable)
@@ -85,7 +85,14 @@ if ( ! $record ) {
 my @all_items = GetItemsInfo($biblionumber);
 my @items2hide;
 if (scalar @all_items >= 1) {
-    push @items2hide, GetHiddenItemnumbers(@all_items);
+    my $borcat;
+    if ( C4::Context->preference('OpacHiddenItemsExceptions') ) {
+
+        # we need to fetch the borrower info here, so we can pass the category
+        my $borrower = GetMember( borrowernumber => $borrowernumber );
+        $borcat = $borrower->{categorycode};
+    }
+    push @items2hide, GetHiddenItemnumbers({ items => \@all_items, borcat => $botcat });
 
     if (scalar @items2hide == scalar @all_items ) {
         print $query->redirect("/cgi-bin/koha/errors/404.pl");
index cc735ee..3b44468 100755 (executable)
@@ -83,8 +83,17 @@ $biblionumber = int($biblionumber);
 
 my @all_items = GetItemsInfo($biblionumber);
 my @hiddenitems;
-if (scalar @all_items >= 1) {
-    push @hiddenitems, GetHiddenItemnumbers(@all_items);
+if ( scalar @all_items >= 1 ) {
+    my $borcat;
+    if ( C4::Context->preference('OpacHiddenItemsExceptions') ) {
+
+        # we need to fetch the borrower info here, so we can pass the category
+        my $borrower = GetMember( borrowernumber => $borrowernumber );
+        $borcat = $borrower->{categorycode};
+    }
+
+    push @hiddenitems,
+      GetHiddenItemnumbers( { items => \@all_items, borcat => $borcat } );
 
     if (scalar @hiddenitems == scalar @all_items ) {
         print $query->redirect("/cgi-bin/koha/errors/404.pl"); # escape early
@@ -233,7 +242,7 @@ if ($session->param('busc')) {
         for (my $i=0;$i<@servers;$i++) {
             my $server = $servers[$i];
             $hits = $results_hashref->{$server}->{"hits"};
-            @newresults = searchResults('opac', '', $hits, $results_per_page, $offset, $arrParamsBusc->{'scan'}, $results_hashref->{$server}->{"RECORDS"});
+            @newresults = searchResults({ 'interface' => 'opac' }, '', $hits, $results_per_page, $offset, $arrParamsBusc->{'scan'}, $results_hashref->{$server}->{"RECORDS"});
         }
         return \@newresults;
     }#searchAgain
index 7aaffef..dc1d410 100755 (executable)
@@ -51,6 +51,7 @@ use C4::Koha;
 use C4::Tags qw(get_tags);
 use C4::SocialData;
 use C4::External::OverDrive;
+use C4::Borrowers qw(GetMember);
 
 use Koha::ItemTypes;
 use Koha::Ratings;
@@ -621,6 +622,14 @@ if (not $tag and ( $@ || $error)) {
 # At this point, each server has given us a result set
 # now we build that set for template display
 my @sup_results_array;
+my $search_context = {};
+$search_context->{'interface'} = 'opac';
+if (C4::Context->preference('OpacHiddenItemsExceptions')){
+    # we need to fetch the borrower info here, so we can pass the category
+    my $borrower = GetMember( borrowernumber => $borrowernumber );
+    $search_context->{'category'} = $borrower->{'categorycode'};
+}
+
 for (my $i=0;$i<@servers;$i++) {
     my $server = $servers[$i];
     if ($server && $server =~/biblioserver/) { # this is the local bibliographic server
@@ -632,12 +641,12 @@ for (my $i=0;$i<@servers;$i++) {
                 # because pazGetRecords handles retieving only the records
                 # we want as specified by $offset and $results_per_page,
                 # we need to set the offset parameter of searchResults to 0
-                my @group_results = searchResults( 'opac', $query_desc, $group->{'group_count'},$results_per_page, 0, $scan,
+                my @group_results = searchResults( $search_context, $query_desc, $group->{'group_count'},$results_per_page, 0, $scan,
                                                    $group->{"RECORDS"});
                 push @newresults, { group_label => $group->{'group_label'}, GROUP_RESULTS => \@group_results };
             }
         } else {
-            @newresults = searchResults('opac', $query_desc, $hits, $results_per_page, $offset, $scan,
+            @newresults = searchResults( $search_context, $query_desc, $hits, $results_per_page, $offset, $scan,
                                         $results_hashref->{$server}->{"RECORDS"});
         }
         $hits = 0 unless @newresults;
index 3ccd38e..a0ffb5b 100755 (executable)
@@ -198,20 +198,20 @@ subtest 'GetHiddenItemnumbers tests' => sub {
     push @items, GetItem( $item2_itemnumber );
 
     # Empty OpacHiddenItems
-    t::lib::Mocks::mock_preference('OpacHiddenItems','');
-    ok( !defined( GetHiddenItemnumbers( @items ) ),
+    C4::Context->set_preference('OpacHiddenItems','');
+    ok( !defined( GetHiddenItemnumbers( { items => \@items } ) ),
         "Hidden items list undef if OpacHiddenItems empty");
 
     # Blank spaces
-    t::lib::Mocks::mock_preference('OpacHiddenItems','  ');
-    ok( scalar GetHiddenItemnumbers( @items ) == 0,
+    C4::Context->set_preference('OpacHiddenItems','  ');
+    ok( scalar GetHiddenItemnumbers( { items => \@items } ) == 0,
         "Hidden items list empty if OpacHiddenItems only contains blanks");
 
     # One variable / value
     $opachiddenitems = "
         withdrawn: [1]";
     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
-    @hidden = GetHiddenItemnumbers( @items );
+    @hidden = GetHiddenItemnumbers( { items => \@items } );
     ok( scalar @hidden == 1, "Only one hidden item");
     is( $hidden[0], $item1_itemnumber, "withdrawn=1 is hidden");
 
@@ -219,7 +219,8 @@ subtest 'GetHiddenItemnumbers tests' => sub {
     $opachiddenitems = "
         withdrawn: [1,0]";
     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
-    @hidden = GetHiddenItemnumbers( @items );
+    C4::Context->set_preference( 'OpacHiddenItems', $opachiddenitems );
+    @hidden = GetHiddenItemnumbers( { items => \@items } );
     ok( scalar @hidden == 2, "Two items hidden");
     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and withdrawn=0 hidden");
 
@@ -229,13 +230,13 @@ subtest 'GetHiddenItemnumbers tests' => sub {
         homebranch: [$library2->{branchcode}]
     ";
     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
-    @hidden = GetHiddenItemnumbers( @items );
+    @hidden = GetHiddenItemnumbers( { items => \@items } );
     ok( scalar @hidden == 2, "Two items hidden");
     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and homebranch library2 hidden");
 
     # Valid OpacHiddenItems, empty list
     @items = ();
-    @hidden = GetHiddenItemnumbers( @items );
+    @hidden = GetHiddenItemnumbers( { items => \@items } );
     ok( scalar @hidden == 0, "Empty items list, no item hidden");
 
     $schema->storage->txn_rollback;
index d4a749a..be919fb 100644 (file)
@@ -373,7 +373,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     ($error, $results_hashref, $facets_loop) = getRecords($query,$simple_query,[ ], [ 'biblioserver' ],20,0,undef,\%branches,\%itemtypes,$query_type,0);
     is($results_hashref->{biblioserver}->{hits}, 19, "getRecords generated keyword search for 'salud' matched right number of records");
 
-    my @newresults = searchResults('opac', $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 18, 0, 0,
+    my @newresults = searchResults({'interface' => 'opac'}, $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 18, 0, 0,
         $results_hashref->{'biblioserver'}->{"RECORDS"});
     is(scalar @newresults,18, "searchResults returns requested number of hits");
 
@@ -455,7 +455,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     ($error, $results_hashref, $facets_loop) = getRecords($query,$simple_query,[ ], [ 'biblioserver' ],20,0,undef,\%branches,\%itemtypes,$query_type,0);
     is($results_hashref->{biblioserver}->{hits}, 26, "getRecords generated availability-limited search matched right number of records");
 
-    @newresults = searchResults('opac', $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
+    @newresults = searchResults({'interface'=>'opac'}, $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
         $results_hashref->{'biblioserver'}->{"RECORDS"});
     my $allavailable = 'true';
     foreach my $result (@newresults) {
@@ -593,7 +593,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     # Let's just test a few other bits and bobs, just for fun
 
     ($error, $results_hashref, $facets_loop) = getRecords("Godzina pąsowej róży","Godzina pąsowej róży",[ ], [ 'biblioserver' ],20,0,undef,\%branches,\%itemtypes,$query_type,0);
-    @newresults = searchResults('intranet', $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
+    @newresults = searchResults({'interface'=>'intranet'}, $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
         $results_hashref->{'biblioserver'}->{"RECORDS"});
     is($newresults[0]->{'alternateholdings_count'}, 1, 'Alternate holdings filled in correctly');
 
@@ -612,7 +612,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     });
 
     ($error, $results_hashref, $facets_loop) = getRecords("TEST12121212","TEST12121212",[ ], [ 'biblioserver' ],20,0,undef,\%branches,\%itemtypes,$query_type,0);
-    @newresults = searchResults('intranet', $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
+    @newresults = searchResults({'interface'=>'intranet'}, $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 17, 0, 0,
         $results_hashref->{'biblioserver'}->{"RECORDS"});
     ok(!exists($newresults[0]->{norequests}), 'presence of a transit does not block hold request action (bug 10741)');
 
@@ -620,7 +620,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     ( undef, $results_hashref, $facets_loop ) =
         getRecords('ti:punctuation', 'punctuation', [], [ 'biblioserver' ], '19', 0, undef, \%branches, \%itemtypes, 'ccl', undef);
     is($results_hashref->{biblioserver}->{hits}, 1, "search for ti:punctuation returned expected number of records");
-    warning_like { @newresults = searchResults('intranet', $query_desc,
+    warning_like { @newresults = searchResults({'intranet' => 'intranet'}, $query_desc,
                     $results_hashref->{'biblioserver'}->{'hits'}, 20, 0, 0,
                     $results_hashref->{'biblioserver'}->{"RECORDS"}) }
                 qr/^ERROR DECODING RECORD - Tag "50%" is not a valid tag/,
@@ -763,7 +763,7 @@ ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],'
     ( undef, $results_hashref, $facets_loop ) =
         getRecords('ti:marc the large record', '', [], [ 'biblioserver' ], '20', 0, undef, \%branches, \%itemtypes, 'ccl', undef);
     is($results_hashref->{biblioserver}->{hits}, 1, "Can do a search that retrieves an over-large bib record (bug 11096)");
-    @newresults = searchResults('opac', $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 10, 0, 0,
+    @newresults = searchResults({'interface' =>'opac'}, $query_desc, $results_hashref->{'biblioserver'}->{'hits'}, 10, 0, 0,
         $results_hashref->{'biblioserver'}->{"RECORDS"});
     is($newresults[0]->{title}, 'Marc the Large Record', 'Able to render the title for over-large bib record (bug 11096)');
     is($newresults[0]->{biblionumber}, '300', 'Over-large bib record has the correct biblionumber (bug 11096)');