site/cgi-bin/modules/BSE/FileHandler/Base.pm
site/cgi-bin/modules/BSE/FileHandler/Default.pm
site/cgi-bin/modules/BSE/FileHandler/FLV.pm
+site/cgi-bin/modules/BSE/FileMetaMeta.pm
site/cgi-bin/modules/BSE/Formatter.pm
site/cgi-bin/modules/BSE/Formatter/Article.pm
site/cgi-bin/modules/BSE/Formatter/Subscription.pm
BEGIN { $ENV{DISPLAY} = '192.168.32.54:0.0' }
use strict;
use FindBin;
-use CGI::Carp 'fatalsToBrowser';
+#use CGI::Carp 'fatalsToBrowser';
use Carp 'verbose'; # remove the 'verbose' in production
+use Carp 'confess';
use lib "$FindBin::Bin/../modules";
use Articles;
use BSE::Request;
a_ajax_get => 'req_ajax_get',
a_ajax_save_body => 'req_ajax_save_body',
a_ajax_set => 'req_ajax_set',
+ a_filemeta => 'req_filemeta',
);
}
$self->_refresh_filelist($req, $article);
}
+sub req_filemeta {
+ my ($self, $req, $article, $articles, $errors) = @_;
+
+ my $cgi = $req->cgi;
+
+ my $id = $cgi->param('file_id');
+
+ my ($file) = grep $_->{id} == $id, $self->get_files($article)
+ or return $self->edit_form($req, $article, $articles,
+ "No such file");
+ $req->user_can(edit_files_save => $article)
+ or return $self->edit_form($req, $article, $articles,
+ "You don't have access to save file information for this article");
+
+ my $name = $cgi->param('name');
+ $name && $name =~ /^\w+$/
+ or return $self->edit_form($req, $article, $articles,
+ "Missing or invalid metadata name");
+
+ my $meta = $file->meta_by_name($name)
+ or return $self->edit_form($req, $article, $articles,
+ "Metadata $name not defined for this file");
+
+ return
+ {
+ type => $meta->content_type,
+ content => $meta->value,
+ };
+}
+
sub tag_old_checked {
my ($errors, $cgi, $file, $key) = @_;
return $errors ? $cgi->param($key) : $file->{$key};
}
+sub tag_filemeta_value {
+ my ($file, $args, $acts, $funcname, $templater) = @_;
+
+ my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater)
+ or return "* no meta name supplied *";
+
+ my $meta = $file->meta_by_name($name)
+ or return "";
+
+ $meta->content_type eq "text/plain"
+ or return "* $name has type " . $meta->content_type . " and cannot be displayed inline *";
+
+ return escape_html($meta->value);
+}
+
+sub tag_ifFilemeta_set {
+ my ($file, $args, $acts, $funcname, $templater) = @_;
+
+ my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater)
+ or return "* no meta name supplied *";
+
+ my $meta = $file->meta_by_name($name)
+ or return 0;
+
+ return 1;
+}
+
+sub tag_filemeta_source {
+ my ($file, $args, $acts, $funcname, $templater) = @_;
+
+ my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater)
+ or return "* no meta name supplied *";
+
+ return "$ENV{SCRIPT_NAME}?a_filemeta=1&id=$file->{articleId}&file_id=$file->{id}&name=$name";
+}
+
+sub tag_filemeta_select {
+ my ($cgi, $allmeta, $rcurr_meta, $file, $args, $acts, $funcname, $templater) = @_;
+
+ my $meta;
+ if ($args =~ /\S/) {
+ my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater)
+ or return "* cannot parse *";
+ ($meta) = grep $_->name eq $name, @$allmeta
+ or return "* cannot find meta field *";
+ }
+ elsif ($$rcurr_meta) {
+ $meta = $$rcurr_meta;
+ }
+ else {
+ return "* use in filemeta iterator or supply a name *";
+ }
+
+ $meta->type eq "enum"
+ or return "* can only use filemeta_select on enum metafields *";
+
+ my %labels;
+ my @values = $meta->values;
+ @labels{@values} = $meta->labels;
+
+ my $field_name = "meta_" . $meta->name;
+ my ($def) = $cgi->param($field_name);
+ unless (defined $def) {
+ my $value = $file->meta_by_name($meta->name);
+ if ($value && $value->is_text) {
+ $def = $value->value;
+ }
+ }
+ defined $def or $def = $values[0];
+
+ return popup_menu
+ (
+ -name => $field_name,
+ -values => \@values,
+ -labels => \%labels,
+ -default => $def,
+ );
+}
+
+sub tag_filemeta_select_label {
+ my ($allmeta, $rcurr_meta, $file, $args, $acts, $funcname, $templater) = @_;
+
+ my $meta;
+ if ($args =~ /\S/) {
+ my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater)
+ or return "* cannot parse *";
+ ($meta) = grep $_->name eq $name, @$allmeta
+ or return "* cannot find meta field *";
+ }
+ elsif ($$rcurr_meta) {
+ $meta = $$rcurr_meta;
+ }
+ else {
+ return "* use in filemeta iterator or supply a name *";
+ }
+
+ $meta->type eq "enum"
+ or return "* can only use filemeta_select_label on enum metafields *";
+
+ my %labels;
+ my @values = $meta->values;
+ @labels{@values} = $meta->labels;
+
+ my $field_name = "meta_" . $meta->name;
+ my $value = $file->meta_by_name($meta->name);
+ if ($value) {
+ if ($value->is_text) {
+ if (exists $labels{$value->value}) {
+ return escape_html($labels{$value->value});
+ }
+ else {
+ return escape_html($value->value);
+ }
+ }
+ else {
+ return "* cannot display type " . $value->content_type . " inline *";
+ }
+ }
+ else {
+ return "* " . $meta->name . " not set *";
+ }
+}
+
sub req_edit_file {
my ($self, $req, $article, $articles, $errors) = @_;
or return $self->edit_form($req, $article, $articles,
"You don't have access to save file information for this article");
+ my @metafields = $file->metafields($self->cfg);
+
+ my $it = BSE::Util::Iterate->new;
+ my $current_meta;
my %acts;
%acts =
(
error_img => [ \&tag_error_img, $req->cfg, $errors ],
ifOldChecked =>
[ \&tag_old_checked, $errors, $cgi, $file ],
+ $it->make
+ (
+ plural => "filemetas",
+ single => "filemeta",
+ data => \@metafields,
+ store => \$current_meta,
+ ),
+ filemeta_value =>
+ [ \&tag_filemeta_value, $file ],
+ ifFilemeta_set =>
+ [ \&tag_ifFilemeta_set, $file ],
+ filemeta_source =>
+ [ \&tag_filemeta_source, $file ],
+ filemeta_select =>
+ [ \&tag_filemeta_select, $cgi, \@metafields, \$current_meta, $file ],
+ filemeta_select_label =>
+ [ \&tag_filemeta_select_label, \@metafields, \$current_meta, $file ],
);
return $req->response('admin/file_edit', \%acts);
}
}
+ my @meta;
+ my @meta_delete;
+ my @metafields = grep !$_->ro, $file->metafields($self->cfg);
+ my %current_meta = map { $_ => 1 } $file->metanames;
+ for my $meta (@metafields) {
+ my $name = $meta->name;
+ my $cgi_name = "meta_$name";
+ if ($cgi->param("delete_$cgi_name")) {
+ for my $metaname ($meta->metanames) {
+ push @meta_delete, $metaname
+ if $current_meta{$metaname};
+ }
+ }
+ else {
+ my $new;
+ if ($meta->is_text) {
+ my ($value) = $cgi->param($cgi_name);
+ if (defined $value &&
+ ($value =~ /\S/ || $current_meta{$meta->name})) {
+ my $error;
+ if ($meta->validate(value => $value, error => \$error)) {
+ push @meta,
+ {
+ name => $name,
+ value => $value,
+ };
+ }
+ else {
+ $errors{$cgi_name} = $error;
+ }
+ }
+ }
+ else {
+ my $im = $cgi->param($cgi_name);
+ my $up = $cgi->upload($cgi_name);
+ if (defined $im && $up) {
+ my $data = do { local $/; <$up> };
+ my ($width, $height, $type) = imgsize(\$data);
+
+ if ($width && $height) {
+ push @meta,
+ (
+ {
+ name => $meta->data_name,
+ value => $data,
+ content_type => "image/\L$type",
+ },
+ {
+ name => $meta->width_name,
+ value => $width,
+ },
+ {
+ name => $meta->height_name,
+ value => $height,
+ },
+ );
+ }
+ else {
+ $errors{$cgi_name} = $type;
+ }
+ }
+ }
+ }
+ }
+
if ($cgi->param('save_file_flags')) {
my $download = 0 + defined $cgi->param("download");
if ($download ne $file->{download}) {
and $req->flash("Could not move $file->{displayName} to $storage: $@");
}
+ for my $meta_delete (@meta_delete, map $_->{name}, @meta) {
+ $file->delete_meta_by_name($meta_delete);
+ }
+ for my $meta (@meta) {
+ $file->add_meta(%$meta, appdata => 1);
+ }
+
# remove the replaced files
if (my ($old_name, $old_storage) = @old_file) {
$mgr->unstore($old_name, $old_storage);
use strict;
use Carp qw(confess);
+=head1 NAME
+
+BSE::FileHandler::Base - base class for file handlers
+
+=head1 SYNOPSIS
+
+ my $fh = BSE::FileHandler::XXX->new($cfg);
+
+ # for derived classes to define:
+ my $cfg_section = $fh->section;
+ eval {
+ $fh->process_file($file);
+ };
+ my $html = $fh->inline($file, $parameters);
+ my $content = $fh->metacontent($file, $meta_name);
+ my @fields = $fh->metametadata;
+
+ # for derived classes to use:
+ my $cfg = $fh->cfg;
+ my $value = $fh->cfg_entry("key", $default);
+
+=head1 METHODS
+
+=over
+
+=item $class->new($cfg)
+
+Create a new file handler object.
+
+This is not connected to a single file, but should construct quickly.
+
+$cfg must be a BSE::Cfg compatible object.
+
+=cut
+
sub new {
my ($class, $cfg) = @_;
}, $class;
}
+=item $obj->cfg
+
+Returns the $cfg value supplied to new().
+
+=cut
+
sub cfg {
$_[0]{cfg};
}
+=item $obj->cfg_entry($key)
+
+=item $obj->cfg_entry($key, $default)
+
+Retrieve a single configuration value from the $cfg object supplied to
+new.
+
+Uses the section name returned by the section method, which the
+derived class must define.
+
+=cut
+
sub cfg_entry {
my ($self, $key, $def) = @_;
return $self->cfg->entry($self->section, $key, $def);
}
+=item $obj->process_file($file)
+
+Attempt to detect the type and extract metadata from the file
+specified by $file, a BSE::TB::ArticleFile object.
+
+If there is an error while extracting metadata, die with a message.
+
+=cut
+
sub process_file {
my ($self, $file) = @_;
confess "$class hasn't implemented process-file";
}
+=item $obj->inline($file, $parameters)
+
+Called when handling:
+
+ <:filen - fieldname:>
+ <:filen name fieldname:>
+
+or:
+
+ file[id]
+ file[id|fieldname]
+
+where the $parameters field is set to fieldname.
+
+Only called when $parameters isn't "link", "meta.I<name>" or a field
+name found in an file record.
+
+The default implementation uses a template under the meta/ directory,
+constructed as:
+
+ meta/I<prefix>_I<metaname>
+
+where I<prefix> is the result of the metaprefix method.
+
+=item $obj->metacontent($file, $meta_name)
+
+Return generated meta data.
+
+=cut
+
sub metacontent {
+ my ($self, $file, $meta_name) = @_;
+
+ require BSE::Template;
+ my %meta = map { $_->name => $_->value }
+ grep $_->content_type eq "text/plain", $file->metadata;
+
+ my @prefix = ( $self->metaprefix, "base" );
+ my $template;
+ my $found;
+ for my $prefix (@prefix) {
+ $template = "meta/${prefix}_$meta_name";
+ $found = BSE::Template->find_source($template)
+ and last;
+ }
+ unless ($found) {
+ return
+ {
+ headers =>
+ [
+ "Status: 404",
+ "Content-Type: text/plain",
+ ],
+ content =>
+ "No metadata template meta/prefix_name found"
+ };
+ }
+
+ my %acts =
+ (
+ BSE::Util::Tags->static(undef, $self->cfg),
+ meta => [ \&tag_hash, \%meta ],
+ file => [ \&tag_hash, $file ],
+ src => scalar(escape_html($file->url($self->cfg))),
+ );
+
+ return BSE::Template->get_response($template, $self->cfg, \%acts);
+}
+
+=item $obj->metametadata
+
+Return descriptions of the metadata set by $obj->process file.
+
+Should return a list of hashes, each hash can have the following keys:
+
+=over
+
+=item *
+
+name - the name of the metadata item (required). Note: for image
+types this should be the base name of the metadata, without the
+_data/_width/_height suffix.
+
+=item *
+
+title - a descriptive name of the metadata item
+
+=item *
+
+type - one of "integer", "real", "image", "string", "text", "enum".
+If enum, I<values> should be set. If string, I<size> should be set.
+
+=item *
+
+unit - text to display after the field, typically a unit of measure or
+a format display.
+
+=item *
+
+rules - validation rules as per DevHelp::Validate.
+
+=item *
+
+help - extended help text formatted as HTML
+
+=back
+
+=cut
+
+sub metametadata {
return;
}
1;
+
return $html;
}
+sub metaprefix {
+ "def"
+}
+
1;
$file->add_meta(name => 'audio_type', value => $info{audio_type},
appdata => 0);
- if ($self->cfg_entry("ffmpeg", 1)) {
- my $raw_frame = $self->cfg_entry("raw_frame", 1);
+ if ($self->_have_ffmpeg) {
+ my $raw_frame = $self->_save_raw_frame;
my $fmt = $self->cfg_entry("frame_fmt", "jpeg");
my $content_type = $self->cfg_entry("frame_content_type", "image/$fmt");
my $bin = $self->cfg_entry("ffmpeg_bin", "ffmpeg");
- my @geo_names = split /,/, $self->cfg_entry("frame_thumbs", "");
+ my @geo_names = $self->_thumb_geometries;
my @cvt_options = split /,/, $self->cfg_entry("ffmpeg_options", "");
my $ss = 2+ rand(10);
if ($ss > $dur_secs / 2) {
value => $image_data,
content_type => $content_type,
appdata => 0);
+ $file->add_meta(name => "ph_width",
+ value => $width,
+ appdata => 0);
+ $file->add_meta(name => "ph_height",
+ value => $height,
+ appdata => 0);
}
if (@geo_names) {
return BSE::Template->get_page($template, $self->cfg, \%acts);
}
-sub metacontent {
- my ($self, $file, $meta_name) = @_;
+sub metaprefix { "flv" }
- require BSE::Template;
- my %meta = map { $_->name => $_->value }
- grep $_->content_type eq "text/plain", $file->metadata;
-
- my $template = "meta/flv_$meta_name";
+sub metametadata {
+ my ($self) = @_;
- my %acts =
+ my @fields =
(
- BSE::Util::Tags->static(undef, $self->cfg),
- meta => [ \&tag_hash, \%meta ],
- file => [ \&tag_hash, $file ],
- src => scalar(escape_html($file->url($self->cfg))),
+ {
+ name => "width",
+ title => "Video Width",
+ unit => "pixels",
+ type => "integer",
+ },
+ {
+ name => "height",
+ title => "Video Height",
+ unit => "pixels",
+ type => "integer",
+ },
+ {
+ name => "duration",
+ title => "Video Duration",
+ unit => "seconds",
+ type => "real",
+ },
+ {
+ name => "duration_formatted",
+ title => "Formatted Video Duration",
+ unit => "(hh:mm:ss)",
+ type => "string",
+ },
+ {
+ name => "audio_type",
+ title => "Audio Format",
+ type => "enum",
+ values => "mono;stereo",
+ labels => "Mono;Stereo",
+ },
);
+ if ($self->_have_ffmpeg) {
+ if ($self->_save_raw_frame) {
+ push @fields,
+ {
+ name => "ph",
+ title => "Placeholder",
+ type => "image"
+ };
+ }
+
+ for my $geo ($self->_thumb_geometries) {
+ push @fields,
+ {
+ name => "ph_${geo}",
+ title => "\u$geo thumbnail of placeholder",
+ type => "image",
+ };
+ }
+ }
+
+ return @fields;
+}
+
+sub _have_ffmpeg {
+ my $self = shift;
+
+ return $self->cfg_entry("ffmpeg", 1);
+}
+
+sub _save_raw_frame {
+ my $self = shift;
+
+ return $self->cfg_entry("raw_frame", 1);
+}
+
+sub _thumb_geometries {
+ my $self = shift;
- return BSE::Template->get_response($template, $self->cfg, \%acts);
+ return split /,/, $self->cfg_entry("frame_thumbs", "")
}
1;
--- /dev/null
+package BSE::FileMetaMeta;
+use strict;
+use Carp qw(confess);
+
+my %meta_rules =
+ (
+ meta_real =>
+ {
+ match => qr/^\s*[+-]?(?:\d+(?:\.\d+)|\.\d+)(?:[eE][+-]?\d+)?\s*\z/,
+ error => '$n must be a number',
+ },
+ );
+
+my %rule_map =
+ (
+ integer => "integer",
+ string => "dh_one_line",
+ real => "meta_real",
+ enum => "meta_enum", # generated
+ );
+
+sub new {
+ my $class = shift;
+ my %opts =
+ (
+ rules => '',
+ ro => 0,
+ values => [],
+ cond => "1",
+ type => "string",
+ unit => '',
+ help => '',
+ @_
+ );
+
+ $opts{cfg} && $opts{cfg}->can("entry")
+ or confess "Missing or invalid cfg parameter";
+ $opts{name}
+ or confess "Missing name parameter";
+ $opts{name} =~ /^[a-z]\w*$/i
+ or confess "Invalid metadata name parameter";
+
+ my $name = $opts{name};
+ for my $subkey (qw/data width height/) {
+ my $key = $subkey . "_name";
+ defined $opts{$key} or $opts{$key} = $name . "_" . $subkey;
+ }
+ $opts{title} ||= $name;
+
+ if ($opts{type} eq "enum") {
+ if ($opts{values}) {
+ unless (ref $opts{values}) {
+ $opts{values} = [ split /;/, $opts{values} ];
+ }
+ @{$opts{values}}
+ or confess "$opts{name} has enum type but no values";
+ }
+ else {
+ confess "$opts{name} has enum type but no values";
+ }
+
+ if ($opts{labels}) {
+ unless (ref $opts{labels}) {
+ $opts{labels} = [ split /;/, $opts{labels} ];
+ }
+ @{$opts{labels}}
+ or confess "$opts{name} has enum type but no labels";
+ }
+ else {
+ $opts{labels} = $opts{values};
+ }
+ }
+
+ ref $opts{rules} or $opts{rules} = [ split /[,;]/, $opts{rules} ];
+
+ if ($opts{cond}) {
+ my $code = $opts{cond};
+ $opts{cond} = eval 'sub { my $file = shift; ' . $code . ' }'
+ or die "Cannot compile condition code <$code> for $opts{name}: $@";
+ }
+ else {
+ $opts{cond} = sub { 1 };
+ }
+
+ bless \%opts, $class;
+}
+
+sub name { $_[0]{name} }
+
+sub type { $_[0]{type} }
+
+sub title { $_[0]{title} }
+
+sub rules { @{$_[0]{rules}} }
+
+sub values { @{$_[0]{values}} }
+
+sub labels { @{$_[0]{labels}} }
+
+sub ro { $_[0]{ro} }
+
+sub unit { $_[0]{unit} }
+
+sub is_text {
+ $_[0]{type} ne "image";
+}
+
+sub cond {
+ my ($self, $file) = @_;
+
+ return $self->{cond}->($file);
+}
+
+sub validate {
+ my ($self, %opts) = @_;
+
+ my $value = delete $opts{value};
+ defined $value
+ or confess "value not supplied\n";
+ my $rerror = delete $opts{error}
+ or confess "error ref not supplied\n";
+
+ # kind of clumsy
+ require DevHelp::Validate;
+ my @field_rules = $self->rules;
+ $rule_map{$self->type} && unshift @field_rules, $rule_map{$self->type};
+ my %values =
+ (
+ value => $value
+ );
+ my %fields =
+ (
+ value =>
+ {
+ rules => \@field_rules,
+ description => $self->title,
+ },
+ );
+ my %rules = %meta_rules;
+ if ($self->type eq "enum") {
+ $rules{meta_enum} =
+ {
+ match => "^(?:" . join("|", map quotemeta, $self->values) . ")\\z",
+ error => '$n must be one of ' . join(", ", $self->values),
+ };
+ }
+
+ my $val = DevHelp::Validate::Hash->new
+ (
+ fields => \%fields,
+ rules => \%rules,
+ cfg => $self->{cfg},
+ section => "file metadata validation",
+ );
+ my %errors;
+ $val->validate(\%values, \%errors);
+ if (keys %errors) {
+ $$rerror = $errors{value};
+ return;
+ }
+
+ return 1;
+}
+
+sub metanames {
+ my ($self) = @_;
+
+ if ($self->type eq 'image') {
+ return ( $self->data_name, $self->width_name, $self->height_name );
+ }
+ else {
+ return $self->name;
+ }
+}
+
+sub data_name {
+ $_[0]{data_name}
+}
+
+sub width_name {
+ $_[0]{width_name}
+}
+
+sub height_name {
+ $_[0]{height_name}
+}
+
+sub keys {
+ qw/title help rules ro values labels type data_name width_name height_name cond unit/;
+}
+
+1;
BSE::DB->run(bseClearArticleFileSysMetadata => $self->{id});
}
+sub delete_meta_by_name {
+ my ($self, $name) = @_;
+
+ BSE::DB->run(bseDeleteArticleFileMetaByName => $self->{id}, $name);
+}
+
sub set_handler {
my ($self, $cfg) = @_;
}
}
+=item metanames
+
+returns the names of each metadatum defined for the file.
+
+=cut
+
+sub metanames {
+ my ($self) = @_;
+
+ require BSE::TB::ArticleFileMetas;
+ return map $_->{name}, BSE::TB::ArticleFileMetas->getColumnsBy
+ (
+ [ "name" ],
+ file_id => $self->id,
+ );
+}
+
+=item metainfo
+
+Returns all but the value for metadata defined for the file.
+
+=cut
+
+sub metainfo {
+ my ($self) = @_;
+
+ require BSE::TB::ArticleFileMetas;
+ my @cols = grep $_ ne "value", BSE::TB::ArticleFileMeta->columns;
+ return BSE::TB::ArticleFileMetas->getColumnsBy
+ (
+ \@cols,
+ file_id => $self->id,
+ );
+}
+
+sub metafields {
+ my ($self, $cfg) = @_;
+
+ my %metanames = map { $_ => 1 } $self->metanames;
+
+ my @fields = grep $metanames{$_->name} || $_->cond($self), BSE::TB::ArticleFiles->all_metametadata($cfg);
+
+ my $handler = $self->handler($cfg);
+
+ my @handler_fields = map BSE::FileMetaMeta->new(%$_, ro => 1, cfg => $cfg), $handler->metametadata;
+
+ return ( @fields, @handler_fields );
+}
+
1;
appdata => 1,
}
+sub is_text {
+ $_[0]->content_type eq "text/plain"
+}
+
1;
return BSE::StorageMgr::Files->new(cfg => $cfg);
}
+sub all_metametadata {
+ my ($self, $cfg) = @_;
+
+ $cfg
+ or confess "Missing cfg parameter";
+
+ require BSE::FileMetaMeta;
+ my @metafields;
+ my @keys = $cfg->orderCS("global file metadata");
+ for my $name (@keys) {
+ my %opts = ( name => $name );
+ my $section = "file metadata $name";
+ for my $key (BSE::FileMetaMeta->keys) {
+ my $value = $cfg->entry($section, $key);
+ if (defined $value) {
+ $opts{$key} = $value;
+ }
+ }
+ push @metafields, BSE::FileMetaMeta->new(%opts, cfg => $cfg);
+ }
+
+ return @metafields;
+}
+
1;
return $sth;
}
+sub getColumnsBy {
+ my ($self, $cols, %find) = @_;
+
+ my @db_cols = $self->rowClass->db_columns;
+ my @code_cols = $self->rowClass->columns;
+ my %map;
+ @map{@code_cols} = @db_cols;
+
+ my @conds;
+ my @args;
+ for my $col (keys %find) {
+ my $db_col = $map{$col}
+ or confess "Cannot generate query: unknown column $col";
+ # this doesn't handle null, but that should use a "special"
+ push @conds, "$db_col = ?";
+ push @args, $find{$col};
+ }
+ my @result_cols = map $map{$_}, @$cols;
+
+ my $sql = "select " . join(",", @result_cols) .
+ " from " . $self->rowClass->table .
+ " where " . join(" and ", @conds);
+
+ my $sth = $dh->{dbh}->prepare($sql)
+ or confess "Cannot prepare generated $sql: ", $dh->{dbh}->errstr;
+
+ $sth->execute(@args)
+ or confess "Cannot execute $sql: ",$dh->{dbh}->errstr;
+
+ my @rows;
+ while (my $row = $sth->fetchrow_arrayref) {
+ my %row;
+ @row{@$cols} = @$row;
+ push @rows, \%row;
+ }
+
+ return wantarray ? @rows : \@rows;
+}
+
sub getSpecial {
my ($self, $name, @args) = @_;
use vars qw($VERSION);
use strict;
use Carp qw/cluck confess/;
-use constant DEBUG => 0;
+use constant DEBUG => 1;
$VERSION="0.09";
where file_id = ? and appdata = 0
SQL
+name: bseDeleteArticleFileMetaByName
+sql_statement: <<SQL
+delete from bse_article_file_meta
+where file_id = ? and name = ?
+SQL
=back
+=head2 [global file metadata]
+
+Each key represents an item of metadata for files attached to
+articles.
+
+The values are ignored.
+
+For each key, extra information is defined in the [file metadata
+I<name>] section.
+
+=head2 [file metadata I<name>]
+
+Definition for the file metadata item I<name>.
+
+=over
+
+=item *
+
+title - descriptive name of the metadata. Defaults to I<name>.
+
+=item *
+
+rules - validation rules, separated by ;. Custom rules can be defined
+in [file metadata validation].
+
+=item *
+
+ro - if non-zero the metadata cannot be modified directly by the admin
+(applications can still generate it). Default: writable.
+
+=item *
+
+type - the data type of the metadata, any of string, text, enum,
+integer, real, image. If this is enum values must be defined and
+labels should be. Default: string.
+
+The types are:
+
+=over
+
+=item *
+
+string - single line of text
+
+=item *
+
+text - one or more lines of text
+
+=item *
+
+integer - whole number
+
+=item *
+
+real - number with decimal points
+
+=item *
+
+enum - select from a list of possible values.
+
+=item *
+
+image - image file.
+
+=back
+
+=item *
+
+values - semi-colon separated list of values for this metadata.
+
+=item *
+
+labels - semi-colon separated list of labels for the values
+
+=item *
+
+help - help html to display for the metadata
+
+=item *
+
+data_name - (images only) the key to use to store the image data.
+Default: I<name>_data.
+
+=item *
+
+width_name - (images only) the key to use to store the image width.
+Default: I<name>_width.
+
+=item *
+
+height_name - (images only) the key to use to store the image height.
+Default: I<name>_height.
+
+=item *
+
+cond - a perl expression indicating whether the metadata should be
+prompted for, for this file. $file is the file object. Default: 1.
+
+=item *
+
+unit - text displayed after the entry box for the metadata. Default:
+empty. Useful for including a unit ("pixels") or format help
+("hh:mm").
+
+=back
+
=head1 AUTHOR
Tony Cook <tony@develop-help.com>
text-align: center;
}
+.help_display { display: none }
+
+.help_display_trigger {
+ position: relative;
+}
+
+.help_display_trigger:hover .help_display {
+ display: block;
+ position: absolute;
+ width: 200px;
+ left: 0px;
+ border: 1px solid #888;
+ background-color: #fff;
+ z-index: 10;
+ padding: 2px;
+ text-align: left;
+ font-weight: normal;
+}
+
#bse_video_stage {
position: fixed;
width: 100%;
border: 1px solid #CCC;
}
+.meta_string { width: 95%; }
+.meta_real { width: 10em; }
+.meta_integer { width: 5em; }
<input type="hidden" name="file_id" value="<:efile id:>" />
<input type="hidden" name="_t" value="file" />
<input type="hidden" name="save_file_flags" value="1" />
- <table class="editform editformsmall">
+ <table class="editform editformsmall" id="filelist">
<tr>
<th align="left">Replacement file:</th>
<td>
</td>
<td nowrap="nowrap"><:help file hide_from_list:> <:error_img hide_from_list:></td>
</tr>
+<:if Filemetas:>
+<tr>
+ <th colspan="3">File metadata</th>
+</tr>
+<:iterator begin filemetas:>
+<tr>
+ <th><:filemeta title:></th>
+ <td>
+<:if Filemeta ro:>
+<:switch:>
+<:case Match [filemeta type] "^(integer|string|real)$" |x:>
+<:filemeta_value [filemeta name] :> <:filemeta unit:>
+<:case Eq [filemeta type] "text":>
+<textarea name="meta_<:filemeta name:>" class="meta_text_ro"><:filemeta_value [filemeta name]:></textarea>
+<:case Eq [filemeta type] "enum":>
+<:filemeta_select_label:> <:filemeta unit:>
+<:case Eq [filemeta type] "image":>
+<:ifFilemeta_set [filemeta data_name]:><span class="file_display_trigger">Show
+<div class="file_display"><img src="<:filemeta_source [filemeta data_name]:>" width="<:filemeta_value [filemeta width_name]:>" height="<:filemeta_value [filemeta height_name]:>" /></div></span><:or:>(No image set)<:eif:>
+<:case default:>
+Unknown metadata type <:filemeta type:>
+<:endswitch:>
+<:or Filemeta:>
+<:switch:>
+<:case Match [filemeta type] "^(integer|string|real)$" |x:>
+<input type="text" name="meta_<:filemeta name:>" value="<:filemeta_value [filemeta name] :>" class="meta_<:filemeta type:>" /> <:filemeta unit:>
+<:case Eq [filemeta type] "text":>
+<textarea name="meta_<:filemeta name:>" class="meta_text"><:filemeta_value [filemeta name]:></textarea>
+<:case Eq [filemeta type] "enum":>
+<:filemeta_select:> <:filemeta unit:>
+<:case Eq [filemeta type] "image":>
+<input type="file" name="meta_<:filemeta name:>" />
+<:ifFilemeta_set [filemeta data_name]:><span class="file_display_trigger">Show
+<div class="file_display"><img src="<:filemeta_source [filemeta data_name]:>" width="<:filemeta_value [filemeta width_name]:>" height="<:filemeta_value [filemeta height_name]:>" /></div></span>
+<input type="checkbox" name="delete_meta_<:filemeta name:>" value="1" /> Delete
+<:or:>(No image set)<:eif:>
+<:case default:>
+Unknown metadata type <:filemeta type:>
+<:endswitch:>
+<:if Match [filemeta type] "image":><:or Match:>
+<:ifFilemeta_set [filemeta name]:><input type="checkbox" name="delete_meta_<:filemeta name:>" value="1" /> Delete<:or:><:eif:>
+<:eif Match:>
+<:eif Filemeta:>
+ </td>
+ <td>
+<:if Filemeta help:>
+<span class="help_display_trigger"><img src="/images/admin/help.gif" />
+<div class="help_display"><:filemeta help |z:></div></span>
+<:or Filemeta:><:eif Filemeta:>
+ </td>
+</tr>
+<:iterator end filemetas:>
+<:or Filemetas:><:eif Filemetas:>
<tr>
<td colspan="3" align="right">
<input type="submit" name="a_save_file" value="Save File" />
<h2>Manage files</h2>
-<form method="post" action="<:script:>" enctype="multipart/form-data">
+<form method="post" action="<:script:>" enctype="multipart/form-data" id="filelist">
<input type="hidden" name="id" value="<: article id :>" />
<input type="hidden" name="_t" value="file" />
<input type="hidden" name="save_file_flags" value="1" />