From 749471e6c9e6e22537d83238876d80732486e00b Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Mon, 6 May 2013 09:44:01 -0400 Subject: [PATCH] Bug 10278 - Add ability to hide items and records from search results for Independent Branches For the staff intranet, enabling IndependentBranchesRecordsAndItems will automatically add a branch limit filter to the search results thus hiding all records without one or more items owned by the logged in library. In add addition, all items whose homebranch is not that libraries will be filtered and hidden from the search results and record details. This system preference will not affect the OPAC, unless the environment variable BRANCHCODE is defined in the koha-httpd.conf file. If it is defined, the same filters are applied to the OPAC, but based on the branchcode value of the environment variable BRANCHCODE, rather than the logged in branch. Test Plan: 1) Apply patch 2) Run updatedatabase.pl 3) Perform a search that will give results for both records which have items owned by the logged in library, and records which have no items owned by the logged in library. 4) Enable the new system preference IndependentBranchesRecordesAndItems 5) Perform the same search again, any records without items owned by the currently logged in library should not appear. --- C4/Auth.pm | 2 +- C4/Branch.pm | 123 ++++++++++++++++++++ C4/Context.pm | 36 ++++--- C4/Items.pm | 10 ++- C4/Koha.pm | 74 ++++++------ C4/Search.pm | 34 ++++++ catalogue/search.pl | 52 ++++++--- cataloguing/additem.pl | 45 +++++--- installer/data/mysql/updatedatabase.pl | 20 +++ .../intranet-tmpl/prog/en/includes/cat-search.inc | 64 ++++++----- .../prog/en/modules/admin/preferences/admin.pref | 7 + .../prog/en/modules/catalogue/advsearch.tt | 59 +++++---- 12 files changed, 387 insertions(+), 139 deletions(-) diff --git a/C4/Auth.pm b/C4/Auth.pm index 9dba387..c2a7935 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -658,7 +658,7 @@ sub checkauth { $session->param('surname'), $session->param('branch'), $session->param('branchname'), $session->param('flags'), $session->param('emailaddress'), $session->param('branchprinter'), - $session->param('persona') + $session->param('persona'), $type ); C4::Context::set_shelves_userenv('bar',$session->param('barshelves')); C4::Context::set_shelves_userenv('pub',$session->param('pubshelves')); diff --git a/C4/Branch.pm b/C4/Branch.pm index 5770f7b..acac720 100644 --- a/C4/Branch.pm +++ b/C4/Branch.pm @@ -41,6 +41,7 @@ BEGIN { &GetCategoryTypes &GetBranchCategories &GetBranchesInCategory + &GetCategoriesForBranch &ModBranchCategoryInfo &DelBranch &DelBranchCategory @@ -447,6 +448,128 @@ sub GetBranchesInCategory { return( \@branches ); } +=head2 GetCategoriesForBranch + + my @categories = GetCategoriesForBranch({ + branchcode => $branchcode, + categorytype => 'independent_group' + }); + + Called in a list context, returns an array of branch category codes + that branch is part of. + + Called in a scalar context, returns an array ref. + +=cut + +sub GetCategoriesForBranch { + my ( $params ) = @_; + my $branchcode = $params->{branchcode}; + my $categorytype = $params->{categorytype} || '%'; + + carp("Missing branchcode parameter!") unless ( $branchcode ); + + my $sql = q{ + SELECT categorycode FROM branchrelations + JOIN branchcategories USING ( categorycode ) + WHERE branchcode = ? + AND categorytype = ? + }; + + my $categories = C4::Context->dbh->selectcol_arrayref( $sql, {}, ( $branchcode, $categorytype ) ); + + return wantarray() ? $categories : @$categories; +} + +=head2 GetIndependentGroupModificationRights + + GetIndependentGroupModificationRights( + { + branch => $this_branch, + for => $other_branch, + stringify => 1, + } + ); + + Returns a list of branches this branch shares a common + independent group with. + + If 'branch' is not provided, it will be looked up via + C4::Context->userenv->{branch}. + + If 'for' is provided, the lookup is limited to that branch. + + If called in a list context, returns a list of + branchcodes ( including $this_branch ). + + If called in a scalar context, it returns + a count of matching branchcodes. Returns 1 if + + If stringify param is passed, the return value will + be a string of the comma delimited branchcodes. This + is useful for "branchcode IN $branchcodes" clauses + in SQL queries. + + $this_branch and $other_branch are equal for efficiency. + + So you can write: + my @branches = GetIndependentGroupModificationRights(); + or something like: + if ( GetIndependentGroupModificationRights( { for => $other_branch } ) ) { do_stuff(); } + +=cut + +sub GetIndependentGroupModificationRights { + my ($params) = @_; + + my $this_branch = $params->{branch}; + my $other_branch = $params->{for}; + my $stringify = $params->{stringify}; + + $this_branch ||= C4::Context->userenv->{branch}; + $this_branch ||= $ENV{BRANCHCODE}; + + return 1 if ( $this_branch && $this_branch eq $other_branch ); + + my $allow_all = 0; + $allow_all = 1 if C4::Context->IsSuperLibrarian(); + $allow_all = 1 if C4::Context->userenv->{type} eq 'opac' && !$ENV{BRANCHCODE}; + + my $sql; + my @params; + if ( $allow_all ) { + $sql = q{ + SELECT branchcode FROM branches WHERE 1 + } + } else { + $sql = q{ + SELECT DISTINCT(branchcode) + FROM branchrelations + JOIN branchcategories USING ( categorycode ) + WHERE categorycode IN ( + SELECT categorycode + FROM branchrelations + WHERE branchcode = ? + ) + AND branchcategories.categorytype = 'independent_group' + }; + push( @params, $this_branch ); + } + + if ($other_branch) { + $sql .= q{ AND branchcode = ? }; + push( @params, $other_branch ); + } + + my $dbh = C4::Context->dbh; + my @branchcodes = @{ $dbh->selectcol_arrayref( $sql, {}, @params ) }; + + return join( ',', map { qq{'$_'} } ( @branchcodes, $this_branch ) ) + if ($stringify); + + return wantarray() ? ( @branchcodes, $this_branch ) : scalar(@branchcodes); +} + =head2 GetBranchInfo $results = GetBranchInfo($branchcode); diff --git a/C4/Context.pm b/C4/Context.pm index db0d498..fc3bd94 100644 --- a/C4/Context.pm +++ b/C4/Context.pm @@ -1104,23 +1104,31 @@ set_userenv is called in Auth.pm #' sub set_userenv { - my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter, $persona)= @_; - my $var=$context->{"activeuser"} || ''; + my ( + $usernum, $userid, $usercnum, $userfirstname, + $usersurname, $userbranch, $branchname, $userflags, + $emailaddress, $branchprinter, $persona, $type + ) = @_; + + my $var = $context->{"activeuser"} || ''; + my $cell = { - "number" => $usernum, - "id" => $userid, - "cardnumber" => $usercnum, - "firstname" => $userfirstname, - "surname" => $usersurname, - #possibly a law problem - "branch" => $userbranch, - "branchname" => $branchname, - "flags" => $userflags, - "emailaddress" => $emailaddress, - "branchprinter" => $branchprinter, - "persona" => $persona, + "number" => $usernum, + "id" => $userid, + "cardnumber" => $usercnum, + "firstname" => $userfirstname, + "surname" => $usersurname, + "branch" => $userbranch, + "branchname" => $branchname, + "flags" => $userflags, + "emailaddress" => $emailaddress, + "branchprinter" => $branchprinter, + "persona" => $persona, + "type" => $type, }; + $context->{userenv}->{$var} = $cell; + return $cell; } diff --git a/C4/Items.pm b/C4/Items.pm index 212a9d3..c9dd70d 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -1201,6 +1201,11 @@ If this is set, it is set to C. sub GetItemsInfo { my ( $biblionumber ) = @_; + + my $IndependentBranchesRecordsAndItems = + C4::Context->preference('IndependentBranchesRecordsAndItems') + && !C4::Context->IsSuperLibrarian(); + my $dbh = C4::Context->dbh; # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance. my $query = " @@ -1230,7 +1235,10 @@ sub GetItemsInfo { LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber LEFT JOIN itemtypes ON itemtypes.itemtype = " . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype'); - $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ; + $query .= " WHERE items.biblionumber = ? "; + $query .= " AND items.homebranch IN ( " . GetIndependentGroupModificationRights({ stringify => 1}) . " ) " if ( $IndependentBranchesRecordsAndItems ); + $query .= " ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ; + my $sth = $dbh->prepare($query); $sth->execute($biblionumber); my $i = 0; diff --git a/C4/Koha.pm b/C4/Koha.pm index 9976995..fd2cf57 100644 --- a/C4/Koha.pm +++ b/C4/Koha.pm @@ -36,43 +36,43 @@ BEGIN { $VERSION = 3.07.00.049; require Exporter; @ISA = qw(Exporter); - @EXPORT = qw( - &slashifyDate - &subfield_is_koha_internal_p - &GetPrinters &GetPrinter - &GetItemTypes &getitemtypeinfo - &GetCcodes - &GetSupportName &GetSupportList - &get_itemtypeinfos_of - &getframeworks &getframeworkinfo - &getauthtypes &getauthtype - &getallthemes - &getFacets - &displayServers - &getnbpages - &get_infos_of - &get_notforloan_label_of - &getitemtypeimagedir - &getitemtypeimagesrc - &getitemtypeimagelocation - &GetAuthorisedValues - &GetAuthorisedValueCategories - &IsAuthorisedValueCategory - &GetKohaAuthorisedValues - &GetKohaAuthorisedValuesFromField - &GetKohaAuthorisedValueLib - &GetAuthorisedValueByCode - &GetKohaImageurlFromAuthorisedValues - &GetAuthValCode - &AddAuthorisedValue - &GetNormalizedUPC - &GetNormalizedISBN - &GetNormalizedEAN - &GetNormalizedOCLCNumber - &xml_escape - - $DEBUG - ); + @EXPORT = qw( + &slashifyDate + &subfield_is_koha_internal_p + &GetPrinters &GetPrinter + &GetItemTypes &getitemtypeinfo + &GetCcodes + &GetSupportName &GetSupportList + &get_itemtypeinfos_of + &getframeworks &getframeworkinfo + &getauthtypes &getauthtype + &getallthemes + &getFacets + &displayServers + &getnbpages + &get_infos_of + &get_notforloan_label_of + &getitemtypeimagedir + &getitemtypeimagesrc + &getitemtypeimagelocation + &GetAuthorisedValues + &GetAuthorisedValueCategories + &IsAuthorisedValueCategory + &GetKohaAuthorisedValues + &GetKohaAuthorisedValuesFromField + &GetKohaAuthorisedValueLib + &GetAuthorisedValueByCode + &GetKohaImageurlFromAuthorisedValues + &GetAuthValCode + &AddAuthorisedValue + &GetNormalizedUPC + &GetNormalizedISBN + &GetNormalizedEAN + &GetNormalizedOCLCNumber + &xml_escape + + $DEBUG + ); $DEBUG = 0; @EXPORT_OK = qw( GetDailyQuote ); } diff --git a/C4/Search.pm b/C4/Search.pm index aec024b..f3868e8 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -36,6 +36,7 @@ use URI::Escape; use Business::ISBN; use MARC::Record; use MARC::Field; +use List::MoreUtils qw(none); use utf8; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG); @@ -1828,14 +1829,47 @@ sub searchResults { my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1; my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref + my $IndependentBranchesRecordsAndItems; + if ( $search_context eq 'opac' ) { + # For the OPAC, if IndependentBranchesRecordsAndItems is enabled, + # and BRANCHCODE has been set in the httpd conf, + # we need to filter the items + $IndependentBranchesRecordsAndItems = + C4::Context->preference('IndependentBranchesRecordsAndItems') + && $ENV{BRANCHCODE}; + } + else { + # For the intranet, if IndependentBranchesRecordsAndItems is enabled, + # and the user is not a superlibrarian, + # we need to filter the items + $IndependentBranchesRecordsAndItems = + C4::Context->preference('IndependentBranchesRecordsAndItems') + && !C4::Context->IsSuperLibrarian(); + } + my @allowed_branches = $IndependentBranchesRecordsAndItems ? GetIndependentGroupModificationRights() : undef; + # loop through every item + my $index = -1; foreach my $field (@fields) { + $index++; my $item; # populate the items hash foreach my $code ( keys %subfieldstosearch ) { $item->{$code} = $field->subfield( $subfieldstosearch{$code} ); } + + # if IndependentBranchesRecordsAndItems is enabled, and this record + # isn't allowed to be viewed, remove it from the items list and go + # right to the next item. + if ( $IndependentBranchesRecordsAndItems ) { + if ( none { $_ eq $item->{homebranch} } @allowed_branches ) { + splice(@fields, $index, 1); + $items_count--; + next; + } + } + $item->{description} = $itemtypes{ $item->{itype} }{description}; # OPAC hidden items diff --git a/catalogue/search.pl b/catalogue/search.pl index d4e98b1..c8d72da 100755 --- a/catalogue/search.pl +++ b/catalogue/search.pl @@ -135,6 +135,8 @@ Not yet completed... use strict; # always use #use warnings; FIXME - Bug 2505 +use List::MoreUtils qw(any); + ## STEP 1. Load things that are used in both search page and # results page and decide which template to load, operations # to perform, etc. @@ -179,9 +181,11 @@ else { flagsrequired => { catalogue => 1 }, } ); + if (C4::Context->preference("marcflavour") eq "UNIMARC" ) { $template->param('UNIMARC' => 1); } + if (C4::Context->preference("IntranetNumbersPreferPhrase")) { $template->param('numbersphr' => 1); } @@ -226,20 +230,10 @@ my $branches = GetBranches(); # Populate branch_loop with all branches sorted by their name. If # IndependentBranches is activated, set the default branch to the borrower # branch, except for superlibrarian who need to search all libraries. -my $user = C4::Context->userenv; -my @branch_loop = map { - { - value => $_, - branchname => $branches->{$_}->{branchname}, - selected => $user->{branch} eq $_ && C4::Branch::onlymine(), - } -} sort { - $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} -} keys %$branches; - -my $categories = GetBranchCategories(undef,'searchdomain'); - -$template->param(branchloop => \@branch_loop, searchdomainloop => $categories); +$template->param( + branchloop => GetBranchesLoop(), + searchdomainloop => GetBranchCategories( undef, 'searchdomain' ), +); # load the Type stuff my $itemtypes = GetItemTypes; @@ -411,7 +405,35 @@ my @operands = $cgi->param('q'); # limits are use to limit to results to a pre-defined category such as branch or language my @limits = $cgi->param('limit'); -if($params->{'multibranchlimit'}) { +if ( C4::Context->preference('IndependentBranchesRecordsAndItems') ) { + # Get list of branches this branch can access + my @branches = GetIndependentGroupModificationRights(); + + # Strip out any branch search limits that are not in the list of allowable branches + my $has_valid_branch_limits; # If at least one allowable branch limit is passed, + # use the valid ones, otherwise make it an "all branches" + # search with the allowed branches + + my @new_limits; + foreach my $limit ( @limits ) { + if ( $limit =~ /^branch:/ ) { + my ( undef, $branch ) = split(':', $limit); + push( @new_limits, $limit ) if any { $_ eq $branch } @branches; + $has_valid_branch_limits = 1; + } else { + push( @new_limits, $limit ); + } + } + @limits = @new_limits; + + # If the limits contain any branch limits, if not, do a search on all allowable branches + unless ($has_valid_branch_limits) { + my $new_branch_limit = + '(' . join( " or ", map { "branch: $_ " } @branches ) . ')'; + push( @limits, $new_branch_limit ); + } +} +elsif( $params->{'multibranchlimit'} ) { my $multibranch = '('.join( " or ", map { "branch: $_ " } @{ GetBranchesInCategory( $params->{'multibranchlimit'} ) } ).')'; push @limits, $multibranch if ($multibranch ne '()'); } diff --git a/cataloguing/additem.pl b/cataloguing/additem.pl index a2eacef..315faf8 100755 --- a/cataloguing/additem.pl +++ b/cataloguing/additem.pl @@ -666,6 +666,10 @@ if ( C4::Context->preference('EasyAnalyticalRecords') ) { } } +my $IndependentBranches = !C4::Context->IsSuperLibrarian() + && C4::Context->preference('IndependentBranches'); +my $IndependentBranchesRecordsAndItems = $IndependentBranches + && C4::Context->preference('IndependentBranchesRecordsAndItems'); foreach my $field (@fields) { next if ( $field->tag() < 10 ); @@ -689,31 +693,38 @@ foreach my $field (@fields) { || $subfieldvalue; } - if (($field->tag eq $branchtagfield) && ($subfieldcode eq $branchtagsubfield) && C4::Context->preference("IndependentBranches")) { + if ( $field->tag eq $branchtagfield + && $subfieldcode eq $branchtagsubfield + && $IndependentBranches ) + { #verifying rights - my $userenv = C4::Context->userenv(); - unless (($userenv->{'flags'} == 1) or (($userenv->{'branch'} eq $subfieldvalue))){ + unless ( + GetIndependentGroupModificationRights( + { for => $subfieldvalue } + ) + ) + { $this_row{'nomod'} = 1; } } - $this_row{itemnumber} = $subfieldvalue if ($field->tag() eq $itemtagfield && $subfieldcode eq $itemtagsubfield); - if ( C4::Context->preference('EasyAnalyticalRecords') ) { - foreach my $hostitemnumber (@hostitemnumbers){ - if ($this_row{itemnumber} eq $hostitemnumber){ - $this_row{hostitemflag} = 1; - $this_row{hostbiblionumber}= GetBiblionumberFromItemnumber($hostitemnumber); - last; - } - } + $this_row{itemnumber} = $subfieldvalue if ($field->tag() eq $itemtagfield && $subfieldcode eq $itemtagsubfield); -# my $countanalytics=GetAnalyticsCount($this_row{itemnumber}); -# if ($countanalytics > 0){ -# $this_row{countanalytics} = $countanalytics; -# } - } + if ( C4::Context->preference('EasyAnalyticalRecords') ) { + foreach my $hostitemnumber (@hostitemnumbers) { + if ( $this_row{itemnumber} eq $hostitemnumber ) { + $this_row{hostitemflag} = 1; + $this_row{hostbiblionumber} = + GetBiblionumberFromItemnumber($hostitemnumber); + last; + } + } + } } + + next if ( $this_row{'nomod'} && $IndependentBranchesRecordsAndItems ); + if (%this_row) { push(@big_array, \%this_row); } diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index f3a90d8..2a4238a 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -6983,6 +6983,26 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( SetVersion($DBversion); } +$DBversion = "3.13.00.XXX"; +if ( CheckVersion($DBversion) ) { + print "Upgrade to $DBversion done (IndependentBranches)\n"; + $dbh->do(" + INSERT INTO systempreferences ( + variable, + value, + options, + explanation, + type + ) VALUES ( + 'IndependentBranchesRecordsAndItems', + '0', + '', + 'If on, the staff interface search will hide all records that do not contain an item owned by the logged in branch, and hide the items themselves.', + 'YesNo' + ) + "); + SetVersion ($DBversion); +} =head1 FUNCTIONS diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-search.inc index 186f1d1..4cf3e71 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-search.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-search.inc @@ -1,33 +1,41 @@
-

[% LibraryName %]

-
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref index 1215ac1..7433a53 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref @@ -70,6 +70,13 @@ Administration: yes: Prevent no: "Don't prevent" - staff (but not superlibrarians) from modifying objects (holds, items, patrons, etc.) belonging to other libraries. + - + - pref: IndependentBranchesRecordsAndItems + default: 0 + choices: + yes: Prevent + no: "Don't prevent" + - staff from seeing items owned by other libraries, and records without any items the library. CAS Authentication: - - pref: casAuthentication diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt index c982eb4..1dac415 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt @@ -1,3 +1,4 @@ +[% USE Koha %] [% INCLUDE 'doc-head-open.inc' %] Koha › Catalog › Advanced search [% INCLUDE 'doc-head-close.inc' %] @@ -229,34 +230,40 @@ [% END %] -
Location and availability -
-

-
+
Location and availability +
+

+
-
-

- - [% IF ( searchdomainloop ) %] -

OR

-

- [% END %] -
+
+

+ + +

+ + [% IF searchdomainloop && !Koha.Preference('IndependentBranchesRecordsAndItems') %] +

OR

+

+ + +

+ [% END %]
+
-- 1.7.2.5