Bug 1633: [SIGNED-OFF] Add support for uploading images to Koha
authorJared Camins-Esakov <jcamins@cpbibliography.com>
Tue, 13 Dec 2011 13:20:41 +0000 (08:20 -0500)
committerPaul Poulain <paul.poulain@biblibre.com>
Tue, 24 Jan 2012 10:16:02 +0000 (11:16 +0100)
A frequently-requested feature for Koha, especially by special libraries, is
the ability to upload local cover images into Koha.

This patch adds a bibliocoverimage table, and image handling code in the
C4::Images module. Key features of the implementation include:
1. The ability to have multiple cover images for a biblio
2. Handling for "full size" (800x600) and thumbnail-size (200x140) images
3. Uploading images directly from the record view

The image display functionality by Koustubha Kale of Anant Corporation will
follow in a second patch.

Special thanks to Koustubha Kale and Anant Corporation for the initial
implementation of local cover images, and to Chris Nighswonger of Foundation
Bible College for his prior work on patron images.

Signed-off-by: Jared Camins-Esakov <jcamins@cpbibliography.com>
Signed-off-by: Magnus Enger <magnus@enger.priv.no>
Will add comments on Bugzilla.

Patch failed to apply because installer/data/mysql/sysprefs.sql had changed in master.
Corrected the same with this new patch.

13 files changed:
C4/Auth.pm
C4/Images.pm [new file with mode: 0644]
C4/UploadedFile.pm
installer/data/mysql/atomicupdate/local_cover_images.pl [new file with mode: 0755]
installer/data/mysql/en/mandatory/userpermissions.sql
installer/data/mysql/kohastructure.sql
installer/data/mysql/sysprefs.sql
koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc
koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref
koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt [new file with mode: 0644]
tools/upload-cover-image.pl [new file with mode: 0755]

index 3439948..42deeb3 100755 (executable)
@@ -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"));
diff --git a/C4/Images.pm b/C4/Images.pm
new file mode 100644 (file)
index 0000000..99d0198
--- /dev/null
@@ -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;
index da29c1f..e8c9080 100644 (file)
@@ -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();
diff --git a/installer/data/mysql/atomicupdate/local_cover_images.pl b/installer/data/mysql/atomicupdate/local_cover_images.pl
new file mode 100755 (executable)
index 0000000..a698e7f
--- /dev/null
@@ -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";
index ec61ea0..873089a 100644 (file)
@@ -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'),
index 5673e35..336aaa1 100644 (file)
@@ -2670,6 +2670,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 */;
index 918df5c..2a59b47 100755 (executable)
@@ -330,3 +330,6 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES
 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 ('CircAutoPrintQuickSlip', '1', 'Choose what should happen when an empty barcode field is submitted in circulation: Display a print quick slip window or Clear the screen.',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');
index 413d7f4..4276133 100644 (file)
@@ -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 %]&amp;frameworkcode=&amp;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 %]&amp;frameworkcode=&amp;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 %]
index e3896cc..8a739d5 100644 (file)
@@ -70,6 +70,9 @@
     [% IF ( CAN_user_tools_manage_staged_marc ) %]
        <li><a href="/cgi-bin/koha/tools/manage-marc-import.pl">Staged MARC management</a></li>
     [% END %]
+    [% IF ( CAN_user_tools_upload_local_cover_images ) %]
+       <li><a href="/cgi-bin/koha/tools/upload-cover-image.pl">Upload Local Cover Image</a></li>
+    [% END %]
 </ul>
 <h5>Additional Tools</h5>
 <ul>
index 171b5f4..f5828ae 100644 (file)
@@ -311,3 +311,22 @@ Enhanced Content:
             - pref: TagsExternalDictionary
               class: file
             - on the server to be approved without moderation.
+    Local Cover Images:
+        -
+            - pref: LocalCoverImages
+              choices:
+                  yes: Display
+                  no: "Don't display"
+            - local cover images on intranet search and details pages.
+        -
+            - pref: OPACLocalCoverImages
+              choices:
+                  yes: Display
+                  no: "Don't display"
+            - local cover images on OPAC search and details pages.
+        -
+            - pref: AllowMultipleCovers
+              choices:
+                  yes: Allow
+                  no: "Don't allow"
+            - multiple images to be attached to each bibliographic record.
index 05bdb47..b71a1ee 100644 (file)
     <dd>Managed staged MARC records, including completing and reversing imports</dd>
     [% END %]
 
