1 package BSE::TB::ArticleFile;
3 # represents a file associated with an article from the database
6 @ISA = qw/Squirrel::Row/;
9 our $VERSION = "1.011";
12 return qw/id articleId displayName filename sizeInBytes description
13 contentType displayOrder forSale download whenUploaded
14 requireUser notes name hide_from_list storage src category
23 require BSE::Util::SQL;
29 whenUploaded => BSE::Util::SQL::now_datetime(),
43 my ($self, $cfg) = @_;
45 $cfg ||= BSE::Cfg->single;
47 my $downloadPath = BSE::TB::ArticleFiles->download_path($cfg);
48 return $downloadPath . "/" . $self->{filename};
52 my ($self, $cfg) = @_;
54 return -f $self->full_filename($cfg);
58 my ($self, $cfg) = @_;
60 $self->clear_metadata;
62 $cfg or confess "No \$cfg supplied to ",ref $self,"->remove()";
64 my $filename = $self->full_filename($cfg);
65 my $debug_del = $cfg->entryBool('debug', 'file_unlink', 0);
68 or print STDERR "Error deleting $filename: $!\n";
74 $self->SUPER::remove();
79 require BSE::TB::Articles;
81 return BSE::TB::Articles->getByPkey($self->{articleId});
85 my ($file, $cfg) = @_;
87 # return "/cgi-bin/user.pl/download_file/$file->{id}";
89 $cfg ||= BSE::Cfg->single;
91 if ($file->storage eq "local"
94 || $cfg->entryBool("downloads", "require_logon", 0)
95 || $cfg->entry("downloads", "always_redirect", 0)) {
96 return "/cgi-bin/user.pl/download_file/$file->{id}";
104 my ($self, $cfg) = @_;
106 return BSE::TB::ArticleFiles->handler($self->file_handler, $cfg);
112 BSE::DB->run(bseClearArticleFileMetadata => $self->{id});
115 sub clear_app_metadata {
118 BSE::DB->run(bseClearArticleFileAppMetadata => $self->{id});
121 sub clear_sys_metadata {
124 BSE::DB->run(bseClearArticleFileSysMetadata => $self->{id});
127 sub delete_meta_by_name {
128 my ($self, $name) = @_;
130 BSE::DB->run(bseDeleteArticleFileMetaByName => $self->{id}, $name);
134 my ($self, $cfg) = @_;
136 my %errors; # save for later setting into the metadata
138 for my $handler_entry (BSE::TB::ArticleFiles->file_handlers($cfg)) {
139 my ($key, $handler) = @$handler_entry;
141 $self->clear_sys_metadata;
142 $handler->process_file($self);
146 # set errors from handlers that failed
147 for my $key (keys %errors) {
148 $self->add_meta(name => "${key}_error",
149 value => $errors{$key},
152 $self->set_file_handler($key);
162 # we should never get here
163 $self->set_file_handler("");
164 print STDERR "** Ran off the end of ArticleFile->set_handler()\n";
169 my ($self, %opts) = @_;
171 require BSE::TB::ArticleFileMetas;
172 return BSE::TB::ArticleFileMetas->make
174 file_id => $self->{id},
182 require BSE::TB::ArticleFileMetas;
183 return BSE::TB::ArticleFileMetas->getBy
192 require BSE::TB::ArticleFileMetas;
193 return BSE::TB::ArticleFileMetas->getBy
195 file_id => $self->id,
196 content_type => "text/plain",
201 my ($self, $name) = @_;
203 require BSE::TB::ArticleFileMetas;
204 my ($result) = BSE::TB::ArticleFileMetas->getBy
206 file_id => $self->id,
215 my ($file, %opts) = @_;
217 my $cfg = delete $opts{cfg}
218 or confess "Missing cfg parameter";
220 my $field = delete $opts{field};
221 defined $field or $field = "";
223 if ($field && exists $file->{$field}) {
224 require BSE::Util::HTML;
225 return BSE::Util::HTML::escape_html($file->{$field});
227 elsif ($field =~ /^meta\.(\w+)$/) {
229 my $meta = $file->meta_by_name($name)
231 $meta->content_type eq "text/plain"
232 or return "* metadata $name isn't text *";
234 require BSE::Util::HTML;
235 return BSE::Util::HTML::escape_html($meta->value);
237 elsif ($field eq "link" || $field eq "url") {
238 my $url = "/cgi-bin/user.pl?download_file=1&file=$file->{id}";
239 require BSE::Util::HTML;
240 my $eurl = BSE::Util::HTML::escape_html($url);
241 if ($field eq 'url') {
244 my $class = $file->{download} ? "file_download" : "file_inline";
245 my $html = qq!<a class="$class" href="$eurl">! . BSE::Util::HTML::escape_html($file->{displayName}) . '</a>';
249 my $handler = $file->handler($cfg);
250 return $handler->inline($file, $field);
255 # returns file type specific metadata
257 my ($file, %opts) = @_;
259 my $cfg = delete $opts{cfg}
260 or confess "Missing cfg parameter";
262 my $name = delete $opts{name}
263 or confess "Missing name parameter";
265 my $handler = $file->handler($cfg);
266 return $handler->metacontent($file, $name);
270 my ($self, $cfg, $mgr, $storage) = @_;
272 defined $storage or $storage = 'local';
274 if ($storage ne $self->storage) {
275 if ($self->storage ne "local") {
276 $mgr->unstore($self->filename, $self->storage);
277 $self->set_storage("local");
279 if ($storage ne "local") {
280 my $src = $mgr->store($self->filename, $storage, $self);
283 $self->{storage} = $storage;
292 returns the names of each metadatum defined for the file.
299 require BSE::TB::ArticleFileMetas;
300 return BSE::TB::ArticleFileMetas->getColumnBy
303 [ file_id => $self->id ],
309 Returns all but the value for metadata defined for the file.
316 require BSE::TB::ArticleFileMetas;
317 my @cols = grep $_ ne "value", BSE::TB::ArticleFileMeta->columns;
318 return BSE::TB::ArticleFileMetas->getColumnsBy
321 [ file_id => $self->id ],
326 my ($self, $cfg) = @_;
328 my %metanames = map { $_ => 1 } $self->metanames;
330 my @fields = grep $metanames{$_->name} || $_->cond($self), BSE::TB::ArticleFiles->all_metametadata($cfg);
332 my $handler = $self->handler($cfg);
334 my @handler_fields = map BSE::FileMetaMeta->new(%$_, ro => 1, cfg => $cfg), $handler->metametadata;
336 return ( @fields, @handler_fields );
339 sub user_orders_for {
340 my ($self, $user) = @_;
342 require BSE::TB::Orders;
343 return BSE::TB::Orders->getSpecial(fileOrdersByUser => $self->id, $user->id);
346 sub downloadable_by {
347 my ($self, $user, $error) = @_;
357 my @orders = $self->user_orders_for($user);
363 if (BSE::TB::ArticleFiles->downloads_must_be_paid) {
364 @orders = grep $_->paidFor, @orders;
371 if (BSE::TB::ArticleFiles->downloads_must_be_filled) {
372 @orders = grep $_->filled, @orders;
374 $$error = 'unfilled';
383 my ($self, %opts) = @_;
385 my $actor = $opts{_actor}
386 or confess "Missing _actor parameter";
388 my $warnings = $opts{_warnings}
389 or confess "Missing _warnings parameter";
391 my $cfg = BSE::Cfg->single;
392 my $file_dir = BSE::TB::ArticleFiles->download_path($cfg);
393 my $old_storage = $self->storage;
395 if ($opts{filename} || $opts{file}) {
396 my $src_filename = delete $opts{filename};
399 if ($src_filename =~ /^\Q$file_dir\E/) {
400 # created in the right place, use it
401 $filename = $src_filename;
404 open my $in_fh, "<", $src_filename
405 or die "Cannot open $src_filename: $!\n";
408 require DevHelp::FileUpload;
410 ($filename) = DevHelp::FileUpload->
411 make_fh_copy($in_fh, $file_dir, $opts{displayName}, \$msg)
415 elsif ($opts{file}) {
416 my $file = delete $opts{file};
417 require DevHelp::FileUpload;
419 ($filename) = DevHelp::FileUpload->
420 make_fh_copy($file, $file_dir, $opts{displayName}, \$msg)
424 my $fullpath = $file_dir . '/' . $filename;
425 $self->set_filename($filename);
426 $self->set_sizeInBytes(-s $fullpath);
427 $self->setDisplayName($opts{displayName});
429 unless ($opts{contentType}) {
430 require BSE::Util::ContentType;
431 $self->set_contentType(BSE::Util::ContentType::content_type($cfg, $opts{displayName}));
434 $self->set_handler($cfg);
437 my $type = delete $opts{contentType};
439 $self->set_contentType($type);
442 for my $field (qw(displayName description forSale download requireUser notes hide_from_list category)) {
443 my $value = delete $opts{$field};
444 if (defined $value) {
445 my $method = "set_$field";
446 $self->$method($value);
450 my $name = $opts{name};
451 if (defined $name && $name =~ /\S/) {
453 or die "name must be a single word\n";
454 my ($other) = BSE::TB::ArticleFiles->getBy(articleId => $self->id,
456 $other && $other->id != $self->id
457 and die "Duplicate file name (identifier)\n";
459 $self->set_name($name);
464 my $mgr = BSE::TB::ArticleFiles->file_manager($cfg);
466 if ($old_storage ne "local") {
467 $mgr->unstore($delete_file);
469 unlink "$file_dir/$delete_file";
471 $old_storage = "local";
474 my $storage = delete $opts{storage} || '';
479 $mgr->select_store($self->filename, $storage, $self);
480 if ($old_storage ne $new_storage) {
481 # handles both new images (which sets storage to local) and changing
482 # the storage for old images
483 my $src = $mgr->store($self->filename, $new_storage, $self);
484 $self->set_src($src);
485 $self->set_storage($new_storage);
492 require BSE::TB::AuditLog;
493 BSE::TB::AuditLog->log
495 component => "admin:edit:saveimage",
499 msg => "Error saving file to storage $new_storage: $msg",
501 push @$warnings, "msg:bse/admin/edit/file/save/savetostore:$msg";
504 if ($self->storage ne $old_storage && $old_storage ne "local") {
506 $mgr->unstore($self->filename, $old_storage);
511 require BSE::TB::AuditLog;
512 BSE::TB::AuditLog->log
514 component => "admin:edit:savefile",
518 msg => "Error saving file to storage $new_storage: $msg",
520 push @$warnings, "msg:bse/admin/edit/file/save/delfromstore:$msg";