From c40201fd63ed3481b961694d7ae9753de95df418 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> 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 <http://www.gnu.org/licenses>. 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 = " - <script type=\"text/javascript\"> - function Click$function_name(event) { + return <<"SCRIPT"; +<script type=\"text/javascript\"> + function Click$params->{id}(event) { var index = event.data.id; - var id = document.getElementById(index).value; - var IsFileUploadUrl=0; - if (id.match(/opac-retrieve-file/)) { - IsFileUploadUrl=1; - } - if(id.match(/id=([0-9a-f]+)/)){ - id = RegExp.\$1; - } - var newin=window.open(\"../cataloguing/plugin_launcher.pl?plugin_name=upload.pl&index=\"+index+\"&id=\"+id+\"&from_popup=0\"+\"&IsFileUploadUrl=\"+IsFileUploadUrl, 'upload', 'width=600,height=400,toolbar=false,scrollbars=no'); - newin.focus(); - } - </script> -"; - 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> +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 %] + <title>Upload plugin</title> +[% ELSE %] + <title>Koha › Tools › Upload</title> +[% END %] +[% INCLUDE 'doc-head-close.inc' %] + +[% BLOCK plugin_pars %] + [% IF plugin %] + <input type="hidden" name="plugin" value="1" /> + <input type="hidden" name="index" value="[% index %]" /> + [% END %] +[% END %] + +[% BLOCK breadcrumbs %] + <div id="breadcrumbs"> + <a href="/cgi-bin/koha/mainpage.pl">Home</a> + › + <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> + › + <a href="/cgi-bin/koha/tools/upload.pl">Upload</a> + › + <span id="lastbreadcrumb"> + [% IF mode=='new' || mode =='deleted'%] + Add new upload or search + [% ELSE %] + Results + [% END %] + </span> + </div> +[% END %] + +[% BLOCK form_new %] + <form method="post" action="[% SCRIPT_NAME %]" id="uploadfile" enctype="multipart/form-data"> + [% PROCESS plugin_pars %] + <fieldset class="rows" id="uploadform"> + <legend>Upload new files</legend> + <ol> + <li> + <div id="fileuploadform"> + <label for="fileToUpload">Select files: </label> + <input type="file" id="fileToUpload" name="fileToUpload" multiple/> + </div> + </li> + <li> + <label for="uploadcategory">Category: </label> + <select id="uploadcategory" name="uploadcategory"> + [% IF !plugin %] + <option value="" disabled hidden selected></option> + [% END %] + [% FOREACH cat IN uploadcategories %] + <option value="[% cat.code %]">[% cat.name %]</option> + [% END %] + </select> + </li> + [% IF !plugin %] + <li> + <div class="hint">Note: For temporary uploads do not select a category. The file will not be made available for public downloading.</div> + </li> + [% END %] + <li> + [% IF plugin %] + <input type="hidden" id="public" name="public" value="1"/> + [% ELSE %] + <label> </label> + <input type="checkbox" id="public" name="public"> + Allow public downloads + </input> + [% END %] + </li> + </ol> + <fieldset class="action"> + <button id="fileuploadbutton" onclick="StartUpload(); return false;">Upload</button> + <button id="fileuploadcancel" onclick="CancelUpload(); return false;">Cancel</button> + </fieldset> + </fieldset> + <div id="fileuploadpanel"> + <div id="fileuploadstatus">Upload progress: + <progress id="fileuploadprogress" min="0" max="100" value="0"> + </progress> + <span class="fileuploadpercent">0</span>% + </div> + <div id="fileuploadfailed"></div> + </div> + </form> +[% END %] + +[% BLOCK form_search %] + <form method="post" id="searchfile" action="[% SCRIPT_NAME %]" enctype="multipart/form-data"> + [% PROCESS plugin_pars %] + <input type="hidden" name="op" value="search"/> + <fieldset class="rows"> + <legend>Search uploads by name or hashvalue</legend> + <ol> + <li> + <label for="searchupload">Search term: </label> + <input type="text" id="term" name="term" value=""/> + </li> + <li> + <fieldset class="action"> + <button id="searchbutton" onclick="return CheckSearch();" class="submit">Search</button> + </fieldset> + </li> + </ol> + </fieldset> + </form> +[% END %] + +[% BLOCK submitter %] + <form id="submitter" style="display:none;" method="post"> + [% PROCESS plugin_pars %] + <input type="hidden" name="op" id="op" value=""/> + <input type="hidden" name="id" id="id" value="" /> + <input type="hidden" name="msg" id="msg" value="" /> + </form> +[% END %] + +[% BLOCK closer %] + [% IF plugin %] + <form id="closer"> + <fieldset class="action"> + <button onclick="window.close();return false;">Close</button> + </fieldset> + </form> + [% END %] +[% END %] + +[% BLOCK newsearch %] + <form id="newsearch"> + <fieldset class="action"> + <button onclick="SubmitMe('new'); return false;">New search</button> + [% IF plugin %] + <button onclick="window.close();return false;">Close</button> + [% END %] + </fieldset> + </form> +[% END %] + +[% BLOCK table_results %] + <table> + <thead> + <tr> + <th>Filename</td> + <th>Size</td> + <th>Hashvalue</td> + <th>Category</td> + [% IF !plugin %]<th>Public</td>[% END %] + <th>Actions</td> + </tr> + </thead> + <tbody> + [% FOREACH record IN uploads %] + <tr> + <td>[% record.name %]</td> + <td>[% record.filesize %]</td> + <td>[% record.hashvalue %]</td> + <td>[% record.categorycode %]</td> + [% IF !plugin %] + <td>[% IF record.public %]Yes[% ELSE %]No[% END %]</td> + [% END %] + <td> + [% IF plugin %] + <a href="" onclick="Choose('[% record.hashvalue %]'); return false;">Choose</a> + [% END %] + <a href="" onclick="SubmitMe( 'download', [% record.id %] ); return false;">Download</a> + <a href="" onclick="ClearField(); SubmitMe( 'delete', [% record.id %] ); return false;">Delete</a> + </td> + </tr> + [% END %] + </tbody> + </table> +[% END %] + +<style type="text/css"> + #fileuploadstatus,#fileuploadfailed { display : none; } + #fileuploadstatus { margin:.4em; } + #fileuploadprogress { width:150px;height:10px;border:1px solid #666;background:url('[% interface %]/[% theme %]/img/progress.png') -300px 0px no-repeat; } +</style> + +<script type="text/javascript"> +//<![CDATA[ + var errMESSAGES = [ + "Error 0: Not in use", + _("This file already exists (in this category)."), + _("File could not be created. Check permissions."), + _("Your koha-conf.xml does not contain a valid upload_path."), + _("No temporary directory found."), + _("File could not be read."), + _("File has been deleted."), + _("File could not be deleted."), + ]; +//]]> +</script> +<script type="text/javascript" src="[% themelang %]/js/file-upload.js"></script> +<script type="text/javascript"> +//<![CDATA[ +function StartUpload() { + if( $('#fileToUpload').prop('files').length == 0 ) return; + $('#fileToUpload').prop('disabled',true); + $('#fileuploadbutton').hide(); + $("#fileuploadcancel").show(); + $("#fileuploadfailed").html(''); + $("#myalerts").hide(''); + $("#myalerts").html(''); + $("#fileuploadstatus").show(); + $("#uploadedfileid").val(''); + $("#searchfile").hide(); + $("#lastbreadcrumb").text( _("Add a new upload") ); + + var xtra=''; + var catg = encodeURIComponent( $("#uploadcategory").val() ); + if( catg ) xtra= xtra + 'category=' + catg; + [% IF plugin %] + xtra = xtra + '&public=1'; + [% ELSE %] + if( $('#public').prop('checked') ) xtra = xtra + '&public=1'; + [% END %] + xhr= AjaxUpload( $('#fileToUpload'), $('#fileuploadprogress'), xtra, cbUpload ); +} +function CancelUpload() { + if( xhr ) xhr.abort(); + $("#fileuploadstatus").hide(); + $('#fileToUpload').prop('disabled', false); + $('#fileuploadbutton').show(); + $("#fileuploadcancel").hide(); + $("#fileuploadfailed").show(); + $("#fileuploadfailed").text( _("Upload status: Cancelled ") ); +} +function cbUpload( status, fileid, err ) { + $('#fileToUpload').prop('disabled', false); + if( status=='done' ) { + var e = err? JSON.stringify(err): ''; + SubmitMe( 'search', fileid, e ); + } else { + $('#fileuploadbutton').show(); + $("#fileuploadcancel").hide(); + $("#fileuploadstatus").hide(); + $("#fileuploadfailed").show(); + $("#fileuploadfailed").html( _("Upload status: ") + + ( status=='failed'? _("Failed"): + ( status=='denied'? _("Denied"): status )) + ); + ShowAlerts( err ); + } +} +function ShowAlerts(err) { + var str = ''; + for( var file in err ) { + str= str + '<p>' + file + ': ' + + errMESSAGES[ err[file] ] + '</p>'; + } + if( str ) { + $('#myalerts').html(str); + $('#myalerts').show(); + } +} +function CheckSearch() { + if( $("#term").val()=="" ) { + alert( _("Please enter a search term.") ); + return false; + } + return true; +} +function SubmitMe(op, id, msg ) { + $("#submitter #op").val( op ); + $("#submitter #id").val( id ); + $("#submitter #msg").val( msg ); + $("#submitter").submit(); +} +function ClearField() { + [% IF plugin %] + $(window.opener.document).find('#[% index %]').val( '' ); + [% END %] +} +function Choose(hashval) { + var res = '[% Koha.Preference('OPACBaseURL') %]'; + res = res.replace( /\/$/, ''); + res = res + '/cgi-bin/koha/opac-retrieve-file.pl?id=' + hashval; + [% IF index %] + $(window.opener.document).find('#[% index %]').val( res ); + [% END %] + window.close(); +} +$(document).ready(function() { + [% IF msg %] + ShowAlerts( [% msg %] ); + [% END %] + $("#fileuploadcancel").hide(); +}); +//]]> +</script> +</head> + +<body id="tools_upload" class="tools"> +[% IF !plugin %] + [% INCLUDE 'header.inc' %] + [% INCLUDE 'cat-search.inc' %] + [% PROCESS breadcrumbs %] +[% END %] + +<div id="doc3" class="yui-t2"> + <div id="bd"> + <div id="yui-main"> + <div class="yui-b"> + +<h1>Upload</h1> + +<div class="dialog alert" id="myalerts" style="display:none;"></div> + +[% PROCESS submitter %] +[% IF mode == 'new' || mode == 'deleted' %] + [% PROCESS form_new %] + [% PROCESS form_search %] + [% PROCESS closer %] +[% ELSIF mode == 'report' %] + [% IF uploads %] + <h3>Your request gave the following results:</h3> + [% PROCESS table_results %] + [% PROCESS closer %] + [% ELSE %] + <h4>Sorry, your request had no results.</h4> + [% PROCESS newsearch %] + [% END %] +[% END %] + +</div> +</div> +</div> + +[% 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 <http://www.gnu.org/licenses>. 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 <http://www.gnu.org/licenses>. + +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