@@ -, +, @@ --- C4/ShelfBrowser.pm | 235 ++++++++++++++++++++ installer/data/mysql/en/mandatory/sysprefs.sql | 3 + installer/data/mysql/updatedatabase.pl | 9 + .../prog/en/modules/admin/preferences/opac.pref | 34 +++- .../opac-tmpl/prog/en/modules/opac-detail.tmpl | 2 +- opac/opac-detail.pl | 123 ++--------- 6 files changed, 293 insertions(+), 113 deletions(-) create mode 100644 C4/ShelfBrowser.pm --- a/C4/ShelfBrowser.pm +++ a/C4/ShelfBrowser.pm @@ -0,0 +1,235 @@ +#!/usr/bin/perl + +package C4::ShelfBrowser; + +# Copyright 2010 Catalyst IT +# +# 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 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use C4::Biblio; +use C4::Branch; +use C4::Context; +use C4::Koha; + +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); + +BEGIN { + $VERSION = 3.02; + require Exporter; + @ISA = qw(Exporter); + @EXPORT = qw( + &GetNearbyItems + ); + @EXPORT_OK = qw( + ); +} + +=head1 NAME + +C4::ShelfBrowser - functions that deal with the shelf browser feature found in +the OPAC. + +=head1 SYNOPSIS + + use C4::ShelfBrowser; + +=head1 DESCRIPTION + +This module provides functions to get items nearby to another item, for use +in the shelf browser function. + +'Nearby' is controlled by a handful of system preferences that specify what +to take into account. + +=head1 FUNCTIONS + +=head2 GetNearbyItems($itemnumber, [$num_each_side]) + + $nearby = GetNearbyItems($itemnumber, [$num_each_side]); + + @next = @{ $nearby->{next} }; + @prev = @{ $nearby->{prev} }; + + foreach (@next) { + # These won't format well like this, but here are the fields + print $_->{title}; + print $_->{biblionumber}; + print $_->{itemnumber}; + print $_->{browser_normalized_upc}; + print $_->{browser_normalized_oclc}; + print $_->{browser_normalized_isbn}; + } + + # This is the information required to scroll the browser to the next left + # or right set. Can be derived from next/prev, but it's here for convenience. + print $nearby->{prev_itemnumber}; + print $nearby->{next_itemnumber}; + print $nearby->{prev_biblionumber}; + print $nearby->{next_biblionumber}; + + # These will be undef if the values are not used to calculate the + # nearby items. + print $nearby->{starting_homebranch}->{code}; + print $nearby->{starting_homebranch}->{description}; + print $nearby->{starting_location}->{code}; + print $nearby->{starting_location}->{description}; + print $nearby->{starting_ccode}->{code}; + print $nearby->{starting_ccode}->{description}; + + print $nearby->{starting_itemnumber}; + +This finds the items that are nearby to the supplied item, and supplies +those previous and next, along with the other useful information for displaying +the shelf browser. + +It automatically applies the following user preferences to work out how to +calculate things: C, C, +C. + +The option C<$num_each_side> value determines how many items will be fetched +each side of the supplied item. Note that the item itself is the first entry +in the 'next' set, and counts towards this limit (this is to keep the +behaviour consistant with the code that this is a refactor of.) Default is +3. + +This will throw an exception if something went wrong. + +=cut + +sub GetNearbyItems { + my ($itemnumber, $num_each_side) = @_; + $num_each_side ||= 3; + + my $dbh = C4::Context->dbh; + my $marcflavour = C4::Context->preference("marcflavour"); + my $branches = GetBranches(); + + my $sth_get_item_details = $dbh->prepare("SELECT cn_sort,homebranch,location,ccode from items where itemnumber=?"); + $sth_get_item_details->execute($itemnumber); + my $item_details_result = $sth_get_item_details->fetchrow_hashref(); + die "Unable to find item '$itemnumber' for shelf browser" if (!$sth_get_item_details); + my $start_cn_sort = $item_details_result->{'cn_sort'}; + + my ($start_homebranch, $start_location, $start_ccode); + if (C4::Context->preference('ShelfBrowserUsesHomeBranch') && + defined($item_details_result->{'homebranch'})) { + $start_homebranch->{code} = $item_details_result->{'homebranch'}; + $start_homebranch->{description} = $branches->{$item_details_result->{'homebranch'}}{branchname}; + } + if (C4::Context->preference('ShelfBrowserUsesLocation') && + defined($item_details_result->{'location'})) { + $start_location->{code} = $item_details_result->{'location'}; + $start_location->{description} = GetAuthorisedValueDesc('','',$item_details_result->{'location'},'','','LOC','opac'); + } + if (C4::Context->preference('ShelfBrowserUsesCcode') && + defined($item_details_result->{'ccode'})) { + $start_ccode->{code} = $item_details_result->{'ccode'}; + $start_ccode->{description} = GetAuthorisedValueDesc('', '', $item_details_result->{'ccode'}, '', '', 'CCODE', 'opac'); + } + + # Build the query for previous and next items + my $prev_query =' + SELECT * + FROM items + WHERE + ((cn_sort = ? AND itemnumber < ?) OR cn_sort < ?) '; + my $next_query =' + SELECT * + FROM items + WHERE + ((cn_sort = ? AND itemnumber >= ?) OR cn_sort > ?) '; + my @params; + my $query_cond; + push @params, ($start_cn_sort, $itemnumber, $start_cn_sort); + if ($start_homebranch) { + $query_cond .= 'AND homebranch = ? '; + push @params, $start_homebranch->{code}; + } + if ($start_location) { + $query_cond .= 'AND location = ? '; + push @params, $start_location->{code}; + } + if ($start_ccode) { + $query_cond .= 'AND ccode = ? '; + push @params, $start_ccode->{code}; + } + + my $sth_prev_items = $dbh->prepare($prev_query . $query_cond . ' ORDER BY cn_sort DESC, itemnumber LIMIT ?'); + my $sth_next_items = $dbh->prepare($next_query . $query_cond . ' ORDER BY cn_sort, itemnumber LIMIT ?'); + push @params, $num_each_side; + $sth_prev_items->execute(@params); + $sth_next_items->execute(@params); + + # Now we have the query run, suck out the data like marrow + my @prev_items = reverse GetShelfInfo($sth_prev_items, $marcflavour); + my @next_items = GetShelfInfo($sth_next_items, $marcflavour); + + my $next_itemnumber = $next_items[-1]->{itemnumber} if @next_items; + my $next_biblionumber = $next_items[-1]->{biblionumber} if @next_items; + + my $prev_itemnumber = $prev_items[0]->{itemnumber} if @prev_items; + my $prev_biblionumber = $prev_items[0]->{biblionumber} if @prev_items; + + my %result = ( + next => \@next_items, + prev => \@prev_items, + next_itemnumber => $next_itemnumber, + next_biblionumber => $next_biblionumber, + prev_itemnumber => $prev_itemnumber, + prev_biblionumber => $prev_biblionumber, + starting_itemnumber => $itemnumber, + ); + $result{starting_homebranch} = $start_homebranch if $start_homebranch; + $result{starting_location} = $start_location if $start_location; + $result{starting_ccode} = $start_ccode if $start_ccode; + return \%result; +} + +# This runs through a statement handle and pulls out all the items in it, fills +# them up with additional info that shelves want, and returns those as a list. +# Not really intended to be exported. +sub GetShelfInfo { + my ($sth, $marcflavour) = @_; + + my @items; + while (my $this_item = $sth->fetchrow_hashref()) { + my $this_biblio = GetBibData($this_item->{biblionumber}); + next if (!defined($this_biblio)); + $this_item->{'title'} = $this_biblio->{'title'}; + my $this_record = GetMarcBiblio($this_biblio->{'biblionumber'}); + $this_item->{'browser_normalized_upc'} = GetNormalizedUPC($this_record,$marcflavour); + $this_item->{'browser_normalized_oclc'} = GetNormalizedOCLCNumber($this_record,$marcflavour); + $this_item->{'browser_normalized_isbn'} = GetNormalizedISBN(undef,$this_record,$marcflavour); + push @items, $this_item; + } + return @items; +} + +# Fetches some basic biblio data needed by the shelf stuff +sub GetBibData { + my ($bibnum) = @_; + + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("SELECT biblionumber, title FROM biblio WHERE biblionumber=?"); + $sth->execute($bibnum); + my $bib = $sth->fetchrow_hashref(); + return $bib; +} + +1; --- a/installer/data/mysql/en/mandatory/sysprefs.sql +++ a/installer/data/mysql/en/mandatory/sysprefs.sql @@ -280,3 +280,6 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ILS-DI:AuthorizedIPs','','.','Restricts usage of ILS-DI to some IPs','Free'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('OverduesBlockCirc','noblock','When checking out an item should overdues block checkout, generate a confirmation dialogue, or allow checkout','noblock|confirmation|block','Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('DisplayMultiPlaceHold','1','Display the ability to place multiple holds or not','','YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesLocation','1','Use the item location when finding items for the shelf browser.','1','YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesHomeBranch','1','Use the item home branch when finding items for the shelf browser.','1','YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesCcode','1','Use the item collection code when finding items for the shelf browser.','0','YesNo'); --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -3941,6 +3941,15 @@ $DBversion = "3.03.00.013"; if (C4::Context->preference("Version") < TransformToNum($DBversion)) { $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('OpacPublic','1','If set to OFF and user is not logged in, all OPAC pages require authentication, and OPAC searchbar is removed)','','YesNo')"); print "Upgrade to $DBversion done (added 'OpacPublic' syspref)\n"; + SetVersion ($DBversion); +} + +$DBversion = "3.03.00.xxx"; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesLocation','1','Use the item location when finding items for the shelf browser.','1','YesNo')"); + $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesHomeBranch','1','Use the item home branch when finding items for the shelf browser.','1','YesNo')"); + $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('ShelfBrowserUsesCcode','0','Use the item collection code when finding items for the shelf browser.','1','YesNo')"); + print "Upgrade to $DBversion done (Add flexible shelf browser constraints)\n"; SetVersion ($DBversion); } --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref @@ -208,12 +208,6 @@ OPAC: no: "Don't allow" - patrons to see what books they have checked out in the past. - - - pref: OPACShelfBrowser - choices: - yes: Show - no: "Don't show" - - "a shelf browser on item details pages, allowing patrons to see what's near that item on the shelf. Note that this uses up a fairly large amount of resources on your server, and should be avoided if your collection has a large number of items." - - - pref: OpacTopissue choices: yes: Allow @@ -300,3 +294,31 @@ OPAC: yes: Keep no: "Don't keep" - patron search history in the OPAC. + Shelf Browser: + - + - pref: OPACShelfBrowser + choices: + yes: Show + no: "Don't show" + - "a shelf browser on item details pages, allowing patrons to see what's near that item on the shelf. Note that this uses up a fairly large amount of resources on your server, and should be avoided if your collection has a large number of items." + - + - pref: ShelfBrowserUsesLocation + default: 1 + choices: + yes: Use + no: "Don't use" + - "the item location when finding items for the shelf browser." + - + - pref: ShelfBrowserUsesHomeBranch + default: 1 + choices: + yes: Use + no: "Don't use" + - "the item home branch when finding items for the shelf browser." + - + - pref: ShelfBrowserUsesCcode + default: 0 + choices: + yes: Use + no: "Don't use" + - "the item collection code when finding items for the shelf browser." --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl +++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tmpl @@ -422,7 +422,7 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
-
Browsing Shelves, Shelving Location: ">Close Shelf Browser
+
Browsing Shelves, Shelving Location: , Collection Code: ">Close Shelf Browser
--- a/opac/opac-detail.pl +++ a/opac/opac-detail.pl @@ -39,6 +39,7 @@ use C4::Review; use C4::Members; use C4::VirtualShelves; use C4::XSLT; +use C4::ShelfBrowser; BEGIN { if (C4::Context->preference('BakerTaylorEnabled')) { @@ -197,7 +198,6 @@ for my $itm (@items) { my $dbh = C4::Context->dbh; my $marcflavour = C4::Context->preference("marcflavour"); my $marcnotesarray = GetMarcNotes ($record,$marcflavour); -my $marcisbnsarray = GetMarcISBN ($record,$marcflavour); my $marcauthorsarray = GetMarcAuthors ($record,$marcflavour); my $marcsubjctsarray = GetMarcSubjects($record,$marcflavour); my $marcseriesarray = GetMarcSeries ($record,$marcflavour); @@ -210,7 +210,6 @@ my $subtitle = GetRecordValue('subtitle', $record, GetFrameworkCode($bib MARCAUTHORS => $marcauthorsarray, MARCSERIES => $marcseriesarray, MARCURLS => $marcurlsarray, - MARCISBNS => $marcisbnsarray, norequests => $norequests, RequestOnOpac => C4::Context->preference("RequestOnOpac"), itemdata_ccode => $itemfields{ccode}, @@ -446,111 +445,23 @@ if ( C4::Context->preference("Babeltheque") ) { if (C4::Context->preference("OPACShelfBrowser")) { # pick the first itemnumber unless one was selected by the user my $starting_itemnumber = $query->param('shelfbrowse_itemnumber'); # || $items[0]->{itemnumber}; - $template->param( OpenOPACShelfBrowser => 1) if $starting_itemnumber; - # find the right cn_sort value for this item - my ($starting_cn_sort, $starting_homebranch, $starting_location); - my $sth_get_cn_sort = $dbh->prepare("SELECT cn_sort,homebranch,location from items where itemnumber=?"); - $sth_get_cn_sort->execute($starting_itemnumber); - while (my $result = $sth_get_cn_sort->fetchrow_hashref()) { - $starting_cn_sort = $result->{'cn_sort'}; - $starting_homebranch->{code} = $result->{'homebranch'}; - $starting_homebranch->{description} = $branches->{$result->{'homebranch'}}{branchname}; - $starting_location->{code} = $result->{'location'}; - $starting_location->{description} = GetAuthorisedValueDesc('','', $result->{'location'} ,'','','LOC', 'opac'); - - } - - ## List of Previous Items - # order by cn_sort, which should include everything we need for ordering purposes (though not - # for limits, those need to be handled separately - my $sth_shelfbrowse_previous; - if (defined $starting_location->{code}) { - $sth_shelfbrowse_previous = $dbh->prepare(" - SELECT * - FROM items - WHERE - ((cn_sort = ? AND itemnumber < ?) OR cn_sort < ?) AND - homebranch = ? AND location = ? - ORDER BY cn_sort DESC, itemnumber LIMIT 3 - "); - $sth_shelfbrowse_previous->execute($starting_cn_sort, $starting_itemnumber, $starting_cn_sort, $starting_homebranch->{code}, $starting_location->{code}); - } else { - $sth_shelfbrowse_previous = $dbh->prepare(" - SELECT * - FROM items - WHERE - ((cn_sort = ? AND itemnumber < ?) OR cn_sort < ?) AND - homebranch = ? - ORDER BY cn_sort DESC, itemnumber LIMIT 3 - "); - $sth_shelfbrowse_previous->execute($starting_cn_sort, $starting_itemnumber, $starting_cn_sort, $starting_homebranch->{code}); - } - my @previous_items; - while (my $this_item = $sth_shelfbrowse_previous->fetchrow_hashref()) { - my $sth_get_biblio = $dbh->prepare("SELECT biblio.*,biblioitems.isbn AS isbn FROM biblio LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber WHERE biblio.biblionumber=?"); - $sth_get_biblio->execute($this_item->{biblionumber}); - while (my $this_biblio = $sth_get_biblio->fetchrow_hashref()) { - $this_item->{'title'} = $this_biblio->{'title'}; - my $this_record = GetMarcBiblio($this_biblio->{'biblionumber'}); - $this_item->{'browser_normalized_upc'} = GetNormalizedUPC($this_record,$marcflavour); - $this_item->{'browser_normalized_oclc'} = GetNormalizedOCLCNumber($this_record,$marcflavour); - $this_item->{'browser_normalized_isbn'} = GetNormalizedISBN(undef,$this_record,$marcflavour); - } - unshift @previous_items, $this_item; - } - - ## List of Next Items; this also intentionally catches the current item - my $sth_shelfbrowse_next; - if (defined $starting_location->{code}) { - $sth_shelfbrowse_next = $dbh->prepare(" - SELECT * - FROM items - WHERE - ((cn_sort = ? AND itemnumber >= ?) OR cn_sort > ?) AND - homebranch = ? AND location = ? - ORDER BY cn_sort, itemnumber LIMIT 3 - "); - $sth_shelfbrowse_next->execute($starting_cn_sort, $starting_itemnumber, $starting_cn_sort, $starting_homebranch->{code}, $starting_location->{code}); - } else { - $sth_shelfbrowse_next = $dbh->prepare(" - SELECT * - FROM items - WHERE - ((cn_sort = ? AND itemnumber >= ?) OR cn_sort > ?) AND - homebranch = ? - ORDER BY cn_sort, itemnumber LIMIT 3 - "); - $sth_shelfbrowse_next->execute($starting_cn_sort, $starting_itemnumber, $starting_cn_sort, $starting_homebranch->{code}); - } - my @next_items; - while (my $this_item = $sth_shelfbrowse_next->fetchrow_hashref()) { - my $sth_get_biblio = $dbh->prepare("SELECT biblio.*,biblioitems.isbn AS isbn FROM biblio LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber WHERE biblio.biblionumber=?"); - $sth_get_biblio->execute($this_item->{biblionumber}); - while (my $this_biblio = $sth_get_biblio->fetchrow_hashref()) { - $this_item->{'title'} = $this_biblio->{'title'}; - my $this_record = GetMarcBiblio($this_biblio->{'biblionumber'}); - $this_item->{'browser_normalized_upc'} = GetNormalizedUPC($this_record,$marcflavour); - $this_item->{'browser_normalized_oclc'} = GetNormalizedOCLCNumber($this_record,$marcflavour); - $this_item->{'browser_normalized_isbn'} = GetNormalizedISBN(undef,$this_record,$marcflavour); - } - push @next_items, $this_item; + if (defined($starting_itemnumber)) { + $template->param( OpenOPACShelfBrowser => 1) if $starting_itemnumber; + my $nearby = GetNearbyItems($starting_itemnumber,3); + + $template->param( + starting_homebranch => $nearby->{starting_homebranch}->{description}, + starting_location => $nearby->{starting_location}->{description}, + starting_ccode => $nearby->{starting_ccode}->{description}, + starting_itemnumber => $nearby->{starting_itemnumber}, + shelfbrowser_prev_itemnumber => $nearby->{prev_itemnumber}, + shelfbrowser_next_itemnumber => $nearby->{next_itemnumber}, + shelfbrowser_prev_biblionumber => $nearby->{prev_biblionumber}, + shelfbrowser_next_biblionumber => $nearby->{next_biblionumber}, + PREVIOUS_SHELF_BROWSE => $nearby->{prev}, + NEXT_SHELF_BROWSE => $nearby->{next}, + ); } - - # alas, these won't auto-vivify, see http://www.perlmonks.org/?node_id=508481 - my $shelfbrowser_next_itemnumber = $next_items[-1]->{itemnumber} if @next_items; - my $shelfbrowser_next_biblionumber = $next_items[-1]->{biblionumber} if @next_items; - - $template->param( - starting_homebranch => $starting_homebranch->{description}, - starting_location => $starting_location->{description}, - starting_itemnumber => $starting_itemnumber, - shelfbrowser_prev_itemnumber => (@previous_items ? $previous_items[0]->{itemnumber} : 0), - shelfbrowser_next_itemnumber => $shelfbrowser_next_itemnumber, - shelfbrowser_prev_biblionumber => (@previous_items ? $previous_items[0]->{biblionumber} : 0), - shelfbrowser_next_biblionumber => $shelfbrowser_next_biblionumber, - PREVIOUS_SHELF_BROWSE => \@previous_items, - NEXT_SHELF_BROWSE => \@next_items, - ); } if (C4::Context->preference("BakerTaylorEnabled")) { --