From c40201fd63ed3481b961694d7ae9753de95df418 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy Date: Sat, 8 Aug 2015 17:28:25 +0200 Subject: [PATCH] Bug 14321: Redirect upload plugin to general upload script Content-Type: text/plain; charset=utf-8 This patch makes the following changes to achieve that: [1] Adds a tools/upload script and template. It allows to upload multiple files at once. [2] Adds additional parameter to js function AjaxUpload (for category and public column). [3] Adds methods to Upload: count, delete, getCategories, httpheaders. [4] Makes upload-file return error messages in JSON. For a multiple upload, we could have some files with errors and others without errors. The upload is now marked as Failed only if there was no upload at all. [5] Adds decode_utf8 statement for UTF-8 chars in filenames (in the CGI hook). Note that we do not want the -utf8 flag here for binary uploads. [6] The upload plugin is converted to use tools/upload with plugin param. Deleting an upload is now presented via the search results form. NOTE: A unit test is supplied in a follow-up patch. Test plan: [1] Upload three files via tools/upload.pl with a category and marked as public. Check the results in the table. [2] Pick one new file and one of the files of step 1. Upload them in the same category. One upload should succeed. Check for reported error. [3] Upload a file via stage-marc-import. Stage it. [4] Go to Cataloguing editor. Connect upload.pl to field 856$u. In an empty 856$u, click the tag editor. Upload a file and click Choose. Save the record. Open the record in the OPAC and click the link. Copy this link to your clipboard for step 5. [5] Go back to editor. Click the tag editor on the same 856 field. Choose for Delete. Open the link in your clipboard again. [6] Check the process of upload, search, download and delete of an upload with some diacritical characters in the filename. (Bonus points for adding special chars in the category code.) --- Koha/Upload.pm | 123 +++++++- cataloguing/value_builder/upload.pl | 153 ++------- koha-tmpl/intranet-tmpl/prog/en/js/file-upload.js | 6 +- .../prog/en/modules/offline_circ/process_koc.tt | 2 +- .../prog/en/modules/tools/stage-marc-import.tt | 2 +- .../prog/en/modules/tools/upload-images.tt | 2 +- .../intranet-tmpl/prog/en/modules/tools/upload.tt | 332 ++++++++++++++++++++ tools/upload-file.pl | 41 ++- tools/upload.pl | 98 ++++++ 9 files changed, 600 insertions(+), 159 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload.tt create mode 100755 tools/upload.pl diff --git a/Koha/Upload.pm b/Koha/Upload.pm index d382699..1468a4f 100644 --- a/Koha/Upload.pm +++ b/Koha/Upload.pm @@ -51,6 +51,7 @@ use constant BYTES_DIGEST => 2048; use Modern::Perl; use CGI; # no utf8 flag, since it may interfere with binary uploads use Digest::MD5; +use Encode; use File::Spec; use IO::File; use Time::HiRes; @@ -58,6 +59,7 @@ use Time::HiRes; use base qw(Class::Accessor); use C4::Context; +use C4::Koha; __PACKAGE__->mk_ro_accessors( qw||); @@ -94,6 +96,17 @@ sub cgi { } } +=head2 count + + Returns number of uploaded files without errors + +=cut + +sub count { + my ( $self ) = @_; + return scalar grep { !exists $self->{files}->{$_}->{errcode} } keys $self->{files}; +} + =head2 result Returns new object based on Class::Accessor. @@ -102,8 +115,10 @@ sub cgi { sub result { my ( $self ) = @_; - my @a = map { $self->{files}->{$_}->{id} } keys $self->{files}; - return join ',', @a; + my @a = map { $self->{files}->{$_}->{id} } + grep { !exists $self->{files}->{$_}->{errcode} } + keys $self->{files}; + return @a? ( join ',', @a ): undef; } =head2 err @@ -136,11 +151,17 @@ sub get { my ( @rv, $res); foreach my $r ( @$temp ) { undef $res; + foreach( qw[id hashvalue filesize categorycode public] ) { + $res->{$_} = $r->{$_}; + } $res->{name} = $r->{filename}; - $res->{path}= $self->_full_fname($r); + $res->{path} = $self->_full_fname($r); if( $res->{path} && -r $res->{path} ) { - $res->{fh} = IO::File->new( $res->{path}, "r" ) - if $params->{filehandle}; + if( $params->{filehandle} ) { + my $fh = IO::File->new( $res->{path}, "r" ); + $fh->binmode if $fh; + $res->{fh} = $fh; + } push @rv, $res; } else { $self->{files}->{ $r->{filename} }->{errcode}=5; #not readable @@ -150,9 +171,59 @@ sub get { return wantarray? @rv: $res; } +=head2 delete + + Returns array of deleted filenames or undef. + Since it now only accepts id as parameter, you should not expect more + than one filename. + +=cut + +sub delete { + my ( $self, $params ) = @_; + return if !$params->{id}; + my @res; + my $temp = $self->_lookup({ id => $params->{id} }); + foreach( @$temp ) { + my $d = $self->_delete( $_ ); + push @res, $d if $d; + } + return if !@res; + return @res; +} + sub DESTROY { } +# ************** HELPER ROUTINES / CLASS METHODS ****************************** + +=head2 getCategories + + getCategories returns a list of upload category codes and names + +=cut + +sub getCategories { + my ( $class ) = @_; + my $cats = C4::Koha::GetAuthorisedValues('UPLOAD'); + [ map {{ code => $_->{authorised_value}, name => $_->{lib} }} @$cats ]; +} + +=head2 httpheaders + + httpheaders returns http headers for a retrievable upload + Will be extended by report 14282 + +=cut + +sub httpheaders { + my ( $class, $name ) = @_; + return ( + '-type' => 'application/octet-stream', + '-attachment' => $name, + ); +} + # ************** INTERNAL ROUTINES ******************************************** sub _init { @@ -192,7 +263,9 @@ sub _create_file { } else { my $dir = $self->_dir; my $fn = $self->{files}->{$filename}->{hash}. '_'. $filename; - if( -e "$dir/$fn" ) { + if( -e "$dir/$fn" && @{ $self->_lookup({ + hashvalue => $self->{files}->{$filename}->{hash} }) } ) { + # if the file exists and it is registered, then set error $self->{files}->{$filename}->{errcode} = 1; #already exists return; } @@ -232,6 +305,7 @@ sub _full_fname { sub _hook { my ( $self, $filename, $buffer, $bytes_read, $data ) = @_; + $filename= Encode::decode_utf8( $filename ); # UTF8 chars in filename $self->_compute( $filename, $buffer ); my $fh = $self->_fh( $filename ) // $self->_create_file( $filename ); print $fh $buffer if $fh; @@ -269,19 +343,46 @@ sub _register { sub _lookup { my ( $self, $params ) = @_; my $dbh = C4::Context->dbh; - my $sql = 'SELECT id,hashvalue,filename,dir,categorycode '. + my $sql = 'SELECT id,hashvalue,filename,dir,filesize,categorycode,public '. 'FROM uploaded_files '; + my @pars; if( $params->{id} ) { - $sql.= "WHERE id=?"; - } else { + return [] if $params->{id} !~ /^\d+(,\d+)*$/; + $sql.= "WHERE id IN ($params->{id})"; + @pars = (); + } elsif( $params->{hashvalue} ) { $sql.= "WHERE hashvalue=?"; + @pars = ( $params->{hashvalue} ); + } elsif( $params->{term} ) { + $sql.= "WHERE (filename LIKE ? OR hashvalue LIKE ?)"; + @pars = ( '%'.$params->{term}.'%', '%'.$params->{term}.'%' ); + } else { + return []; } $sql.= $self->{public}? " AND public=1": ''; - my $temp= $dbh->selectall_arrayref( $sql, { Slice => {} }, - ( $params->{id} // $params->{hashvalue} // 0 ) ); + $sql.= ' ORDER BY id'; + my $temp= $dbh->selectall_arrayref( $sql, { Slice => {} }, @pars ); return $temp; } +sub _delete { + my ( $self, $rec ) = @_; + my $dbh = C4::Context->dbh; + my $sql = 'DELETE FROM uploaded_files WHERE id=?'; + my $file = $self->_full_fname($rec); + if( !-e $file ) { # we will just delete the record + # TODO Should we add a trace here for the missing file? + $dbh->do( $sql, undef, ( $rec->{id} ) ); + return $rec->{filename}; + } elsif( unlink($file) ) { + $dbh->do( $sql, undef, ( $rec->{id} ) ); + return $rec->{filename}; + } + $self->{files}->{ $rec->{filename} }->{errcode} = 7; + #NOTE: errcode=6 is used to report successful delete (see template) + return; +} + sub _compute { # Computes hash value when sub hook feeds the first block # For temporary files, the id is made unique with time diff --git a/cataloguing/value_builder/upload.pl b/cataloguing/value_builder/upload.pl index 72bed79..e26bca7 100755 --- a/cataloguing/value_builder/upload.pl +++ b/cataloguing/value_builder/upload.pl @@ -4,6 +4,7 @@ # This file is part of Koha. # +# Copyright (C) 2015 Rijksmuseum # Copyright (C) 2011-2012 BibLibre # # Koha is free software; you can redistribute it and/or modify it @@ -20,145 +21,33 @@ # along with Koha; if not, see . use Modern::Perl; -use CGI qw/-utf8/; -use C4::Auth; -use C4::Context; -use C4::Output; -use C4::UploadedFiles; +# This plugin does not use the plugin launcher. It refers to tools/upload.pl. +# That script and template support using it as a plugin. + +# If the plugin is called with the pattern [id=some_hashvalue] in the +# corresponding field, it starts the upload script as a search, providing +# the possibility to delete the uploaded file. If the field is empty, you +# can upload a new file. my $builder = sub { my ( $params ) = @_; - my $function_name = $params->{id}; - my $res = " - -"; - return $res; -}; - -my $launcher = sub { - my ( $params ) = @_; - my $input = $params->{cgi}; - my $index = $input->param('index'); - my $id = $input->param('id'); - my $delete = $input->param('delete'); - my $uploaded_file = $input->param('uploaded_file'); - my $from_popup = $input->param('from_popup'); - my $isfileuploadurl = $input->param('IsFileUploadUrl'); - my $dangling = C4::UploadedFiles::DanglingEntry($id,$isfileuploadurl); - my $template_name; - if ($delete || ($id && ($dangling==0 || $dangling==1))) { - $template_name = "upload_delete_file.tt"; - } - else { - $template_name = "upload.tt"; - } - - my ( $template, $loggedinuser, $cookie ) = get_template_and_user( - { template_name => "cataloguing/value_builder/$template_name", - query => $input, - type => "intranet", - authnotrequired => 0, - flagsrequired => { editcatalogue => '*' }, - debug => 1, - } - ); - - if ($dangling==2) { - $template->param( dangling => 1 ); - } - - # Dealing with the uploaded file - my $dir = $input->param('uploadcategory'); - if ($uploaded_file and $dir) { - my $fh = $input->upload('uploaded_file'); - - $id = C4::UploadedFiles::UploadFile($uploaded_file, $dir, $fh->handle); - my $OPACBaseURL = C4::Context->preference('OPACBaseURL') // ''; - $OPACBaseURL =~ s#/$##; - if (!$OPACBaseURL) { - $template->param(MissingURL => 1); - } - if($id && $OPACBaseURL) { - my $return = "$OPACBaseURL/cgi-bin/koha/opac-retrieve-file.pl?id=$id"; - $template->param( - success => 1, - return => $return, - uploaded_file => $uploaded_file, - ); - } else { - $template->param(error => 1); - } - } elsif ($delete || ($id && ($dangling==0 || $dangling==1))) { - # If there's already a file uploaded for this field, - # We handle its deletion - if ($delete) { - if(C4::UploadedFiles::DelUploadedFile($id)==0) {; - $template->param(error => 1); + var str = document.getElementById(index).value; + var myurl, term; + if( str && str.match(/id=([0-9a-f]+)/) ) { + term = RegExp.\$1; + myurl = '../tools/upload.pl?op=search&index='+index+'&term='+term+'&plugin=1'; } else { - $template->param(success => 1); + myurl = '../tools/upload.pl?op=new&index='+index+'&plugin=1'; } + window.open( myurl, 'tag_editor', 'width=800,height=400,toolbar=false,scrollbars=yes' ); } - } else { - my $upload_path = C4::Context->config('upload_path'); - if ($upload_path) { - my $filefield = CGI::filefield( - -name => 'uploaded_file', - -size => 50, - ); - $template->param( - filefield => $filefield, - uploadcategories => C4::UploadedFiles::getCategories(), - ); - } else { - $template->param( error_upload_path_not_configured => 1 ); - } - - if (!$uploaded_file && !$dir && $from_popup) { - $template->param(error_nothing_selected => 1); - } - elsif (!$uploaded_file && $dir) { - $template->param(error_no_file_selected => 1); - } - if ($uploaded_file and not $dir) { - $template->param(error_no_dir_selected => 1); - } - - } - - $template->param( - index => $index, - id => $id, - ); - - output_html_with_http_headers $input, $cookie, $template->output; + +SCRIPT }; -return { builder => $builder, launcher => $launcher }; - -1; - -__END__ - -=head1 upload.pl - -This plugin allows to upload files on the server and reference it in a marc -field. - -It uses config variable upload_path and pref OPACBaseURL. - -=cut +return { builder => $builder }; diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/file-upload.js b/koha-tmpl/intranet-tmpl/prog/en/js/file-upload.js index 789f11d..f6817cb 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/js/file-upload.js +++ b/koha-tmpl/intranet-tmpl/prog/en/js/file-upload.js @@ -1,4 +1,4 @@ -function AjaxUpload ( input, progressbar, callback ) { +function AjaxUpload ( input, progressbar, xtra, callback ) { // input and progressbar are jQuery objects // callback is the callback function for completion var formData= new FormData(); @@ -6,7 +6,7 @@ function AjaxUpload ( input, progressbar, callback ) { formData.append( "uploadfile", file ); }); var xhr= new XMLHttpRequest(); - var url= '/cgi-bin/koha/tools/upload-file.pl'; + var url= '/cgi-bin/koha/tools/upload-file.pl?' + xtra; progressbar.val( 0 ); progressbar.next('.fileuploadpercent').text( '0' ); xhr.open('POST', url, true); @@ -21,7 +21,7 @@ function AjaxUpload ( input, progressbar, callback ) { progressbar.val( 100 ); progressbar.next('.fileuploadpercent').text( '100' ); } - callback( data.status, data.fileid ); + callback( data.status, data.fileid, data.errors ); } xhr.onerror = function (e) { // Probably only fires for network failure diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/process_koc.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/process_koc.tt index 2a44d82..0f9e578 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/process_koc.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/process_koc.tt @@ -20,7 +20,7 @@ function StartUpload() { $("#fileuploadstatus").show(); $("form#processfile #uploadedfileid").val(''); $("form#enqueuefile #uploadedfileid").val(''); - xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), cbUpload ); + xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), '', cbUpload ); } function cbUpload( status, fileid ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stage-marc-import.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stage-marc-import.tt index fed3104..0dff7d8 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stage-marc-import.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/stage-marc-import.tt @@ -39,7 +39,7 @@ function StartUpload() { $("#processfile").hide(); $("#fileuploadstatus").show(); $("#uploadedfileid").val(''); - xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), cbUpload ); + xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), '', cbUpload ); $("#fileuploadcancel").show(); } function CancelUpload() { 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 812df23..bf9535b 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 @@ -18,7 +18,7 @@ function StartUpload() { $('#uploadform button.submit').prop('disabled',true); $("#fileuploadstatus").show(); $("#uploadedfileid").val(''); - xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), cbUpload ); + xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), '', cbUpload ); } function cbUpload( status, fileid ) { if( status=='done' ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload.tt new file mode 100644 index 0000000..2f5b062 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload.tt @@ -0,0 +1,332 @@ +[% INCLUDE 'doc-head-open.inc' %] +[% USE Koha %] +[% IF plugin %] + Upload plugin +[% ELSE %] + Koha › Tools › Upload +[% END %] +[% INCLUDE 'doc-head-close.inc' %] + +[% BLOCK plugin_pars %] + [% IF plugin %] + + + [% END %] +[% END %] + +[% BLOCK breadcrumbs %] + +[% END %] + +[% BLOCK form_new %] +
+ [% PROCESS plugin_pars %] +
+ Upload new files +
    +
  1. +
    + + +
    +
  2. +
  3. + + +
  4. + [% IF !plugin %] +
  5. +
    Note: For temporary uploads do not select a category. The file will not be made available for public downloading.
    +
  6. + [% END %] +
  7. + [% IF plugin %] + + [% ELSE %] + + + Allow public downloads + + [% END %] +
  8. +
