Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
######################################################################
#
# EPrints::DataObj::Document
#
######################################################################
#
#
######################################################################
=pod
=for Pod2Wiki
=head1 NAME
B<EPrints::DataObj::Document> - A single format of a record.
=head1 DESCRIPTION
Document represents a single format of an EPrint (eg. PDF) - the
actual file(s) rather than the metadata.
This class is a subclass of DataObj, with the following metadata fields:
=over 4
=item docid (text)
The unique ID of the document. This is a string of the format 123-02
where the first number is the eprint id and the second is the document
number within that eprint.
This should probably have been and "int" but isn't. I later version
of EPrints may change this.
=item eprintid (itemref)
The id number of the eprint to which this document belongs.
=item placement (int)
Placement of the document - the order documents should be shown in.
=item format (namedset)
The format of this document. One of the types of the dataset "document".
=item formatdesc (text)
An additional description of this document. For example the specific version
of a format.
=item language (namedset)
The ISO ID of the language of this document. The default configuration of
EPrints does not set this.
=item security (namedset)
The security type of this document - who can view it. One of the types
of the dataset "security".
=item main (text)
The file which we should link to. For something like a PDF file this is
the only file. For an HTML document with images it would be the name of
the actual HTML file.
=item files (subobject, multiple)
A virtual field which represents the list of Files which are
part of this record.
=item media
A compound field containing a description of the document media - dimensions, codec etc.
=back
=head1 METHODS
=over 4
=cut
######################################################################
#
# INSTANCE VARIABLES:
#
# From DataObj.
#
######################################################################
package EPrints::DataObj::Document;
@ISA = ( 'EPrints::DataObj::SubObject' );
use EPrints;
use EPrints::Search;
use File::Copy;
use File::Find;
use Cwd;
use Fcntl qw(:DEFAULT :seek);
use URI::Heuristic;
use strict;
# Field to use for unsupported formats (if repository allows their deposit)
$EPrints::DataObj::Document::OTHER = "OTHER";
######################################################################
=pod
=item $metadata = EPrints::DataObj::Document->get_system_field_info
Return an array describing the system metadata of the Document dataset.
=cut
######################################################################
sub get_system_field_info
{
my( $class ) = @_;
return
(
{ name=>"docid", type=>"counter", required=>1, import=>0, show_in_html=>0, can_clone=>0,
sql_counter=>"documentid" },
{ name=>"rev_number", type=>"int", required=>1, can_clone=>0, show_in_html=>0,
default_value=>1 },
{ name=>"files", type=>"subobject", datasetid=>"file", multiple=>1 },
{ name=>"eprintid", type=>"itemref",
datasetid=>"eprint", required=>1, show_in_html=>0 },
{ name=>"pos", type=>"int", required=>1 },
{ name=>"placement", type=>"int", },
{ name=>"mime_type", type=>"id", volatile=>1, },
{ name=>"format", type=>"namedset", required=>1, input_rows=>1,
set_name=>"document", },
{ name=>"formatdesc", type=>"text", input_cols=>40 },
{ name=>"language", type=>"namedset", required=>1, input_rows=>1,
set_name=>"languages" },
{ name=>"security", type=>"namedset", required=>1, input_rows=>1,
set_name=>"security" },
{ name=>"license", type=>"namedset", required=>0, input_rows=>1,
set_name=>"licenses", "search_input_style" => "default" },
{ name=>"main", type=>"set", required=>1, options=>[], input_rows=>1,
input_tags=>\&main_input_tags,
render_option=>\&main_render_option },
{ name=>"date_embargo", type=>"date", required=>0,
min_resolution=>"day" },
{ name=>"date_embargo_retained", type=>"date", required=>0,
min_resolution=>"day" },
{ name => "embargo_reason", type => "namedset", set_name => 'embargo_reason', required=>0, input_rows => 1, },
{ name=>"content", type=>"namedset", required=>0, input_rows=>1,
set_name=>"content" },
{ name=>"relation", type=>"relation", multiple=>1, },
{
name => "media",
type => "compound",
multiple => 0,
fields => [
{ sub_name => "duration", type => "id", sql_index => 0},
{ sub_name => "audio_codec", type => "id", sql_index => 0},
{ sub_name => "video_codec", type => "id", sql_index => 0},
{ sub_name => "width", type => "int", sql_index => 0},
{ sub_name => "height", type => "int", sql_index => 0},
{ sub_name => "aspect_ratio", type => "id", sql_index => 0},
{ sub_name => "sample_start", type => "id", sql_index => 0},
{ sub_name => "sample_stop", type => "id", sql_index => 0},
],
},
);
}
sub main_input_tags
{
my( $session, $object ) = @_;
my %files = $object->files;
my @tags;
foreach ( sort keys %files ) { push @tags, $_; }
return( @tags );
}
sub main_render_option
{
my( $session, $option ) = @_;
return $session->make_text( $option );
}
sub doc_with_eprintid_and_pos
{
my( $repository, $eprintid, $pos ) = @_;
my $dataset = $repository->dataset( "document" );
my $results = $dataset->search(
filters => [
{
meta_fields => [qw( eprintid )],
value => $eprintid
},
{
meta_fields => [qw( pos )],
value => $pos
},
]);
#if no results, fall back to a different document dataset
if( !defined $results->item( 0 ) )
{
$dataset = $repository->dataset( "dark_document" );
#try dark_document dataset
if ( EPrints::Utils::is_set( $dataset ) )
{
$results = $dataset->search(
filters => [
{
meta_fields => [qw( eprintid )],
value => $eprintid
},
{
meta_fields => [qw( pos )],
value => $pos
},
]);
}
}
#return the result
return $results->item( 0 );
}
######################################################################
=pod
=item $dataset = EPrints::DataObj::Document->get_dataset_id
Returns the id of the L<EPrints::DataSet> object to which this record belongs.
=cut
######################################################################
sub get_dataset_id
{
return "document";
}
######################################################################
# =pod
#
# =item $doc = EPrints::DataObj::Document::create( $session, $eprint )
#
# Create and return a new Document belonging to the given $eprint object,
# get the initial metadata from set_document_defaults in the configuration
# for this repository.
#
# Note that this creates the document in the database, not just in memory.
#
# =cut
######################################################################
sub create
{
my( $session, $eprint ) = @_;
return EPrints::DataObj::Document->create_from_data(
$session,
{
_parent => $eprint,
eprintid => $eprint->get_id
},
$session->dataset( "document" ) );
}
######################################################################
# =pod
#
# =item $dataobj = EPrints::DataObj::Document->create_from_data( $session, $data, $dataset )
#
# Returns undef if a bad (or no) subjectid is specified.
#
# Otherwise calls the parent method in EPrints::DataObj.
#
# =cut
######################################################################
sub create_from_data
{
my( $class, $session, $data, $dataset ) = @_;
my $eprintid = $data->{eprintid};
$data->{_parent} ||= delete $data->{eprint};
my $files = $data->{files};
$files = [] if !defined $files;
if( !EPrints::Utils::is_set( $data->{main} ) && @$files > 0 )
{
$data->{main} = $files->[0]->{filename};
}
if( !EPrints::Utils::is_set( $data->{mime_type} ) && @$files > 0 )
{
foreach my $file (@$files)
{
$data->{mime_type} = $file->{mime_type}, last
if $file->{filename} eq $data->{main};
}
}
my $document = $class->SUPER::create_from_data( $session, $data, $dataset );
return undef unless defined $document;
if( scalar @$files )
{
$document->queue_files_modified;
}
my $eprint = $document->get_parent;
if( defined $eprint && !$eprint->under_construction )
{
local $eprint->{non_volatile_change} = 1;
$eprint->set_value( "fileinfo", $eprint->fileinfo );
$eprint->commit( 1 );
}
return $document;
}
######################################################################
=pod
=item $defaults = EPrints::DataObj::Document->get_defaults( $session, $data )
Return default values for this object based on the starting data.
=cut
######################################################################
sub get_defaults
{
my( $class, $session, $data, $dataset ) = @_;
$class->SUPER::get_defaults( $session, $data, $dataset );
#if more than one document dataset, we want the next pos from any of them
if( $session->can_call( "ultimate_doc_pos" ) )
{
$data->{pos} = $session->call( "ultimate_doc_pos", $session->get_database, $data->{eprintid} );
}
else #otherwise just get the next one for the document dataset
{
$data->{pos} = $session->get_database->next_doc_pos( $data->{eprintid} );
}
$data->{placement} = $data->{pos};
return $data;
}
######################################################################
=pod
=item $newdoc = $doc->clone( $eprint )
Attempt to clone this document. Both the document metadata and the
actual files. The clone will be associated with the given EPrint.
=cut
######################################################################
sub clone
{
my( $self, $eprint ) = @_;
my $data = EPrints::Utils::clone( $self->{data} );
# cloning within the same eprint, in which case get a new position!
if( defined $self->parent && $eprint->id eq $self->parent->id )
{
$data->{pos} = undef;
}
$data->{eprintid} = $eprint->get_id;
$data->{_parent} = $eprint;
# First create a new doc object
my $new_doc = $self->{dataset}->create_object( $self->{session}, $data );
return undef if !defined $new_doc;
my $ok = 1;
# Copy files
foreach my $file (@{$self->get_value( "files" )})
{
$file->clone( $new_doc ) or $ok = 0, last;
}
if( !$ok )
{
$new_doc->remove();
return undef;
}
return $new_doc;
}
######################################################################
=pod
=item $success = $doc->remove
Attempt to completely delete this document
=cut
######################################################################
sub remove
{
my( $self ) = @_;
# remove dependent objects and relations
$self->search_related->map(sub {
my( undef, undef, $doc ) = @_;
$doc->set_parent( $self->parent );
if( $doc->has_relation( $self, "isVolatileVersionOf" ) )
{
$doc->remove_relation( $self );
$doc->remove;
}
else
{
$doc->remove_relation( $self );
$doc->commit;
}
});
foreach my $file (@{($self->get_value( "files" ))})
{
$file->remove();
}
# Remove database entry
my $success = $self->SUPER::remove();
if( !$success )
{
my $db_error = $self->{session}->get_database->error;
$self->{session}->get_repository->log( "Error removing document ".$self->get_value( "docid" )." from database: $db_error" );
return( 0 );
}
my $eprint = $self->get_parent;
if( defined $eprint && !$eprint->under_construction )
{
$eprint->set_value( "fileinfo", $eprint->fileinfo );
$eprint->commit();
}
return( $success );
}
######################################################################
=pod
=item $eprint = $doc->get_eprint
Return the EPrint this document is associated with.
This is a synonym for get_parent().
=cut
######################################################################
sub get_eprint { &get_parent }
sub get_parent
{
my( $self, $datasetid, $objectid ) = @_;
# document without an eprint parent is broken
return undef if !$self->is_set( "eprintid" );
$datasetid = "eprint";
$objectid = $self->get_value( "eprintid" );
return $self->SUPER::get_parent( $datasetid, $objectid );
}
######################################################################
=pod
=item $url = $doc->get_baseurl( [$staff] )
Return the base URL of the document. Overrides the stub in DataObj.
$staff is currently ignored.
=cut
######################################################################
sub get_baseurl
{
my( $self ) = @_;
my $repo = $self->{session};
return $repo->config( "base_url" ).$self->path;
}
######################################################################
=pod
=item $boolean = $doc->is_public()
True if this document has no security set and is in the live archive.
=cut
######################################################################
sub is_public
{
my( $self ) = @_;
my $eprint = $self->get_parent;
return 0 unless( EPrints::Utils::is_set( $self->get_value( "security" ) ) );
return 0 if( $self->get_value( "security" ) ne "public" );
return 0 if( $eprint->get_value( "eprint_status" ) ne "archive" );
return 1;
}
=item $path = $doc->path
Returns the relative path to the document WITHOUT any file.
=cut
sub path
{
my( $self ) = @_;
my $eprint = $self->parent;
return undef if !defined $eprint;
return undef if !defined $eprint->path;
return sprintf("%s%i/",
$eprint->path,
$self->value( "pos" ),
);
}
=item $path = $doc->file_path( [ $filename ] )
Returns the relative path to $filename stored in this document. If $filename is undefined returns the path to the main file.
This is an efficient shortcut to this:
my $file = $doc->stored_file( $filename );
my $path = $file->path;
=cut
sub file_path
{
my( $self, $file ) = @_;
my $path = $self->path;
return undef if !defined $path;
$file = $self->value( "main" ) if !defined $file;
return $path if !defined $file;
return $path . URI::Escape::uri_escape_utf8(
$file,
"^A-Za-z0-9\-\._~\/" # don't escape /
);
}
######################################################################
=pod
=item $url = $doc->get_url( [$file] )
Return the full URL of the document. Overrides the stub in DataObj.
If file is not specified then the "main" file is used.
=cut
######################################################################
sub get_url
{
my( $self, $file ) = @_;
my $path = $self->file_path( $file );
return undef if !defined $path;
my $url = $self->{session}->config( 'base_url' ) . '/';
$url .= 'id/eprint/' if $self->{session}->get_conf( "use_long_url_format");
$url .= $path;
return $url;
}
######################################################################
=pod
=begin InternalDoc
=item $path = $doc->local_path
Return the full path of the directory where this document is stored
in the filesystem.
DEPRECATED
=end InternalDoc
=cut
######################################################################
sub local_path
{
my( $self ) = @_;
my $eprint = $self->get_parent();
if( !defined $eprint )
{
$self->{session}->get_repository->log(
"Document ".$self->get_id." has no eprint (eprintid is ".$self->get_value( "eprintid" )."!" );
return( undef );
}
return( $eprint->local_path()."/".sprintf( "%02d", $self->get_value( "pos" ) ) );
}
######################################################################
=pod
=item %files = $doc->files
Return a hash, the keys of which are all the files belonging to this
document (relative to $doc->local_path). The values are the sizes of
the files, in bytes.
=cut
######################################################################
sub files
{
my( $self ) = @_;
my %files;
foreach my $file (@{($self->get_value( "files" ))})
{
next unless defined $file->get_value( "filename" );
$files{$file->get_value( "filename" )} = $file->get_value( "filesize" );
}
return( %files );
}
# cjg should this function be in some kind of utils module and
# used by generate_static too?
######################################################################
#
# %files = EPrints::DataObj::Document::_get_files( $files, $root, $dir )
#
# Recursively get all the files in $dir. Paths are returned relative
# to $root (i.e. $root is removed from the start of files.)
#
######################################################################
sub _get_files
{
my( $files, $root, $dir ) = @_;
my $fixed_dir = ( $dir eq "" ? "" : $dir . "/" );
# Read directory contents
opendir CDIR, $root . "/" . $dir or return( undef );
my @filesread = readdir CDIR;
closedir CDIR;
# Iterate through files
my $name;
foreach $name (@filesread)
{
if( $name ne "." && $name ne ".." )
{
# If it's a directory, recurse
if( -d $root . "/" . $fixed_dir . $name )
{
_get_files( $files, $root, $fixed_dir . $name );
}
else
{
#my @stats = stat( $root . "/" . $fixed_dir . $name );
$files->{$fixed_dir.$name} = -s $root . "/" . $fixed_dir . $name;
#push @files, $fixed_dir . $name;
}
}
}
}
######################################################################
=pod
=item $success = $doc->remove_file( $filename )
Attempt to remove the given file. Give the filename as it is
returned by get_files().
=cut
######################################################################
sub remove_file
{
my( $self, $filename ) = @_;
my $fileobj = $self->get_stored_file( $filename );
if( defined( $fileobj ) )
{
$fileobj->remove();
$self->queue_files_modified;
}
else
{
$self->{session}->get_repository->log( "Error removing file $filename for doc ".$self->get_value( "docid" ).": $!" );
}
return defined $fileobj;
}
######################################################################
=pod
=item $doc->set_main( $main_file )
Sets the main file and adjusts format and mime type as necessary. Won't affect the database until a $doc->commit().
=cut
######################################################################
sub set_main
{
my( $self, $main_file ) = @_;
if( defined $main_file )
{
# Ensure that the file exists
my $fileobj = UNIVERSAL::isa( $main_file, "EPrints::DataObj::File" ) ?
$main_file :
$self->stored_file( $main_file );
# Set the main file if it does
if( defined $fileobj )
{
$self->set_value( "main", $fileobj->value( "filename" ) );
$self->set_value( "format",
$self->{session}->call( "guess_doc_type",
$self->{session},
$fileobj->value( "filename" ),
$fileobj->value( "mime_type" )
) );
$self->set_value( "mime_type", $fileobj->value( "mime_type" ) );
}
}
else
{
# The caller passed in undef, so we unset the main file
$self->set_value( "main", undef );
}
}
######################################################################
=pod
=item $filename = $doc->get_main
Return the name of the main file in this document.
=cut
######################################################################
sub get_main
{
my( $self ) = @_;
if( defined $self->{data}->{main} )
{
return $self->{data}->{main};
}
# If there's only one file then just claim that's
# the main one!
my %files = $self->files;
my @filenames = keys %files;
if( scalar @filenames == 1 )
{
return $filenames[0];
}
return;
}
######################################################################
=pod
=item $doc->set_format( $format )
Set format. Won't affect the database until a commit(). Just an alias
for $doc->set_value( "format" , $format );
=cut
######################################################################
sub set_format
{
my( $self, $format ) = @_;
$self->set_value( "format" , $format );
}
######################################################################
=pod
=item $doc->set_format_desc( $format_desc )
Set the format description. Won't affect the database until a commit().
Just an alias for
$doc->set_value( "format_desc" , $format_desc );
=cut
######################################################################
sub set_format_desc
{
my( $self, $format_desc ) = @_;
$self->set_value( "format_desc" , $format_desc );
}
######################################################################
=pod
=begin InternalDoc
=item $success = $doc->upload( $filehandle, $filename [, $preserve_path [, $filesize ] ] )
DEPRECATED - use L</add_file>, which will automatically identify the file type.
Upload the contents of the given file handle into this document as
the given filename.
If $preserve_path then make any subdirectories needed, otherwise place
this in the top level.
=end InternalDoc
=cut
######################################################################
sub upload
{
my( $self, $filehandle, $filename, $preserve_path, $filesize ) = @_;
# Get the filename. File::Basename isn't flexible enough (setting
# internal globals in reentrant code very dodgy.)
my $repository = $self->{session}->get_repository;
if( $filename =~ m/^~/ )
{
$repository->log( "Bad filename for file '$filename' in document: starts with ~ (will not add)\n" );
return 0;
}
if( $filename =~ m/\/~/ )
{
$repository->log( "Bad filename for file '$filename' in document: contains /~ (will not add)\n" );
return 0;
}
if( $filename =~ m/\/\.\./ )
{
$repository->log( "Bad filename for file '$filename' in document: contains /.. (will not add)\n" );
return 0;
}
if( $filename =~ m/^\.\./ )
{
$repository->log( "Bad filename for file '$filename' in document: starts with .. (will not add)\n" );
return 0;
}
if( $filename =~ m/^\// )
{
$repository->log( "Bad filename for file '$filename' in document: starts with slash (will not add)\n" );
return 0;
}
if( $filename =~ m/^ / )
{
$repository->log( "Bad filename for file '$filename' in document: starts with a space (will not add)\n" );
return 0;
}
if( $filename =~ m/ $/ )
{
$repository->log( "Bad filename for file '$filename' in document: ends with a space (will not add)\n" );
return 0;
}
$filename = sanitise( $filename );
if( !$filename )
{
$repository->log( "Bad filename in document: no valid characters in file name\n" );
return 0;
}
my $fileobj = $self->add_stored_file(
$filename,
$filehandle,
$filesize
);
if( defined $fileobj )
{
if( !$self->is_set( "main" ) )
{
$self->set_main( $fileobj );
$self->commit();
}
$self->queue_files_modified;
}
return defined $fileobj;
}
######################################################################
=pod
=item $fileobj = $doc->add_file( $file, $filename [, $preserve_path] )
$file is the full path to a file to be added to the document, with
name $filename. $filename is passed through L<EPrints::System/sanitise> before
being written.
If $preserve_path is true then include path components in $filename.
Returns the $fileobj created or undef on failure.
=cut
######################################################################
sub add_file
{
my( $self, $filepath, $filename, $preserve_path ) = @_;
my $fileobj = $self->stored_file( $filename );
$fileobj->remove if defined $fileobj;
$filename = EPrints->system->sanitise( $filename );
$filename = (split '/', $filename)[-1] if !$preserve_path;
open(my $fh, "<", $filepath) or return undef;
$fileobj = $self->create_subdataobj( "files", {
_content => $fh,
_filepath => $filepath,
filename => $filename,
filesize => -s $fh,
});
close $fh;
return $fileobj;
}
######################################################################
=pod
=begin InternalDoc
=item $cleanfilename = sanitise( $filename )
DEPRECATED - use L<EPrints::System/sanitise>.
=end InternalDoc
=cut
######################################################################
sub sanitise
{
my( $filename ) = @_;
$filename = EPrints->system->sanitise( $filename );
$filename =~ s!/!_!g;
return $filename;
}
######################################################################
=pod
=begin InternalDoc
=item $success = $doc->upload_archive( $filehandle, $filename, $archive_format )
DEPRECATED - use L</add_archive>.
Upload the contents of the given archive file. How to deal with the
archive format is configured in SystemSettings.
(In case the over-loading of the word "archive" is getting confusing,
in this context we mean ".zip" or ".tar.gz" archive.)
=end InternalDoc
=cut
######################################################################
sub upload_archive
{
my( $self, $filehandle, $filename, $archive_format ) = @_;
use bytes;
binmode($filehandle);
my $zipfile = File::Temp->new();
binmode($zipfile);
while(sysread($filehandle, $_, 4096))
{
syswrite($zipfile, $_);
}
my $rc = $self->add_archive(
"$zipfile",
$archive_format );
$self->queue_files_modified;
return $rc;
}
######################################################################
=pod
=item $success = $doc->add_archive( $file, $archive_format )
$file is the full path to an archive file, eg. zip or .tar.gz
This function will add the contents of that archive to the document.
=cut
######################################################################
sub add_archive
{
my( $self, $file, $archive_format ) = @_;
my $tmpdir = File::Temp->newdir();
# Do the extraction
my $rc = $self->{session}->get_repository->exec(
$archive_format,
DIR => $tmpdir,
ARC => $file );
$self->add_directory( "$tmpdir" );
return( $rc==0 );
}
######################################################################
=pod
=item $success = $doc->add_directory( $directory )
Upload the contents of $directory to this document. This will not set the main file.
This method expects $directory to have a trailing slash (/).
=cut
######################################################################
sub add_directory
{
my( $self, $directory ) = @_;
$directory =~ s/[\/\\]?$/\//;
my $rc = 1;
if( !-d $directory )
{
EPrints::abort( "Attempt to call upload_dir on a non-directory: $directory" );
}
File::Find::find( {
no_chdir => 1,
wanted => sub {
return unless $rc and !-d $File::Find::name;
my $filepath = $File::Find::name;
my $filename = substr($filepath, length($directory));
my $stored = $self->add_file( $filepath, $filename, 1 );
$rc = defined $stored;
},
}, $directory );
return $rc;
}
######################################################################
=pod
=item $success = $doc->upload_url( $url )
Attempt to grab stuff from the given URL. Grabbing HTML stuff this
way is always problematic, so (by default): only relative links will
be followed and only links to files in the same directory or
subdirectory will be followed.
This (by default) uses wget. The details can be configured in
SystemSettings.
=cut
######################################################################
sub upload_url
{
my( $self, $url_in ) = @_;
# Use the URI heuristic module to attempt to get a valid URL, in case
# users haven't entered the initial http://.
my $url = URI::Heuristic::uf_uri( $url_in );
if( !$url->path )
{
$url->path( "/" );
}
my $tmpdir = File::Temp->newdir();
# save previous dir
my $prev_dir = getcwd();
# Change directory to destination dir., return with failure if this
# fails.
unless( chdir "$tmpdir" )
{
chdir $prev_dir;
return( 0 );
}
# Work out the number of directories to cut, so top-level files go in
# at the top level in the destination dir.
# Count slashes
my $cut_dirs = substr($url->path,1) =~ tr"/""; # ignore leading /
my $rc = $self->{session}->get_repository->exec(
"wget",
CUTDIRS => $cut_dirs,
URL => $url );
chdir $prev_dir;
# If something's gone wrong...
return( 0 ) if ( $rc!=0 );
$self->add_directory( "$tmpdir" );
# Otherwise set the main file if appropriate
if( !$self->is_set( "main" ) )
{
my $endfile = $url;
$endfile =~ s/^.*\///;
# the URL itself, otherwise index.html?
for( $endfile, "index.html", "index.htm" )
{
$self->set_main( $_ ), last if -s "$tmpdir/$_";
}
}
if( !$self->is_set( "main" ) )
{
# only one file so main must be it
my $files = $self->value( "files" );
if( scalar @$files == 1 )
{
$self->set_main( $files->[0] );
}
}
$self->queue_files_modified;
return( 1 );
}
######################################################################
=pod
=item $success = $doc->commit
Commit any changes that have been made to this object to the
database.
Calls "set_document_automatic_fields" in the ArchiveConfig first to
set any automatic fields that may be needed.
=cut
######################################################################
sub commit
{
my( $self, $force ) = @_;
my $dataset = $self->{session}->get_repository->get_dataset( "document" );
my $security_changed = $self->{changed}->{security} if defined $self->{changed}->{security};
$self->update_triggers(); # might cause a new revision
if( scalar( keys %{$self->{changed}} ) == 0 )
{
# don't do anything if there isn't anything to do
return( 1 ) unless $force;
}
if( $self->{non_volatile_change} )
{
$self->set_value( "rev_number", ($self->get_value( "rev_number" )||0) + 1 );
if ( defined $self->{changed}->{date_embargo} || defined $self->get_value( 'date_embargo' ) )
{
if ( !defined $self->{session}->current_user && $0 =~ /lift_embargos/ )
{
$self->set_value( 'date_embargo_retained', $self->{changed}->{date_embargo} ) if EPrints::Utils::is_set( $self->{changed}->{date_embargo} );
}
else
{
$self->set_value( 'date_embargo_retained', $self->get_value( 'date_embargo' ) );
}
}
}
# SUPER::commit clears non_volatile_change
my $success;
{
local $self->{non_volatile_change};
$success = $self->SUPER::commit( $force );
}
if( $self->{non_volatile_change} )
{
my $eprint = $self->parent();
if( defined $eprint && !$eprint->under_construction )
{
$eprint->set_value( "fileinfo", $eprint->fileinfo );
$eprint->commit( $force );
}
}
# Make sure full text is reindexed to add/remove documents whose access restrictions have changed.
if ( defined $security_changed && $security_changed eq "public" || $self->get_value( "security" ) eq "public" )
{
$self->get_parent->queue_fulltext;
}
if ( defined $security_changed )
{
my @derived_docs = $self->get_derived_versions;
foreach my $ddoc ( @derived_docs )
{
$ddoc->set_value( 'security', $self->get_value( "security" ) );
$ddoc->commit;
}
}
return( $success );
}
######################################################################
=pod
=item @derived_docs = $doc->get_derived_versions
Return an array of documents that are derived from the current
document through the isVersionOf relation.
=cut
######################################################################
sub get_derived_versions
{
my( $self ) = @_;
my $dataset = $self->{session}->get_repository->get_dataset( "document" );
my $search_exp = $dataset->prepare_search();
$search_exp->add_field(
fields => [ $dataset->field( 'relation_type' ) ],
value => 'http://eprints.org/relation/isVolatileVersionOf',
match => "EQ",
);
$search_exp->add_field(
fields => [ $dataset->field( 'relation_uri' ) ],
value => '/id/document/' . $self->id,
match => "EQ",
);
return $search_exp->perform_search->slice;
}
######################################################################
=pod
=item $problems = $doc->validate( [$for_archive] )
Return an array of XHTML DOM objects describing validation problems
with the entire document, including the metadata and repository config
specific requirements.
A reference to an empty array indicates no problems.
=cut
######################################################################
sub validate
{
my( $self, $for_archive ) = @_;
return [] if $self->get_parent->skip_validation;
my @problems;
unless( EPrints::Utils::is_set( $self->get_type() ) )
{
# No type specified
my $fieldname = $self->{session}->make_element( "span", class=>"ep_problem_field:documents" );
push @problems, $self->{session}->html_phrase(
"lib/document:no_type",
fieldname=>$fieldname );
}
# System default checks:
# Make sure there's at least one file!!
my %files = $self->files();
if( scalar keys %files ==0 )
{
my $fieldname = $self->{session}->make_element( "span", class=>"ep_problem_field:documents" );
push @problems, $self->{session}->html_phrase( "lib/document:no_files", fieldname=>$fieldname );
}
elsif( !$self->is_set( "main" ) )
{
# No file selected as main!
my $fieldname = $self->{session}->make_element( "span", class=>"ep_problem_field:documents" );
push @problems, $self->{session}->html_phrase( "lib/document:no_first", fieldname=>$fieldname );
}
# Site-specific checks
push @problems, @{ $self->SUPER::validate( $for_archive ) };
return( \@problems );
}
######################################################################
#
# $boolean = $doc->user_can_view( $user )
#
# Return true if this documents security settings allow the given user
# to view it.
#
######################################################################
sub user_can_view
{
my( $self, $user ) = @_;
if( !defined $user )
{
$self->{session}->get_repository->log( '$doc->user_can_view called with undefined $user object.' );
return( 0 );
}
my $result = $self->{session}->get_repository->call(
"can_user_view_document",
$self,
$user );
return( 1 ) if( $result eq "ALLOW" );
return( 0 ) if( $result eq "DENY" );
$self->{session}->get_repository->log( "Response from can_user_view_document was '$result'. Only ALLOW, DENY are allowed." );
return( 0 );
}
######################################################################
=pod
=item $type = $doc->get_type
Return the type of this document.
=cut
######################################################################
sub get_type
{
my( $self ) = @_;
return $self->get_value( "format" );
}
sub queue_files_modified
{
my( $self ) = @_;
# volatile documents shouldn't be modified after creation
if( $self->has_relation( undef, "isVolatileVersionOf" ) )
{
return;
}
EPrints::DataObj::EventQueue->create_unique( $self->{session}, {
pluginid => "Event::FilesModified",
action => "files_modified",
params => [$self->internal_uri],
});
}
######################################################################
=pod
=item $doc->files_modified
This method does all the things that need doing when a file has been
modified.
=cut
######################################################################
sub files_modified
{
my( $self ) = @_;
my $rc = $self->make_thumbnails;
if( $self->{session}->can_call( "on_files_modified" ) )
{
$self->{session}->call( "on_files_modified", $self->{session}, $self );
}
$self->{dataset}->run_trigger( EPrints::Const::EP_TRIGGER_FILES_MODIFIED,
dataobj => $self,
);
$self->commit;
$self->get_parent->queue_fulltext;
return $rc;
}
######################################################################
=pod
=item $doc->rehash
Recalculate the hash value of the document. Uses MD5 of the files (in
alphabetic order), but can use user specified hashing function instead.
=cut
######################################################################
sub rehash
{
my( $self ) = @_;
my $files = $self->get_value( "files" );
my $tmpfile = File::Temp->new;
my $hashfile = $self->get_value( "docid" ).".".
EPrints::Platform::get_hash_name();
EPrints::Probity::create_log_fh(
$self->{session},
$files,
$tmpfile );
seek($tmpfile, 0, 0);
# Probity files must not be deleted when the document is deleted, therefore
# we store them in the parent Eprint
$self->get_parent->add_stored_file( $hashfile, $tmpfile, -s "$tmpfile" );
}
######################################################################
=pod
=item $doc = $doc->make_indexcodes()
Make the indexcodes document for this document. Returns the generated document or undef on failure.
=cut
######################################################################
sub make_indexcodes
{
my( $self ) = @_;
# if we're a volatile version of another document, don't make indexcodes
if( $self->has_relation( undef, "isVolatileVersionOf" ) )
{
return undef;
}
my $eprint = $self->parent;
local $eprint->{under_construction} = 1; # prevent parent commit
# find a conversion plugin to convert us to indexcodes
my $type = "indexcodes";
my %types = $self->{session}->plugin( "Convert" )->can_convert( $self, $type );
if( !exists $types{$type} )
{
$self->remove_indexcodes;
return undef;
}
my $plugin = $types{$type}->{"plugin"};
my $doc = $self->search_related( "isIndexCodesVersionOf" )->item( 0 );
# update the indexcodes file
if( defined $doc )
{
$doc->set_parent( $eprint );
my $dir = File::Temp->newdir;
my @files = $plugin->export( $dir, $self, $type );
if( !@files )
{
$self->remove_indexcodes;
return;
}
open(my $fh, "<", "$dir/$files[0]" );
my $file = $doc->stored_file( "indexcodes.txt" );
if( !defined $file )
{
$doc->add_stored_file( "indexcodes.txt", $fh, -s $fh );
}
else
{
$file->set_file( sub {
return "" if !sysread($fh, my $buffer, 4092);
return $buffer;
}, -s $fh);
}
close($fh);
}
# convert us to indexcodes
else
{
my $epdata = {
relation => [{
type => EPrints::Utils::make_relation( "isVersionOf" ),
uri => $self->internal_uri(),
},{
type => EPrints::Utils::make_relation( "isVolatileVersionOf" ),
uri => $self->internal_uri(),
},{
type => EPrints::Utils::make_relation( "isIndexCodesVersionOf" ),
uri => $self->internal_uri(),
}],
};
$doc = $plugin->convert(
$eprint,
$self,
$type,
$epdata
);
}
return $doc;
}
######################################################################
=pod
=item $doc = $doc->remove_indexcodes()
Remove any documents containing index codes for this document. Returns the
number of documents removed.
=cut
######################################################################
sub remove_indexcodes
{
my( $self ) = @_;
# if we're a volatile version of another document, don't make indexcodes
if( $self->has_relation( undef, "isVolatileVersionOf" ) )
{
return 0;
}
my $c = 0;
my $eprint = $self->parent;
local $eprint->{under_construction} = 1; # prevent parent commit
# remove any existing indexcodes documents
$self->search_related( "isIndexCodesVersionOf" )->map(sub {
my( undef, undef, $doc ) = @_;
$doc->set_parent( $eprint );
$doc->remove_relation( $self );
$doc->remove;
++$c;
});
return $c;
}
######################################################################
=pod
=begin InternalDoc
=item $filename = $doc->cache_file( $suffix );
Return a cache filename for this document with the givven suffix.
DEPRECATED
=end InternalDoc
=cut
######################################################################
sub cache_file
{
my( $self, $suffix ) = @_;
my $eprint = $self->get_parent;
return unless( defined $eprint );
return $eprint->local_path."/".
$self->get_value( "docid" ).".".$suffix;
}
######################################################################
#
# $doc->register_parent( $eprint )
#
# Give the document the EPrints::DataObj::EPrint object that it belongs to.
#
# This may cause reference loops, but it does avoid two identical
# EPrints objects existing at once.
#
######################################################################
sub register_parent
{
my( $self, $parent ) = @_;
$self->set_parent( $parent );
}
sub thumbnail_url
{
my( $self, $size ) = @_;
$size = "small" unless defined $size;
my $relation = "is${size}ThumbnailVersionOf";
my $thumbnails = $self->search_related( $relation );
return undef if $thumbnails->count == 0;
$relation = "has${size}ThumbnailVersion";
my $path = $self->path;
$path =~ s! /$ !.$relation/!x;
if( $self->is_set( "main" ) )
{
$path .= URI::Escape::uri_escape_utf8(
$self->value( "main" ),
"^A-Za-z0-9\-\._~\/"
);
}
return $self->{session}->current_url(
host => 1,
path => "static",
$path
);
}
# size => "small","medium","preview" (small is default)
# public => 0 : show thumbnail only on public docs
# public => 1 : show thumbnail on all docs if poss.
sub icon_url
{
my( $self, %opts ) = @_;
$opts{public} = 1 unless defined $opts{public};
$opts{size} = "small" unless defined $opts{size};
if( !$opts{public} || $self->is_public )
{
my $thumbnail_url = $self->thumbnail_url( $opts{size} );
return $thumbnail_url if defined $thumbnail_url;
}
my $session = $self->{session};
my $langid = $session->get_langid;
my @static_dirs = $session->get_repository->get_static_dirs( $langid );
my $icon = "other.png";
my $rel_path = "style/images/fileicons";
my $format = $self->value( "format" );
return $session->config( "base_url" )."/$rel_path/$icon" unless defined $format;
# support old-style MIME types e.g. application/zip
my( $major, $minor ) = split '/', $format, 2;
$minor = "" if !defined $minor;
DIR: foreach my $dir (@static_dirs)
{
# we'll use the major type if available e.g. 'image.png'
foreach my $fn ("$major\_$minor.png", "$major.png")
{
if( -e "$dir/$rel_path/$fn" )
{
$icon = $fn;
last DIR;
}
}
}
return $session->config( "base_url" )."/$rel_path/$icon";
}
=item $frag = $doc->render_icon_link( %opts )
Render a link to the icon for this document.
Options:
=over 4
=item new_window => 1
Make link go to _blank not current window.
=item preview => 1
If possible, provide a preview pop-up.
=item public => 0
Show thumbnail/preview only on public docs.
=item public => 1
Show thumbnail/preview on all docs if poss.
=item with_link => 0
Do not link.
=back
=cut
sub render_icon_link
{
my( $self, %opts ) = @_;
$opts{public} = 1 unless defined $opts{public};
$opts{with_link} = 1 unless defined $opts{with_link};
if( $opts{public} && !$self->is_public )
{
$opts{preview} = 0;
}
my %aopts;
$aopts{class} = "ep_document_link";
$aopts{target} = "_blank" if( $opts{new_window} );
$aopts{href} = $self->{session}->current_url(
host => 1,
path => "static",
$self->file_path
);
my $preview_id = "doc_preview_".$self->get_id;
my $preview_url;
if( $opts{preview} )
{
$preview_url = $self->thumbnail_url( "preview" );
if( !defined $preview_url ) { $opts{preview} = 0; }
}
if( $opts{preview} )
{
$opts{preview_side} = 'right' unless defined $opts{preview_side};
$aopts{onmouseover} = "EPJS_ShowPreview( event, '$preview_id', '$opts{preview_side}' );";
$aopts{onmouseout} = "EPJS_HidePreview( event, '$preview_id', '$opts{preview_side}' );";
$aopts{onfocus} = "EPJS_ShowPreview( event, '$preview_id', '$opts{preview_side}' );";
$aopts{onblur} = "EPJS_HidePreview( event, '$preview_id', '$opts{preview_side}' );";
}
my $f = $self->{session}->make_doc_fragment;
my $img_alt = $self->value('main');
$img_alt = $self->value('formatdesc') if EPrints::Utils::is_set( $self->value('formatdesc') );
my $img = $self->{session}->make_element(
"img",
class=>"ep_doc_icon",
alt=>"[thumbnail of $img_alt]",
src=>$self->icon_url( public=>$opts{public} ),
border=>0 );
if ( $opts{with_link} )
{
my $a = $self->{session}->make_element( "a", %aopts );
$a->appendChild( $img );
$f->appendChild( $a );
}
else
{
$f->appendChild( $img );
}
if( $opts{preview} )
{
my $preview = $self->{session}->make_element( "div",
id => $preview_id,
class => "ep_preview", );
my $pdiv = $self->{session}->make_element( "div" );
$preview->appendChild( $pdiv );
my $cdiv = $self->{session}->make_element( "div" );
my $span = $self->{session}->make_element( "span" );
$cdiv->appendChild( $span );
$pdiv->appendChild( $cdiv );
$span->appendChild( $self->{session}->make_element(
"img",
class=>"ep_preview_image",
id=>$preview_id . "_img",
alt=>"",
src=>$preview_url,
border=>0 ));
my $tdiv = $self->{session}->make_element( "div", class=>"ep_preview_title" );
$tdiv->appendChild( $self->{session}->html_phrase( "lib/document:preview"));
$span->appendChild( $tdiv );
$f->appendChild( $preview );
}
return $f;
}
=item $frag = $doc->render_preview_link( %opts )
Render a link to the preview for this document (if available) using a lightbox.
Options:
=over 4
=item caption => $frag
XHTML fragment to use as the caption, defaults to empty.
=item set => "foo"
The name of the set this document belongs to, defaults to none (preview won't be shown as part of a set).
=back
=cut
sub render_preview_link
{
my( $self, %opts ) = @_;
my $f = $self->{session}->make_doc_fragment;
my $caption = $opts{caption} || $self->{session}->make_doc_fragment;
my $set = $opts{set};
if( EPrints::Utils::is_set($set) )
{
$set = "[$set]";
}
else
{
$set = "";
}
my $size = $opts{size};
$size = "lightbox" if !defined $size;
my $url = $self->thumbnail_url( $size );
if( defined $url )
{
my $link;
if ( EPrints::Utils::tree_to_utf8($caption) eq EPrints::Utils::tree_to_utf8( $self->{session}->html_phrase( "lib/document:preview" ) ) )
{
$link = $self->{session}->make_element( "a",
href=>$url,
rel=>"lightbox$set nofollow",
);
}
else {
$link = $self->{session}->make_element( "a",
href=>$url,
rel=>"lightbox$set nofollow",
title=>EPrints::Utils::tree_to_utf8($caption),
);
}
$link->appendChild( $self->{session}->html_phrase( "lib/document:preview" ) );
$f->appendChild( $link );
}
EPrints::XML::dispose($caption);
return $f;
}
sub thumbnail_plugin
{
my( $self, $size ) = @_;
my $convert = $self->{session}->plugin( "Convert" );
my %types = $convert->can_convert( $self );
my $def = $types{'thumbnail_'.$size};
return unless defined $def;
return $def->{ "plugin" };
}
# DEPRECATED
sub thumbnail_path
{
my( $self ) = @_;
my $eprint = $self->get_parent();
if( !defined $eprint )
{
$self->{session}->get_repository->log(
"Document ".$self->get_id." has no eprint (eprintid is ".$self->get_value( "eprintid" )."!" );
return( undef );
}
return( $eprint->local_path()."/thumbnails/".sprintf("%02d",$self->get_value( "pos" )) );
}
sub thumbnail_types
{
my( $self ) = @_;
my @list = qw/ small medium preview lightbox audio_ogg audio_mp4 video_ogg video_mp4 /;
if( $self->{session}->can_call( "thumbnail_types" ) )
{
$self->{session}->call( "thumbnail_types", \@list, $self->{session}, $self );
}
$self->{session}->run_trigger( EPrints::Const::EP_TRIGGER_THUMBNAIL_TYPES,
dataobj => $self,
list => \@list,
);
return reverse @list;
}
sub remove_thumbnails
{
my( $self ) = @_;
my $eprint = $self->parent;
my $under_construction = $eprint->under_construction;
$eprint->{under_construction} = 1;
my @sizes = $self->thumbnail_types;
my %lookup = map { $_ => 1 } @sizes;
my $regexp = EPrints::Utils::make_relation( "is(\\w+)ThumbnailVersionOf" );
$regexp = qr/^$regexp$/;
$self->search_related->map(sub {
my( undef, undef, $doc ) = @_;
for(@{$doc->value( "relation" )})
{
next if $_->{uri} ne $self->internal_uri;
next if $_->{type} !~ $regexp;
# only remove configured sizes (otherwise we can nuke externally
# generated thumbnails)
if( exists($lookup{$1}) )
{
$doc->set_parent( $self->parent );
$doc->remove_relation( $self );
$doc->remove;
return;
}
}
});
$eprint->{under_construction} = $under_construction;
$eprint->set_value( "fileinfo", $eprint->fileinfo );
# we don't want to do anything heavy after changing thumbnails
local $eprint->{non_volatile_change};
$eprint->commit();
}
sub make_thumbnails
{
my( $self ) = @_;
# If we're a volatile version of another document, don't make thumbnails
# otherwise we'll cause a recursive loop
if( $self->has_relation( undef, "isVolatileVersionOf" ) )
{
return;
}
my $src_main = $self->get_stored_file( $self->value( "main" ) );
return unless defined $src_main;
my $eprint = $self->parent;
my $under_construction = $eprint->under_construction;
$eprint->{under_construction} = 1;
my %thumbnails;
my $regexp = EPrints::Utils::make_relation( "is(\\w+)ThumbnailVersionOf" );
$regexp = qr/^$regexp$/;
$self->search_related->map(sub {
my( undef, undef, $doc ) = @_;
for(@{$doc->value( "relation" )})
{
next if $_->{uri} ne $self->internal_uri;
next if $_->{type} !~ $regexp;
$doc->set_parent( $self->parent );
# remove multiple thumbnails of the same type, keeping the first
if( defined $thumbnails{$1} )
{
$doc->remove;
return;
}
$thumbnails{$1} = $doc;
}
});
my @list = $self->thumbnail_types;
SIZE: foreach my $size ( @list )
{
my $tgt = $thumbnails{$size};
# remove the existing thumbnail
if( defined($tgt) )
{
my $tgt_main = $tgt->get_stored_file( $tgt->value( "main" ) );
if( defined $tgt_main && $tgt_main->get_datestamp gt $src_main->get_datestamp )
{
# ignore if tgt's main file is newer than document's main file
next SIZE;
}
$tgt->remove_relation( $self );
$tgt->remove;
}
my $plugin = $self->thumbnail_plugin( $size );
next if !defined $plugin;
$tgt = $plugin->convert( $self->get_parent, $self, 'thumbnail_'.$size );
next if !defined $tgt;
$tgt->add_relation( $self, "is${size}ThumbnailVersionOf" );
$tgt->commit();
}
if( $self->{session}->get_repository->can_call( "on_generate_thumbnails" ) )
{
$self->{session}->get_repository->call( "on_generate_thumbnails", $self->{session}, $self );
}
$eprint->{under_construction} = $under_construction;
$eprint->set_value( "fileinfo", $eprint->fileinfo );
# we don't want to do anything heavy after changing thumbnails
local $eprint->{non_volatile_change};
$eprint->commit();
}
# DEPRECATED - use $doc->value( "mime_type" )
sub mime_type
{
my( $self, $file ) = @_;
# Primary doc if no filename
$file = $self->value( "main" ) unless( defined $file );
my $fileobj = $self->get_stored_file( $file );
return undef unless defined $fileobj;
return $fileobj->get_value( "mime_type" );
}
sub get_parent_dataset_id
{
"eprint";
}
sub get_parent_id
{
my( $self ) = @_;
return $self->get_value( "eprintid" );
}
=item $doc->add_relation( $tgt, @types )
Add one or more relations to $doc pointing to $tgt (does not modify $tgt).
=cut
sub add_relation
{
my( $self, $tgt, @types ) = @_;
@types = map { EPrints::Utils::make_relation( $_ ) } @types;
my %lookup = map { $_ => 1 } @types;
my @relation;
for(@{$self->value( "relation" )})
{
next if
$_->{uri} eq $tgt->internal_uri &&
exists($lookup{$_->{type}});
push @relation, $_;
}
push @relation,
map { { uri => $tgt->internal_uri, type => $_ } } @types;
$self->set_value( "relation", \@relation );
}
=item $doc->remove_relation( $tgt [, @types ] )
Removes the relations in $doc to $tgt. If @types isn't given removes all relations to $tgt. If $tgt is undefined removes all relations given in @types.
If you want to remove all relations do $doc->set_value( "relation", [] );
=cut
sub remove_relation
{
my( $self, $tgt, @types ) = @_;
return if !defined($tgt) && !@types;
@types = map { EPrints::Utils::make_relation( $_ ) } @types;
my %lookup = map { $_ => 1 } @types;
my @relation;
for(@{$self->value( "relation" )})
{
# logic:
# we remove the relation if uri and type match
# we remove the relation if uri and type=* match
# we remove the relation if uri=* and type match
# otherwise we keep it
next if defined($tgt) && $_->{uri} eq $tgt->internal_uri && exists($lookup{$_->{type}});
next if defined($tgt) && $_->{uri} eq $tgt->internal_uri && !@types;
next if !defined($tgt) && exists($lookup{$_->{type}});
push @relation, $_;
}
$self->set_value( "relation", \@relation );
}
=item $bool = $doc->has_relation( $tgt [, @types ] )
Returns true if $doc has relations to $tgt. If @types is given checks that $doc satisfies all of the given types. $tgt may be undefined.
=cut
sub has_relation
{
my( $self, $tgt, @types ) = @_;
@types = map { EPrints::Utils::make_relation( $_ ) } @types;
my %lookup = map { $_ => 1 } @types;
for(@{$self->value( "relation" )})
{
next if defined($tgt) && $_->{uri} ne $tgt->internal_uri;
return 1 if !@types;
delete $lookup{$_->{type}} if EPrints::Utils::is_set( $_->{type} );
}
return !scalar(keys %lookup);
}
=item $list = $doc->search_related( [ $type ] )
Return a L<EPrints::List> that contains all documents related to this document. If $type is defined returns only those documents related by $type.
=cut
sub search_related
{
my( $self, $type ) = @_;
if( $type )
{
return $self->dataset->search(
filters => [{
meta_fields => [qw( relation )],
value => {
uri => $self->internal_uri,
type => EPrints::Utils::make_relation( $type )
},
match => "EX",
},{
meta_fields => [qw( eprintid )],
value => $self->parent->id,
}]);
}
else
{
return $self->dataset->search(
filters => [{
meta_fields => [qw( relation_uri )],
value => $self->internal_uri,
match => "EX",
},{
meta_fields => [qw( eprintid )],
value => $self->parent->id,
}]);
}
}
# Add ep_document_link class
sub render_citation_link
{
my( $self , $style , %params ) = @_;
$params{url} = $self->get_url;
$params{class} = "ep_document_link";
return $self->render_citation( $style, %params );
}
# Utility function for renderering a html5 video preview with optional subtitles
# access / security concerns should be addressed at a higher level
sub render_video_preview
{
my ( $self, $css_class ) = @_;
my $session = $self->{session};
my $class = ( $css_class ) ? $css_class : "ep_embedded_video";
my $eprint = $self->get_eprint;
my $doc_frag = $session->make_doc_fragment();
my @thumbnail_types = $self->thumbnail_types();
my $preview_docs;
my $format_to_show;
foreach my $thumbnail_type (@thumbnail_types)
{
next unless !($thumbnail_type =~ /(small)|(medium)|(lightbox)/); # we dont want to use small, medium or lightbox for the preview
$self->search_related( "is${thumbnail_type}ThumbnailVersionOf" )->map( sub {
my ( undef, undef, $doc ) = @_;
$format_to_show = "video" if( $thumbnail_type =~ /(video)/ );
$format_to_show = "audio" if( $thumbnail_type =~ /(audio)/ );
$preview_docs->{$thumbnail_type} = $doc;
});
}
if( defined $preview_docs && defined $format_to_show && $format_to_show =~ /(video)/ )
{
my $video_link = $session->make_element( "video", class=>$class, controls=>"", id=>"doc_".$self->get_id, "data-src"=>"#doc_".$self->get_id );
my $video_type;
foreach my $video_format (qw/ video_mp4 video_ogg video_webm /)
{
$video_type = $video_format;
$video_type =~ s/_/\//g;
my $video_url = $self->thumbnail_url( $video_format );
# $video_url =~ s/^http/https/;
$video_link->appendChild( $session->make_element("source", src=>$video_url, type=>$video_type) );
}
$video_link->appendChild( $session->make_text("Your browser does not support HTML5 video") );
$doc_frag->appendChild( $video_link );
# add subtitles - assume for now that tracks are supplied in vtt format with a name matching the video file
my $subs = $self->get_value("main");
$subs =~ s/\.([^.]+)$/.vtt/; # the file we are looking for
my @docs = $eprint->get_all_documents();
foreach my $d ( @docs )
{
# assume suitable access rights
if( $d->get_value("main") eq $subs )
{
my $srclang = $d->get_value( "language" );
$srclang = "en" unless $srclang; # default to English
my $label = EPrints::Utils::tree_to_utf8( $session->html_phrase( "languages_typename_" . $srclang ) );
my $track = $session->make_element( "track", src => $d->get_url, kind => "subtitles", srclang => $srclang, label => $label );
$video_link->appendChild( $track );
}
}
}
return $doc_frag;
}
sub permit
{
my( $self, $priv, $user ) = @_;
if( $priv eq "document/view" )
{
my $r;
if( defined $user )
{
eval {
$r = $self->{session}->call( "can_user_view_document",
$self,
$user
);
};
EPrints->abort( "can_user_view_document call caused an error" ) if $@;
return 1 if $r eq "ALLOW";
EPrints->abort( "can_user_view_document returned '$r': expected ALLOW or DENY" ) if $r ne "DENY";
}
eval {
$r = $self->{session}->call( "can_request_view_document",
$self,
$self->{session}->{request}
);
};
EPrints->abort( 'can_request_view_document call caused an error. Hint: is security.pl still using $r->connection()->remote_ip();' ) if $@;
return 1 if $r eq "ALLOW";
return 0 if $r eq "DENY" || $r eq "USER";
EPrints->abort( "can_request_view_document returned '$r': expected ALLOW, DENY or USER" );
}
return $self->SUPER::permit( $priv, $user );
}
1;
######################################################################
=pod
=back
=cut
=head1 COPYRIGHT
=for COPYRIGHT BEGIN
Copyright 2021 University of Southampton.
EPrints 3.4 is supplied by EPrints Services.
http://www.eprints.org/eprints-3.4/
=for COPYRIGHT END
=for LICENSE BEGIN
This file is part of EPrints 3.4 L<http://www.eprints.org/>.
EPrints 3.4 and this file are released under the terms of the
GNU Lesser General Public License version 3 as published by
the Free Software Foundation unless otherwise stated.
EPrints 3.4 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with EPrints 3.4.
If not, see L<http://www.gnu.org/licenses/>.
=for LICENSE END