@@ -, +, @@ --- C4/Auth.pm | 5 +- C4/Images.pm | 155 ++++++++++++++++++ C4/UploadedFile.pm | 18 ++ .../data/mysql/atomicupdate/local_cover_images.pl | 20 +++ .../data/mysql/en/mandatory/userpermissions.sql | 1 + installer/data/mysql/kohastructure.sql | 15 ++ installer/data/mysql/sysprefs.sql | 4 +- .../intranet-tmpl/prog/en/includes/cat-toolbar.inc | 3 +- .../intranet-tmpl/prog/en/includes/tools-menu.inc | 3 + .../admin/preferences/enhanced_content.pref | 19 +++ .../prog/en/modules/tools/tools-home.tt | 5 + .../prog/en/modules/tools/upload-images.tt | 130 +++++++++++++++ tools/upload-cover-image.pl | 167 ++++++++++++++++++++ 13 files changed, 542 insertions(+), 3 deletions(-) create mode 100644 C4/Images.pm create mode 100755 installer/data/mysql/atomicupdate/local_cover_images.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt create mode 100755 tools/upload-cover-image.pl --- a/C4/Auth.pm +++ a/C4/Auth.pm @@ -389,7 +389,9 @@ sub get_template_and_user { virtualshelves => C4::Context->preference("virtualshelves"), StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"), NoZebra => C4::Context->preference('NoZebra'), - EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'), + EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'), + LocalCoverImages => C4::Context->preference('LocalCoverImages'), + AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'), ); } else { @@ -494,6 +496,7 @@ sub get_template_and_user { SyndeticsAwards => C4::Context->preference("SyndeticsAwards"), SyndeticsSeries => C4::Context->preference("SyndeticsSeries"), SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"), + OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"), ); $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic")); --- a/C4/Images.pm +++ a/C4/Images.pm @@ -0,0 +1,155 @@ +package C4::Images; +use strict; +use warnings; +use 5.010; + +use C4::Context; +use GD; + +use vars qw($debug $VERSION @ISA @EXPORT); + +BEGIN { + # set the version for version checking + $VERSION = 3.03; + require Exporter; + @ISA = qw(Exporter); + @EXPORT = qw( + &PutImage + &RetrieveImage + &ListImagesForBiblio + &DelImage + ); + $debug = $ENV{KOHA_DEBUG} || $ENV{DEBUG} || 0; +} + +=head2 PutImage + + PutImage($biblionumber, $srcimage, $replace); + +Stores binary image data and thumbnail in database, optionally replacing existing images for the given biblio. + +=cut + +sub PutImage { + my ($biblionumber, $srcimage, $replace) = @_; + + return -1 unless defined($srcimage); + + if ($replace) { + foreach (ListImagesForBiblio($biblionumber)) { + DelImage($_); + } + } + + my $dbh = C4::Context->dbh; + my $query = "INSERT INTO biblioimages (biblionumber, mimetype, imagefile, thumbnail) VALUES (?,?,?,?);"; + my $sth = $dbh->prepare($query); + + my $mimetype = 'image/png'; # GD autodetects three basic image formats: PNG, JPEG, XPM; we will convert all to PNG which is lossless... +# Check the pixel size of the image we are about to import... + my $thumbnail = _scale_image($srcimage, 140, 200); # MAX pixel dims are 140 X 200 for thumbnail... + my $fullsize = _scale_image($srcimage, 600, 800); # 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(),$thumbnail->png()); + my $dberror = $sth->errstr; + warn "Error returned inserting $biblionumber.$mimetype." if $sth->errstr; + undef $thumbnail; + undef $fullsize; + return $dberror; +} + +=head2 RetrieveImage + my ($imagedata, $error) = RetrieveImage($imagenumber); + +Retrieves the specified image. + +=cut + +sub RetrieveImage { + my ($imagenumber) = @_; + + my $dbh = C4::Context->dbh; + my $query = 'SELECT mimetype, imagefile, thumbnail FROM biblioimages WHERE imagenumber = ?'; + my $sth = $dbh->prepare($query); + $sth->execute($imagenumber); + my $imagedata = $sth->fetchrow_hashref; + if ($sth->err) { + warn "Database error!"; + return undef; + } else { + return $imagedata; + } +} + +=head2 ListImagesForBiblio + my (@images) = ListImagesForBiblio($biblionumber); + +Gets a list of all images associated with a particular biblio. + +=cut + + +sub ListImagesForBiblio { + my ($biblionumber) = @_; + + my @imagenumbers; + my $dbh = C4::Context->dbh; + my $query = 'SELECT imagenumber FROM biblioimages WHERE biblionumber = ?'; + my $sth = $dbh->prepare($query); + $sth->execute($biblionumber); + warn "Database error!" if $sth->errstr; + if (!$sth->errstr && $sth->rows > 0) { + while (my $row = $sth->fetchrow_hashref) { + push @imagenumbers, $row->{'imagenumber'}; + } + return @imagenumbers; + } else { + return undef; + } +} + +=head2 DelImage + + my ($dberror) = DelImage($imagenumber); + +Removes the image with the supplied imagenumber. + +=cut + +sub DelImage { + my ($imagenumber) = @_; + warn "Imagenumber passed to DelImage is $imagenumber" if $debug; + my $dbh = C4::Context->dbh; + my $query = "DELETE FROM biblioimages WHERE imagenumber = ?;"; + my $sth = $dbh->prepare($query); + $sth->execute($imagenumber); + my $dberror = $sth->errstr; + warn "Database error!" if $sth->errstr; + return $dberror; +} + +sub _scale_image { + my ($image, $maxwidth, $maxheight) = @_; + my ($width, $height) = $image->getBounds(); + $debug and warn "image is $width pix X $height pix."; + if ($width > $maxwidth || $height > $maxheight) { +# $debug and warn "$filename exceeds the maximum pixel dimensions of $maxwidth X $maxheight. Resizing..."; + my $percent_reduce; # Percent we will reduce the image dimensions by... + if ($width > $maxwidth) { + $percent_reduce = sprintf("%.5f",($maxwidth/$width)); # If the width is oversize, scale based on width overage... + } else { + $percent_reduce = sprintf("%.5f",($maxheight/$height)); # otherwise scale based on height overage. + } + my $width_reduce = sprintf("%.0f", ($width * $percent_reduce)); + my $height_reduce = sprintf("%.0f", ($height * $percent_reduce)); + $debug and warn "Reducing image by " . ($percent_reduce * 100) . "\% or to $width_reduce pix X $height_reduce pix"; + my $newimage = GD::Image->new($width_reduce, $height_reduce, 1); #'1' creates true color image... + $newimage->copyResampled($image,0,0,0,0,$width_reduce,$height_reduce,$width,$height); + return $newimage; + } else { + return $image; + } +} + +1; --- a/C4/UploadedFile.pm +++ a/C4/UploadedFile.pm @@ -159,6 +159,24 @@ sub name { } } +=head2 filename + + my $filename = $uploaded_file->filename(); + +Accessor method for the name by which the file is to be known. + +=cut + +sub filename { + my $self = shift; + if (@_) { + $self->{'tmp_file_name'} = shift; + $self->_serialize(); + } else { + return $self->{'tmp_file_name'}; + } +} + =head2 max_size my $max_size = $uploaded_file->max_size(); --- a/installer/data/mysql/atomicupdate/local_cover_images.pl +++ a/installer/data/mysql/atomicupdate/local_cover_images.pl @@ -0,0 +1,20 @@ +#! /usr/bin/perl +use strict; +use warnings; +use C4::Context; +my $dbh=C4::Context->dbh; + +$dbh->do( q|CREATE TABLE `biblioimages` ( + `imagenumber` int(11) NOT NULL AUTO_INCREMENT, + `biblionumber` int(11) NOT NULL, + `mimetype` varchar(15) NOT NULL, + `imagefile` mediumblob NOT NULL, + `thumbnail` mediumblob NOT NULL, + PRIMARY KEY (`imagenumber`), + CONSTRAINT `bibliocoverimage_fk1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8|); +$dbh->do( q|INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('OPACLocalCoverImages','0','Display local cover images on OPAC search and details pages.','1','YesNo')|); +$dbh->do( q|INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('LocalCoverImages','0','Display local cover images on intranet search and details pages.','1','YesNo')|); +$dbh->do( q|INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('AllowMultipleCovers','0','Allow multiple cover images to be attached to each bibliographic record.','1','YesNo')|); +$dbh->do( q|INSERT INTO permissions (module_bit, code, description) VALUES (13, 'upload_local_cover_images', 'Upload local cover images')|); +print "Upgrade done (Added support for local cover images)\n"; --- a/installer/data/mysql/en/mandatory/userpermissions.sql +++ a/installer/data/mysql/en/mandatory/userpermissions.sql @@ -36,6 +36,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES (13, 'manage_csv_profiles', 'Manage CSV export profiles'), (13, 'moderate_tags', 'Moderate patron tags'), (13, 'rotating_collections', 'Manage rotating collections'), + (13, 'upload_local_cover_images', 'Upload local cover images'), (15, 'check_expiration', 'Check the expiration of a serial'), (15, 'claim_serials', 'Claim missing serials'), (15, 'create_subscription', 'Create a new subscription'), --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -2667,6 +2667,21 @@ CREATE TABLE `fieldmapping` ( -- koha to keyword mapping PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table structure for table `bibliocoverimage` +-- + +DROP TABLE IF EXISTS `bibliocoverimage`; + +CREATE TABLE `bibliocoverimage` ( + `imagenumber` int(11) NOT NULL AUTO_INCREMENT, + `biblionumber` int(11) NOT NULL, + `mimetype` varchar(15) NOT NULL, + `imagefile` mediumblob NOT NULL, + `thumbnail` mediumblob NOT NULL, + PRIMARY KEY (`imagenumber`), + CONSTRAINT `bibliocoverimage_fk1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; --- a/installer/data/mysql/sysprefs.sql +++ a/installer/data/mysql/sysprefs.sql @@ -328,4 +328,6 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES(' INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('OpacKohaUrl','1',"Show 'Powered by Koha' text on OPAC footer.",NULL,NULL); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('EasyAnalyticalRecords','0','If on, display in the catalogue screens tools to easily setup analytical record relationships','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacShowRecentComments',0,'If ON a link to recent comments will appear in the OPAC masthead',NULL,'YesNo'); - +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('OPACLocalCoverImages','0','Display local cover images on OPAC search and details pages.','1','YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('LocalCoverImages','0','Display local cover images on intranet details pages.','1','YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('AllowMultipleCovers','0','Allow multiple cover images to be attached to each bibliographic record.','1','YesNo'); --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -101,7 +101,8 @@ function confirm_items_deletion() { [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]{ text: _("Edit Record"), url: "/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=[% biblionumber %]&frameworkcode=&op=" },[% END %] [% IF ( CAN_user_editcatalogue_edit_items ) %]{ text: _("Edit Items"), url: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber=[% biblionumber %]" },[% END %] [% IF ( CAN_user_editcatalogue_edit_items ) %]{ text: _("Attach Item"), url: "/cgi-bin/koha/cataloguing/moveitem.pl?biblionumber=[% biblionumber %]" },[% END %] - [% IF ( EasyAnalyticalRecords ) %][% IF ( CAN_user_editcatalogue_edit_items ) %]{ text: _("Link to Host Item"), url: "/cgi-bin/koha/cataloguing/linkitem.pl?biblionumber=[% biblionumber %]" },[% END %][% END %] + [% IF ( EasyAnalyticalRecords ) %][% IF ( CAN_user_editcatalogue_edit_items ) %]{ text: _("Link to Host Item"), url: "/cgi-bin/koha/cataloguing/linkitem.pl?biblionumber=[% biblionumber %]" },[% END %][% END %] + [% IF ( LocalCoverImages ) %][% IF ( CAN_user_tools_upload_local_cover_images ) %]{ text: _("Upload Image"), url: "/cgi-bin/koha/tools/upload-cover-image.pl?biblionumber=[% biblionumber %]&filetype=image" },[% END %][% END %] [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]{ text: _("Edit as New (Duplicate)"), url: "/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=[% biblionumber %]&frameworkcode=&op=duplicate" },[% END %] [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]{ text: _("Replace Record via Z39.50"), onclick: {fn: PopupZ3950 } },[% END %] [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]{ text: _("Delete Record"), onclick: {fn: confirm_deletion }[% IF ( count ) %],id:'disabled'[% END %] },[% END %] --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -70,6 +70,9 @@ [% IF ( CAN_user_tools_manage_staged_marc ) %]
  • Staged MARC management
  • [% END %] + [% IF ( CAN_user_tools_upload_local_cover_images ) %] +
  • Upload Local Cover Image
  • + [% END %]
    Additional Tools