+
+ + +
+
+
+
Upload progress: + + + 0% +
+
+
+
+[% END %] + +[% BLOCK form_search %] +
+ [% PROCESS plugin_pars %] + +
+ Search uploads by name or hashvalue +
    +
  1. + + +
  2. +
  3. +
    + +
    +
  4. +
+
+
+[% END %] + +[% BLOCK submitter %] + +[% END %] + +[% BLOCK closer %] + [% IF plugin %] +
+
+ +
+
+ [% END %] +[% END %] + +[% BLOCK newsearch %] +
+
+ + [% IF plugin %] + + [% END %] +
+
+[% END %] + +[% BLOCK table_results %] + + + + + + + [% FOREACH record IN uploads %] + + + + + + [% IF !plugin %] + + [% END %] + + + [% END %] + +
Filename + Size + Hashvalue + Category + [% IF !plugin %]Public[% END %] + Actions +
[% record.name %][% record.filesize %][% record.hashvalue %][% record.categorycode %][% IF record.public %]Yes[% ELSE %]No[% END %] + [% IF plugin %] + Choose  + [% END %] + Download  + Delete +
+[% END %] + + + + + + + + + +[% IF !plugin %] + [% INCLUDE 'header.inc' %] + [% INCLUDE 'cat-search.inc' %] + [% PROCESS breadcrumbs %] +[% END %] + +
+
+
+
+ +

