From e8aef621745ae8b8cdfd65bc4a5709b340b60263 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Tue, 28 Jul 2020 21:18:20 +0200 Subject: [PATCH] Bug 26145: Add the ability to upload a cover image per item This patchset adds the ability to attach cover images at item level. This commit message will explain the different patches that are following. The main idea is to have cover images for a given item. This is useful for a bibliographic record linked with a subscription. Each item could have the cover image for the serial numbers. In this first patch there is a limitation to allow only 1 cover per item, but a later patch will remove it. That way we will take advantage of the recent work done to display nicely cover images (bug 25031), and reuse it in this development (staff interface only). In order to use a flexible and robust code, the legacy C4 code (C4::Images) has been moved to Koha::CoverImages. Also the DB table biblioimages has been renamed cover_images. Test plan (for the whole patch set): 0. Turn off AllowMultipleCovers 1. Create a new bibliographic record and items 2. Attach a cover image to the bibliographic record 3. In the item list of the bibliographic detail page, you will notice a new "Upload image" action. Select it 4. Select an image => Notice the new column in the item table 5. Upload another image => You cannot upload 2 images, you are going to replace the existing one 6. Turn on AllowMultipleCovers 7. Attach another image to the image => Notice the 2 images are displayed nicely, with navigation controls 8. Confirm you can view an image if you click on it and you can delete it 9. Test the OPAC view now => Cover image for items are displayed in the table, there is no navigation controls here however. Sponsored-by: Gerhard Sondermann Dialog e.K. (presseplus.de, presseshop.at, presseshop.ch) --- C4/Images.pm | 49 +++++++++++++++---- catalogue/detail.pl | 14 ++++++ catalogue/imageviewer.pl | 24 +++++++-- .../prog/en/modules/catalogue/detail.tt | 23 ++++++++- .../prog/en/modules/catalogue/imageviewer.tt | 2 +- .../prog/en/modules/tools/upload-images.tt | 34 ++++++++----- svc/cover_images | 13 +++-- tools/upload-cover-image.pl | 33 +++++++++---- 8 files changed, 145 insertions(+), 47 deletions(-) diff --git a/C4/Images.pm b/C4/Images.pm index 37b24179aa..811c144991 100644 --- a/C4/Images.pm +++ b/C4/Images.pm @@ -18,12 +18,11 @@ package C4::Images; # You should have received a copy of the GNU General Public License # along with Koha; if not, see . -use strict; -use warnings; -use 5.010; +use Modern::Perl; use C4::Context; use GD; +use Koha::Exceptions; use vars qw($debug $noimage @ISA @EXPORT); @@ -47,18 +46,32 @@ BEGIN { =head2 PutImage - PutImage($biblionumber, $srcimage, $replace); + PutImage({ biblionumber => $biblionumber, itemnumber => $itemnumber, src_image => $srcimage, replace => $replace }); -Stores binary image data and thumbnail in database, optionally replacing existing images for the given biblio. +Stores binary image data and thumbnail in database, optionally replacing existing images for the given biblio or item. =cut sub PutImage { - my ( $biblionumber, $srcimage, $replace ) = @_; + my ( $params ) = @_; + + my $biblionumber = $params->{biblionumber}; + my $itemnumber = $params->{itemnumber}; + my $srcimage = $params->{src_image}; + my $replace = $params->{replace}; + + Koha::Exceptions::WrongParameter->throw( + 'PutImage cannot be called with both biblionumber and itemnumber') + if $biblionumber and $itemnumber; + + Koha::Exceptions::WrongParameter->throw( + 'PutImage must be called with "replace" if itemnumber is passed. Only 1 cover per item is allowed.') + if $itemnumber and not $replace; + return -1 unless defined($srcimage); - if ($replace) { + if ($biblionumber && $replace) { foreach ( ListImagesForBiblio($biblionumber) ) { DelImage($_); } @@ -66,7 +79,7 @@ sub PutImage { my $dbh = C4::Context->dbh; my $query = -"INSERT INTO biblioimages (biblionumber, mimetype, imagefile, thumbnail) VALUES (?,?,?,?);"; +"INSERT INTO biblioimages (biblionumber, itemnumber, mimetype, imagefile, thumbnail) VALUES (?,?,?,?,?);"; my $sth = $dbh->prepare($query); my $mimetype = 'image/png' @@ -79,10 +92,10 @@ sub PutImage { ; # MAX pixel dims are 600 X 800 for full-size image... $debug and warn "thumbnail is " . length($thumbnail) . " bytes."; - $sth->execute( $biblionumber, $mimetype, $fullsize->png(), + $sth->execute( $biblionumber, $itemnumber, $mimetype, $fullsize->png(), $thumbnail->png() ); my $dberror = $sth->errstr; - warn "Error returned inserting $biblionumber.$mimetype." if $sth->errstr; + warn sprintf("Error returned inserting %s.%s.", ($biblionumber || $itemnumber, $mimetype)) if $sth->errstr; undef $thumbnail; undef $fullsize; return $dberror; @@ -135,6 +148,22 @@ sub ListImagesForBiblio { return @imagenumbers; } +=head2 GetImageForItem + my $image = GetImageForItem($itemnumber); + +Gets the image associated with a particular item. + +=cut + +sub GetImageForItem { + my ($itemnumber) = @_; + + my $dbh = C4::Context->dbh; + return $dbh->selectrow_array( + 'SELECT imagenumber FROM biblioimages WHERE itemnumber = ?', + undef, $itemnumber ); +} + =head2 DelImage my ($dberror) = DelImage($imagenumber); diff --git a/catalogue/detail.pl b/catalogue/detail.pl index f454f22c29..3246468015 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -322,6 +322,7 @@ if ($currentbranch and C4::Context->preference('SeparateHoldings')) { $template->param(SeparateHoldings => 1); } my $separatebranch = C4::Context->preference('SeparateHoldingsBranch') || 'homebranch'; +my ( $itemloop_has_images, $otheritemloop_has_images ); foreach my $item (@items) { my $itembranchcode = $item->{$separatebranch}; @@ -406,17 +407,30 @@ foreach my $item (@items) { } } + if ( C4::Context->preference("LocalCoverImages") == 1 ) { + $item->{imagenumber} = + C4::Images::GetImageForItem( $item->{itemnumber} ); + } + if ($currentbranch and C4::Context->preference('SeparateHoldings')) { if ($itembranchcode and $itembranchcode eq $currentbranch) { push @itemloop, $item; + $itemloop_has_images++ if $item->{imagenumber}; } else { push @otheritemloop, $item; + $otheritemloop_has_images++ if $item->{imagenumber}; } } else { push @itemloop, $item; + $itemloop_has_images++ if $item->{imagenumber}; } } +$template->param( + itemloop_has_images => $itemloop_has_images, + otheritemloop_has_images => $otheritemloop_has_images, +); + # Display only one tab if one items list is empty if (scalar(@itemloop) == 0 || scalar(@otheritemloop) == 0) { $template->param(SeparateHoldings => 0); diff --git a/catalogue/imageviewer.pl b/catalogue/imageviewer.pl index ac7d3204d0..d20fae1364 100755 --- a/catalogue/imageviewer.pl +++ b/catalogue/imageviewer.pl @@ -28,6 +28,7 @@ use C4::Images; use C4::Search; use Koha::Biblios; +use Koha::Items; use Koha::Patrons; my $query = new CGI; @@ -40,7 +41,8 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( } ); -my $biblionumber = $query->param('biblionumber') || $query->param('bib'); +my $itemnumber = $query->param('itemnumber'); +my $biblionumber = $query->param('biblionumber') || $query->param('bib') || Koha::Items->find($itemnumber)->biblionumber; my $imagenumber = $query->param('imagenumber'); my $biblio = Koha::Biblios->find( $biblionumber ); my $itemcount = $biblio ? $biblio->items->count : 0; @@ -65,10 +67,22 @@ if( $query->cookie("searchToOrder") ){ } if ( C4::Context->preference("LocalCoverImages") ) { - my @images = ListImagesForBiblio($biblionumber); - $template->{VARS}->{'LocalCoverImages'} = 1; - $template->{VARS}->{'images'} = \@images; - $template->{VARS}->{'imagenumber'} = $imagenumber || $images[0] || ''; + if ( $itemnumber ) { + my $image = C4::Images::GetImageForItem($itemnumber); + $template->param( + LocalCoverImages => 1, + images => [$image], + imagenumber => $imagenumber, + ); + + } else { + my @images = ListImagesForBiblio($biblionumber); + $template->param( + LocalCoverImages => 1, + images => \@images, + imagenumber => $imagenumber || $images[0] || '', + ); + } } $template->{VARS}->{'count'} = $itemcount; $template->{VARS}->{'biblionumber'} = $biblionumber; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt index 239f4f5b07..d38136fa48 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -285,6 +285,9 @@ [% IF (StaffDetailItemSelection) %][% END %] + [% IF ( tab == 'holdings' && itemloop_has_images || tab == 'otherholdings' && otheritemloop_has_images ) %] + Cover image + [% END %] [% IF ( item_level_itypes ) %]Item type[% END %] Current library Home library @@ -323,6 +326,16 @@ [% END %] + [% IF ( tab == 'holdings' && itemloop_has_images || tab == 'otherholdings' && otheritemloop_has_images ) %] + + [% IF item.imagenumber %] + + + + [% END %] + + [% END %] + [% IF ( item_level_itypes ) %] [% IF !noItemTypeImages && item.imageurl %] @@ -533,7 +546,12 @@ Note that permanent location is a code, and location may be an authval. [% IF CAN_user_editcatalogue_edit_items %] [% UNLESS item.cannot_be_edited %] - Edit +
+ Edit + +
[% END %] [% END %] @@ -541,6 +559,7 @@ Note that permanent location is a code, and location may be an authval. [% END %] + [% END %][%# end of block items_table %]
@@ -1054,7 +1073,7 @@ Note that permanent location is a code, and location may be an authval. thumbnail.find("img").css("opacity", ".2"); thumbnail.find("a.remove").html(""); $.ajax({ - url: "/cgi-bin/koha/svc/cover_images?action=delete&biblionumber=" + biblionumber + "&imagenumber=" + imagenumber, + url: "/cgi-bin/koha/svc/cover_images?action=delete&imagenumber=" + imagenumber, success: function(data) { $(data).each( function(i) { if ( this.deleted == 1 ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/imageviewer.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/imageviewer.tt index cac9877e04..a363b56d88 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/imageviewer.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/imageviewer.tt @@ -133,7 +133,7 @@ thumbnail.find("a.remove").html(""); $.ajax({ - url: "/cgi-bin/koha/svc/cover_images?action=delete&biblionumber=" + biblionumber + "&imagenumber=" + imagenumber, + url: "/cgi-bin/koha/svc/cover_images?action=delete&imagenumber=" + imagenumber, success: function(data) { $(data).each( function() { if ( this.deleted == 1 ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt index ee8296b7d4..9abce8f7ec 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt @@ -77,24 +77,34 @@
File type
    -
  1. - [% IF (filetype != 'image' ) %][% ELSE %][% END %] - -
  2. -
  3. - [% IF (filetype == 'image' ) %][% ELSE %][% END %] - -
  4. -
  5. - [% IF ( filetype == 'image' ) %][% ELSE %] -
  6. + [% UNLESS itemnumber %] +
  7. + [% IF (filetype != 'image' ) %][% ELSE %][% END %] + +
  8. +
  9. + [% IF (filetype == 'image' ) %][% ELSE %][% END %] + +
  10. +
  11. + [% IF ( filetype == 'image' ) %][% ELSE %] +
  12. + [% ELSE %] + + + + [% END %]
Options
  1. - [% IF AllowMultipleCovers == 0 %][% ELSE %][% END %] + [% IF AllowMultipleCovers == 0 OR itemnumber%] + + [% ELSE %] + + [% END %]
diff --git a/svc/cover_images b/svc/cover_images index 590a243dc8..5894410dd7 100755 --- a/svc/cover_images +++ b/svc/cover_images @@ -37,28 +37,27 @@ if ( $auth_status ne "ok" ) { } my $action = $input->param('action'); -my $biblionumber = $input->param('biblionumber'); my @imagenumbers = $input->param('imagenumber'); # Array to store the reponse JSON my $response = []; if ( $action eq "delete" ) { - # Build a hash of valid imagenumbers fr the given biblionumber - my %valid_imagenumbers = map {$_ => 1} ListImagesForBiblio($biblionumber); foreach my $imagenumber ( @imagenumbers ) { - if ( exists( $valid_imagenumbers{ $imagenumber } ) ) { + eval { DelImage($imagenumber); + }; + if ( $@ ) { push @$response, { imagenumber => $imagenumber, - deleted => 1 + deleted => 0, + error => "MSG_INVALID_IMAGENUMBER" }; } else { push @$response, { imagenumber => $imagenumber, - deleted => 0, - error => "MSG_INVALID_IMAGENUMBER" + deleted => 1 }; } } diff --git a/tools/upload-cover-image.pl b/tools/upload-cover-image.pl index b6614df6ff..735987c98e 100755 --- a/tools/upload-cover-image.pl +++ b/tools/upload-cover-image.pl @@ -46,6 +46,7 @@ use C4::Context; use C4::Auth; use C4::Output; use C4::Images; +use Koha::Items; use Koha::UploadedFiles; use C4::Log; @@ -66,6 +67,7 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( my $filetype = $input->param('filetype'); my $biblionumber = $input->param('biblionumber'); +my $itemnumber = $input->param('itemnumber'); #my $uploadfilename = $input->param('uploadfile'); # obsolete? my $replace = !C4::Context->preference("AllowMultipleCovers") || $input->param('replace'); @@ -75,8 +77,11 @@ my $sessionID = $cookies{'CGISESSID'}->value; my $error; -$template->{VARS}->{'filetype'} = $filetype; -$template->{VARS}->{'biblionumber'} = $biblionumber; +$template->param( + filetype => $filetype, + biblionumber => $biblionumber, + itemnumber => $itemnumber, +); my $total = 0; @@ -87,7 +92,7 @@ if ($fileID) { my $srcimage = GD::Image->new($fh); $fh->close if $fh; if ( defined $srcimage ) { - my $dberror = PutImage( $biblionumber, $srcimage, $replace ); + my $dberror = PutImage( { biblionumber => $biblionumber, itemnumber => $itemnumber, src_image => $srcimage, replace => $replace } ); if ($dberror) { $error = 'DBERR'; } @@ -157,9 +162,13 @@ if ($fileID) { my $srcimage = GD::Image->new("$dir/$filename"); if ( defined $srcimage ) { $total++; - my $dberror = - PutImage( $biblionumber, $srcimage, - $replace ); + my $dberror = PutImage( + { + biblionumber => $biblionumber, + src_image => $srcimage, + replace => $replace + } + ); if ($dberror) { $error = 'DBERR'; } @@ -178,10 +187,14 @@ if ($fileID) { } } } - $template->{VARS}->{'total'} = $total; - $template->{VARS}->{'uploadimage'} = 1; - $template->{VARS}->{'error'} = $error; - $template->{VARS}->{'biblionumber'} = $biblionumber; + + $template->param( + total => $total, + uploadimage => 1, + error => $error, + biblionumber => $biblionumber || Koha::Items->find($itemnumber)->biblionumber, + itemnumber => $itemnumber, + ); } output_html_with_http_headers $input, $cookie, $template->output; -- 2.20.1