Koha/C4/UploadedFiles.pm
Marcel de Rooy 7f797b3244 Bug 6874: [QA Follow-up] Last adjustments for future developments
This patch does:

[1] Some trivial template changes. Modified some comments (POD lines).
[2] Converted plugin to new style.
[3] Table updates: renames id to hashvalue, adds a autoincrement id,
    adds filesize, timestamp, owner and category.
    RM: This db rev is in a separate sql file in atomicupdate.
[4] Code references to computed hash renamed to hashvalue instead of id.
[5] Removed some code pertaining to exposing upload dir structure. The user
    now may choose a category; the uploader takes care of storage.
    The list of upload categories is now taken from authorised values; this
    might become a separate table in the future. (If there are none,
    the upload process creates one default fallback.)
    We can add e.g. permissions later, subdir structure, etc. (So dir will
    not necessarily be category anymore.)

Test plan:
[1] Run the db revision.
[2] Upload new file. Check the record in the table. Delete it again; check.
[3] Run t/db../UploadedFiles.t.
[4] Run t/db../FrameworkPlugins.t -incl cataloguing/value_builder/upload.pl

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Tomas Cohen Arazi <tomascohen@unc.edu.ar>
2015-08-07 15:23:36 -03:00

323 lines
7.5 KiB
Perl

