site/cgi-bin/modules/BSE/Message.pm
site/cgi-bin/modules/BSE/NLFilter/SQL.pm
site/cgi-bin/modules/BSE/Permissions.pm
+site/cgi-bin/modules/BSE/ProductImportXLS.pm
site/cgi-bin/modules/BSE/Report.pm
site/cgi-bin/modules/BSE/Request.pm
site/cgi-bin/modules/BSE/Request/Base.pm
site/cgi-bin/modules/BSE/TB/AdminPerms.pm
site/cgi-bin/modules/BSE/TB/AdminUser.pm
site/cgi-bin/modules/BSE/TB/AdminUsers.pm
+site/cgi-bin/modules/BSE/TB/FileAccessLog.pm
+site/cgi-bin/modules/BSE/TB/FileAccessLogEntry.pm
site/cgi-bin/modules/BSE/TB/Location.pm
site/cgi-bin/modules/BSE/TB/Locations.pm
site/cgi-bin/modules/BSE/TB/Order.pm
site/cgi-bin/modules/BSE/TB/OrderItemOption.pm
site/cgi-bin/modules/BSE/TB/OrderItemOptions.pm
site/cgi-bin/modules/BSE/TB/OrderItems.pm
-site/cgi-bin/modules/BSE/ProductImportXLS.pm
+site/cgi-bin/modules/BSE/TB/OwnedFile.pm
+site/cgi-bin/modules/BSE/TB/OwnedFiles.pm
site/cgi-bin/modules/BSE/TB/Seminar.pm
site/cgi-bin/modules/BSE/TB/ProductOption.pm
site/cgi-bin/modules/BSE/TB/ProductOptions.pm
site/cgi-bin/modules/BSE/UI/Tellafriend.pm
site/cgi-bin/modules/BSE/UI/Thumb.pm
site/cgi-bin/modules/BSE/UI/User.pm
+site/cgi-bin/modules/BSE/UI/UserCommon.pm
site/cgi-bin/modules/BSE/UserReg.pm
site/cgi-bin/modules/BSE/Util/ContentType.pm
site/cgi-bin/modules/BSE/Util/DynSort.pm
site/htdocs/images/titles/the_shop.gif
site/htdocs/images/titles/your_site.gif
site/htdocs/images/trans_pixel.gif
+site/htdocs/js/admin_dragdrop.js
site/htdocs/js/admin_prodopts.js
site/htdocs/js/builder.js
site/htdocs/js/controls.js
# site/templates/admin/edit_4.tmpl
# site/templates/admin/edit_5.tmpl
site/templates/admin/edit_catalog.tmpl
+site/templates/admin/edit_dragdrop.tmpl
site/templates/admin/edit_groups.tmpl
+site/templates/admin/edit_kidsof.tmpl
site/templates/admin/edit_prodopts.tmpl
site/templates/admin/edit_product.tmpl
site/templates/admin/edit_seminar.tmpl
site/templates/admin/subscr/list.tmpl
site/templates/admin/userlist.tmpl
site/templates/admin/users/add.tmpl
+site/templates/admin/users/add_group_file.tmpl
+site/templates/admin/users/add_user_file.tmpl
+site/templates/admin/users/delete_group_file.tmpl
+site/templates/admin/users/delete_user_file.tmpl
site/templates/admin/users/edit.tmpl
+site/templates/admin/users/edit_files.tmpl
site/templates/admin/users/edit_groups.tmpl
+site/templates/admin/users/edit_group_file.tmpl
site/templates/admin/users/edit_orders.tmpl
+site/templates/admin/users/edit_user_file.tmpl
+site/templates/admin/users/fileaccess.tmpl
site/templates/admin/users/groupadd.tmpl
site/templates/admin/users/groupdelete.tmpl
site/templates/admin/users/groupedit.tmpl
+site/templates/admin/users/groupedit_files.tmpl
site/templates/admin/users/grouplist.tmpl
site/templates/admin/users/groupmembers.tmpl
+site/templates/admin/users/inc_add_user_file.tmpl
+site/templates/admin/users/inc_group_menu.tmpl
+site/templates/admin/users/inc_user_menu.tmpl
site/templates/admin/users/list.tmpl
site/templates/admin/users/list_low.tmpl
site/templates/admin/users/view.tmpl
site/templates/user/base_editbooking.tmpl
site/templates/user/base_orderdetail.tmpl
site/templates/user/base_redirect.tmpl
+site/templates/user/base_userpage_files.tmpl
+site/templates/user/base_userpage_orders.tmpl
site/templates/user/base_userpage_wishlist.tmpl
site/templates/user/base_wishlist.tmpl
site/templates/user/blacklistdone_base.tmpl
display_order integer not null,
index item_order(order_item_id, display_order)
) type=innodb;
+
+drop table if exists bse_owned_files;
+create table bse_owned_files (
+ id integer not null auto_increment primary key,
+
+ -- owner type, either 'U' or 'G'
+ owner_type char not null,
+
+ -- siteuser_id when owner_type is 'U'
+ -- group_id when owner_type is 'G'
+ owner_id integer not null,
+
+ category varchar(20) not null,
+ filename varchar(255) not null,
+ display_name varchar(255) not null,
+ content_type varchar(80) not null,
+ download integer not null,
+ title varchar(255) not null,
+ body text not null,
+ modwhen datetime not null,
+ size_in_bytes integer not null,
+ index by_owner_category(owner_type, owner_id, category)
+);
+
+drop table if exists bse_file_subscriptions;
+create table bse_file_subscriptions (
+ id integer not null,
+ siteuser_id integer not null,
+ category varchar(20) not null,
+
+ index by_siteuser(siteuser_id),
+ index by_category(category)
+);
+
+drop table if exists bse_file_notifies;
+create table bse_file_notifies (
+ id integer not null auto_increment primary key,
+ siteuser_id integer not null,
+ file_id integer not null,
+ index by_siteuser(siteuser_id)
+);
+
+drop table if exists bse_file_access_log;
+create table bse_file_access_log (
+ id integer not null auto_increment primary key,
+ when_at datetime not null,
+ siteuser_id integer not null,
+ siteuser_logon varchar(40) not null,
+
+ file_id integer not null,
+ owner_type char not null,
+ owner_id integer not null,
+ category varchar(20) not null,
+ filename varchar(255) not null,
+ display_name varchar(255) not null,
+ content_type varchar(80) not null,
+ download integer not null,
+ title varchar(255) not null,
+ modwhen datetime not null,
+ size_in_bytes integer not null,
+
+ index by_when_at(when_at),
+ index by_file(file_id),
+ index by_user(siteuser_id, when_at)
+);
user/unsubone.tmpl = user,user/unsubone_base.tmpl
user/userpage.tmpl = user,user/userpage_base.tmpl
user/userpage_wishlist.tmpl = user,user/base_userpage_wishlist.tmpl
+user/userpage_files.tmpl = user,user/base_userpage_files.tmpl
+user/userpage_orders.tmpl = user,user/base_userpage_orders.tmpl
user/wishlist.tmpl = user,user/base_wishlist.tmpl
user/bookseminar.tmpl = user,user/base_bookseminar.tmpl
user/bookconfirm.tmpl = user,user/base_bookconfirm.tmpl
use BSE::Cfg;
require Exporter;
@ISA = qw(Exporter);
-@EXPORT_OK = qw(bse_cfg bse_make_product bse_make_catalog bse_encoding bse_add_image bse_add_step_child);
+@EXPORT_OK = qw(bse_cfg bse_make_product bse_make_catalog bse_encoding bse_add_image bse_add_step_child bse_add_owned_file bse_delete_owned_file bse_replace_owned_file);
use Carp qw(confess croak);
+use Fcntl qw(:seek);
my %acticle_defaults =
(
return BSE::Edit::Base->article_class($article, 'Articles', $cfg);
}
+# File::Copy doesn't like CGI.pm's fake fhs
+sub _copy_file_from_fh {
+ my ($in_fh, $out_fh) = @_;
+
+ binmode $out_fh;
+ binmode $in_fh;
+ seek $in_fh, 0, SEEK_SET;
+ my $data;
+ local $/ = \8192;
+ while (defined ($data = <$in_fh>)) {
+ print $out_fh $data;
+ }
+
+ 1;
+}
+
+sub bse_add_owned_file {
+ my ($cfg, $owner, %opts) = @_;
+
+ defined $opts{display_name} && $opts{display_name} =~ /\S/
+ or croak "bse_add_owned_file: display_name must be non-blank";
+
+ defined $opts{title} && $opts{title} =~ /\S/
+ or $opts{title} = $opts{display_name};
+
+ unless ($opts{content_type}) {
+ require BSE::Util::ContentType;
+ $opts{content_type} = BSE::Util::ContentType::content_type($cfg, $opts{display_name});
+ }
+
+ my $file = delete $opts{file}
+ or die "No source file provided\n";;
+
+ # copy the file to the right place
+ require DevHelp::FileUpload;
+ my $file_dir = $cfg->entryVar('paths', 'downloads');
+ my $msg;
+ my ($saved_name, $out_fh) = DevHelp::FileUpload->
+ make_img_filename($file_dir, $opts{display_name}, \$msg)
+ or die "$msg\n";
+ _copy_file_from_fh($file, $out_fh)
+ or die "$!\n";
+ unless (close $out_fh) {
+ die "Error saving file: $!\n";
+ }
+
+ $opts{owner_type} = $owner->file_owner_type;
+ $opts{size_in_bytes} = -s "$file_dir/$saved_name";
+ $opts{owner_id} = $owner->id;
+ $opts{category} ||= '';
+ $opts{filename} = $saved_name;
+
+ require BSE::TB::OwnedFiles;
+ return BSE::TB::OwnedFiles->make(%opts);
+}
+
+sub bse_delete_owned_file {
+ my ($cfg, $owned_file) = @_;
+
+ my $file_dir = $cfg->entryVar('paths', 'downloads');
+ unlink "$file_dir/$owned_file->{filename}";
+ $owned_file->remove;
+}
+
+sub bse_replace_owned_file {
+ my ($cfg, $owned_file, %opts) = @_;
+
+ my $file_dir = $cfg->entryVar('paths', 'downloads');
+ my $old_name;
+ if ($opts{file}) {
+ my $msg;
+ require DevHelp::FileUpload;
+ my ($saved_name, $out_fh) = DevHelp::FileUpload->
+ make_img_filename($file_dir, $opts{display_name}, \$msg)
+ or die "$msg\n";
+ _copy_file_from_fh($opts{file}, $out_fh)
+ or die "$!\n";
+ unless (close $out_fh) {
+ die "Error saving file: $!\n";
+ }
+ $old_name = $owned_file->{filename};
+ $owned_file->{filename} = $saved_name;
+ $owned_file->{size_in_bytes} = -s "$file_dir/$saved_name";
+ }
+
+ for my $field (qw/category display_name content_type download title body modwhen size_in_bytes/) {
+ defined $opts{$field}
+ and $owned_file->{$field} = $opts{$field};
+ }
+ $owned_file->save;
+ $old_name
+ and unlink "$file_dir/$old_name";
+
+ 1;
+}
+
1;
use strict;
use base qw(BSE::UI::AdminDispatch BSE::UI::SiteuserCommon);
use BSE::Util::Tags qw(tag_error_img tag_hash);
-use DevHelp::HTML;
+use DevHelp::HTML qw(:default popup_menu);
use SiteUsers;
use BSE::Util::Iterate;
use BSE::Util::DynSort qw(sorter tag_sorthelp);
use BSE::CfgInfo qw(custom_class);
use constant SITEUSER_GROUP_SECT => 'BSE Siteuser groups validation';
use BSE::Template;
+use DevHelp::Date qw(dh_parse_date_sql dh_parse_time_sql);
my %actions =
(
groupmemberform => 'bse_members_user_edit',
savegroupmembers => 'bse_members_user_edit',
confirm => 'bse_members_confirm',
+ adduserfile => 'bse_members_user_add_file',
+ adduserfileform => 'bse_members_user_add_file',
+ edituserfile => 'bse_members_user_edit_file',
+ saveuserfile => 'bse_members_user_edit_file',
+ deluserfileform => 'bse_members_user_del_file',
+ deluserfile => 'bse_members_user_del_file',
+
+ addgroupfile => 'bse_members_group_add_file',
+ addgroupfileform => 'bse_members_group_add_file',
+ editgroupfile => 'bse_members_group_edit_file',
+ savegroupfile => 'bse_members_group_edit_file',
+ delgroupfileform => 'bse_members_group_del_file',
+ delgroupfile => 'bse_members_group_del_file',
+ fileaccesslog => 'bse_members_file_log',
);
my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation flags affiliate_name previousLogon); # flags is saved separately
$msg = $req->message($errors);
}
+ require BSE::TB::OwnedFiles;
+ my @file_cats = BSE::TB::OwnedFiles->categories($req->cfg);
+ my %subbed = map { $_ => 1 } $siteuser->subscribed_file_categories;
+ for my $cat (@file_cats) {
+ $cat->{subscribed} = exists $subbed{$cat->{id}} ? 1 : 0;
+ }
+
my @subs = grep $_->{visible}, BSE::SubscriptionTypes->all;
my $sub_index;
require BSE::SubscribedUsers;
my @usersubs = BSE::SubscribedUsers->getBy(userId=>$siteuser->{id});
my %usersubs = map { $_->{subId}, $_ } @usersubs;
my $current_group;
+ my $current_file;
my %acts;
%acts =
(
- BSE::Util::Tags->basic(undef, $req->cgi, $req->cfg),
- BSE::Util::Tags->secure($req),
- BSE::Util::Tags->admin(undef, $req->cfg),
+ $req->admin_tags,
message => $msg,
siteuser => [ \&tag_hash, $siteuser ],
error_img => [ \&tag_error_img, $req->cfg, $errors ],
ifMember => [ \&tag_ifUserMember, $siteuser, \$current_group ],
$it->make_iterator([ \&iter_seminar_bookings, $siteuser],
'booking', 'bookings'),
+ $it->make
+ (
+ code => [ files => $siteuser ],
+ single => "userfile",
+ plural => "userfiles",
+ store => \$current_file,
+ ),
+ userfile_category => [ tag_userfile_category => $class, $req, \$current_file ],
+ $it->make
+ (
+ data => \@file_cats,
+ single => "filecat",
+ plural => "filecats"
+ ),
);
return $req->dyn_response($template, \%acts);
}
+sub tag_userfile_category {
+ my ($self, $req, $rfile) = @_;
+
+ my ($current) = $req->cgi->param("category");
+ unless (defined $current) {
+ if ($rfile && $$rfile) {
+ $current = $$rfile->category;
+ }
+ }
+ defined $current
+ or $current = "";
+
+ require BSE::TB::OwnedFiles;
+ my @all = BSE::TB::OwnedFiles->categories($req->cfg);
+ return popup_menu
+ (
+ -name => "category",
+ -default => $current,
+ -values => [ map $_->{id}, @all ],
+ -labels => { map { $_->{id} => $_->{name} } @all },
+ );
+}
+
sub iter_seminar_bookings {
my ($siteuser) = @_;
$class->save_subs($req, $user);
}
+ if ($cgi->param('save_file_subs')) {
+ my @new_subs = $cgi->param("file_subscriptions");
+ $user->set_subscribed_file_categories($cfg, @new_subs);
+ }
+
$custom->siteusers_changed($cfg);
$custom->can('siteuser_edit')
and $custom->siteuser_edit($user, 'admin', $cfg);
my ($req, $msg) = @_;
my $id = $req->cgi->param('id');
- defined $id && $id =~ /^\d+$/
+ defined $id && $id =~ /^-?\d+$/
or do { $$msg = "Missing or invalid group id"; return };
+
+ my $group;
require BSE::TB::SiteUserGroups;
- my $group = BSE::TB::SiteUserGroups->getByPkey($id);
+ if ($id < 0) {
+ $group = BSE::TB::SiteUserGroups->getQueryGroup($req->cfg, $id);
+ }
+ else {
+ $group = BSE::TB::SiteUserGroups->getByPkey($id);
+ }
$group
or do { $$msg = "Unknown group id"; return };
my ($class, $req, $errors) = @_;
require BSE::TB::SiteUserGroups;
- my @groups = BSE::TB::SiteUserGroups->all;
+ my @groups = BSE::TB::SiteUserGroups->admin_and_query_groups($req->cfg);
my $msg = $req->message($errors);
or return $class->req_grouplist($req, { id=> $msg });
$msg = $req->message($errors);
-
+ my $it = BSE::Util::Iterate->new;
+ my $current_file;
my %acts;
%acts =
(
message=>$msg,
error_img => [ \&tag_error_img, $req->cfg, $errors ],
group => [ \&tag_hash, $group ],
+ $it->make
+ (
+ code => [ files => $group ],
+ single => "groupfile",
+ plural => "groupfiles",
+ store => \$current_file,
+ ),
);
return $req->dyn_response($template, \%acts);
return BSE::Template->get_refresh($r, $req->cfg);
}
+my %file_fields =
+ (
+ content_type =>
+ {
+ description => "Content type",
+ rules => "dh_one_line",
+ },
+ category =>
+ {
+ description => "Category",
+ rules => "dh_one_line",
+ },
+ modwhen_date =>
+ {
+ description => "Last Modified date",
+ rules => "date",
+ requried_if => "modwhen_time",
+ },
+ modwhen_time =>
+ {
+ description => "Last Modified time",
+ rules => "time",
+ required_if => "modwhen_date",
+ },
+ title =>
+ {
+ description => "Title",
+ rules => "dh_one_line",
+ },
+ body =>
+ {
+ description => "Body",
+ },
+ );
+
+my %save_file_fields =
+ (
+ content_type =>
+ {
+ description => "Content type",
+ rules => "dh_one_line",
+ },
+ category =>
+ {
+ description => "Category",
+ rules => "dh_one_line",
+ },
+ modwhen_date =>
+ {
+ description => "Last Modified date",
+ rules => "date",
+ requried => 1,
+ },
+ modwhen_time =>
+ {
+ description => "Last Modified time",
+ rules => "time",
+ required => 1,
+ },
+ title =>
+ {
+ description => "Title",
+ rules => "dh_one_line",
+ required => 1,
+ },
+ body =>
+ {
+ description => "Body",
+ },
+ );
+
+sub req_adduserfileform {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $siteuser = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my %acts =
+ (
+ $req->admin_tags,
+ message => $msg,
+ siteuser => [ \&tag_hash, $siteuser ],
+ error_img => [ \&tag_error_img, $req->cfg, $errors ],
+ userfile_category => [ tag_userfile_category => $self, $req, undef ],
+ );
+
+ return $req->dyn_response("admin/users/add_user_file", \%acts);
+}
+
+sub req_adduserfile {
+ my ($self, $req) = @_;
+
+ my $msg;
+ my $user = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $cgi = $req->cgi;
+
+ $req->check_csrf("admin_user_add_file")
+ or return $self->csrf_error($req, "admin_user_add_file", "Add Member File");
+
+ my %errors;
+ $req->validate(fields => \%file_fields,
+ errors => \%errors);
+
+ my $file = $cgi->param("file");
+ my $file_fh = $cgi->upload("file");
+ unless ($file) {
+ $errors{file} = "Please select a file";
+ }
+ if ($file && -z $file) {
+ $errors{file} = "File is empty";
+ }
+ if (!$errors{$file} && !$file_fh) {
+ $errors{file} = "Something is wrong with the upload form or your file wasn't found";
+ }
+
+ keys %errors
+ and return $self->req_adduserfileform($req, undef, \%errors);
+
+ require BSE::API;
+ BSE::API->import("bse_add_owned_file");
+
+ my %file;
+ $file{file} = $file_fh;
+ for my $field (qw/content_type category title body/) {
+ my ($value) = $cgi->param($field);
+ defined $value or $value = "";
+ $file{$field} = $value;
+ }
+ $file{download} = $cgi->param('download') ? 1 : 0;
+ my $mod_date = $cgi->param("modwhen_date");
+ my $mod_time = $cgi->param("modwhen_time");
+ if ($mod_date && $mod_time) {
+ $file{modwhen} = dh_parse_date_sql($mod_date) . " "
+ . dh_parse_time_sql($mod_time);
+ }
+ $file{display_name} = $file . "";
+ my $upload_info = $cgi->uploadInfo($file);
+# some content types come through strangely
+# if (!$file{content_type} && $upload_info->{"Content-Type"}) {
+# $file{content_type} = $upload_info->{"Content-Type"}
+# }
+ for my $key (keys %$upload_info) {
+ print STDERR "uploadinfo: $key: $upload_info->{$key}\n";
+ }
+ local $SIG{__DIE__};
+ my $owned_file = eval { bse_add_owned_file($req->cfg, $user, %file) };
+ unless ($owned_file) {
+ $errors{file} = $@;
+ return $self->req_edit($req, undef, \%errors);
+ }
+
+ my $r = $cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_edit => 1, _t => "files", id => $user->id, m => "File created" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub _get_user_file {
+ my ($req, $user, $msg) = @_;
+
+ my $file_id = $req->cgi->param("file_id");
+ unless (defined $file_id && $file_id =~ /^\d+$/) {
+ $$msg = "Missing or invalid file id";
+ return;
+ }
+ require BSE::TB::OwnedFiles;
+ my ($file) = BSE::TB::OwnedFiles->getBy
+ (
+ owner_type => $user->file_owner_type,
+ owner_id => $user->id,
+ id => $file_id
+ );
+ unless ($file) {
+ $$msg = "No such file found";
+ return;
+ }
+
+ return $file;
+}
+
+sub _show_userfile {
+ my ($self, $req, $template, $siteuser, $file, $errors) = @_;
+
+ my $message = $req->message($errors);
+
+ my %acts =
+ (
+ $req->admin_tags,
+ userfile => [ \&tag_hash, $file ],
+ message => $message,
+ siteuser => [ \&tag_hash, $siteuser ],
+ error_img => [ \&tag_error_img, $req->cfg, $errors ],
+ userfile_category => [ tag_userfile_category => $self, $req, \$file ],
+ );
+
+ return $req->dyn_response($template, \%acts);
+}
+
+sub req_edituserfile {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $siteuser = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_user_file($req, $siteuser, \$msg)
+ or return $self->req_list($req, $msg);
+
+ return $self->_show_userfile($req, "admin/users/edit_user_file", $siteuser, $file, $errors);
+}
+
+sub req_deluserfileform {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $siteuser = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_user_file($req, $siteuser, \$msg)
+ or return $self->req_list($req, $msg);
+
+ return $self->_show_userfile($req, "admin/users/delete_user_file", $siteuser, $file, $errors);
+}
+
+sub req_saveuserfile {
+ my ($self, $req) = @_;
+
+ $req->check_csrf("admin_user_edit_file")
+ or return $self->csrf_error($req, "admin_user_edit_file", "Edit Member File");
+
+ my $msg;
+ my $siteuser = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_user_file($req, $siteuser, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my %errors;
+ $req->validate(fields => \%file_fields,
+ errors => \%errors);
+
+ my %changes;
+ my $cgi = $req->cgi;
+ my $new_file = $cgi->param("file");
+ my $new_fh = $cgi->upload("file");
+
+ if ($new_file) {
+ if (!$new_fh) {
+ $errors{file} = "Something is wrong with the upload form or your file wasn't found";
+ }
+ }
+ unless ($errors{file}) {
+ -z $new_file
+ and $errors{file} = "File is empty";
+ }
+
+ keys %errors
+ and return $self->req_edituserfile($req, \%errors);
+
+ for my $field (qw/content_type category title body/) {
+ my ($value) = $cgi->param($field);
+ defined $value
+ and $changes{$field} = $value;
+ }
+ if ($new_file && $new_fh) {
+ $changes{file} = $new_fh;
+ $changes{display_name} = $new_file;
+ my $upload_info = $cgi->uploadInfo($new_file);
+# some content types come through strangely
+# if (!$changes{content_type} && $upload_info->{"Content-Type"}) {
+# $changes{content_type} = $upload_info->{"Content-Type"}
+# }
+ }
+ if (defined $changes{content_type} && !$changes{content_type} =~ /\S/) {
+ $errors{content_type} = "Content type must be set";
+ }
+ $changes{download} = $cgi->param('download') ? 1 : 0;
+ my $mod_date = $cgi->param("modwhen_date");
+ my $mod_time = $cgi->param("modwhen_time");
+ if ($mod_date && $mod_time) {
+ $changes{modwhen} = dh_parse_date_sql($mod_date) . " "
+ . dh_parse_time_sql($mod_time);
+ }
+
+ require BSE::API;
+ BSE::API->import("bse_replace_owned_file");
+ my $good = eval { bse_replace_owned_file($req->cfg, $file, %changes); };
+
+ $good
+ or return $self->req_edituserfile($req, { _ => $@ });
+
+ my $r = $cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_edit => 1, _t => "files", id => $siteuser->id, m => "File saved" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub req_deluserfile {
+ my ($self, $req) = @_;
+
+ $req->check_csrf("admin_user_del_file")
+ or return $self->csrf_error($req, "admin_user_del_file", "Delete Member File");
+
+ my $msg;
+ my $siteuser = _get_user($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_user_file($req, $siteuser, \$msg)
+ or return $self->req_list($req, $msg);
+
+ require BSE::API;
+ BSE::API->import("bse_delete_owned_file");
+ my $good = eval { bse_delete_owned_file($req->cfg, $file); };
+
+ $good
+ or return $self->req_deluserfileform($req, { _ => $@ });
+
+ my $r = $req->cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_edit => 1, _t => "files", id => $siteuser->id, m => "File removed" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub req_addgroupfileform {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my %acts =
+ (
+ $req->admin_tags,
+ message => $msg,
+ group => [ \&tag_hash, $group ],
+ error_img => [ \&tag_error_img, $req->cfg, $errors ],
+ userfile_category => [ tag_userfile_category => $self, $req, undef ],
+ );
+
+ return $req->dyn_response("admin/users/add_group_file", \%acts);
+}
+
+sub req_addgroupfile {
+ my ($self, $req) = @_;
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $cgi = $req->cgi;
+
+ $req->check_csrf("admin_group_add_file")
+ or return $self->csrf_error($req, "admin_group_add_file", "Add Member File");
+
+ my %errors;
+ $req->validate(fields => \%file_fields,
+ errors => \%errors);
+
+ my $file = $cgi->param("file");
+ my $file_fh = $cgi->upload("file");
+ unless ($file) {
+ $errors{file} = "Please select a file";
+ }
+ if ($file && -z $file) {
+ $errors{file} = "File is empty";
+ }
+ if (!$errors{$file} && !$file_fh) {
+ $errors{file} = "Something is wrong with the upload form or your file wasn't found";
+ }
+
+ keys %errors
+ and return $self->req_addgroupfileform($req, undef, \%errors);
+
+ require BSE::API;
+ BSE::API->import("bse_add_owned_file");
+
+ my %file;
+ $file{file} = $file_fh;
+ for my $field (qw/content_type category title body/) {
+ my ($value) = $cgi->param($field);
+ defined $value or $value = "";
+ $file{$field} = $value;
+ }
+ $file{download} = $cgi->param('download') ? 1 : 0;
+ my $mod_date = $cgi->param("modwhen_date");
+ my $mod_time = $cgi->param("modwhen_time");
+ if ($mod_date && $mod_time) {
+ $file{modwhen} = dh_parse_date_sql($mod_date) . " "
+ . dh_parse_time_sql($mod_time);
+ }
+ $file{display_name} = $file . "";
+ my $upload_info = $cgi->uploadInfo($file);
+# some content types come through strangely
+# if (!$file{content_type} && $upload_info->{"Content-Type"}) {
+# $file{content_type} = $upload_info->{"Content-Type"}
+# }
+ for my $key (keys %$upload_info) {
+ print STDERR "uploadinfo: $key: $upload_info->{$key}\n";
+ }
+ local $SIG{__DIE__};
+ my $owned_file = eval { bse_add_owned_file($req->cfg, $group, %file) };
+ unless ($owned_file) {
+ $errors{file} = $@;
+ return $self->req_edit($req, undef, \%errors);
+ }
+
+ my $r = $cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_editgroup => 1, _t => "files", id => $group->id, m => "File created" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub _get_group_file {
+ my ($req, $group, $msg) = @_;
+
+ my $file_id = $req->cgi->param("file_id");
+ unless (defined $file_id && $file_id =~ /^\d+$/) {
+ $$msg = "Missing or invalid file id";
+ return;
+ }
+ require BSE::TB::OwnedFiles;
+ my ($file) = BSE::TB::OwnedFiles->getBy
+ (
+ owner_type => $group->file_owner_type,
+ owner_id => $group->id,
+ id => $file_id
+ );
+ unless ($file) {
+ $$msg = "No such file found";
+ return;
+ }
+
+ return $file;
+}
+
+sub _show_groupfile {
+ my ($self, $req, $template, $group, $file, $errors) = @_;
+
+ my $message = $req->message($errors);
+
+ my %acts =
+ (
+ $req->admin_tags,
+ groupfile => [ \&tag_hash, $file ],
+ message => $message,
+ group => [ \&tag_hash, $group ],
+ error_img => [ \&tag_error_img, $req->cfg, $errors ],
+ userfile_category => [ tag_userfile_category => $self, $req, \$file ],
+ );
+
+ return $req->dyn_response($template, \%acts);
+}
+
+sub req_editgroupfile {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_group_file($req, $group, \$msg)
+ or return $self->req_list($req, $msg);
+
+ return $self->_show_groupfile($req, "admin/users/edit_group_file", $group, $file, $errors);
+}
+
+sub req_delgroupfileform {
+ my ($self, $req, $errors) = @_;
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_group_file($req, $group, \$msg)
+ or return $self->req_list($req, $msg);
+
+ return $self->_show_groupfile($req, "admin/users/delete_group_file", $group, $file, $errors);
+}
+
+sub req_savegroupfile {
+ my ($self, $req) = @_;
+
+ $req->check_csrf("admin_group_edit_file")
+ or return $self->csrf_error($req, "admin_group_edit_file", "Edit Member File");
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_group_file($req, $group, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my %errors;
+ $req->validate(fields => \%file_fields,
+ errors => \%errors);
+
+ my %changes;
+ my $cgi = $req->cgi;
+ my $new_file = $cgi->param("file");
+ my $new_fh = $cgi->upload("file");
+
+ if ($new_file) {
+ if (!$new_fh) {
+ $errors{file} = "Something is wrong with the upload form or your file wasn't found";
+ }
+ }
+ unless ($errors{file}) {
+ -z $new_file
+ and $errors{file} = "File is empty";
+ }
+
+ keys %errors
+ and return $self->req_editgroupfile($req, \%errors);
+
+ for my $field (qw/content_type category title body/) {
+ my ($value) = $cgi->param($field);
+ defined $value
+ and $changes{$field} = $value;
+ }
+ if ($new_file && $new_fh) {
+ $changes{file} = $new_fh;
+ $changes{display_name} = $new_file;
+ my $upload_info = $cgi->uploadInfo($new_file);
+# some content types come through strangely
+# if (!$changes{content_type} && $upload_info->{"Content-Type"}) {
+# $changes{content_type} = $upload_info->{"Content-Type"}
+# }
+ }
+ if (defined $changes{content_type} && !$changes{content_type} =~ /\S/) {
+ $errors{content_type} = "Content type must be set";
+ }
+ $changes{download} = $cgi->param('download') ? 1 : 0;
+ my $mod_date = $cgi->param("modwhen_date");
+ my $mod_time = $cgi->param("modwhen_time");
+ if ($mod_date && $mod_time) {
+ $changes{modwhen} = dh_parse_date_sql($mod_date) . " "
+ . dh_parse_time_sql($mod_time);
+ }
+
+ require BSE::API;
+ BSE::API->import("bse_replace_owned_file");
+ my $good = eval { bse_replace_owned_file($req->cfg, $file, %changes); };
+
+ $good
+ or return $self->req_editgroupfile($req, { _ => $@ });
+
+ my $r = $cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_editgroup => 1, _t => "files", id => $group->id, m => "File saved" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub req_delgroupfile {
+ my ($self, $req) = @_;
+
+ $req->check_csrf("admin_group_del_file")
+ or return $self->csrf_error($req, "admin_group_del_file", "Delete Member File");
+
+ my $msg;
+ my $group = _get_group($req, \$msg)
+ or return $self->req_list($req, $msg);
+
+ my $file = _get_group_file($req, $group, \$msg)
+ or return $self->req_list($req, $msg);
+
+ require BSE::API;
+ BSE::API->import("bse_delete_owned_file");
+ my $good = eval { bse_delete_owned_file($req->cfg, $file); };
+
+ $good
+ or return $self->req_delgroupfileform($req, { _ => $@ });
+
+ my $r = $req->cgi->param('r');
+ unless ($r) {
+ $r = $req->url('siteusers', { a_editgroup => 1, _t => "files", id => $group->id, m => "File removed" });
+ }
+
+ return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub _get_user {
+ my ($req, $msg) = @_;
+
+ my $id = $req->cgi->param('id');
+ defined $id && $id =~ /^\d+$/
+ or do { $$msg = "Missing or invalid user id"; return };
+ require BSE::TB::SiteUserGroups;
+ my $group = SiteUsers->getByPkey($id);
+ $group
+ or do { $$msg = "Unknown user id"; return };
+
+ $group;
+}
+
+sub csrf_error {
+ my ($self, $req, $name, $description) = @_;
+
+ my %errors;
+ my $msg = $req->csrf_error;
+ $errors{_csrfp} = $msg;
+ return $self->req_list($req, "$description: $msg ($name)");
+}
+
+sub tag_page_args {
+ my ($self, $page_args, $args) = @_;
+
+ my %args = %$page_args;
+ if ($args) {
+ delete @args{split ' ', $args};
+ }
+
+ return join "&", map { "$_=" . escape_uri($args{$_}) } keys %args;
+}
+
+sub tag_page_argsh {
+ my ($self, $page_args, $args) = @_;
+
+ my %args = %$page_args;
+ if ($args) {
+ delete @args{split ' ', $args};
+ }
+
+ return join "", map
+ {
+ my $value = escape_html($args{$_});
+ qq(<input type="hidden" name="$_" value="$value" />);
+ } keys %args;
+}
+
+sub tag_fileaccess_user {
+ my ($rcurrent, $cache) = @_;
+
+ $$rcurrent
+ or return '';
+ my $id = $$rcurrent->siteuser_id;
+ exists $cache->{$id}
+ or $cache->{$id} = SiteUsers->getByPkey($id);
+
+ $cache->{$id}
+ or return "** No user $id";
+
+ return escape_html($cache->{$id}->userId);
+}
+
+sub tag_ifFileuser {
+ my ($rcurrent, $cache) = @_;
+
+ $$rcurrent
+ or return '';
+ my $id = $$rcurrent->siteuser_id;
+ exists $cache->{$id}
+ or $cache->{$id} = SiteUsers->getByPkey($id);
+
+ return defined $cache->{$id};
+}
+
+sub _find_file_owner {
+ my ($owner_type, $owner_id, $cfg, $cache) = @_;
+
+ require BSE::TB::SiteUserGroups;
+ my $owner;
+ if ($owner_type eq SiteUser->file_owner_type) {
+ if ($cache->{$owner_id} ||= SiteUsers->getByPkey($owner_id)) {
+ $owner = $cache->{$owner_id}->data_only;
+ $owner->{desc} = "User: " . $owner->{userId};
+ }
+ else {
+ return;
+ }
+ }
+ elsif ($owner_type eq BSE::TB::SiteUserGroup->file_owner_type) {
+ my $group;
+ if ($owner_id < 0) {
+ $group = BSE::TB::SiteUserGroups->getQueryGroup($cfg, $owner_id);
+ }
+ else {
+ $group = BSE::TB::SiteUserGroups->getByPkey($owner_id);
+ }
+ $group
+ or return;
+ $owner = $group->data_only;
+ $owner->{desc} = "Group: " . $group->{name};
+ }
+ else {
+ print STDERR "** Unknown file owner type $owner_type\n";
+ return;
+ }
+
+ return $owner;
+}
+
+sub tag_fileowner {
+ my ($rcurrent, $cache, $cfg, $args) = @_;
+
+ $$rcurrent or return "";
+
+ my $owner = _find_file_owner($$rcurrent->{owner_type}, $$rcurrent->{owner_id}, $cfg, $cache)
+ or return "Unknown";
+
+ return tag_hash($owner, $args);
+}
+
+sub tag_filecat {
+ my ($rcurrent, $cats) = @_;
+
+ $$rcurrent
+ or return '';
+
+ $cats->{$$rcurrent->{category}};
+}
+
+sub req_fileaccesslog {
+ my ($self, $req) = @_;
+
+ my @filters;
+ my $cgi = $req->cgi;
+ my %page_args;
+ my $file_id = $cgi->param("file_id");
+ my $file;
+ if ($file_id && $file_id =~ /^\d+$/) {
+ require BSE::TB::OwnedFiles;
+ $file = BSE::TB::OwnedFiles->getByPkey($file_id);
+ if ($file) {
+ push @filters, [ '=', file_id => $file_id ];
+ $page_args{file_id} = $file_id;
+ }
+ }
+ my $siteuser_id = $cgi->param('siteuser_id');
+ my $user;
+ if ($siteuser_id && $siteuser_id =~ /^\d+$/) {
+ $user = SiteUsers->getByPkey($siteuser_id);
+ if ($user) {
+ push @filters, [ '=', siteuser_id => $siteuser_id ];
+ $page_args{siteuser_id} = $siteuser_id;
+ }
+ }
+ my $owner_id = $cgi->param("owner_id");
+ my $owner_type = $cgi->param("owner_type") || "U";
+ my $owner;
+ my $owner_desc = '';
+ my %user_cache;
+ if (defined $owner_id) {
+ push @filters,
+ (
+ [ '=', owner_id => $owner_id ],
+ [ '=', owner_type => $owner_type ],
+ );
+ $owner = _find_file_owner($owner_type, $owner_id, $req->cfg, \%user_cache);
+ if ($owner) {
+ $owner_desc = $owner->{desc};
+ }
+ if ($owner) {
+ $page_args{owner_type} = $owner_type;
+ $page_args{owner_id} = $owner_id;
+ }
+ }
+
+ require BSE::TB::OwnedFiles;
+ my %categories = map { $_->{id} => escape_html($_->{name}) }
+ BSE::TB::OwnedFiles->categories($req->cfg);
+
+ my $category_id = $cgi->param("category");
+ my $category;
+ if (defined $category_id && $categories{$category_id}) {
+ $category = $categories{$category_id};
+ push @filters,
+ [ "=", category => $category_id ];
+ $page_args{category} = $category_id;
+ }
+ use POSIX qw(strftime);
+ my %errors;
+ my $from = $cgi->param("from") || strftime("%d/%m/%Y", localtime(time()-30*86400));
+ my $to = $cgi->param("to") || strftime("%d/%m/%Y", localtime);
+ my $from_sql = dh_parse_date_sql($from)
+ or $errors{from_sql} = "Invalid from date";
+ my $to_sql = dh_parse_date_sql($to)
+ or $errors{to_sql} = "Invalid to date";
+
+ require BSE::TB::FileAccessLog;
+ my @entries;
+ unless (keys %errors) {
+ push @filters, [ between => when_at => $from_sql, $to_sql ];
+ $cgi->param(from => $from);
+ $cgi->param(to => $to);
+ $page_args{from} = $from;
+ $page_args{to} = $to;
+ @entries = map $_->{id}, BSE::TB::FileAccessLog->query
+ (
+ [ qw/id/ ],
+ \@filters,
+ {
+ order => 'when_at desc'
+ },
+ );
+ }
+
+ my $it = BSE::Util::Iterate->new;
+ my $current_access;
+ my %acts =
+ (
+ $req->admin_tags,
+ $it->make_paged
+ (
+ data => \@entries,
+ fetch => [ getByPkey => 'BSE::TB::FileAccessLog' ],
+ cgi => $req->cgi,
+ single => "fileaccess",
+ plural => "fileaccesses",
+ store => \$current_access,
+ name => "fileaccesses",
+ session => $req->session,
+ perpage_parm => "pp=100",
+ ),
+ ifOwner => defined $owner,
+ owner => [ \&tag_hash, $owner ],
+ owner_type => (defined $owner_type ? $owner_type : ''),
+ owner_desc => escape_html($owner_desc),
+ ifSiteuser => defined $user,
+ siteuser => [ \&tag_hash, $user ],
+ ifFile => defined $file,
+ file => [ \&tag_hash, $file ],
+ page_args => [ tag_page_args => $self, \%page_args ],
+ page_argsh => [ tag_page_argsh => $self, \%page_args ],
+ user => [ \&tag_fileaccess_user, \$current_access, \%user_cache ],
+ ifFileuser => [ \&tag_ifFileuser, \$current_access, \%user_cache ],
+ fileowner => [ \&tag_fileowner, \$current_access, \%user_cache, $req->cfg ],
+ filecat => [ \&tag_filecat, \$current_access, \%categories ],
+ error_img =>[ \&tag_error_img, $req->cfg, \%errors ],
+ ifCategory => $category,
+ category => escape_html($category),
+ );
+
+ return $req->dyn_response("admin/users/fileaccess", \%acts);
+}
+
1;
return '(' . join (" $op ", map _query_expr($args, $map, $table_name, @$_), @terms) . ')';
}
else {
- my ($column, $value) = @terms;
+ my ($column, @values) = @terms;
my $db_col = $map->{$column}
or confess "No column '$column' in $table_name";
- push @$args, $value;
- return "$db_col $op ?";
+ if ($op eq "between") {
+ push @$args, @values[0, 1];
+ return "$db_col $op ? and ?";
+ }
+ else {
+ push @$args, $values[0];
+ return "$db_col $op ?";
+ }
}
}
sub generate_query {
- my ($self, $row_class, $columns, $query) = @_;
+ my ($self, $row_class, $columns, $query, $opts) = @_;
my %trans;
@trans{$row_class->columns} = $row_class->db_columns;
{; $trans{$_} or confess "No column '$_' in $table_name" } @$columns;
my $sql = 'select ' . join(',', @out_columns) . ' from ' . $table_name;
my @args;
- if ($query) {
+ if ($query && @$query) {
$sql .= ' where ' . _query_expr(\@args, \%trans, $table_name, 'and', @$query,);
}
+ if ($opts->{order}) {
+ $sql .= "order by " . $opts->{order};
+ }
#print STDERR "generated sql >$sql<\n";
my $sth = $self->{dbh}->prepare($sql)
--- /dev/null
+package BSE::TB::FileAccessLog;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::FileAccessLogEntry;
+use Carp qw(confess);
+
+sub rowClass {
+ 'BSE::TB::FileAccessLogEntry';
+}
+
+sub log_download {
+ my ($self, %opts) = @_;
+
+ my $user = delete $opts{user}
+ or confess "No user parameter to log_download()";
+
+ my $file = delete $opts{file}
+ or confess "No file parameter to log_download()";
+
+ my %args;
+ for my $field (BSE::TB::FileAccessLogEntry->columns) {
+ $args{$field} = $file->{$field}
+ if exists $file->{$field};
+ }
+ delete $args{id};
+ $args{file_id} = $file->id;
+ $args{siteuser_id} = $user->id;
+ $args{siteuser_logon} = $user->userId;
+ defined $opts{download} and $args{download} = $opts{download};
+
+ return BSE::TB::FileAccessLog->make(%args);
+}
+
+1;
--- /dev/null
+package BSE::TB::FileAccessLogEntry;
+use strict;
+use base 'Squirrel::Row';
+use BSE::Util::SQL qw(now_sqldatetime);
+
+sub columns {
+ return qw/id when_at siteuser_id siteuser_logon file_id owner_type owner_id category filename display_name content_type download title modwhen size_in_bytes/;
+}
+
+sub table {
+ "bse_file_access_log";
+}
+
+sub defaults {
+ return
+ (
+ when_at => now_sqldatetime(),
+ );
+}
+
+1;
--- /dev/null
+package BSE::TB::OwnedFile;
+use strict;
+use base 'Squirrel::Row';
+use BSE::Util::SQL qw(now_sqldatetime);
+
+sub columns {
+ return qw/id owner_type owner_id category filename display_name content_type download title body modwhen size_in_bytes/;
+}
+
+sub table {
+ "bse_owned_files";
+}
+
+sub defaults {
+ return
+ (
+ download => 0,
+ body => '',
+ modwhen => now_sqldatetime(),
+ );
+}
+
+1;
--- /dev/null
+package BSE::TB::OwnedFiles;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::OwnedFile;
+
+sub rowClass {
+ 'BSE::TB::OwnedFile';
+}
+
+sub categories {
+ my ($class, $cfg) = @_;
+
+ my @cat_ids = split /,/, $cfg->entry("file categories", "ids", "");
+ grep $_ eq "", @cat_ids
+ or unshift @cat_ids, "";
+
+ my @cats;
+ for my $id (@cat_ids) {
+ my $section = "file category $id";
+ my $def_name = length $id ? ucfirst $id : "(None)";
+ my $name = $cfg->entry($section, "name", $def_name);
+ push @cats, +{ id => $id, name => $name };
+ }
+
+ return @cats;
+}
+
+1;
package BSE::TB::SiteUserGroup;
use strict;
use base 'Squirrel::Row';
+use constant OWNER_TYPE => "G";
sub columns {
qw(id name);
return scalar @membership;
}
+sub file_owner_type {
+ return OWNER_TYPE;
+}
+
+sub files {
+ my ($self) = @_;
+
+ require BSE::TB::OwnedFiles;
+ return BSE::TB::OwnedFiles->getBy(owner_type => OWNER_TYPE,
+ owner_id => $self->id);
+}
+
1;
sub rowClass { 'BSE::TB::SiteUserGroup' }
-sub admin_and_query_groups {
+sub query_groups {
my ($class, $cfg) = @_;
- my @groups = $class->all;
-
+ my @groups;
my $id = 1;
my $name;
while ($name = $cfg->entry(SECT_QUERY_GROUPS, $id)) {
++$id;
}
- @groups;
+ return @groups;
+}
+
+sub admin_and_query_groups {
+ my ($class, $cfg) = @_;
+
+ return
+ (
+ $class->all,
+ $class->query_groups($cfg),
+ );
}
sub getQueryGroup {
}
package BSE::TB::SiteUserQueryGroup;
+use constant OWNER_TYPE => "G";
+
+sub id { $_[0]{id} }
+
+sub name { $_[0]{name} }
sub contains_user {
my ($self, $user) = @_;
return 0;
}
+sub file_owner_type {
+ return OWNER_TYPE;
+}
+
+sub files {
+ my ($self) = @_;
+
+ require BSE::TB::OwnedFiles;
+ return BSE::TB::OwnedFiles->getBy(owner_type => OWNER_TYPE,
+ owner_id => $self->id);
+}
+
+sub data_only {
+ return +{ %{$_[0]} };
+}
+
1;
push @{$result->{headers}}, "Cache-Control: no-cache";
}
}
+ if (!grep /^content-length:/, @{$result->{headers}}) {
+ my $length;
+ if (defined $result->{content}) {
+ $length = length $result->{content};
+ }
+ elsif (defined $result->{content_fh}) {
+ # this may need to change if we support byte ranges
+ $length = -s $result->{content_fh};
+ }
+ if (defined $length) {
+ push @{$result->{headers}}, "Content-Length: $length";
+ }
+ }
if (exists $ENV{GATEWAY_INTERFACE}
&& $ENV{GATEWAY_INTERFACE} =~ /^CGI-Perl\//) {
require Apache;
print "$_\n" for @{$result->{headers}};
print "\n";
}
- print $result->{content};
+ if (defined $result->{content}) {
+ print $result->{content};
+ }
+ elsif ($result->{content_fh}) {
+ # in the future this could be updated to support byte ranges
+ local $/ = \8192;
+ my $fh = $result->{content_fh};
+ binmode $fh;
+ while (my $data = <$fh>) {
+ print $data;
+ }
+ }
+ else {
+ print STDERR "$ENV{SCRIPT_NAME}: ** No content supplied\n";
+ print "** Internal error\n";
+ }
}
1;
use BSE::Util::Iterate;
use BSE::Util::SQL qw/now_datetime/;
use DevHelp::Date qw(dh_strftime_sql_datetime);
+use base 'BSE::UI::UserCommon';
my %actions =
(
'subscription', 'subscriptions'),
$it->make_iterator([ \&iter_sembookings, $user ],
'booking', 'bookings'),
+ $it->make
+ (
+ code => [ iter_userfiles => $self, $user, $req ],
+ single => 'userfile',
+ plural => 'userfiles',
+ ),
);
return $req->dyn_response('user/userpage', \%acts);
--- /dev/null
+package BSE::UI::UserCommon;
+use strict;
+
+# code common to both BSE::UserReg and BSE::UI::User
+# see also BSE::UI::SiteuserCommon
+
+my %num_file_fields = map { $_=> 1 }
+ qw/id owner_id size_in_bytes/;
+
+sub iter_userfiles {
+ my ($self, $user, $req, $args) = @_;
+
+ my @files = map $_->data_only, $user->visible_files($req->cfg);
+
+ # produce a url for each file
+ my $base = '/cgi-bin/user.pl?a_downufile=1&id=';
+ for my $file (@files) {
+ $file->{url} = $base . $file->{id};
+ }
+ defined $args or $args = '';
+
+ my $sort;
+ if ($args =~ s/\bsort:\s?(-?\w+(?:,-?\w+)*)\b//) {
+ $sort = $1;
+ }
+ my $cgi_sort = $req->cgi->param('userfile_sort');
+ $cgi_sort
+ and $sort = $cgi_sort;
+ if ($sort && @files > 1) {
+ my @fields = map
+ {
+ my $work = $_;
+ my $rev = $work =~ s/^-//;
+ [ $rev, $work ]
+ } split /,/, $sort;
+
+ @fields = grep exists $files[0]{$_->[1]}, @fields;
+
+ @files = sort
+ {
+ for my $field (@fields) {
+ my $name = $field->[1];
+ my $diff = $num_file_fields{$name}
+ ? $a->{$name} <=> lc $b->{$name}
+ : $a->{$name} cmp lc $b->{$name};
+ if ($diff) {
+ return $field->[0] ? -$diff : $diff;
+ }
+ }
+ return 0;
+ } @files;
+ }
+
+ $args =~ /\S/
+ or return @files;
+
+ if ($args =~ /^\s*filter:(.*)$/) {
+ my $expr = $1;
+ my $func = eval 'sub { my $file = $_[0];' . $expr . '}';
+ unless ($func) {
+ print STDERR "** Cannot compile userfile filter $expr: $@\n";
+ return;
+ }
+ return grep $func->($_), @files;
+ }
+
+ if ($args =~ /^\s*(!)?(\w+(?:,\w+)*)\s*$/) {
+ my ($not, $cats) = ( $1, $2 );
+ my %matches = map { $_ => 1 } split ',', $cats, -1;
+ if ($not) {
+ return grep !$matches{$_->{category}}, @files;
+ }
+ else {
+ return grep $matches{$_->{category}}, @files;
+ }
+ }
+
+ print STDERR "** unparsable arguments to userfile: $args\n";
+
+ return;
+}
+
+1;
use BSE::CfgInfo qw(custom_class);
use BSE::WebUtil qw/refresh_to/;
use BSE::Util::Iterate;
+use base 'BSE::UI::UserCommon';
use constant MAX_UNACKED_CONF_MSGS => 3;
use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
image => 'req_image',
orderdetail => 'req_orderdetail',
wishlist => 'req_wishlist',
+ downufile => 'req_downufile',
);
sub actions { \%actions }
$message = '';
}
}
+ require BSE::TB::OwnedFiles;
+ my @file_cats = BSE::TB::OwnedFiles->categories($cfg);
+ my %subbed = map { $_ => 1 } $user->subscribed_file_categories;
+ for my $cat (@file_cats) {
+ $cat->{subscribed} = exists $subbed{$cat->{id}} ? 1 : 0;
+ }
+ my $it = BSE::Util::Iterate->new;
my %acts;
%acts =
(
$self->_edit_tags($user, $cfg),
ifSubscribedTo => [ \&tag_ifSubscribedTo, $user ],
partial_logon => $partial_logon,
+ $it->make
+ (
+ data => \@file_cats,
+ single => "filecat",
+ plural => "filecats"
+ ),
);
my $base = 'user/options';
if $subs && !$user->{confirmed};
}
+ if ($cgi->param('save_file_subs')) {
+ my @new_subs = $cgi->param("file_subscriptions");
+ $user->set_subscribed_file_categories($cfg, @new_subs);
+ }
if ($partial_logon) {
$user->{previousLogon} = $user->{lastLogon};
'subscription', 'subscriptions'),
$it->make_iterator([ \&iter_sembookings, $user ],
'booking', 'bookings'),
+ $it->make
+ (
+ code => [ iter_userfiles => $self, $user, $req ],
+ single => 'userfile',
+ plural => 'userfiles',
+ ),
);
my $base_template = 'user/userpage';
my $template = $base_template;
BSE::Template->show_page($template, $req->cfg, \%acts);
}
+=item req_downufile
+
+=target a_downufile
+
+Download a user file.
+
+=cut
+
+sub req_downufile {
+ my ($self, $req) = @_;
+
+ require BSE::TB::OwnedFiles;
+ my $cgi = $req->cgi;
+ my $cfg = $req->cfg;
+ my $id = $cgi->param("id");
+ defined $id && $id =~ /^\d+$/
+ or return $self->error($req, "Invalid or missing file id");
+
+ # return the same error to avoid giving someone a mechanism to find
+ # which files are in use
+ my $file = BSE::TB::OwnedFiles->getByPkey($id)
+ or return $self->error($req, "Invalid or missing file id");
+
+ my $user = $self->_get_user($req, 'downufile')
+ or return;
+
+ require BSE::TB::SiteUserGroups;
+ my $accessible = 0;
+ if ($file->owner_type eq $user->file_owner_type) {
+ $accessible = $user->id == $file->owner_id;
+ }
+ elsif ($file->owner_type eq BSE::TB::SiteUserGroup->file_owner_type) {
+ my $owner_id = $file->owner_id;
+ my $group = $owner_id < 0
+ ? BSE::TB::SiteUserGroups->getQueryGroup($cfg, $owner_id)
+ : BSE::TB::SiteUserGroups->getByPkey($owner_id);
+ if ($group) {
+ $accessible = $group->contains_user($user);
+ }
+ else {
+ print STDERR "** downufile: unknown group id ", $file->owner_id, " in file ", $file->id, "\n";
+ }
+ }
+ else {
+ print STDERR "** downufile: Unknown file owner type ", $file->owner_type, " in file ", $file->id, "\n";
+ $accessible = 0;
+ }
+
+ $accessible
+ or return $self->error($req, "Sorry, you don't have access to this file");
+
+ my $filebase = $cfg->entryVar('paths', 'downloads');
+ require IO::File;
+ my $fh = IO::File->new("$filebase/" . $file->filename, "r")
+ or return $self->error($req, "Cannot open stored file: $!");
+
+ my @headers;
+ my %result =
+ (
+ content_fh => $fh,
+ headers => \@headers,
+ );
+ my $download = $cgi->param("force_download") || $file->download;
+ if ($download) {
+ push @headers, "Content-Disposition: attachment; filename=".$file->display_name;
+ $result{type} = "application/octet-stream";
+ }
+ else {
+ push @headers, "Content-Disposition: inline; filename=" . $file->display_name;
+ $result{type} = $file->content_type;
+ }
+ if ($cfg->entry("download", "log_downuload", 0)) {
+ my $max_age = $cfg->entry("download", "log_downuload_maxage", 30);
+ BSE::DB->run(bseDownloadLogAge => $max_age);
+ require BSE::TB::FileAccessLog;
+ BSE::TB::FileAccessLog->log_download
+ (
+ user => $user,
+ file => $file,
+ download => $download,
+ );
+ }
+
+ BSE::Template->output_result($req, \%result);
+}
+
1;
--$month;
# passing the isdst as 0 seems to provide a more accurate result than
# -1 on glibc.
- return bse_strftime($cfg, $fmt, $sec, $min, $hour, $day, $month, $year, -1, -1, 0);
+ return bse_strftime($cfg, $fmt, $sec, $min, $hour, $day, $month, $year, -1, -1, -1);
},
today => [ \&tag_today, $cfg ],
money =>
sub dh_parse_time {
my ($time, $rmsg) = @_;
- if ($time =~ /^\s*(\d+)[:. ]?(\d{2})\s*$/) {
+ if ($time =~ /^\s*(\d+)[:. ]?(\d{2})(?:[:.](\d{2}))?\s*$/) {
# 24 hour time
- my ($hour, $min) = ($1, $2);
+ my ($hour, $min, $sec) = ($1, $2, $3);
if ($hour > 23) {
$$rmsg = "Hour must be from 0 to 23 for 24-hour time";
$$rmsg = "Minutes must be from 0 to 59";
return;
}
+ defined $sec or $sec = 0;
+ if ($sec > 59) {
+ $$rmsg = "Seconds must be from 0 to 59";
+ return;
+ }
- return (0+$hour, 0+$min, 0);
+ return (0+$hour, 0+$min, 0+$sec);
}
else {
# try for 12 hour time
- my ($hour, $min, $ampm);
+ my ($hour, $min, $sec, $ampm);
if ($time =~ /^\s*(\d+)\s*(?:([ap])m?)\s*$/i) {
# "12am", "2pm", etc
- ($hour, $min, $ampm) = ($1, 0, $2);
+ ($hour, $min, $sec, $ampm) = ($1, 0, 0, $2);
}
elsif ($time =~ /^\s*(\d+)[.: ](\d{2})\s*(?:([ap])m?)\s*$/i) {
- ($hour, $min, $ampm) = ($1, $2, $3);
+ ($hour, $min, $sec, $ampm) = ($1, $2, 0, $3);
+ }
+ elsif ($time =~ /^\s*(\d+)[.: ](\d{2})[:.](\d{2})\s*(?:([ap])m?)\s*$/i) {
+ ($hour, $min, $sec, $ampm) = ($1, $2, $3, $4);
}
else {
$$rmsg = "Unknown time format";
$$rmsg = "Minutes must be from 0 to 59";
return;
}
+ if ($sec > 59) {
+ $$rmsg = "Seconds must be from 0 to 59";
+ return;
+ }
$hour = 0 if $hour == 12;
$hour += 12 if lc $ampm eq 'p';
- return (0+$hour, 0+$min, 0);
+ return (0+$hour, 0+$min, 0+$sec);
}
}
if (++${$state->{index}} < @{$state->{data}}) {
my $item = $state->{data}[${$state->{index}}];
+ if ($state->{fetch}) {
+ my $fetch = $state->{fetch};
+ my $code = $fetch;
+ my @args;
+ if (ref $fetch eq 'ARRAY') {
+ ($code, @args) = @$fetch;
+ }
+ if ($state->{state}) {
+ push @args, $state->{state};
+ }
+ push @args, $item;
+ if (ref $code) {
+ ($item) = $code->(@args);
+ }
+ else {
+ my $object = shift @args;
+ ($item) = $object->$code(@args);
+ }
+ }
+ $state->{item} = $item;
${$state->{store}} = $item if $state->{store};
$self->next_item($item, $state->{single});
return 1;
}
else {
+ $state->{item} = undef;
+ ${$state->{store}} = undef if $state->{store};
$self->next_item(undef, $state->{single});
}
return;
sub _iter_item {
my ($self, $state, $args) = @_;
- ${$state->{index}} >= 0 && ${$state->{index}} < @{$state->{data}}
+ $state->{item}
or return "** $state->{single} should only be used inside iterator $state->{plural} **";
- return $self->item($state->{data}[${$state->{index}}], $args);
+ return $self->item($state->{item}, $args);
}
sub _iter_number_paged {
(?:[01]?\d|2[0-3]) # hour 0-23
[:.] # separator
[0-5]\d # minute
+ (?:[:.][0-5]\d)? # optional seconds
| # or 12 hour time:
(?:0?[1-9]|1[012]) # hour 1-12
(?:[:.] # optionally separator followed
- [0-5]\d)? # by minutes
+ [0-5]\d # by minutes
+ (?:[:.][0-5]\d)? # optionall by seconds
+ )?
[ap]m? # followed by afternoon/morning
)$!ix,
error=>'Invalid time $n',
use constant MAX_UNACKED_CONF_MSGS => 3;
use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
+use constant OWNER_TYPE => "U";
sub columns {
return qw/id userId password email keepAddress whenRegistered lastLogon
$self->_set_wishlist_order($order->[$index-1]{product_id}, $order->[$index]{display_order});
}
+# files owned specifically by this user
+sub files {
+ my ($self) = @_;
+
+ require BSE::TB::OwnedFiles;
+ return BSE::TB::OwnedFiles->getBy(owner_type => OWNER_TYPE,
+ owner_id => $self->id);
+}
+
+sub admin_group_files {
+ my ($self) = @_;
+
+ require BSE::TB::OwnedFiles;
+ return BSE::TB::OwnedFiles->getSpecial(userVisibleGroupFiles => $self->{id});
+}
+
+sub query_group_files {
+ my ($self, $cfg) = @_;
+
+ require BSE::TB::SiteUserGroups;
+ return
+ (
+ map $_->files, BSE::TB::SiteUserGroups->query_groups($cfg)
+ );
+}
+
+# files the user can see, both owned and owned by groups
+sub visible_files {
+ my ($self, $cfg) = @_;
+
+ return
+ (
+ $self->files,
+ $self->admin_group_files,
+ $self->query_group_files($cfg)
+ );
+}
+
+sub file_owner_type {
+ return OWNER_TYPE;
+}
+
+sub subscribed_file_categories {
+ my ($self) = @_;
+
+ return map $_->{category}, BSE::DB->query(siteuserSubscribedFileCategories => $self->{id});
+}
+
+sub set_subscribed_file_categories {
+ my ($self, $cfg, @new) = @_;
+
+ require BSE::TB::OwnedFiles;
+ my %current = map { $_ => 1 } $self->subscribed_file_categories;
+ my %new = map { $_ => 1 } @new;
+ my @all = BSE::TB::OwnedFiles->categories($cfg);
+ for my $cat (@all) {
+ if ($new{$cat->{id}} && !$current{$cat->{id}}) {
+ eval {
+ BSE::DB->run(siteuserAddFileCategory => $self->{id}, $cat->{id});
+ }; # a race condition might cause a duplicate key error here
+ }
+ elsif (!$new{$cat->{id}} && $current{$cat->{id}}) {
+ BSE::DB->run(siteuserRemoveFileCategory => $self->{id}, $cat->{id});
+ }
+ }
+}
+
1;
sub query {
my ($self, $columns, $query, $opts) = @_;
- $dh->generate_query($self, $columns, $query, $opts);
+ $dh->generate_query($self->rowClass, $columns, $query, $opts);
}
sub make {
where am.user_id = ? and am.group_id = ab.id and ab.id = ag.base_id
SQL
+name: siteuserAddFileCategory
+sql_statement: <<SQL
+insert into bse_file_subscriptions(siteuser_id, category)
+ values(?, ?)
+SQL
+
+name: siteuserRemoveFileCategory
+sql_statement: <<SQL
+delete from bse_file_subscriptions where siteuser_id = ? and category = ?
+SQL
+
+name: siteuserSubscribedFileCategories
+sql_statement: <<SQL
+select category
+from bse_file_subscriptions
+where siteuser_id = ?
+SQL
+
+name: OwnedFiles.userVisibleGroupFiles
+sql_statement: <<SQL
+select of.*
+from bse_owned_files of, bse_siteuser_membership sm
+where sm.siteuser_id = ?
+ and sm.group_id = of.owner_id
+ and of.owner_type = 'G'
+SQL
+
+name: bseDownloadLogAge
+sql_statement: <<SQL
+delete from bse_file_access_log
+ where when_at < date_sub(now(), interval ? day)
+SQL
\ No newline at end of file
if non-zero the user must be registered/logged on to download I<any>
file.
+=item log_downufile
+
+if non-zero, downloads of userfiles will be logged. Default: 0
+
+=item log_downufile_maxage
+
+the maximum age of entries in the user file download log, in days.
+Default: 30.
+
=back
=head2 [confirmations]
width: 20px;
}
+table.editform td textarea {
+ width: 95%;
+}
+
+table.editform td input.wide {
+ width: 95%;
+}
+
table.editform td.check {
text-align: center;
}
.inplaceeditor-saving {
padding-right: 20px;
background: url(/images/admin/busy.gif) bottom right no-repeat;
+}
+
+#sitedragdrop li {
+ margin-top: 0px;
+ margin-bottom: 0px;
+ padding-bottom: 0px;
+ padding-top: 2px;
+}
+
+#sitedragdrop ul {
+ padding-top: 6px;
+ padding-bottom: 0px;
+}
+
+#filelist div.fileentry {
+ border: 1px solid #000;
+}
+
+#filelist div.fileentry .title {
+ background-color: #fff;
+ border-bottom: 1px solid black;
+}
+
+#fileaccesslog .col_size {
+ text-align: right;
+}
+
+#fileaccesslog a:link,
+#fileaccesslog a:visited,
+#fileaccesslog a:active {
+ font-weight: normal;
}
\ No newline at end of file
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
- hoverclass: options.hoverclass
+ hoverclass: options.hoverclass,
+ tree: options.tree
};
// fix for gecko engine
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Group Files - Add File":>
+<h1>Add Group File</h1>
+
+<:include admin/users/inc_group_menu.tmpl:>
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="addfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:group id:>" />
+<:csrfp admin_group_add_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><input type="file" name="file" /><:error_img file:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><input type="text" name="content_type" value="<:old content_type:>" /><:error_img content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><input type="checkbox" name="download" <:ifOld download:>checked="checked" <:or:><:eif:>/><:error_img download:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:userfile_category:><:error_img category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <input type="text" name="modwhen_date" value="<:old modwhen_date:>" size="10" /><:error_img modwhen_date:> dd/mm/yyyy<br />Time: <input type="text" name="modwhen_time" value="<:old modwhen_time:>" size="10" /><:error_img modwhen_time:> HH:MM:SS</td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><input type="text" name="title" value="<:old title:>" class="wide" /><:error_img title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5"><:old body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_addgroupfile" value="Add File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Site Member Files - Add File":>
+<h1>Add Member File</h1>
+
+<p>
+| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |
+<a href="/cgi-bin/admin/siteusers.pl">Site Members</a> |
+<a href="mailto:<:siteuser email:>">Email</a> |
+<:ifUserCan bse_members_user_edit:><a href="/cgi-bin/admin/siteusers.pl?a_edit=1&id=<:siteuser id:>">Edit User</a> |<:or:><:eif:>
+<a href="/cgi-bin/admin/siteusers.pl?a_view=1&id=<:siteuser id:>">User Details</a> |</p>
+
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<:include admin/users/inc_add_user_file.tmpl:>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Group Files - Delete File":>
+<h1>Delete Group File</h1>
+
+<:include admin/users/inc_group_menu.tmpl:>
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="delfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:group id:>" />
+<input type="hidden" name="file_id" value="<:groupfile id:>" />
+<:csrfp admin_group_del_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><:groupfile display_name:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><:groupfile content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><:ifGroupfile download:>Yes<:or:>No<:eif:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:groupfile category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <:date "%d/%m/%Y" groupfile modwhen:><br />Time: <:date "%H:%M:%S" groupfile modwhen:></td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><:groupfile title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5" readonly="readonly"><: groupfile body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_delgroupfile" value="Delete File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Site Member Files - Delete File":>
+<h1>Delete Member File</h1>
+
+<:include admin/users/inc_user_menu.tmpl:>
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="delfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:siteuser id:>" />
+<input type="hidden" name="file_id" value="<:userfile id:>" />
+<:csrfp admin_user_del_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><:userfile display_name:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><:userfile content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><:ifUserfile download:>Yes<:or:>No<:eif:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:userfile category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <:date "%d/%m/%Y" userfile modwhen:><br />Time: <:date "%H:%M:%S" userfile modwhen:></td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><:userfile title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5" readonly="readonly"><: userfile body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_deluserfile" value="Delete File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
</tr>
<:eif Cfg:>
<:or UserCan:><:eif UserCan:>
+<:if Filecats:>
+ <tr>
+ <th>File subscriptions:</th>
+ <td>
+ <input type="hidden" name="save_file_subs" value="1" />
+ <:iterator begin filecats:>
+ <input type="checkbox" name="file_subscriptions" value="<:filecat id:>" <:ifFilecat subscribed:>checked="checked" <:or:><:eif:> /> <:filecat name:>
+ <:iterator separator filecats:>
+ <br />
+ <:iterator end filecats:>
+ </td>
+ <td class="help"><:help editsiteuser filesubs:></td></tr>
+<:or Filecats:><:eif Filecats:>
+
<:include admin/users/custom_edit.tmpl optional:>
<tr>
<td class="buttons"colspan="3">
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Site Member Files":>
+<h1>Site Member Files</h1>
+<:include admin/users/inc_user_menu.tmpl:>
+<:ifUserCan bse_members_user_add_file:><p><a href="/cgi-bin/admin/siteusers.pl?a_adduserfileform=1&id=<:siteuser id:>">Add a file</a></p><:or:><:eif:>
+
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="filelist">
+<table class="editform">
+<tr>
+ <th>Filename</th>
+ <th>Content Type</th>
+ <th>Size</th>
+ <th>Category</th>
+ <th>Last Modified</th>
+ <th>Title</th>
+ <th>Body (partial)</th>
+ <th></th>
+</tr>
+<:if Userfiles:>
+ <:iterator begin userfiles:>
+ <tr>
+ <td><:userfile display_name:></td>
+ <td><:userfile content_type:></td>
+ <td><:kb userfile size_in_bytes:>b</td>
+ <td><:userfile category:></td>
+ <td><:date userfile modwhen:></td>
+ <td><:userfile title:></td>
+ <td><:replace [userfile body] ^([\w\W]{25})[\w\W]+ "$1 ...":></td>
+ <td><a href="<:script:>?a_deluserfileform=1&id=<:siteuser id:>&file_id=<:userfile id:>">Delete</a></div> <a href="<:script:>?a_edituserfile=1&id=<:siteuser id:>&file_id=<:userfile id:>">Edit</a></div></td>
+ </tr>
+ <:iterator end userfiles:>
+<:or Userfiles:>
+<tr><td colspan="8">There are no files attached to this user</td></tr>
+<:eif Userfiles:>
+</table>
+</div>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Group Files - Edit File":>
+<h1>Edit Group File</h1>
+
+<:include admin/users/inc_group_menu.tmpl:>
+
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="editfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:group id:>" />
+<input type="hidden" name="file_id" value="<:groupfile id:>" />
+<:csrfp admin_group_edit_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><input type="file" name="file" /><:error_img file:>
+<br />Currently: <:groupfile display_name:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><input type="text" name="content_type" value="<:old content_type groupfile content_type:>" /><:error_img content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><input type="checkbox" name="download" <:if Cgi a_savegroupfile:><:ifOld download:>checked="checked" <:or:><:eif:><:or Cgi:><:ifGroupfile download:>checked="checked" <:or:><:eif:><:eif Cgi:>/><:error_img download:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:userfile_category:><:error_img category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <input type="text" name="modwhen_date" value="<:old modwhen_date date "%d/%m/%Y" groupfile modwhen:>" size="10" /><:error_img modwhen_date:> dd/mm/yyyy<br />Time: <input type="text" name="modwhen_time" value="<:old modwhen_time date "%H:%M:%S" groupfile modwhen:>" size="10" /><:error_img modwhen_time:> HH:MM:SS</td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><input type="text" name="title" value="<:old title groupfile title:>" class="wide" /><:error_img title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5"><:old body groupfile body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_savegroupfile" value="Save File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Site Member Files - Edit File":>
+<h1>Edit Member File</h1>
+
+<:include admin/users/inc_user_menu.tmpl:>
+
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="editfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:siteuser id:>" />
+<input type="hidden" name="file_id" value="<:userfile id:>" />
+<:csrfp admin_user_edit_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><input type="file" name="file" /><:error_img file:>
+<br />Currently: <:userfile display_name:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><input type="text" name="content_type" value="<:old content_type userfile content_type:>" /><:error_img content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><input type="checkbox" name="download" <:if Cgi a_saveuserfile:><:ifOld download:>checked="checked" <:or:><:eif:><:or Cgi:><:ifUserfile download:>checked="checked" <:or:><:eif:><:eif Cgi:>/><:error_img download:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:userfile_category:><:error_img category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <input type="text" name="modwhen_date" value="<:old modwhen_date date "%d/%m/%Y" userfile modwhen:>" size="10" /><:error_img modwhen_date:> dd/mm/yyyy<br />Time: <input type="text" name="modwhen_time" value="<:old modwhen_time date "%H:%M:%S" userfile modwhen:>" size="10" /><:error_img modwhen_time:> HH:MM:SS</td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><input type="text" name="title" value="<:old title userfile title:>" class="wide" /><:error_img title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5"><:old body userfile body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_saveuserfile" value="Save File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"File Access Log":>
+<h1>File Access Log</h1>
+<p>| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |</p>
+<:if Owner:><p>Only files owned by <a href="<:script:>?id=<:owner_id:>&_t=files&<:ifEq [owner_type] G:>a_editgroup=1<:or:>a_edit=1<:eif:>"><:owner_desc:></a> <a href="<:script:>?a_fileaccesslog=1&<:page_args owner_id owner_type:>">(Remove this filter)</a></p><:or Owner:><:eif Owner:>
+<:ifSiteuser:><p>Files downloaded by <a href="<:script:>?a_edit=1&id=<:siteuser id:>"><:siteuser userId:></a> <a href="<:script:>?a_fileaccesslog=1&<:page_args siteuser_id:>">(Remove this filter)</a></p><:or:><:eif:>
+<:if File:><p>File <a href="<:script:>?<:ifEq [file owner_type] U:>a_edituserfile<:or:>a_editgroupfile<:eif:>=1&id=<:file owner_id:>&file_id=<:file id:>"><:file display_name:></a> <a href="<:script:>?a_fileaccesslog=1&<:page_args file_id:>">(Remove this filter)</a></p><:or File:><:eif File:>
+<:ifCategory:>Files in category: <:category name:> <a href="<:script:>?a_fileaccesslog=1&<:page_args category:>">(Remove this filter)</a><:or:><:eif:>
+
+<p>Page <:fileaccesses_pagenum:> of <:fileaccesses_pagecount:>
+<:ifFirstFileaccessesPage:>First Previous<:or:><a href="<:script:>?a_fileaccesslog=1&p=1&pp=<:fileaccesses_perpage:>">First</a> <a href="<:script:>?a_fileaccesslog=1&p=<:prevFileaccessesPage:>&pp=<:fileaccesses_perpage:>&<:page_args:>">Previous</a><:eif:>
+<:iterator begin repeats [fileaccesses_pagecount]:>
+<:if Eq [repeat value] [fileaccesses_pagenum]:><:repeat value:><:or Eq:><a href="<:script:>?a_fileaccesslog=1&p=<:repeat value:>&pp=<:fileaccesses_perpage:>&<:page_args:>"><:repeat value:></a><:eif Eq:>
+<:iterator end repeats:>
+<:ifLastFileaccessesPage:>Next Last<:or:><a href="<:script:>?a_fileaccesslog=1&p=<:nextFileaccessesPage:>&pp=<:fileaccesses_perpage:>&<:page_args:>">Next</a> <a href="<:script:>?a_fileaccesslog=1&p=<:fileaccesses_pagecount:>&pp=<:fileaccesses_perpage:>&<:page_args:>">Last</a><:eif:>
+</p>
+<form action="<:script:>"><:page_argsh from to:><p>Records from <input type="text" name="from" size="12" value="<:cgi from:>" /> to <input type="text" name="to" size="12" value="<:cgi to:>" /> <input type="submit" name="a_fileaccesslog" value="Go" /></form>
+
+<table class="editform" id="fileaccesslog">
+ <tr>
+ <th>When</th>
+ <th>User</th>
+ <th>File</th>
+ <th>Owner</th>
+ <th>Title</th>
+ <th>Category</th>
+ <th>Size</th>
+ <th>Filters</th>
+ </tr>
+<:iterator begin fileaccesses:>
+ <tr>
+ <td class="col_when_at"><:date "%d/%m/%Y %H:%M" fileaccess when_at:></td>
+ <td class="col_user"><a href="<:script:>?a_edit=1&id=<:fileaccess siteuser_id:>"><:user:></a>
+ </td>
+ <td class="col_filename"><a href="<:script:>?<:ifEq [fileaccess owner_type] "U":>a_edituserfile<:or:>a_editgroupfile<:eif:>=1&id=<:fileaccess owner_id:>&file_id=<:fileaccess file_id:>"><:fileaccess display_name:> (<:fileaccess file_id:>)</a></td>
+ <td class="col_owner"><a href="<:script:>?<:ifEq [fileaccess owner_type] "U":>a_edit<:or:>a_editgroup<:eif:>=1&<:ifMatch [fileaccess owner_id] ^-:>_t=files&<:or:><:eif:>=1&id=<:fileaccess owner_id:>"><:fileowner desc:></a></td>
+ <td class="col_title"><:fileaccess title:></td>
+ <td class="col_category"><:filecat:></td>
+ <td class="col_size"><:fileaccess size_in_bytes:></td>
+ <td class="col_links">
+<a href="<:script:>?a_fileaccesslog=1&<:page_args siteuser_id:>&siteuser_id=<:fileaccess siteuser_id:>">User</a>
+<a href="<:script:>?a_fileaccesslog=1&<:page_args file_id:>&file_id=<:fileaccess file_id:>">File</a>
+
+<a href="<:script:>?a_fileaccesslog=1&<:page_args owner_type owner_id:>&owner_id=<:fileaccess owner_id:>&owner_type=<:fileaccess owner_type:>">Owner</a>
+<a href="<:script:>?a_fileaccesslog=1&<:page_args category:>&category=<:fileaccess category:>">Category</a>
+</td>
+ </tr>
+<:iterator end fileaccesses:>
+</table>
<:wrap admin/xbase.tmpl title=>"Edit Member Group":>
<h2>Add Member Group</h2>
-<p>
-|
-<a href="/cgi-bin/admin/menu.pl">Admin menu</a>
-|
-<a href="<:script:>">Member List</a>
-|
-<a href="<:script:>?a_grouplist=1">Group List</a>
-|
-</p>
+<:include admin/users/inc_group_menu.tmpl:>
<:ifMsg:><p><:msg:></p><:or:><:eif:>
<form method="post" action="<:script:>" name="editgroup">
--- /dev/null
+<:wrap admin/xbase.tmpl title=>"Group Files":>
+<h1>Group Files: <:group name:></h1>
+<:include admin/users/inc_group_menu.tmpl:>
+<:ifUserCan bse_members_group_add_file:><p><a href="/cgi-bin/admin/siteusers.pl?a_addgroupfileform=1&id=<:group id:>">Add a file</a></p><:or:><:eif:>
+
+<:ifMessage:><div id="message"><:message:></div><:or:><:eif:>
+<div id="filelist">
+<table class="editform">
+<tr>
+ <th>Filename</th>
+ <th>Content Type</th>
+ <th>Size</th>
+ <th>Category</th>
+ <th>Last Modified</th>
+ <th>Title</th>
+ <th>Body (partial)</th>
+ <th></th>
+</tr>
+<:if Groupfiles:>
+ <:iterator begin groupfiles:>
+ <tr>
+ <td><:groupfile display_name:></td>
+ <td><:groupfile content_type:></td>
+ <td><:kb groupfile size_in_bytes:>b</td>
+ <td><:groupfile category:></td>
+ <td><:date groupfile modwhen:></td>
+ <td><:groupfile title:></td>
+ <td><:replace [groupfile body] ^([\w\W]{25})[\w\W]+ "$1 ...":></td>
+ <td><a href="<:script:>?a_delgroupfileform=1&id=<:group id:>&file_id=<:groupfile id:>">Delete</a></div> <a href="<:script:>?a_editgroupfile=1&id=<:group id:>&file_id=<:groupfile id:>">Edit</a></div></td>
+ </tr>
+ <:iterator end groupfiles:>
+<:or Groupfiles:>
+<tr><td colspan="8">There are no files attached to this group</td></tr>
+<:eif Groupfiles:>
+</table>
+</div>
<tr>
<td><:group name:></td>
<td>
+<:if Match [group id] ^\d:>
<a href="<:script:>?a_editgroup=1&id=<:group id:>">Edit</a>
<a href="<:script:>?a_deletegroupform=1&id=<:group id:>">Delete</a>
<a href="<:script:>?a_groupmemberform=1&id=<:group id:>">Members</a>
+<:or Match:><:eif Match:>
+ <a href="<:script:>?a_editgroup=1&_t=files&id=<:group id:>">Files</a>
</td>
</tr>
<:iterator end groups:>
--- /dev/null
+<div id="addfile">
+<form enctype="multipart/form-data" method="post">
+<input type="hidden" name="id" value="<:siteuser id:>" />
+<:csrfp admin_user_add_file hidden:>
+<table class="editform editformsmall">
+ <tr>
+ <th>File:</th>
+ <td><input type="file" name="file" /><:error_img file:></td>
+ </tr>
+ <tr>
+ <th>Content-type:</th>
+ <td><input type="text" name="content_type" value="<:old content_type:>" /><:error_img content_type:></td>
+ </tr>
+ <tr>
+ <th>Download:</th>
+ <td><input type="checkbox" name="download" <:ifOld download:>checked="checked" <:or:><:eif:>/><:error_img download:></td>
+ </tr>
+ <tr>
+ <th>Category:</th>
+ <td><:userfile_category:><:error_img category:></td>
+ </tr>
+ <tr>
+ <th>Last modified:</th>
+ <td>Date: <input type="text" name="modwhen_date" value="<:old modwhen_date:>" size="10" /><:error_img modwhen_date:> dd/mm/yyyy<br />Time: <input type="text" name="modwhen_time" value="<:old modwhen_time:>" size="10" /><:error_img modwhen_time:> HH:MM:SS</td>
+ </tr>
+ <tr>
+ <th>Title:</th>
+ <td><input type="text" name="title" value="<:old title:>" class="wide" /><:error_img title:></td>
+ </tr>
+ <tr>
+ <th>Body:</th>
+ <td><textarea name="body" rows="5"><:old body:></textarea><:error_img body:></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2">
+ <input type="submit" name="a_adduserfile" value="Add File" />
+ </td>
+ </tr>
+</table>
+</form>
+</div>
--- /dev/null
+<p>
+|
+<a href="/cgi-bin/admin/menu.pl">Admin menu</a>
+|
+<a href="<:script:>">Member List</a>
+|
+<a href="<:script:>?a_editgroup=1&id=<:group id:>&_t=files">Group files</a>
+|
+<a href="<:script:>?a_grouplist=1">Group List</a>
+|
+</p>
--- /dev/null
+<p>
+| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |
+<a href="/cgi-bin/admin/siteusers.pl">Site Members</a> |
+<a href="mailto:<:siteuser email:>">Email</a>
+<:ifUserorders:>| <a href="/cgi-bin/admin/siteusers.pl?a_view=1&id=<:siteuser id:>&_t=orders">Orders</a><:or:><:eif:> |
+<a href="/cgi-bin/admin/admin_seminar.pl?a_addattendseminar=1&siteuser_id=<:siteuser id:>">Add to seminar</a> |
+<a href="/cgi-bin/admin/siteusers.pl?a_view=1&id=<:siteuser id:>&_t=bookings">Seminar Bookings</a> |
+<a href="<:script:>?a_edit=1&id=<:siteuser id:>&_t=groups">Groups</a> |
+<a href="/cgi-bin/admin/siteusers.pl?a_edit=1&id=<:siteuser id:>&_t=files">Files</a> |
+</p>
+
-<div class="usermenu"><a href="<:script:>">Your Page</a> <a href="<:script:>?show_opts=1">Your Profile</a> <a href="<:script:>?_t=wishlist">Your Wishlist</a></div>
+<div class="usermenu">
+<a href="<:script:>">Your Page</a>
+<a href="<:script:>?show_opts=1">Your Profile</a>
+<a href="<:script:>?_t=wishlist">Your Wishlist</a>
+<a href="<:script:>?_t=orders">Your Orders</a>
+<:ifUserfiles:>
+<a href="<:script:>?_t=files">Your Files</a>
+<:or:><:eif:>
+</div>
--- /dev/null
+<:wrap base.tmpl:>
+<:include include/usermenu.tmpl:>
+<p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Hello <:ifUser
+ name1:><:user name1:><:or:><:user userId:><:eif:>, this section contains your
+ personal account details. From here you can <:ifCfg subscriptions enabled:> manage
+ your newsletter subscriptions,<:or:><:eif:><:ifCfg shop enabled:> monitor the current status
+ or purchase history of your orders from our on-line store<:or:><:eif:><:ifOr [cfg shop enabled] [cfg subscriptions enabled]:> and<:or:><:eif:> modify
+ your personal details, for example, your password, email and mailing addresses
+ etc.</font></p>
+<p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">To modify your
+ account options, <:ifCfg subscriptions enabled:>like subscribing to one of our available newsletters,<:or:><:eif:> please
+ proceed to edit your “<a href="<:script:>?show_opts=1">User Profile</a>”
+ and make your changes <:ifCfg subscriptions enabled:>eg: select a newsletter from the available list<:or:><:eif:> then
+ select “Save Options”.</font></p>
+
+<table>
+<tr>
+ <th>Name</th>
+ <th>Date</th>
+ <th>Title</th>
+ <th>Size</th>
+</tr>
+<:iterator begin userfiles sort: -modwhen:>
+<tr>
+ <td><a href="<:userfile url:>"><:userfile display_name:></a></td>
+ <td><:date userfile modwhen:></td>
+ <td><:userfile title:></td>
+ <td><:kb userfile size_in_bytes:></td>
+</tr>
+<:iterator end userfiles:>
+</table>
--- /dev/null
+<:wrap base.tmpl:>
+<:include include/usermenu.tmpl:>
+<p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Hello <:ifUser
+ name1:><:user name1:><:or:><:user userId:><:eif:>, this section contains your
+ personal account details. From here you can <:ifCfg subscriptions enabled:> manage
+ your newsletter subscriptions,<:or:><:eif:><:ifCfg shop enabled:> monitor the current status
+ or purchase history of your orders from our on-line store<:or:><:eif:><:ifOr [cfg shop enabled] [cfg subscriptions enabled]:> and<:or:><:eif:> modify
+ your personal details, for example, your password, email and mailing addresses
+ etc.</font></p>
+<p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">To modify your
+ account options, <:ifCfg subscriptions enabled:>like subscribing to one of our available newsletters,<:or:><:eif:> please
+ proceed to edit your “<a href="<:script:>?show_opts=1">User Profile</a>”
+ and make your changes <:ifCfg subscriptions enabled:>eg: select a newsletter from the available list<:or:><:eif:> then
+ select “Save Options”.</font></p>
+<div align="center">
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td>
+ <form name="userprofile" action="<:script:>">
+ <input type="submit" name="Submit" value="Edit user profile" class="user-buttons">
+ <input type="hidden" name="show_opts" value="1">
+ </form>
+ </td>
+ <:ifCfg shop enabled:><td>
+ <form name="ff" action="/cgi-bin/shop.pl">
+ <input type="submit" name="cart" value="View shopping cart" class="user-buttons">
+ </form>
+ </td><:or:><:eif:>
+ </tr>
+ </table>
+ <br>
+
+<:if Message:><p class="message"><:message:></p> <:or Message:><:eif Message:>
+ <table width="100%" cellpadding="0" cellspacing="1">
+ <tr>
+ <th align="center" height="20"><font face="Verdana, Arial, Helvetica, sans-serif" size="3">Your
+ Account</font></th>
+ </tr>
+ <tr>
+ <td align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Hello
+ <:ifUser name1:><:user name1:> <:user name2:><:or:><:user userId:><:eif:></font></b></p>
+ <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#999999">Last
+ logged in: <:date user previousLogon:><br>
+ Registered since: <:date user whenRegistered:></font><br>
+ <br>
+ </p></td>
+ </tr>
+ <:if Cfg shop enabled:>
+ <:if Orders:> <:iterator begin orders:>
+ <tr>
+ <td bgcolor="#CCCCCC"> <table width="100%" cellpadding="3" cellspacing="1">
+ <tr>
+ <th align="center" nowrap width="25%" bgcolor="#666666"><a href="<:script:>?a_orderdetail=1&id=<:order id:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#CCCCCC">Order
+ No:</font><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#FFFFFF">
+ <:order id:></font></a></th>
+ <th align="center" width="25%" nowrap bgcolor="#666666"><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#CCCCCC">Date:</font><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#FFFFFF">
+ <:date order orderDate:></font></th>
+ <th align="center" width="25%" nowrap bgcolor="<:ifOrder filled:>#CC0033<:or:>#66CC00<:eif:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="<:ifOrder filled:>#CCCCCC<:or:>#000000<:eif:>">Status:</font><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#FFFFFF">
+ <:ifOrder filled:>Complete<:or:>Processing<:eif:></font></th>
+ <th align="center" width="25%" nowrap bgcolor="#FF7F00"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Total:</font><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#FFFFFF">
+ $<:money order total:></font></th>
+ </tr>
+ </table>
+ <:if Items:> <table width="100%" cellpadding="3" cellspacing="1">
+ <tr bgcolor="#EEEEEE">
+ <th width="100%" align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Product</font></th>
+ <th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Quantity</font></th>
+ <th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Unit</font></th>
+ </tr>
+ <:iterator begin items:>
+ <tr bgcolor="#FFFFFF">
+ <td width="100%"><a href="<:product link:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:product
+ description:></font></a></td>
+ <td align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:item
+ units:></font></td>
+ <td align="right"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">$<:money
+ item price:></font></td>
+ </tr>
+ <:iterator end items:>
+ </table>
+ <:if Orderfiles:> <table width="100%" cellpadding="3" cellspacing="1">
+ <tr bgcolor="#CCCCCC">
+ <th colspan="4"><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#666666"><:if
+ Order filled:>Files available<:or Order:><:ifCfg downloads must_be_filled:>Files
+ available when order status is ‘Complete’<:or:>Files<:eif:><:eif
+ Order:></font></th>
+ </tr>
+ <tr bgcolor="#EEEEEE">
+ <th width="50%" align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Description</font></th>
+ <th nowrap width="50%" align="left" colspan="2"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">File</font></th>
+ <th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Size</font></th>
+ </tr>
+ <:iterator begin orderfiles:>
+ <tr bgcolor="#FFFFFF">
+ <td width="50%"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:orderfile
+ description:></font></td>
+ <td nowrap width="50%"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:if
+ FileAvail:><a href="/cgi-bin/user.pl?download=1&file=<:orderfile id:>&order=<:order id:>&item=<:orderfile item_id:>"><:orderfile
+ displayName:></a><:or FileAvail:><:orderfile displayName:><:eif
+ FileAvail:></font></td>
+ <td><:if FileAvail:><a href="/cgi-bin/user.pl?download=1&file=<:orderfile id:>&order=<:order id:>&item=<:orderfile item_id:>"><img src="/images/filestatus/download.gif" width="15" height="15" alt="Download now" title="Download now" border="0"></a><:or
+ FileAvail:><img src="/images/filestatus/locked.gif" width="15" height="15" alt="Locked" title="Locked"><:eif
+ FileAvail:></td>
+ <td align="right"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:kb
+ orderfile sizeInBytes:></font></td>
+ </tr>
+ <:iterator end orderfiles:>
+ </table>
+ <:or Orderfiles:><:eif Orderfiles:> <:or Items:><:eif Items:> </td>
+ </tr>
+ <:iterator separator orders:>
+ <tr>
+ <td > </td>
+ </tr>
+ <:iterator end orders:> <:or Orders:>
+ <tr>
+ <td align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">You
+ haven't made any orders yet.</font></td>
+ </tr>
+ <:eif Orders:>
+ <:or Cfg:>
+ <:eif Cfg:>
+ </table>
+
+<:if Subscriptions:>
+<table>
+<tr><th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Name</font></th><th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Expires</font></th></tr>
+<:iterator begin subscriptions:>
+<tr><td><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:subscription title:></font></td><td><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:date subscription ends_at:></font></td></tr>
+<:iterator end subscriptions:>
+</table>
+<:or Subscriptions:><:eif Subscriptions:>
+</div>
</td>
</tr>
<:eif Cfg:>
+<:if Filecats:>
+ <tr>
+ <th nowrap="nowrap" align="left">File subscriptions:</th>
+ <td>
+ <input type="hidden" name="save_file_subs" value="1" />
+ <:iterator begin filecats:>
+ <input type="checkbox" name="file_subscriptions" value="<:filecat id:>" <:ifFilecat subscribed:>checked="checked" <:or:><:eif:> /> <:filecat name:>
+ <:iterator separator filecats:>
+ <br />
+ <:iterator end filecats:>
+ </td>
+ </tr>
+<:or Filecats:><:eif Filecats:>
<tr>
<td colspan="2" align="right">
<input type="submit" name="saveopts" value="Save Options" class="user-buttons" />
Column article_id;int(11);NO;NULL;
Column group_id;int(11);NO;NULL;
Index PRIMARY;1;[article_id;group_id]
+Table bse_file_access_log
+Column id;int(11);NO;NULL;auto_increment
+Column when_at;datetime;NO;NULL;
+Column siteuser_id;int(11);NO;NULL;
+Column siteuser_logon;varchar(40);NO;NULL;
+Column file_id;int(11);NO;NULL;
+Column owner_type;char(1);NO;NULL;
+Column owner_id;int(11);NO;NULL;
+Column category;varchar(20);NO;NULL;
+Column filename;varchar(255);NO;NULL;
+Column display_name;varchar(255);NO;NULL;
+Column content_type;varchar(80);NO;NULL;
+Column download;int(11);NO;NULL;
+Column title;varchar(255);NO;NULL;
+Column modwhen;datetime;NO;NULL;
+Column size_in_bytes;int(11);NO;NULL;
+Index PRIMARY;1;[id]
+Index by_file;0;[file_id]
+Index by_user;0;[siteuser_id;when_at]
+Index by_when_at;0;[when_at]
+Table bse_file_notifies
+Column id;int(11);NO;NULL;auto_increment
+Column siteuser_id;int(11);NO;NULL;
+Column file_id;int(11);NO;NULL;
+Index PRIMARY;1;[id]
+Index by_siteuser;0;[siteuser_id]
+Table bse_file_subscriptions
+Column id;int(11);NO;NULL;
+Column siteuser_id;int(11);NO;NULL;
+Column category;varchar(20);NO;NULL;
+Index by_category;0;[category]
+Index by_siteuser;0;[siteuser_id]
Table bse_locations
Column id;int(11);NO;NULL;auto_increment
Column description;varchar(255);NO;NULL;
Column display_order;int(11);NO;NULL;
Index PRIMARY;1;[id]
Index item_order;0;[order_item_id;display_order]
+Table bse_owned_files
+Column id;int(11);NO;NULL;auto_increment
+Column owner_type;char(1);NO;NULL;
+Column owner_id;int(11);NO;NULL;
+Column category;varchar(20);NO;NULL;
+Column filename;varchar(255);NO;NULL;
+Column display_name;varchar(255);NO;NULL;
+Column content_type;varchar(80);NO;NULL;
+Column download;int(11);NO;NULL;
+Column title;varchar(255);NO;NULL;
+Column body;text;NO;NULL;
+Column modwhen;datetime;NO;NULL;
+Column size_in_bytes;int(11);NO;NULL;
+Index PRIMARY;1;[id]
+Index by_owner_category;0;[owner_type;owner_id;category]
Table bse_product_option_values
Column id;int(11);NO;NULL;auto_increment
Column product_option_id;int(11);NO;NULL;
#!perl -w
use strict;
-use Test::More tests=>42;
+use Test::More tests=>44;
my $gotmodule;
BEGIN { $gotmodule = use_ok('DevHelp::Date', ':all'); }
is_deeply([ dh_parse_time("1101", \$msg) ], [ 11, 1, 0 ], "parse 1101");
is($msg, undef, "no error");
+ is_deeply([ dh_parse_time("11:01:02", \$msg) ], [ 11, 1, 2 ],
+ "parse 11:01:02") or diag $msg;
+ is_deeply([ dh_parse_time("11:01:02pm", \$msg) ], [23, 1, 2 ],
+ "parse 11:01:02pm") or diag $msg;
+
# fail a bit
undef $msg;
is_deeply([ dh_parse_time("xxx", \$msg) ], [], "parse xxx");