Upload

+ + + +[% PROCESS submitter %] +[% IF mode == 'new' || mode == 'deleted' %] + [% PROCESS form_new %] + [% PROCESS form_search %] + [% PROCESS closer %] +[% ELSIF mode == 'report' %] + [% IF uploads %] +

Your request gave the following results:

+ [% PROCESS table_results %] + [% PROCESS closer %] + [% ELSE %] +

Sorry, your request had no results.

+ [% PROCESS newsearch %] + [% END %] +[% END %] + +
+
+
+ +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/tools/upload-file.pl b/tools/upload-file.pl index a1a2b65..ffa9d30 100755 --- a/tools/upload-file.pl +++ b/tools/upload-file.pl @@ -18,10 +18,13 @@ # along with Koha; if not, see . use Modern::Perl; -use CGI::Cookie; use CGI qw ( -utf8 ); -#use CGI::Session; +use CGI::Cookie; +use Encode; +use JSON; +use URI::Escape; + use C4::Context; use C4::Auth qw/check_cookie_auth haspermission/; use Koha::Upload; @@ -57,18 +60,36 @@ if ($auth_failure) { exit 0; } -my $upload = Koha::Upload->new({ }); -if( !$upload || !$upload->cgi || $upload->err ) { - send_reply( 'failed' ); +my $upload = Koha::Upload->new( extr_pars($ENV{QUERY_STRING}) ); +if( !$upload || !$upload->cgi || !$upload->count ) { + # not one upload succeeded + send_reply( 'failed', undef, $upload? $upload->err: undef ); } else { - send_reply( 'done', $upload->result ); + # in case of multiple uploads, at least one got through + send_reply( 'done', $upload->result, $upload->err ); } exit 0; sub send_reply { # response will be sent back as JSON - my ( $upload_status, $data ) = @_; + my ( $upload_status, $data, $error ) = @_; my $reply = CGI->new(""); - print $reply->header(-type => 'text/html'); - print '{"status":"' . $upload_status . - ( $data? '","fileid":"' . $data: '' ) . '"}'; + print $reply->header( -type => 'text/html', -charset => 'UTF-8' ); + print JSON::encode_json({ + status => $upload_status, + fileid => $data, + errors => $error, + }); +} + +sub extr_pars { + my ( $qstr ) = @_; + $qstr = Encode::decode_utf8( uri_unescape( $qstr ) ); + # category could include a utf8 character + my $rv = {}; + foreach my $p ( qw[public category] ) { + if( $qstr =~ /(^|&)$p=(\w+)(&|$)/ ) { + $rv->{$p} = $2; + } + } + return $rv; } diff --git a/tools/upload.pl b/tools/upload.pl new file mode 100755 index 0000000..96d74b1 --- /dev/null +++ b/tools/upload.pl @@ -0,0 +1,98 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright (C) 2015 Rijksmuseum +# +# 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 3 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, see . + +use Modern::Perl; +use CGI qw/-utf8/; +use JSON; + +use C4::Auth; +use C4::Output; +use Koha::Upload; + +my $input = CGI::->new; +my $op = $input->param('op') // 'new'; +my $plugin = $input->param('plugin'); +my $index = $input->param('index'); # MARC editor input field id +my $term = $input->param('term'); +my $id = $input->param('id'); +my $msg = $input->param('msg'); + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { template_name => "tools/upload.tt", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { editcatalogue => '*' }, + } +); + +$template->param( + plugin => $plugin, + index => $index, +); + +my $upar = $plugin? { public => 1 }: {}; +if( $op eq 'new' ) { + $template->param( + mode => 'new', + uploadcategories => Koha::Upload->getCategories, + ); + output_html_with_http_headers $input, $cookie, $template->output; +} elsif( $op eq 'search' ) { + my $h = $id? { id => $id }: { term => $term }; + my @uploads = Koha::Upload->new( $upar )->get( $h ); + $template->param( + mode => 'report', + msg => $msg, + uploads => \@uploads, + ); + output_html_with_http_headers $input, $cookie, $template->output; +} elsif( $op eq 'delete' ) { + # delete only takes the id parameter + my $upl = Koha::Upload->new( $upar ); + my ( $fn ) = $upl->delete({ id => $id }); + my $e = $upl->err; + my $msg = $fn? JSON::to_json({ $fn => 6 }): + $e? JSON::to_json( $e ): undef; + $template->param( + mode => 'deleted', + msg => $msg, + uploadcategories => $upl->getCategories, + ); + output_html_with_http_headers $input, $cookie, $template->output; +} elsif( $op eq 'download' ) { + my $upl = Koha::Upload->new( $upar ); + my $rec = $upl->get({ id => $id, filehandle => 1 }); + my $fh = $rec->{fh}; + if( !$rec || !$fh ) { + $template->param( + mode => 'new', + msg => JSON::to_json({ $id => 5 }), + uploadcategories => $upl->getCategories, + ); + output_html_with_http_headers $input, $cookie, $template->output; + } else { + my @hdr = $upl->httpheaders( $rec->{name} ); + print $input->header( @hdr ); + while( <$fh> ) { + print $_; + } + $fh->close; + } +} -- 1.7.10.4