476 lines
14 KiB
Perl
476 lines
14 KiB
Perl
package Shelf;
|
|
|
|
=head1 NAME
|
|
|
|
Shelf - Perl extension for Virtual Bookshelves
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use C4::Context;
|
|
use Cache::FileCache;
|
|
|
|
=head1 VERSION
|
|
|
|
$Id$
|
|
|
|
=cut
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Module for querying and stocking Virtual Bookshelves
|
|
|
|
1. can contain a list of items, a list of biblioitems, or a list of biblios
|
|
2. can have an arbitrary name, and will have a unique numerical identifier
|
|
3. will have arbitrary metadata (properties) associated with it
|
|
* Sharing information (private, only visible by the owner of the
|
|
shelf; shared with a group of patrons; public, viewable by anybody)
|
|
* Special circulation rules - Do not return to home branch, do not
|
|
circulate, reduced loan time (ie 3 day loan)
|
|
* Search query term - if the shelf is the result of a query, the
|
|
query itself can be stored with the list of books that resulted
|
|
* Creation date - useful for 'retiring' a stale cached query result
|
|
* Access information - who has "write" or "read" access to the shelf.
|
|
* Searchable - If a patron can perform a search query on the contents
|
|
of this shelf
|
|
|
|
|
|
Patrons typically will only use "biblioitem" bookshelves, and will not need to
|
|
be presented with the differences between biblioitem and item bookshelves.
|
|
|
|
|
|
Some uses for VirtualBookshelves
|
|
|
|
1. Cache search results for faster response on popular searches
|
|
2. Name search results so that patrons can pull up saved searches
|
|
3. Creation of sub-collections within a library or branch
|
|
4. replacing "itemtypes" field... this would allow an individual item to be
|
|
a member of more than one itemtype
|
|
5. store a patron's reading record (if he chooses to store such data)
|
|
6. store a patron's "To be read" list
|
|
7. A teacher of a course could add a list of books to a shelf for his course
|
|
and ask that those items be marked non-circulating so students always
|
|
have access to them at the library.
|
|
* The teacher creates the list of materials that she wants to be
|
|
non-circulating (or reduced to 3-day loan) and marks them as such
|
|
* A librarian receives a notice that a shelf requires her attention.
|
|
He can pull up a list of the contents of the shelf, the owner of
|
|
the shelf, and the reason the owner is requesting this change in
|
|
circulation rules. The librarian can approve or deny the request.
|
|
* Optionally, create an access flag that grants teachers the right to
|
|
put items on modified circulation shelves without librarian
|
|
intervention.
|
|
|
|
|
|
=cut
|
|
|
|
=head1 METHODS
|
|
|
|
=head2 C<new()>
|
|
|
|
Base constructor for the class.
|
|
|
|
my $shelf=Shelf->new(56);
|
|
will load bookshelf 56.
|
|
my $shelf=Shelf->new(-name => 'Fiction');
|
|
my $shelf=Shelf->new('Fiction');
|
|
will load the internal 'Fiction' shelf
|
|
my $shelf=Shelf->new('Favourite Books', 'sjohnson');
|
|
my $shelf=Shelf->new(-name => 'Favourite Books', -owner => 'sjohnson');
|
|
will load sjohnson's "Favourite Books" bookshelf
|
|
|
|
Any of the last four invocations will create a new shelf with the name and
|
|
owner given if one doesn't already exist.
|
|
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my $self = {};
|
|
$self->{ID} = undef;
|
|
$self->{NAME}=undef;
|
|
$self->{OWNDER}=undef;
|
|
$self->{BIBLIOCONTENTS}={};
|
|
$self->{BIBLIOITEMCONTENTS}={};
|
|
$self->{ITEMCONTENTS}={};
|
|
$self->{ATTRIBUTES}={};
|
|
$self->{CACHE}=new Cache::FileCache( { 'namespace' => 'KohaShelves' } );
|
|
|
|
if (@_) {
|
|
my $dbh=C4::Context->dbh();
|
|
shift;
|
|
if ($#_ == 0) {
|
|
$self->{ID}=shift;
|
|
# load attributes of shelf #ID
|
|
my $sth;
|
|
$sth=$dbh->prepare("select bookshelfname,bookshelfowner from bookshelves where bookshelfid=?");
|
|
$sth->execute($self->{ID});
|
|
($self->{NAME},$self->{OWNER}) = $sth->fetchrow;
|
|
$sth=$dbh->prepare("select attribute,value from bookshelfattributes where bookshelfid=?");
|
|
$sth->execute($self->{ID});
|
|
while (my ($attribute,$value) = $sth->fetchrow) {
|
|
$self->{ATTRIBUTES}->{$attribute}=$value;
|
|
}
|
|
} elsif ($#_) {
|
|
my ($name,$owner,$attributes);
|
|
if ($_[0] =~/^-/) {
|
|
my %params=@_;
|
|
$name=$params{name};
|
|
$owner=$params{owner};
|
|
$attributes=$params{attributes};
|
|
} else {
|
|
$name=shift;
|
|
$owner=shift;
|
|
$attributes=shift;
|
|
}
|
|
my $sth=$dbh->prepare("select bookshelfid from bookshelves where bookshelfname=? and bookshelfowner=?");
|
|
$sth->execute($name, $owner);
|
|
if ($sth->rows) {
|
|
($self->{ID})=$sth->fetchrow;
|
|
$sth=$dbh->prepare("select attribute,value from bookshelfattributes where bookshelfid=?");
|
|
$sth->execute($self->{ID});
|
|
while (my ($attribute,$value) = $sth->fetchrow) {
|
|
$self->{ATTRIBUTES}->{$attribute}=$value;
|
|
}
|
|
} else {
|
|
$sth=$dbh->prepare("insert into bookshelves (bookshelfname, bookshelfowner) values (?, ?)");
|
|
$sth->execute($name,$owner);
|
|
$sth=$dbh->prepare("select bookshelfid from bookshelves where bookshelfname=? and bookshelfowner=?");
|
|
$sth->execute($name,$owner);
|
|
($self->{ID})=$sth->fetchrow();
|
|
foreach my $attribute (keys %$attributes) {
|
|
my $value=$attributes->{$attribute};
|
|
$self->attribute($attribute,$value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bless($self);
|
|
return $self;
|
|
}
|
|
|
|
|
|
=head2 C<itemcontents()>
|
|
|
|
retrieve a slice of itemnumbers from a shelf.
|
|
|
|
my $arrayref = $shelf->itemcontents(-orderby=>'title',
|
|
-startat=>50,
|
|
-number=>10 );
|
|
|
|
=cut
|
|
|
|
sub itemcontents {
|
|
my $self=shift;
|
|
my ($orderby,$startat,$number);
|
|
if ($_[0]=~/^\-/) {
|
|
my %params=@_;
|
|
$orderby=$params{'-orderby'};
|
|
$startat=$params{'-startat'};
|
|
$number=$params{'-number'};
|
|
} else {
|
|
($orderby,$startat,$number)=@_;
|
|
}
|
|
$number--;
|
|
unless ($self->{ITEMCONTENTS}->{orderby}->{$orderby}) {
|
|
$self->loadcontents(-orderby=>$orderby, -startat=>$startat, -number=>$number);
|
|
}
|
|
my $endat=$startat+$number;
|
|
my @return;
|
|
foreach (@{$self->{ITEMCONTENTS}->{orderby}->{$orderby}}[$startat..$endat]) {
|
|
push @return,$_;
|
|
}
|
|
return \@return;
|
|
}
|
|
|
|
=head2 C<biblioitemcontents()>
|
|
|
|
retrieve a slice of biblioitemnumbers from a shelf.
|
|
|
|
my $arrayref = $shelf->biblioitemcontents(-orderby=>'title',
|
|
-startat=>50,
|
|
-number=>10 );
|
|
|
|
=cut
|
|
|
|
sub biblioitemcontents {
|
|
my $self=shift;
|
|
my ($orderby,$startat,$number);
|
|
if ($_[0]=~/^\-/) {
|
|
my %params=@_;
|
|
$orderby=$params{'-orderby'};
|
|
$startat=$params{'-startat'};
|
|
$number=$params{'-number'};
|
|
} else {
|
|
($orderby,$startat,$number)=@_;
|
|
}
|
|
unless ($self->{BIBLIOITEMCONTENTS}->{orderby}->{$orderby}) {
|
|
$self->loadcontents(-orderby=>$orderby, -startat=>$startat, -number=>$number);
|
|
}
|
|
my $endat=$startat+$number;
|
|
my @return;
|
|
foreach (@{$self->{BIBLIOITEMCONTENTS}->{orderby}->{$orderby}}[$startat..$endat]) {
|
|
push @return,$_;
|
|
}
|
|
return \@return;
|
|
}
|
|
|
|
=head2 C<biblioitemcontents()>
|
|
|
|
retrieve a slice of biblionumbers from a shelf.
|
|
|
|
my $arrayref = $shelf->bibliocontents(-orderby=>'title',
|
|
-startat=>50,
|
|
-number=>10 );
|
|
|
|
=cut
|
|
|
|
sub bibliocontents {
|
|
my $self=shift;
|
|
my ($orderby,$startat,$number);
|
|
if ($_[0]=~/^\-/) {
|
|
my %params=@_;
|
|
$orderby=$params{'-orderby'};
|
|
$startat=$params{'-startat'};
|
|
$number=$params{'-number'};
|
|
} else {
|
|
($orderby,$startat,$number)=@_;
|
|
}
|
|
unless ($self->{BIBLIOCONTENTS}->{orderby}->{$orderby}) {
|
|
$self->loadcontents(-orderby=>$orderby, -startat=>$startat, -number=>$number);
|
|
}
|
|
my $endat=$startat+$number;
|
|
my @return;
|
|
foreach (@{$self->{BIBLIOCONTENTS}->{orderby}->{$orderby}}[$startat..$endat]) {
|
|
push @return,$_;
|
|
}
|
|
return \@return;
|
|
}
|
|
|
|
|
|
=head2 C<itemcounter()>
|
|
|
|
returns the number of items on the shelf
|
|
|
|
my $itemcount=$shelf->itemcounter();
|
|
|
|
=cut
|
|
sub itemcounter {
|
|
my $self=shift;
|
|
unless ($self->{ITEMCONTENTS}->{orderby}->{'natural'}) {
|
|
$self->loadcontents();
|
|
}
|
|
my @temparray=@{$self->{ITEMCONTENTS}->{orderby}->{'natural'}};
|
|
return $#temparray+1;
|
|
}
|
|
|
|
sub shelfcontents {
|
|
my $self=shift;
|
|
}
|
|
|
|
|
|
=head2 C<clearcontents()>
|
|
|
|
Removes all contents from the shelf.
|
|
|
|
$shelf->clearcontents();
|
|
|
|
=cut
|
|
|
|
sub clearcontents {
|
|
my $self=shift;
|
|
my $dbh=C4::Context->dbh();
|
|
my $sth=$dbh->prepare("delete from bookshelfcontents where bookshelfid=?");
|
|
$sth->execute($self->{ID});
|
|
foreach my $level ('ITEM', 'BIBLIOITEM', 'BIBLIO') {
|
|
delete $self->{$level."CONTENTS"};
|
|
$self->{$level."CONTENTS"}={};
|
|
}
|
|
$self->clearcache();
|
|
|
|
}
|
|
|
|
|
|
|
|
=head2 C<addtoshelf()>
|
|
|
|
adds an array of items to a shelf. If any modifications are actually made to
|
|
the shelf then the per process caches and the FileCache for that shelf are
|
|
cleared.
|
|
|
|
$shelf->addtoshelf(-add => [[ 45, 54, 67], [69, 87, 143]]);
|
|
|
|
=cut
|
|
|
|
sub addtoshelf {
|
|
my $self=shift;
|
|
my $add;
|
|
if ($_[0]=~/^\-/) {
|
|
my %params=@_;
|
|
$add=$params{'-add'};
|
|
} else {
|
|
($add)=@_;
|
|
}
|
|
my $dbh=C4::Context->dbh();
|
|
my $sth;
|
|
my $bookshelfid=$self->{ID};
|
|
my $clearcache=0;
|
|
foreach (@$add) {
|
|
my ($biblionumber,$biblioitemnumber,$itemnumber) = @$_;
|
|
$sth=$dbh->prepare("select count(*) from bookshelfcontents where bookshelfid=? and itemnumber=? and biblioitemnumber=? and biblionumber=?");
|
|
$sth->execute($bookshelfid,$itemnumber,$biblioitemnumber,$biblionumber);
|
|
my $rows=$sth->fetchrow();
|
|
if ($rows==0) {
|
|
$sth=$dbh->prepare("insert into bookshelfcontents (bookshelfid,biblionumber,biblioitemnumber,itemnumber) values (?,?,?,?)");
|
|
$sth->execute($bookshelfid,$biblionumber,$biblioitemnumber,$itemnumber);
|
|
$clearcache=1;
|
|
}
|
|
}
|
|
($clearcache) && ($self->clearcache());
|
|
}
|
|
|
|
|
|
sub removefromshelf {
|
|
my $self=shift;
|
|
}
|
|
|
|
=head2 C<attribute()>
|
|
|
|
Returns or sets the value of a given attribute for the shelf.
|
|
|
|
my $loanlength=$shelf->attribute('loanlength');
|
|
$shelf->attribute('loanlength', '21 days');
|
|
|
|
|
|
=cut
|
|
|
|
sub attribute {
|
|
my $self=shift;
|
|
my ($attribute, $value);
|
|
$attribute=shift;
|
|
$value=shift;
|
|
if ($value) {
|
|
$self->{ATTRIBUTES}->{$attribute}=$value;
|
|
my $dbh=C4::Context->dbh();
|
|
my $sth=$dbh->prepare("select value from bookshelfattributes where bookshelfid=? and attribute=?");
|
|
$sth->execute($self->{ID}, $attribute);
|
|
if ($sth->rows) {
|
|
my $sti=$dbh->prepare("update bookshelfattributes set value=? where bookshelfid=? and attribute=?");
|
|
$sti->execute($value, $self->{ID}, $attribute);
|
|
} else {
|
|
my $sti=$dbh->prepare("insert into bookshelfattributes (bookshelfid, attribute, value) values (?, ?, ?)");
|
|
$sti->execute($self->{ID}, $attribute, $value);
|
|
}
|
|
}
|
|
return $self->{ATTRIBUTES}->{$attribute};
|
|
}
|
|
|
|
|
|
=head2 C<attributes()>
|
|
|
|
Returns a hash reference of the shelf attributes
|
|
|
|
my $attributes=$shelf->attributes();
|
|
my $loanlength=$attributes->{loanlength};
|
|
|
|
=cut
|
|
|
|
sub attributes {
|
|
my $self=shift;
|
|
return $self->{ATTRIBUTES};
|
|
}
|
|
|
|
=head2 C<clearcache()>
|
|
|
|
Clears the per process in-memory cache and the FileCache if any changes are
|
|
made to a shelf.
|
|
|
|
$shelf->clearshelf();
|
|
|
|
=cut
|
|
|
|
sub clearcache {
|
|
my $self=shift;
|
|
foreach my $level ('ITEM','BIBLIOITEM','BIBLIO') {
|
|
delete $self->{$level."CONTENTS"};
|
|
foreach my $sorttype (('author', 'title')) {
|
|
$self->{CACHE}->remove($self->{ID}."_".$level."CONTENTS_".$sorttype);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
=head2 C<loadcontents()>
|
|
|
|
loads the contents of a particular shelf and loads into a per process memory
|
|
cache as well as a shared Cache::FileCache.
|
|
|
|
This subroutine is normally only used internally (called by itemcontents,
|
|
biblioitemcontents, or bibliocontents).
|
|
|
|
$shelf->loadcontents(-orderby => 'author', -startat => 30, -number => 10);
|
|
|
|
|
|
=cut
|
|
|
|
sub loadcontents {
|
|
my $self=shift;
|
|
my ($orderby,$startat,$number);
|
|
if ($_[0]=~/^\-/) {
|
|
my %params=@_;
|
|
$orderby=$params{'-orderby'};
|
|
$startat=$params{'-startat'};
|
|
$number=$params{'-number'};
|
|
} else {
|
|
($orderby,$startat,$number)=@_;
|
|
}
|
|
my $bookshelfid=$self->{ID};
|
|
($orderby) || ($orderby='natural');
|
|
$self->{ITEMCONTENTS}->{orderby}->{$orderby}=$self->{CACHE}->get( "$bookshelfid\_ITEMCONTENTS_$orderby" );
|
|
$self->{BIBLIOITEMCONTENTS}->{orderby}->{$orderby}=$self->{CACHE}->get( "$bookshelfid\_BIBLIOITEMCONTENTS_$orderby" );
|
|
$self->{BIBLIOCONTENTS}->{orderby}->{$orderby}=$self->{CACHE}->get( "$bookshelfid\_BIBLIOCONTENTS_$orderby" );
|
|
if ( defined $self->{ITEMCONTENTS}->{orderby}->{$orderby}) {
|
|
return;
|
|
}
|
|
my $dbh=C4::Context->dbh();
|
|
my $sth;
|
|
my $limit='';
|
|
if ($startat && $number) {
|
|
$limit="limit $startat,$number";
|
|
}
|
|
$limit='';
|
|
my $biblionumbers;
|
|
my $biblioitemnumbers;
|
|
if ($orderby eq 'author') {
|
|
$sth=$dbh->prepare("select itemnumber,BSC.biblionumber,BSC.biblioitemnumber from bookshelfcontents BSC, biblio B where BSC.biblionumber=B.biblionumber and bookshelfid=? order by B.author $limit");
|
|
} elsif ($orderby eq 'title') {
|
|
$sth=$dbh->prepare("select itemnumber,BSC.biblionumber,BSC.biblioitemnumber from bookshelfcontents BSC, biblio B where BSC.biblionumber=B.biblionumber and bookshelfid=? order by B.title $limit");
|
|
} else {
|
|
$sth=$dbh->prepare("select itemnumber,biblionumber,biblioitemnumber from bookshelfcontents where bookshelfid=? $limit");
|
|
}
|
|
$sth->execute($bookshelfid);
|
|
my @results;
|
|
my @biblioresults;
|
|
my @biblioitemresults;
|
|
while (my ($itemnumber,$biblionumber,$biblioitemnumber) = $sth->fetchrow) {
|
|
unless ($biblionumbers->{$biblionumber}) {
|
|
$biblionumbers->{$biblionumber}=1;
|
|
push @biblioresults, $biblionumber;
|
|
}
|
|
unless ($biblioitemnumbers->{$biblioitemnumber}) {
|
|
$biblioitemnumbers->{$biblioitemnumber}=1;
|
|
push @biblioitemresults, $biblioitemnumber;
|
|
}
|
|
push @results, $itemnumber;
|
|
}
|
|
$self->{CACHE}->set("$bookshelfid\_ITEMCONTENTS_$orderby", \@results, "3 hours");
|
|
$self->{CACHE}->set("$bookshelfid\_BIBLIOITEMCONTENTS_$orderby", \@results, "3 hours");
|
|
$self->{CACHE}->set("$bookshelfid\_BIBLIOCONTENTS_$orderby", \@results, "3 hours");
|
|
$self->{ITEMCONTENTS}->{orderby}->{$orderby}=\@results;
|
|
$self->{BIBLIOOCONTENTS}->{orderby}->{$orderby}=\@biblioresults;
|
|
$self->{BIBLIOITEMCONTENTS}->{orderby}->{$orderby}=\@biblioitemresults;
|
|
}
|
|
|
|
|
|
|
|
1;
|