+    [% IF ( CAN_user_tools_upload_local_cover_images ) %]
+    <dt><a href="/cgi-bin/koha/tools/upload-cover-image.pl">Upload Local Cover Image</a></dt>
+    <dd>Utility to upload scanned cover images for display in OPAC</dd>
+    [% END %]
+
 </dl>
 </div>
 
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
new file mode 100644 (file)
index 0000000..36d6d37
--- /dev/null
@@ -0,0 +1,130 @@
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; Upload Images</title>
+[% INCLUDE 'doc-head-close.inc' %]
+[% INCLUDE 'file-upload.inc' %]
+[% INCLUDE 'background-job.inc' %]
+<style type="text/css">
+       #uploadpanel,#fileuploadstatus,#fileuploadfailed,#jobpanel,#jobstatus,#jobfailed { display : none; }
+       #fileuploadstatus,#jobstatus { margin:.4em; }
+       #fileuploadprogress,#jobprogress{ width:150px;height:10px;border:1px solid #666;background:url('/intranet-tmpl/prog/img/progress.png') -300px 0px no-repeat; }</style>
+<script type="text/javascript">
+//<![CDATA[
+$(document).ready(function(){
+       $("#processfile").hide();
+       $("#zipfile").click(function(){
+               $("#bibnum").hide();
+       });
+       $("#image").click(function(){
+               $("#bibnum").show();
+       });
+});
+function CheckForm(f) {
+    if ($("#fileToUpload").value == '') {
+        alert(_('Please upload a file first.'));
+    } else {
+        return submitBackgroundJob(f);
+    }
+    return false;
+}
+
+//]]>
+</script>
+</head>
+<body>
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; [% IF ( uploadimage ) %]<a href="/cgi-bin/koha/tools/upload-cover-image.pl">Upload Local Cover Image</a> &rsaquo; Upload Results[% ELSE %]Upload Local Cover Image[% END %]</div>
+
+<div id="doc3" class="yui-t2">
+
+   <div id="bd">
+       <div id="yui-main">
+       <div class="yui-b">
+
+<h1>Upload Local Cover Image</h1>
+[% IF ( uploadimage ) %]
+<p>Image upload results :</p>
+<ul>
+       <li>[% total %]  images found</li>
+    [% IF ( error ) %]
+    <div class="dialog alert">
+    [% IF ( error == 'UZIPFAIL' ) %]<p><b>Failed to unzip archive.<br />Please ensure you are uploading a valid zip file and try again.</b></p>
+    [% ELSIF ( error == 'OPNLINK' ) %]<p><b>Cannot open folder index (idlink.txt or datalink.txt) to read.<br />Please verify that it exists.</b></p>
+    [% ELSIF ( error == 'OPNIMG' ) %]<p><b>Cannot process file as an image.<br />Please ensure you only upload GIF, JPEG, PNG, or XPM images.</b></p>
+    [% ELSIF ( error == 'DELERR' ) %]<p><b>Unrecognized or missing field delimiter.<br />Please verify that you are using either a single quote or a tab.</b></p>
+    [% ELSIF ( error == 'DBERR' ) %]<p><b>Unable to save image to database.</b></p>
+    [% ELSE %]<p><b>An unknown error has occurred.<br />Please review the error log for more details.</b></p>[% END %]
+    </div>
+    </li>
+    [% END %]
+    <li><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber %]">View final record</a></li>
+       <li><a href="/cgi-bin/koha/tools/tools-home.pl">Back</a></li>
+</ul>
+<hr />
+[% END %]
+<ul>
+       <li>Select an image file or ZIP file to upload. The tool will accept images in GIF, JPEG, PNG, and XPM formats.</li>
+</ul>
+<form method="post" action="[% SCRIPT_NAME %]" id="uploadfile" enctype="multipart/form-data">
+<fieldset class="rows" id="uploadform">
+<legend>Upload images</legend>
+<ol>
+       <li>
+        <div id="fileuploadform">
+               <label for="fileToUpload">Select the file to upload: </label>
+               <input type="file" id="fileToUpload" name="fileToUpload" />
+        </div> </li>
+</ol>
+  <fieldset class="action"><button class="submit" onclick="return ajaxFileUpload();">Upload file</button></fieldset>
+</fieldset>
+
+        <div id="uploadpanel"><div id="fileuploadstatus">Upload progress: <div id="fileuploadprogress"></div> <span id="fileuploadpercent">0</span>%</div>
+        <div id="fileuploadfailed"></div></div>
+</form>
+
+    <form method="post" id="processfile" action="[% SCRIPT_NAME %]" enctype="multipart/form-data">
+<fieldset class="rows">
+        <input type="hidden" name="uploadedfileid" id="uploadedfileid" value="" />
+        <input type="hidden" name="runinbackground" id="runinbackground" value="" />
+        <input type="hidden" name="completedJobID" id="completedJobID" value="" />
+       </fieldset>
+  <fieldset class="rows">
+    <legend>File type</legend>
+    <ol>
+      <li class="radio">
+        <input type="radio" id="zipfile" name="filetype" value="zip" [% IF (filetype != 'image' ) %]checked="checked"[% END %] />
+        <label for="zipfile">ZIP file</label>
+      </li>
+      <li class="radio">
+        <input type="radio" id="image" name="filetype" value="image" [% IF (filetype == 'image' ) %]checked="checked"[% END %] />
+        <label for="imagefile">Image file</label>
+      </li>
+      <li class="radio">
+        [% IF ( filetype == 'image' ) %]<span id="bibnum">[% ELSE %]<span id="bibnum" style="display: none">[% END %]<label for="biblionumber">Enter cover biblionumber: </label><input type="text" id="biblionumber" name="biblionumber" value="[% biblionumber %]" size="15" /></span>
+      </li>
+    </ol>
+  </fieldset>
+  <fieldset class="rows">
+    <legend>Options</legend>
+    <ol>
+      <li class="checkbox">
+        <input type="checkbox" id="replace" name="replace" [% IF AllowMultipleCovers == 0 %]checked="checked" disabled="disabled"[% END %] />
+        <label for="replace">Replace existing covers</label>
+      </li>
+    </ol>
+  </fieldset>
+  <fieldset class="action"><input type="submit" value="Process images" /></fieldset>
+
+       <div id="jobpanel"><div id="jobstatus">Job progress: <div id="jobprogress"></div> <span id="jobprogresspercent">0</span>%</div>
+     <div id="jobfailed"></div></div>
+
+</form>
+
+</div>
+</div>
+<div class="yui-b">
+[% INCLUDE 'tools-menu.inc' %]
+</div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/tools/upload-cover-image.pl b/tools/upload-cover-image.pl
new file mode 100755 (executable)
index 0000000..ac9661e
--- /dev/null
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+#
+# Copyright 2011 C & P Bibliography Services
+#
+# 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., 59 Temple Place,
+# Suite 330, Boston, MA  02111-1307 USA
+#
+#
+#
+=head1 NAME
+
+upload-cover-image.pl - Script for handling uploading of both single and bulk coverimages and importing them into the database.
+
+=head1 SYNOPSIS
+
+upload-cover-image.pl
+
+=head1 DESCRIPTION
+
+This script is called and presents the user with an interface allowing him/her to upload a single cover image or bulk cover images via a zip file.
+Images will be resized into thumbnails of 140x200 pixels and larger images of
+800x600 pixels. If the images that are uploaded are larger, they will be
+resized, maintaining aspect ratio.
+
+=cut
+
+
+use strict;
+use warnings;
+
+use File::Temp;
+use CGI;
+use GD;
+use C4::Context;
+use C4::Auth;
+use C4::Output;
+use C4::Images;
+use C4::UploadedFile;
+
+my $debug = 1;
+
+my $input = new CGI;
+
+my $fileID=$input->param('uploadedfileid');
+my ($template, $loggedinuser, $cookie)
+       = get_template_and_user({template_name => "tools/upload-images.tmpl",
+                                       query => $input,
+                                       type => "intranet",
+                                       authnotrequired => 0,
+                                       flagsrequired => { tools => 'upload_cover_images'},
+                                       debug => 0,
+                                       });
+
+my $filetype            = $input->param('filetype');
+my $biblionumber        = $input->param('biblionumber');
+my $uploadfilename      = $input->param('uploadfile');
+my $replace             = $input->param('replace');
+my $op                  = $input->param('op');
+my %cookies             = parse CGI::Cookie($cookie);
+my $sessionID           = $cookies{'CGISESSID'}->value;
+
+my $error;
+
+$template->{VARS}->{'filetype'} = $filetype;
+$template->{VARS}->{'biblionumber'} = $biblionumber;
+
+my $total = 0;
+
+if ($fileID) {
+    my $uploaded_file = C4::UploadedFile->fetch($sessionID, $fileID);
+    if ($filetype eq 'image') {
+        my $fh = $uploaded_file->fh();
+        my $srcimage = GD::Image->new($fh);
+        if (defined $srcimage) {
+            my $dberror = PutImage($biblionumber, $srcimage, $replace);
+            if ($dberror) {
+                $error = 'DBERR';
+            } else {
+                $total = 1;
+            }
+        } else {
+            $error = 'OPNIMG';
+        }
+        undef $srcimage;
+    } else {
+        my $filename = $uploaded_file->filename();
+        my $dirname = File::Temp::tempdir( CLEANUP => 1);
+        unless (system("unzip", $filename,  '-d', $dirname) == 0) {
+            $error = 'UZIPFAIL';
+        } else {
+            my @directories;
+            push @directories, "$dirname";
+            foreach my $recursive_dir ( @directories ) {
+                my $dir;
+                opendir $dir, $recursive_dir;
+                while ( my $entry = readdir $dir ) {
+                    push @directories, "$recursive_dir/$entry" if ( -d "$recursive_dir/$entry" and $entry !~ /^[._]/ );
+                }
+                closedir $dir;
+            }
+            foreach my $dir ( @directories ) {
+                my $file;
+                if ( -e "$dir/idlink.txt" ) {
+                    $file = "$dir/idlink.txt";
+                } elsif ( -e "$dir/datalink.txt" ) {
+                    $file = "$dir/datalink.txt";
+                } else {
+                    next;
+                }
+                if (open (FILE, $file)) {
+                    while (my $line = <FILE>) {
+                        my $delim = ($line =~ /\t/) ? "\t" : ($line =~ /,/) ? "," : "";
+                        #$debug and warn "Delimeter is \'$delim\'";
+                        unless ( $delim eq "," || $delim eq "\t" ) {
+                            warn "Unrecognized or missing field delimeter. Please verify that you are using either a ',' or a 'tab'";
+                            $error = 'DELERR';
+                        } else {
+                            ($biblionumber, $filename) = split $delim, $line;
+                            $biblionumber =~ s/[\"\r\n]//g;  # remove offensive characters
+                            $filename   =~ s/[\"\r\n\s]//g;
+                            my $srcimage = GD::Image->new("$dir/$filename");
+                            if (defined $srcimage) {
+                                $total++;
+                                my $dberror = PutImage($biblionumber, $srcimage, $replace);
+                                if ($dberror) {
+                                    $error = 'DBERR';
+                                }
+                            } else {
+                                $error = 'OPNIMG';
+                            }
+                            undef $srcimage;
+                        }
+                    }
+                    close(FILE);
+                } else {
+                    $error = 'OPNLINK';
+                }
+            }
+        }
+    }
+    $template->{VARS}->{'total'} = $total;
+    $template->{VARS}->{'uploadimage'} = 1;
+    $template->{VARS}->{'error'} = $error;
+    $template->{VARS}->{'biblionumber'} = $biblionumber;
+}
+
+output_html_with_http_headers $input, $cookie, $template->output;
+
+exit 0;
+
+=head1 AUTHORS
+
+Written by Jared Camins-Esakov of C & P Bibliography Services, in part based on
+code by Koustubha Kale of Anant Corporation and Chris Nighswonger of Foundation
+Bible College.
+
+=cut