package C4::UploadedFiles;
# This file is part of Koha.
#
# Copyright (C) 2011-2012 BibLibre
#
# 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>.
=head1 NAME
C4::UploadedFiles - Functions to deal with files uploaded with cataloging plugin upload.pl
=head1 SYNOPSIS
use C4::UploadedFiles;
my $filename = $cgi->param('uploaded_file');
my $file = $cgi->upload('uploaded_file');
my $dir = $input->param('dir');
# upload file
my $id = C4::UploadedFiles::UploadFile($filename, $dir, $file->handle);
# retrieve file infos
my $uploaded_file = C4::UploadedFiles::GetUploadedFile($id);
# delete file
C4::UploadedFiles::DelUploadedFile($id);
=head1 DESCRIPTION
This module provides basic functions for adding, retrieving and deleting files related to
cataloging plugin upload.pl.
It uses uploaded_files table.
It is not related to C4::UploadedFile
=head1 FUNCTIONS
=cut
use Modern::Perl;
use Digest::SHA;
use Fcntl;
use Encode;
use C4::Context;
use C4::Koha;
sub _get_file_path {
my ($hash, $dirname, $filename) = @_;
my $upload_path = C4::Context->config('upload_path');
if( !-d "$upload_path/$dirname" ) {
mkdir "$upload_path/$dirname";
}
my $filepath = "$upload_path/$dirname/${hash}_$filename";
$filepath =~ s|/+|/|g;
return $filepath;
}
=head2 GetUploadedFile
my $file = C4::UploadedFiles::GetUploadedFile($id);
Returns a hashref containing infos on uploaded files.
Hash keys are:
=over 2
=item * id: id of the file (same as given in argument)
=item * filename: name of the file
=item * dir: directory where file is stored (relative to config variable 'upload_path')
=back
It returns undef if file is not found
=cut
sub GetUploadedFile {
my ( $hashvalue ) = @_;
return unless $hashvalue;
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT hashvalue, filename, dir
FROM uploaded_files
WHERE hashvalue = ?
};
my $sth = $dbh->prepare($query);
$sth->execute( $hashvalue );
my $file = $sth->fetchrow_hashref;
if ($file) {
$file->{filepath} = _get_file_path($file->{hashvalue}, $file->{dir},
$file->{filename});
}
return $file;
}
=head2 UploadFile
my $id = C4::UploadedFiles::UploadFile($filename, $dir, $io_handle);
Upload a new file and returns its id (its SHA-1 sum, actually).
Parameters:
=over 2
=item * $filename: name of the file
=item * $dir: directory where to store the file (path relative to config variable 'upload_path'
=item * $io_handle: valid IO::Handle object, can be retrieved with
$cgi->upload('uploaded_file')->handle;
=back
=cut
sub UploadFile {
my ($filename, $dir, $handle) = @_;
$filename = decode_utf8($filename);
if($filename =~ m#(^|/)\.\.(/|$)# or $dir =~ m#(^|/)\.\.(/|$)#) {
warn "Filename or dirname contains '..'. Aborting upload";
return;
}
my $buffer;
my $data = '';
while($handle->read($buffer, 1024)) {
$data .= $buffer;
}
$handle->close;
my $sha = new Digest::SHA;
$sha->add($data);
$sha->add($filename);
$sha->add($dir);
my $hash = $sha->hexdigest;
# Test if this id already exist
my $file = GetUploadedFile($hash);
if ($file) {
return $file->{hashvalue};
}
my $file_path = _get_file_path($hash, $dir, $filename);
my $out_fh;
# Create the file only if it doesn't exist
unless( sysopen($out_fh, $file_path, O_WRONLY|O_CREAT|O_EXCL) ) {
warn "Failed to open file '$file_path': $!";
return;
}
print $out_fh $data;
my $size= tell($out_fh);
close $out_fh;
my $dbh = C4::Context->dbh;
my $query = qq{
INSERT INTO uploaded_files (hashvalue, filename, filesize, dir, categorycode, owner) VALUES (?,?,?,?,?,?);
};
my $sth = $dbh->prepare($query);
my $uid= C4::Context->userenv? C4::Context->userenv->{number}: undef;
# uid is null in unit test
if($sth->execute($hash, $filename, $size, $dir, $dir, $uid)) {
return $hash;
}
return;
}
=head2 DanglingEntry
C4::UploadedFiles::DanglingEntry($id,$isfileuploadurl);
Determine if a entry is dangling.
Returns: 2 == no db entry
1 == no plain file
0 == both a file and db entry.
-1 == N/A (undef id / non-file-upload URL)
=cut
sub DanglingEntry {
my ($id,$isfileuploadurl) = @_;
my $retval;
if (defined($id)) {
my $file = GetUploadedFile($id);
if($file) {
my $file_path = $file->{filepath};
my $file_deleted = 0;
unless( -f $file_path ) {
$retval = 1;
} else {
$retval = 0;
}
}
else {
if ( $isfileuploadurl ) {
$retval = 2;
}
else {
$retval = -1;
}
}
}
else {
$retval = -1;
}
return $retval;
}
=head2 DelUploadedFile
C4::UploadedFiles::DelUploadedFile( $hash );
Remove a previously uploaded file, given its hash value.
Returns: 1 == file deleted
0 == file not deleted
-1== no file to delete / no meaninful id passed
=cut
sub DelUploadedFile {
my ( $hashval ) = @_;
my $retval;
if ( $hashval ) {
my $file = GetUploadedFile( $hashval );
if($file) {
my $file_path = $file->{filepath};
my $file_deleted = 0;
unless( -f $file_path ) {
warn "Id $file->{hashvalue} is in database but no plain file found, removing id from database";
$file_deleted = 1;
} else {
if(unlink $file_path) {
$file_deleted = 1;
}
}
unless($file_deleted) {
warn "File $file_path cannot be deleted: $!";
}
my $dbh = C4::Context->dbh;
my $query = qq{
DELETE FROM uploaded_files
WHERE hashvalue = ?
};
my $sth = $dbh->prepare($query);
my $numrows = $sth->execute( $hashval );
# if either a DB entry or file was deleted,
# then clearly we have a deletion.
if ($numrows>0 || $file_deleted==1) {
$retval = 1;
}
else {
$retval = 0;
}
}
else {
warn "There was no file for hash $hashval.";
$retval = -1;
}
}
else {
warn "DelUploadFile called without hash value.";
$retval = -1;
}
return $retval;
}
=head2 getCategories
getCategories returns a list of upload category codes and names
=cut
sub getCategories {
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 $file= shift;
return
( '-type' => 'application/octet-stream',
'-attachment' => $file, );
}
1;