0.14_03 commit r0_14_03
authorTony Cook <tony@develop-help.com>
Wed, 20 Aug 2003 00:48:13 +0000 (00:48 +0000)
committertony <tony@45cb6cf1-00bc-42d2-bb5a-07f51df49f94>
Wed, 20 Aug 2003 00:48:13 +0000 (00:48 +0000)
28 files changed:
MANIFEST
Makefile
schema/bse.sql
site/cgi-bin/admin/siteusers.pl [new file with mode: 0755]
site/cgi-bin/bse.cfg
site/cgi-bin/modules/Article.pm
site/cgi-bin/modules/BSE/AdminSiteUsers.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/DB/Mysql.pm
site/cgi-bin/modules/BSE/Edit/Article.pm
site/cgi-bin/modules/BSE/UserReg.pm
site/cgi-bin/modules/BSE/Util/Iterate.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/Util/Tags.pm
site/cgi-bin/modules/DevHelp/DynSort.pm [new file with mode: 0644]
site/cgi-bin/modules/DevHelp/Tags.pm
site/cgi-bin/modules/DevHelp/Tags/Iterate.pm [new file with mode: 0644]
site/cgi-bin/modules/Order.pm
site/cgi-bin/modules/SiteUser.pm
site/cgi-bin/modules/Squirrel/Template.pm
site/docs/bse.pod
site/docs/config.pod
site/htdocs/css/style-main.css
site/templates/admin/users/add.tmpl [new file with mode: 0644]
site/templates/admin/users/edit.tmpl [new file with mode: 0644]
site/templates/admin/users/list.tmpl [new file with mode: 0644]
site/templates/checkout_base.tmpl
site/templates/user/options_base.tmpl
site/templates/user/options_billing_base.tmpl [new file with mode: 0644]
test.cfg

index 6bf5e9f..c1edacc 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -26,6 +26,7 @@ site/cgi-bin/admin/move.pl
 site/cgi-bin/admin/reorder.pl
 site/cgi-bin/admin/report.pl
 site/cgi-bin/admin/shopadmin.pl
+site/cgi-bin/admin/siteusers.pl
 site/cgi-bin/admin/subs.pl
 site/cgi-bin/admin/userlist.pl
 site/cgi-bin/bse.cfg
@@ -41,6 +42,7 @@ site/cgi-bin/modules/BSE/Admin/StepParents.pm
 site/cgi-bin/modules/BSE/AdminLogon.pm
 site/cgi-bin/modules/BSE/AdminMenu.pm
 site/cgi-bin/modules/BSE/AdminUsers.pm
+site/cgi-bin/modules/BSE/AdminSiteUsers.pm
 site/cgi-bin/modules/BSE/Arrows.pm
 site/cgi-bin/modules/BSE/Cfg.pm
 site/cgi-bin/modules/BSE/ChangePW.pm
@@ -85,16 +87,19 @@ site/cgi-bin/modules/BSE/TB/AdminUser.pm
 site/cgi-bin/modules/BSE/TB/AdminUsers.pm
 site/cgi-bin/modules/BSE/Template.pm
 site/cgi-bin/modules/BSE/UserReg.pm
+site/cgi-bin/modules/BSE/Util/Iterate.pm
 site/cgi-bin/modules/BSE/Util/SQL.pm
 site/cgi-bin/modules/BSE/Util/Secure.pm
 site/cgi-bin/modules/BSE/Util/Tags.pm
 site/cgi-bin/modules/BSE/Util/Valid.pm
 site/cgi-bin/modules/BSE/Version.pm
 site/cgi-bin/modules/Constants.pm
+site/cgi-bin/modules/DevHelp/DynSort.pm
 site/cgi-bin/modules/DevHelp/Formatter.pm
 site/cgi-bin/modules/DevHelp/HTML.pm
 site/cgi-bin/modules/DevHelp/Report.pm
 site/cgi-bin/modules/DevHelp/Tags.pm
+site/cgi-bin/modules/DevHelp/Tags/Iterate.pm
 site/cgi-bin/modules/Generate.pm
 site/cgi-bin/modules/Generate/Article.pm
 site/cgi-bin/modules/Generate/Catalog.pm
@@ -253,6 +258,9 @@ site/templates/admin/subs/send_error.tmpl
 site/templates/admin/subs/send_form.tmpl
 site/templates/admin/subs/start_send.tmpl
 site/templates/admin/userlist.tmpl
+site/templates/admin/users/add.tmpl
+site/templates/admin/users/edit.tmpl
+site/templates/admin/users/list.tmpl
 site/templates/admin/xbase.tmpl
 site/templates/base.tmpl
 site/templates/cart_base.tmpl
@@ -314,6 +322,7 @@ site/templates/user/lostpassword_base.tmpl
 site/templates/user/lostpwdemail_base.tmpl
 site/templates/user/nopassword_base.tmpl
 site/templates/user/options_base.tmpl
+site/templates/user/options_billing_base.tmpl
 site/templates/user/options_saved_base.tmpl
 site/templates/user/register_base.tmpl
 site/templates/user/subdetail.tmpl
index dac3c6c..909b979 100755 (executable)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.14_02
+VERSION=0.14_03
 DISTNAME=bse-$(VERSION)
 DISTBUILD=$(DISTNAME)
 DISTTAR=../$(DISTNAME).tar
index d38fd0d..be9b39c 100644 (file)
@@ -68,6 +68,11 @@ CREATE TABLE article (
   customStr1 varchar(255) null,
   customStr2 varchar(255) null,
 
+  customInt1 integer null,
+  customInt2 integer null,
+  customInt3 integer null,
+  customInt4 integer null,
+
   PRIMARY KEY (id),
 
   -- if we keep id in the indexes MySQL will sometimes be able to
@@ -222,6 +227,11 @@ create table orders (
   customStr4 varchar(255) null,
   customStr5 varchar(255) null,
 
+  instructions text not null default '',
+  billTelephone varchar(80) not null default '',
+  billFacsimile varchar(80) not null default '',
+  billEmail varchar(255) not null default '',
+
   primary key (id),
   index order_cchash(ccNumberHash),
   index order_userId(userId, orderDate)
@@ -429,6 +439,24 @@ create table site_users (
 
   previousLogon datetime not null,
 
+  -- used for billing information on the checkout form
+  billFirstName varchar(127) not null default '',
+  billLastName varchar(127) not null default '',
+  billStreet varchar(127) not null default '',
+  billSuburb varchar(127) not null default '',
+  billState varchar(40) not null default '',
+  billPostCode varchar(40) not null default '',
+  billCountry varchar(127) not null default '',
+
+  instructions text not null default '',
+  billTelephone varchar(80) not null default '',
+  billFacsimile varchar(80) not null default '',
+  billEmail varchar(255) not null default '',
+
+  adminNotes text not null default '',
+
+  disabled integer not null default 0,
+
   primary key (id),
   unique (userId)
 );
diff --git a/site/cgi-bin/admin/siteusers.pl b/site/cgi-bin/admin/siteusers.pl
new file mode 100755 (executable)
index 0000000..b8aee49
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+# -d:ptkdb
+BEGIN { $ENV{DISPLAY} = '192.168.32.15:0.0' }
+use strict;
+use FindBin;
+use lib "$FindBin::Bin/../modules";
+use BSE::DB;
+use BSE::Request;
+use BSE::Template;
+use Carp 'confess';
+use BSE::AdminSiteUsers;
+
+$SIG{__DIE__} = sub { confess $@ };
+
+my $req = BSE::Request->new;
+
+my $result = BSE::AdminSiteUsers->dispatch($req);
+BSE::Template->output_result($req, $result);
index 923047a..0c5ab8a 100644 (file)
@@ -29,6 +29,7 @@ user/lostpwdemail.tmpl = user,user/lostpwdemail_base.tmpl
 user/nopassword.tmpl = user,user/nopassword_base.tmpl
 user/options.tmpl = user,user/options_base.tmpl
 user/options_saved.tmpl = user,user/options_saved_base.tmpl
+user/options_billing.tmpl = user,user/options_billing_base.tmpl
 user/register.tmpl = user,user/register_base.tmpl
 user/toomany.tmpl = user,user/toomany_base.tmpl
 user/toosoon.tmpl = user,user/toosoon_base.tmpl
@@ -242,11 +243,12 @@ weekly_sales=Weekly Sales
 yearly_sales=Yearly Sales
 total_sales=Total Sales
 users_vs_orders=Users vs Orders
+user_products=Products bought by User
 
 [report sales_summ_by_product]
 sql1=select pr.articleId as "Id", ar.title as "Product", count(*) as "Units" from article ar, product pr, order_item oi where ar.id = pr.articleId and pr.articleId = oi.productId group by pr.articleId, ar.title
-sql1link1=<a href="/cgi-bin/admin/report.pl?s_show=1&r=product_sales_over_time&p1=${articleId}">Sales Over Time</a>
-sql1link2=<a href="/cgi-bin/admin/add.pl?id=${articleId}">Edit</a>
+sql1link1=<a href="/cgi-bin/admin/report.pl?s_show=1&r=product_sales_over_time&p1=${Id}">Sales Over Time</a>
+sql1link2=<a href="/cgi-bin/admin/add.pl?id=${Id}">Edit</a>
 
 [report product_sales_over_time]
 sql1=select year(od.orderDate) as "Year", month(od.orderDate) as "Month", count(*) as 'Units' from order_item oi, orders od where oi.orderId = od.id and productId = ? group by year(od.orderDate), month(od.orderDate) order by 1 DESC, 2 DESC
@@ -270,6 +272,8 @@ sql1=select count(*) as "Orders", format(sum(total)/100.0,2) as '$ Total', count
 
 [report user_products]
 sql1=select pr.title, count(*) as 'Count' from orders od, order_item oi, article pr, site_users su where oi.orderId = od.id and oi.productId = pr.id and su.userId = od.userId and su.id = ? group by pr.id
+sql1params=1
+param1=site_user,User
 
 [report users_vs_orders]
 sql1=select delivFirstName as "First Name", delivLastName as "Last Name", userId as "Logon", format(sum(total)/100.0,2) as '$ Total', count(*) as "Orders" from orders group by delivFirstName, delivLastName order by 5 DESC, 2
index d8dff57..110153c 100644 (file)
@@ -10,7 +10,8 @@ sub columns {
     thumbImage thumbWidth thumbHeight imagePos
     release expire keyword template link admin threshold
     summaryLength generator level listed lastModified flags
-    customDate1 customDate2 customStr1 customStr2/;
+    customDate1 customDate2 customStr1 customStr2
+    customInt1 customInt2 customInt3 customInt4/;
 }
 
 sub step_parents {
diff --git a/site/cgi-bin/modules/BSE/AdminSiteUsers.pm b/site/cgi-bin/modules/BSE/AdminSiteUsers.pm
new file mode 100644 (file)
index 0000000..f7544db
--- /dev/null
@@ -0,0 +1,459 @@
+package BSE::AdminSiteUsers;
+use strict;
+use BSE::Util::Tags qw(tag_error_img tag_hash);
+use DevHelp::HTML;
+use SiteUsers;
+use BSE::Util::Iterate;
+use DevHelp::DynSort qw(sorter tag_sorthelp);
+use BSE::Util::SQL qw/now_datetime/;
+
+my %actions =
+  (
+   list=>1,
+   edit=>1,
+   save=>1,
+   addform=>1,
+   add=>1,
+  );
+
+my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation);
+my %donttouch = map { $_, $_ } @donttouch;
+
+sub dispatch {
+  my ($class, $req) = @_;
+
+  $req->check_admin_logon()
+    or return BSE::Template->get_refresh($req->url('logon'), $req->cfg);
+
+  my $cgi = $req->cgi;
+  my $action;
+  for my $check (keys %actions) {
+    if ($cgi->param("a_$check")) {
+      $action = $check;
+      last;
+    }
+  }
+  $action ||= 'list';
+  my $method = "req_$action";
+  $class->$method($req);
+}
+
+sub req_list {
+  my ($class, $req, $msg) = @_;
+  
+  my $cgi = $req->cgi;
+  if ($msg) {
+    $msg = escape_html($msg);
+  }
+  else {
+    $msg = join("<br />", map escape_html($_), $cgi->param('m'));
+  }
+  my @users = SiteUsers->all;
+  my ($sortby, $reverse) =
+    sorter(data=>\@users, cgi=>$cgi, sortby=>'userId');
+  my $it = BSE::Util::Iterate->new;
+                           
+  my %acts;
+  %acts =
+    (
+     BSE::Util::Tags->admin(undef, $req->cfg),
+     BSE::Util::Tags->basic(undef, $req->cgi, $req->cfg),
+     BSE::Util::Tags->secure($req),
+     message => $msg,
+     $it->make_paged_iterator('siteuser', 'siteusers', \@users, undef,
+                             $cgi, undef, 'pp=20'),
+     sortby=>$sortby,
+     reverse=>$reverse,
+     sorthelp => [ \&tag_sorthelp, $sortby, $reverse ],
+    );
+
+  my $template = 'admin/users/list';
+  my $t = $req->cgi->param('_t');
+  $template .= "_$t" if defined($t) && $t =~ /^\w+$/;
+
+  return BSE::Template->get_response($template, $req->cfg, \%acts);
+}
+
+sub tag_if_required {
+  my ($cfg, $args) = @_;
+
+  return $cfg->entryBool('site users', "require_$args", 0);
+}
+
+sub req_edit {
+  my ($class, $req, $msg, $errors) = @_;
+
+  my $cgi = $req->cgi;
+  my $id = $cgi->param('id');
+  defined $id
+    or return $class->req_list($req, "No site user id supplied");
+  my $siteuser = SiteUsers->getByPkey($id)
+    or return $class->req_list($req, "No such site user found");
+
+  $errors ||= {};
+  if ($msg) {
+    $msg = escape_html($msg);
+  }
+  else {
+    if (keys %$errors) {
+      my %work = %$errors;
+      my @msgs = delete @work{$cgi->param()};
+      push @msgs, values %work;
+      $msg = join "<br />", map escape_html($_), @msgs;
+    }
+    else {
+      $msg = '';
+    }
+  }
+
+  my %acts;
+  %acts =
+    (
+     BSE::Util::Tags->admin(undef, $req->cfg),
+     BSE::Util::Tags->basic(undef, $req->cgi, $req->cfg),
+     BSE::Util::Tags->secure($req),
+     message => $msg,
+     siteuser => [ \&tag_hash, $siteuser ],
+     error_img => [ \&tag_error_img, $req->cfg, $errors ],
+     ifRequired => [ \&tag_if_required, $req->cfg ],
+    );  
+
+  my $template = 'admin/users/edit';
+  my $t = $req->cgi->param('_t');
+  $template .= "_$t" if defined($t) && $t =~ /^\w+$/;
+
+  return BSE::Template->get_response($template, $req->cfg, \%acts);
+}
+
+sub req_save {
+  my ($class, $req) = @_;
+
+  my $cgi = $req->cgi;
+  my $cfg = $req->cfg;
+
+  my $id = $cgi->param('id');
+  $id && $id =~ /^\d+$/
+    or return $class->req_list($req, "No user id supplied");
+
+  my $user = SiteUsers->getByPkey($id)
+    or return $class->req_list($req, "No user $id found");
+
+  my %errors;
+  my $nopassword = $req->cfg->entry('site users', 'nopassword', 0);
+  my @cols = grep !$donttouch{$_}, SiteUser->columns;
+  for my $col (@cols) {
+    my $value = $cgi->param($col);
+    if ($cfg->entryBool('site users', "require_$col")) {
+      if (defined $value && $value eq '') {
+       my $disp = $cfg->entry('site users', "display_$col", "\u$col");
+       $errors{$col} = "$disp is a required field";
+      }
+    }
+  }
+
+  my $saveemail;
+  my $email = $cgi->param('email');
+  if (!$email) {
+    $errors{email} = "Email is a required field";
+  }
+  elsif ($email !~ /.\@./) {
+    $errors{email} = "Email is invalid";
+  }
+  unless ($errors{email}) {
+    if ($nopassword) {
+      my $conf_email = $cgi->param('confirmemail');
+      if ($conf_email) {
+       if ($conf_email eq $email) {
+         my $other = SiteUsers->getBy(userId=>$email);
+         if ($other) {
+           $errors{email} = "That email address is already in use";
+         }
+         else {
+           ++$saveemail;
+         }
+       }
+       else {
+         $errors{confirmemail} =
+           "Confirmation email address doesn't match email address";
+       }
+      }
+      else {
+       $errors{confirmemail} = "Please enter a confirmation email address";
+      }
+    }
+    else {
+      ++$saveemail;
+    }
+  }
+  unless ($errors{email}) {
+    my $checkemail = SiteUser->generic_email($email);
+    require BSE::EmailBlacklist;
+    my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
+    if ($blackentry) {
+      $errors{email} = "Email $email is blacklisted: $blackentry->{why}";
+    }
+  }
+
+  my $newpass;
+  unless ($nopassword) {
+    $newpass = $cgi->param('password');
+    my $confirm = $cgi->param('confirm_password');
+    
+    if (defined $newpass && length $newpass) {
+      my $min_pass_length = $cfg->entry('basic', 'minpassword') || 4;
+         my $error;
+      if (length $newpass < $min_pass_length) {
+       $errors{password} = "The password must be at least $min_pass_length characters";
+      }
+      elsif (!defined $confirm || length $confirm == 0) {
+       $errors{confirm_password} = "Please enter a confirmation password";
+      }
+      elsif ($newpass ne $confirm) {
+       $errors{confirm_password} = "The confirmation password is different from the password";
+      }
+    }
+  }
+
+  keys %errors
+    and return $class->req_edit($req, undef, \%errors);
+  
+  my $newemail;
+  if ($saveemail && $email ne $user->{email}) {
+    $user->{confirmed} = 0;
+    $user->{confirmSecret} = '';
+    $user->{email} = $email;
+    $user->{userId} = $email if $nopassword;
+    ++$newemail;
+  }
+  $user->{password} = $newpass if !$nopassword && $newpass;
+  
+  for my $col (@cols) {
+    my $value = $cgi->param($col);
+    if (defined $value) {
+      $user->{$col} = $value;
+    }
+  }
+
+  $user->{textOnlyMail} = 0 
+    if $cgi->param('saveTextOnlyMail') && !defined $cgi->param('textOnlyMail');
+  $user->{keepAddress} = 0 
+    if $cgi->param('saveKeepAddress') && !defined $cgi->param('keepAddress');
+  $user->{disabled} = 0
+    if $cgi->param('saveDisabled') && !defined $cgi->param('disabled');
+  $user->save;
+
+  my @msgs = ( "User saved" );
+
+  my $sent_ok = 1; # no error handling if true
+  my $code;
+  my $msg;
+  if ($nopassword) {
+    $sent_ok = $user->send_conf_request($req->cgi, $req->cfg, \$code, \$msg) 
+      if $newemail;
+  }
+  else {
+    my @subs = $user->subscriptions;
+    if (@subs && $newemail) {
+      $sent_ok = $user->send_conf_request($req->cgi, $req->cfg, \$code, \$msg);
+    }
+  }
+
+  unless ($sent_ok) {
+    if ($code eq 'blacklist') {
+      push @msgs, "Could not send confirmation: Email address blacklisted: $msg";
+    }
+    elsif ($code eq 'mail') {
+      push @msgs, "Could not send confirmation: Error sending email: $msg";
+    }
+    else {
+      push @msgs, "Could not send confirmation: $msg";
+    }
+  }
+  
+  my $r = $cgi->param('r');
+  unless ($r) {
+    $r = $req->url('siteusers', { list => 1 });
+  }
+  $r .= "&m=".escape_uri($_) for @msgs;
+
+  return BSE::Template->get_refresh($r, $req->cfg);
+}
+
+sub req_addform {
+  my ($class, $req, $msg, $errors) = @_;
+
+  my $cgi = $req->cgi;
+
+  $errors ||= {};
+  if ($msg) {
+    $msg = escape_html($msg);
+  }
+  else {
+    if (keys %$errors) {
+      my %work = %$errors;
+      my @msgs = delete @work{$cgi->param()};
+      push @msgs, values %work;
+      $msg = join "<br />", map escape_html($_), grep $_, @msgs;
+    }
+    else {
+      $msg = '';
+    }
+  }
+
+  my %acts;
+  %acts =
+    (
+     BSE::Util::Tags->admin(undef, $req->cfg),
+     BSE::Util::Tags->basic(undef, $req->cgi, $req->cfg),
+     BSE::Util::Tags->secure($req),
+     message => $msg,
+     error_img => [ \&tag_error_img, $req->cfg, $errors ],
+     ifRequired => [ \&tag_if_required, $req->cfg ],
+    );  
+
+  my $template = 'admin/users/add';
+  my $t = $req->cgi->param('_t');
+  $template .= "_$t" if defined($t) && $t =~ /^\w+$/;
+
+  return BSE::Template->get_response($template, $req->cfg, \%acts);
+}
+
+sub req_add {
+  my ($class, $req) = @_;
+
+  my $cgi = $req->cgi;
+  my $cfg = $req->cfg;
+
+  my %user;
+  my @cols = SiteUser->columns;
+  shift @cols;
+  for my $field (@cols) {
+    $user{$field} = '';
+  }
+
+  my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
+  my %errors;
+  my $email = $cgi->param('email');
+  if (!defined $email or !length $email) {
+    $errors{email} = "Please enter an email address";
+    $email = ''; # prevent undefined value warnings later
+  }
+  elsif ($email !~ /.\@./) {
+    $errors{email} = "Please enter a valid email address";
+  }
+  if ($nopassword) {
+    my $confemail = $cgi->param('confirmemail');
+    if (!defined $confemail or !length $confemail) {
+      $errors{confirmemail} = "Please enter a confirmation email address";
+    }
+    elsif ($email ne $confemail) {
+      $errors{confirmemail} = "Confirmation email should match the Email Address";
+    }
+    my $user = SiteUsers->getBy(userId=>$email);
+    if ($user) {
+      $errors{email} = "Sorry, email $email already exists as a user";
+    }
+    $user{userId} = $email;
+    $user{password} = '';
+  }
+  else {
+    my $min_pass_length = $cfg->entry('basic', 'minpassword') || 4;
+    my $userid = $cgi->param('userId');
+    if (!defined $userid || length $userid == 0) {
+      $errors{userId} = "Please enter a userid";
+    }
+    my $pass = $cgi->param('password');
+    my $pass2 = $cgi->param('confirm_password');
+    if (!defined $pass || length $pass == 0) {
+      $errors{password} = "Please enter a password";
+    }
+    elsif (length $pass < $min_pass_length) {
+      $errors{password} = "The password must be at least $min_pass_length characters";
+    }
+    elsif (!defined $pass2 || length $pass2 == 0) {
+      $errors{confirm_password} = "Please enter a confirmation password";
+    }
+    elsif ($pass ne $pass2) {
+      $errors{confirm_password} = 
+       "The confirmation password is different from the password";
+    }
+    my $user = SiteUsers->getBy(userId=>$userid);
+    if ($user) {
+      # give the user a suggestion
+      my $workuser = $userid;
+      $workuser =~ s/\d+$//;
+      my $suffix = 1;
+      for my $suffix (1..100) {
+       unless (SiteUsers->getBy(userId=>"$workuser$suffix")) {
+         $cgi->param(userid=>"$workuser$suffix");
+         last;
+       }
+      }
+      $errors{userId} = "Sorry, user $userid already exists";
+    }
+    $user{userId} = $userid;
+    $user{password} = $pass;
+  }
+
+  unless ($errors{email}) {
+    my $checkemail = SiteUser->generic_email($email);
+    require 'BSE/EmailBlacklist.pm';
+    my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
+    if ($blackentry) {
+      $errors{email} = "Email $email is blacklisted: $blackentry->{why}";
+    }
+  }
+
+  my @mod_cols = grep !$donttouch{$_}, @cols;
+  for my $col (@mod_cols) {
+    my $value = $cgi->param($col);
+    if ($cfg->entryBool('site users', "require_$col")) {
+      unless (defined $value && $value ne '') {
+       my $disp = $cfg->entry('site users', "display_$col", "\u$col");
+       
+       $errors{$col} = "$disp is a required field";
+      }
+    }
+    if (defined $value) {
+      $user{$col} = $value;
+    }
+  }
+  if (keys %errors) {
+    return $class->req_addform($req, undef, \%errors);
+  }
+
+  $user{email} = $email;
+  $user{lastLogon} = $user{whenRegistered} = 
+    $user{previousLogon} = now_datetime;
+  $user{keepAddress} = 0;
+  $user{wantLetter} = 0;
+  if ($nopassword) {
+    use BSE::Util::Secure qw/make_secret/;
+    $user{password} = make_secret($cfg);
+  }
+
+  my $user;
+  eval {
+    $user = SiteUsers->add(@user{@cols});
+  };
+  if ($user) {
+    # my $subs = $self->_save_subs($user, $session, $cfg, $cgi);
+    my $msg;
+    if ($nopassword) {
+      my $code;
+      my $sent_ok = $user->send_conf_request($cgi, $cfg, \$code, \$msg);
+    }
+    my $r = $cgi->param('r');
+    unless ($r) {
+      $r = $req->url('siteusers', { list => 1, 
+                                   'm' => "User $user->{userId} added" });
+    }
+    $r .= "&m=".escape_url($msg) if $msg;
+    return BSE::Template->get_refresh($r, $cfg);
+  }
+  else {
+    $class->req_add($req, "Database error $@");
+  }
+}
+
+1;
index bed9cd4..8f6f3a9 100644 (file)
@@ -18,9 +18,9 @@ my %statements =
   (
    Articles => 'select * from article',
    replaceArticle =>
-     'replace article values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+     'replace article values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    addArticle =>  
-     'insert article values (null, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+     'insert article values (null, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    deleteArticle => 'delete from article where id = ?',
    getArticleByPkey => 'select * from article where id = ?',
    
@@ -71,8 +71,8 @@ EOS
    Orders => 'select * from orders',
    getOrderByPkey => 'select * from orders where id = ?',
    getOrderItemByOrderId => 'select * from order_item where orderId = ?',
-   addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
-   replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    addOrderItem => 'insert order_item values(null,?,?,?,?,?,?,?)',
    getOrderByUserId => 'select * from orders where userId = ?',
 
@@ -113,8 +113,8 @@ SQL
    'select * from site_users where userId = ?',
    getSiteUserByPkey =>
    'select * from site_users where id = ?',
-   addSiteUser => 'insert site_users values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
-   replaceSiteUser => 'replace site_users values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   addSiteUser => 'insert site_users values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   replaceSiteUser => 'replace site_users values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    'SiteUsers.removeSubscriptions'=>
    'delete from subscribed_users where userId = ?',
    'SiteUsers.removeSub'=>
@@ -123,6 +123,7 @@ SQL
 select si.* from site_users si, subscribed_users su
   where confirmed <> 0 and si.id = su.userId and su.subId = ?
 EOS
+   SiteUsers => 'select * from site_users',
 
    SubscriptionTypes =>
    'select * from subscription_types',
@@ -138,6 +139,10 @@ EOS
 select count(*) as "count" from site_users si, subscribed_users su
   where confirmed <> 0 and si.id = su.userId and su.subId = ?
 EOS
+   'SubscriptionTypes.userSubscribedTo' => <<'EOS',
+select su.* from subscription_types su, subscribed_users us
+  where us.userId = ? and us.subId = su.id
+EOS
 
    addSubscribedUser=>
    'insert subscribed_users values(null,?,?)',
index c363f6d..ee2bd86 100644 (file)
@@ -785,7 +785,11 @@ sub tag_movefiles {
     or return '** movefiles can only be used in the files iterator **';
 
   my $urlbase = $self->{cfg}->entryVar('site', 'url');
-  my $url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}&filelist=1$urladd";
+  my $url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}$urladd";
+  my $t = $req->cgi->param('_t');
+  if ($t && $t =~ /^\w+$/) {
+    $url .= "&_t=$t";
+  }
 
   my $down_url = "";
   if ($$rindex < $#$files) {
@@ -2290,7 +2294,7 @@ sub fileswap {
   use Util 'generate_article';
   generate_article($articles, $article) if $Constants::AUTO_GENERATE;
 
-  $self->_refresh_filelist($req, $article, 'File moved');
+  $self->refresh($article, $req->cgi, undef, 'File moved');
 }
 
 sub filedel {
index d239e13..ef380eb 100644 (file)
@@ -15,6 +15,9 @@ use DevHelp::HTML;
 use constant MAX_UNACKED_CONF_MSGS => 3;
 use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
 
+my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation disabled);
+my %donttouch = map { $_, $_ } @donttouch;
+
 sub user_tags {
   my ($self, $acts, $session, $user) = @_;
 
@@ -92,14 +95,14 @@ sub logon {
     or return $self->show_logon($session, $cgi, $cfg, 
                                $msgs->(needpass=>"Please enter your password"));
   my $user = SiteUsers->getBy(userId => $userid);
-  if ($user->{password} eq '') {
-    return $self->show_logon($session, $cgi, $cfg, 
-                            $msgs->(secretpass=>"Please use the URL from your confirmation message to change your details"));
-  }
   unless ($user && $user->{password} eq $password) {
     return $self->show_logon($session, $cgi, $cfg, 
                             $msgs->(baduserpass=>"Invalid user or password"));
   }
+  if ($user->{disabled}) {
+    return $self->show_logon($session, $cgi, $cfg,
+                            $msgs->(disableduser=>"Account $userid has been disabled"));
+  }
   $session->{userid} = $user->{userId};
   $user->{previousLogon} = $user->{lastLogon};
   $user->{lastLogon} = now_datetime;
@@ -252,6 +255,18 @@ sub tag_if_required {
 sub show_register {
   my ($self, $session, $cgi, $cfg, $message, $errors) = @_;
 
+  my $user_register = $cfg->entryBool('site users', 'user_register', 1);
+  my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
+  unless ($user_register) {
+    if ($nopassword) {
+      return $self->show_lost_password($session, $cgi, $cfg,
+                                      "Registration disabled");
+    }
+    else {
+      return $self->show_logon($session, $cgi, $cfg,
+                              "Registration disabled");
+    }
+  }
   $errors ||= {};
   $message ||= $cgi->param('message');
   if (defined $message) {
@@ -384,6 +399,54 @@ sub show_opts {
   BSE::Template->show_page($template, $cfg, \%acts, $base);
 }
 
+sub _checkemail {
+  my ($user, $errors, $email, $cgi, $msgs, $nopassword) = @_;
+
+  if (!$email) {
+    $errors->{email} = $msgs->(optsnoemail => "Please enter an email address");
+  }
+  elsif ($email !~  /.@./) {
+    $errors->{email} = $msgs->(optsbademail=>
+                              "Please enter a valid email address");
+  }
+  else {
+    if ($nopassword && $email ne $user->{email}) {
+      my $conf_email = $cgi->param('confirmemail');
+      if ($conf_email) {
+       if ($conf_email eq $email) {
+         my $other = SiteUsers->getBy(userId=>$email);
+         if ($other) {
+           $errors->{email} = 
+             $msgs->(optsdupemail =>
+                     "That email address is already in use");
+         }
+       }
+       else {
+         $errors->{confirmemail} = 
+           $msgs->(optsconfemailnw=>
+                   "Confirmation email address doesn't match email address");
+       }
+      }
+      else {
+       $errors->{confirmemail} = 
+         $msgs->(optsnoconfemail=> "Please enter a confirmation email address");
+      }
+      
+    }
+  }
+  if (!$errors->{email}) {
+    my $checkemail = _generic_email($email);
+    require 'BSE/EmailBlacklist.pm';
+    my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
+    if ($blackentry) {
+      $errors->{email} = 
+       $msgs->(optsblackemail => 
+               "Email $email is blacklisted: $blackentry->{why}",
+               $email, $blackentry->{why});
+    }
+  }
+}
+
 sub saveopts {
   my ($self, $session, $cgi, $cfg) = @_;
 
@@ -428,57 +491,18 @@ sub saveopts {
     }
   }
   my $email = $cgi->param('email');
-  if (!$email) {
-    $errors{email} = $msgs->(optsnoemail => "Please enter an email address");
-  }
-  elsif ($email !~  /.@./) {
-    $errors{email} = $msgs->(optsbademail=>
-                            "Please enter a valid email address");
-  }
-  else {
-    if ($nopassword && $email ne $user->{email}) {
-      my $conf_email = $cgi->param('confirmemail');
-      if ($conf_email) {
-       if ($conf_email eq $email) {
-         my $other = SiteUsers->getBy(userId=>$email);
-         if ($other) {
-           $errors{email} = 
-             $msgs->(optsdupemail =>
-                     "That email address is already in use");
-         }
-       }
-       else {
-         $errors{confirmemail} = 
-           $msgs->(optsconfemailnw=>
-                   "Confirmation email address doesn't match email address");
-       }
-      }
-      else {
-       $errors{confirmemail} = $msgs->(optsnoconfemail=>
-                                       "Please enter a confirmation email address");
-      }
-      
-    }
+  my $saveemail;
+  if (defined $email) {
+    ++$saveemail;
+    _checkemail($user, \%errors, $email, $cgi, $msgs, $nopassword);
   }
 
-  if (!$errors{email}) {
-    my $checkemail = _generic_email($email);
-    require 'BSE/EmailBlacklist.pm';
-    my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
-    if ($blackentry) {
-      $errors{email} = $msgs->(optsblackemail => 
-                              "Email $email is blacklisted: $blackentry->{why}",
-                              $email, $blackentry->{why});
-    }
-  }
       
-  my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation);
-  my %donttouch = map { $_, $_ } @donttouch;
   my @cols = grep !$donttouch{$_}, SiteUser->columns;
   for my $col (@cols) {
     my $value = $cgi->param($col);
     if ($cfg->entryBool('site users', "require_$col")) {
-      unless (defined $value && $value ne '') {
+      if (defined $value && $value eq '') {
        my $disp = $cfg->entry('site users', "display_$col", "\u$col");
        $errors{$col} = $msgs->("optsrequired" => 
                                "$disp is a required field", $col, $disp);
@@ -488,7 +512,7 @@ sub saveopts {
   keys %errors
     and return $self->show_opts($session, $cgi, $cfg, undef, \%errors);
   my $newemail;
-  if ($email ne $user->{email}) {
+  if ($saveemail && $email ne $user->{email}) {
     $user->{confirmed} = 0;
     $user->{confirmSecret} = '';
     $user->{email} = $email;
@@ -504,15 +528,23 @@ sub saveopts {
     }
   }
 
-  $user->{textOnlyMail} = 0 unless defined $cgi->param('textOnlyMail');
-  $user->{keepAddress} = 0 unless defined $cgi->param('keepAddress');
+  $user->{textOnlyMail} = 0 
+    if $cgi->param('saveTextOnlyMail') && !defined $cgi->param('textOnlyMail');
+  $user->{keepAddress} = 0 
+    if $cgi->param('saveKeepAddress') && !defined $cgi->param('keepAddress');
   $user->save;
 
   # subscriptions
-  return if $self->_save_subs($user, $session, $cfg, $cgi, 1);
-
-  if ($newemail && $nopassword) {
-    return $self->send_conf_request($session, $cgi, $cfg, $user);
+  if ($cgi->param('saveSubscriptions')) {
+    my $subs = $self->_save_subs($user, $session, $cfg, $cgi);
+    if ($nopassword) {
+      return $self->send_conf_request($session, $cgi, $cfg, $user)
+       if $newemail;
+    }
+    else {
+      return $self->send_conf_request($session, $cgi, $cfg, $user)
+       if $subs && !$user->{confirmed};
+    }
   }
 
   my $url = $cgi->param('r');
@@ -533,7 +565,7 @@ sub saveopts {
 
 # returns true if the caller needs to send output
 sub _save_subs {
-  my ($self, $user, $session, $cfg, $cgi, $sendconf, $suppress_success) = @_;
+  my ($self, $user, $session, $cfg, $cgi) = @_;
 
   my @subids = $cgi->param('subscription');
   $user->removeSubscriptions;
@@ -542,10 +574,12 @@ sub _save_subs {
     my @subs;
     my @cols = BSE::SubscribedUser->columns;
     shift @cols; # don't set id
+    my $found = 0;
     for my $subid (@subids) {
       $subid =~ /^\d+$/ or next;
       my $sub = BSE::SubscriptionTypes->getByPkey($subid)
        or next;
+      ++$found;
       my %usersub;
       $usersub{subId} = $subid;
       $usersub{userId} = $user->{id};
@@ -553,11 +587,9 @@ sub _save_subs {
       push(@usersubs, BSE::SubscribedUsers->add(@usersub{@cols}));
       push(@subs, $sub);
     }
-    if ($sendconf && !$user->{confirmed}) {
-      return $self->send_conf_request($session, $cgi, $cfg, $user, $suppress_success);
-    }
+    return $found;
   }
-  1;
+  return 0;
 }
 
 sub register {
@@ -565,6 +597,18 @@ sub register {
 
   my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
 
+  my $user_register = $cfg->entryBool('site users', 'user_register', 1);
+  my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
+  unless ($user_register) {
+    my $msg = $msgs->(regdisabled => "Registration disabled");
+    if ($nopassword) {
+      return $self->show_lost_password($session, $cgi, $cfg, $msg);
+    }
+    else {
+      return $self->show_logon($session, $cgi, $cfg, $msg);
+    }
+  }
+
   my %user;
   my @cols = SiteUser->columns;
   shift @cols;
@@ -572,7 +616,6 @@ sub register {
     $user{$field} = '';
   }
 
-  my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
   my %errors;
   my $email = $cgi->param('email');
   if (!defined $email or !length $email) {
@@ -652,8 +695,6 @@ sub register {
     }
   }
 
-  my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation);
-  my %donttouch = map { $_, $_ } @donttouch;
   my @mod_cols = grep !$donttouch{$_}, @cols;
   for my $col (@mod_cols) {
     my $value = $cgi->param($col);
@@ -693,13 +734,12 @@ sub register {
                                          -value=>$user->{userId},
                                          -path=>"/"),"\n";
     $session->{userid} = $user->{userId} unless $nopassword;
+    my $subs = $self->_save_subs($user, $session, $cfg, $cgi);
     if ($nopassword) {
-      $self->_save_subs($user, $session, $cfg, $cgi, 0);
-      # send the confirmation immediately
       return $self->send_conf_request($session, $cgi, $cfg, $user);
     }
-    else {
-      return unless $self->_save_subs($user, $session, $cfg, $cgi, 1, 1);
+    elsif ($subs) {
+      return if $self->send_conf_request($session, $cgi, $cfg, $user, 1);
     }
     use Util qw/refresh_to/;
     
@@ -1067,6 +1107,7 @@ sub confirm {
 }
 
 sub _generic_email {
+#  SiteUser->generic_email(shift);
   my ($checkemail) = @_;
 
   # Build a generic form for the email - since an attacker could
@@ -1082,6 +1123,7 @@ sub _generic_email {
   $checkemail;
 }
 
+# returns non-zero iff a page was generated
 sub send_conf_request {
   my ($self, $session, $cgi, $cfg, $user, $suppress_success) = @_;
 
@@ -1106,7 +1148,7 @@ sub send_conf_request {
   unless ($from) {
     $acts{mailerror} = sub { escape_html("Configuration Error: The confirmations from address has not been configured") };
     BSE::Template->show_page('user/email_conferror', $cfg, \%acts);
-    return;
+    return 1;
   }
 
   my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
@@ -1114,7 +1156,7 @@ sub send_conf_request {
   if ($blackentry) {
     $acts{black} = sub { CGI::escapeHTML($blackentry->{$_[0]}) },
     BSE::Template->show_page('user/blacklisted', $cfg, \%acts);
-    return;
+    return 1;
   }
   
   unless ($user->{confirmSecret}) {
@@ -1137,11 +1179,11 @@ sub send_conf_request {
     # check how many
     if ($too_many) {
       BSE::Template->show_page('user/toomany', $cfg, \%acts);
-      return;
+      return 1;
     }
     if ($too_soon) {
       BSE::Template->show_page('user/toosoon', $cfg, \%acts);
-      return;
+      return 1;
     }
   }
   else {
@@ -1183,7 +1225,7 @@ sub send_conf_request {
   ++$confirm->{unackedConfMsgs};
   $confirm->{lastConfSent} = now_datetime;
   $confirm->save;
-  return 1 if $suppress_success;
+  return 0 if $suppress_success;
   BSE::Template->show_page($nopassword ? 'user/confsent_nop' : 'user/confsent', $cfg, \%acts);
 
   return 1;
diff --git a/site/cgi-bin/modules/BSE/Util/Iterate.pm b/site/cgi-bin/modules/BSE/Util/Iterate.pm
new file mode 100644 (file)
index 0000000..3e8265b
--- /dev/null
@@ -0,0 +1,10 @@
+package BSE::Util::Iterate;
+use strict;
+use base 'DevHelp::Tags::Iterate';
+use DevHelp::HTML;
+
+sub escape {
+  escape_html($_[1]);
+}
+
+1;
index 1394e3b..c161ae3 100644 (file)
@@ -4,7 +4,7 @@ use HTML::Entities;
 use DevHelp::Tags;
 use DevHelp::HTML;
 use vars qw(@EXPORT_OK @ISA);
-@EXPORT_OK = qw(tag_error_img);
+@EXPORT_OK = qw(tag_error_img tag_hash);
 @ISA = qw(Exporter);
 require Exporter;
 
@@ -253,6 +253,8 @@ sub tag_old {
 sub basic {
   my ($class, $acts, $cgi, $cfg) = @_;
 
+  require BSE::Util::Iterate;
+  my $it = BSE::Util::Iterate->new;
   return
     (
      $class->static($acts, $cfg),
@@ -267,6 +269,7 @@ sub basic {
        escape_html("@value");
      },
      old => [ \&tag_old, $cgi ],
+     $it->make_iterator(\&DevHelp::Tags::iter_get_repeat, 'repeat', 'repeats'),
     );
 }
 
@@ -557,5 +560,14 @@ sub tag_replace {
   $str;
 }
 
+sub tag_hash {
+  my ($hash, $args) = @_;
+
+  my $value = $hash->{$args};
+  defined $value or $value = '';
+
+  escape_html($value);
+}
+
 1;
 
diff --git a/site/cgi-bin/modules/DevHelp/DynSort.pm b/site/cgi-bin/modules/DevHelp/DynSort.pm
new file mode 100644 (file)
index 0000000..630a9f6
--- /dev/null
@@ -0,0 +1,155 @@
+package DevHelp::DynSort;
+use strict;
+use vars qw(@EXPORT_OK);
+use base 'Exporter';
+@EXPORT_OK = qw(sorter tag_sorthelp);
+use Carp 'confess';
+
+sub sorter {
+  my (%opts) = @_;
+  
+  my $cgi = $opts{cgi}
+    or confess "No cgi object supplied.";
+  my $data = $opts{data}
+    or confess "No data supplied";
+  my $fields = $opts{fields} || {};
+  my $def_sortby = $opts{sortby};
+  my $def_reverse = $opts{reverse};
+  my $sortby_param = $opts{sortparam} || 's';
+  my $tie_field = $opts{tiefield};
+  my $reverse_param = $opts{reverseparam} || 'r';
+
+  my $sortby = $cgi->param($sortby_param);
+  defined $sortby && $sortby =~ /^\w+$/ or $sortby = $def_sortby;
+  my $reverse = $cgi->param($reverse_param);
+  defined $reverse or $reverse = $def_reverse;
+  $reverse = $reverse ? 1 : 0; # make sure it's a number
+
+  if (@$data && $sortby) {
+    exists $data->[0]{$sortby} or $sortby = $def_sortby;
+    exists $data->[0]{$sortby} or $sortby = '';
+    if (defined $sortby && $sortby ne '') {
+      my $tie_numeric;
+      if (defined $tie_field) {
+       exists $data->[0]{$tie_field}
+         or confess "No tie breaker field $tie_field in data";
+       $tie_numeric = 
+         $fields && exists $fields->{$tie_field} && $fields->{$tie_field}{numeric};
+      }
+      
+      my $numeric = 
+       $fields && exists $fields->{$sortby} && $fields->{$sortby}{numeric};
+      @$data = sort
+       {
+         my $left = $a->{$sortby};
+         defined $left or $left = '';
+         my $right = $b->{$sortby};
+         defined $right or $right = '';
+         my $cmp = $numeric ? $left <=> $right : lc $left cmp lc $right;
+         if (!$cmp && $tie_field) {
+           $cmp = $tie_numeric 
+             ? $a->{$tie_field} <=> $b->{$tie_field}
+               : lc $a->{$tie_field} cmp lc $b->{$tie_field};  
+         }
+         $cmp = -$cmp if $reverse;
+         $cmp;
+       } @$data;
+    }
+  }
+
+  return ($sortby, $reverse);
+}
+
+sub tag_sorthelp {
+  my ($sortby, $reverse, $args) = @_;
+
+  if ($args eq $sortby) {
+    my $rev = $reverse ? 0 : 1;
+    return "s=$args&r=$rev";
+  }
+  else {
+    return "s=$args&r=0";
+  }
+}
+
+1;
+
+=head1 NAME
+
+  DevHelp::DynSort - sort listings at run-time.
+
+=head1 SYNOPSIS
+
+  my @data = ....; # array of hashes
+  my ($sortby, $reverse) =
+     sorter(data=>\@data, cgi=>$cgi, ...),
+
+  my %acts;
+  %acts = 
+    (
+     ...
+     sortby => $sortby,
+     reverse => $reverse,
+    );
+
+=head1 DESCRIPTION
+
+Intended for use in sorting supplied data based on user criteria.
+
+=head2 Required Parameters
+
+=over
+
+=item data
+
+An array ref of hashes.  This is sorted in place.
+
+=item cgi
+
+A CGI.pm compatible CGI object.  Only the param() method is called.
+
+=back
+
+=head2 Optional Parameters
+
+=over
+
+=item fields
+
+Hash of hashes, where the key at the top level is the field name, and
+the value configuration information for that field.
+
+The only field in the configuration information is 'numeric' which
+marks the field for numeric sorting if non-zero.
+
+=item sortby
+
+Default field to sort by.  No default (if no sort order is provided by
+the user and this value isn't set, then the data isn't sorted.)
+
+=item reverse
+
+Default reversal.  Default: zero (ascending order).
+
+=item sortparam
+
+The name of the CGI parameter to get the sort order field from.
+Default 's'.
+
+=item reverseparam
+
+The name of the CGI parameter to get the reversal from.  Default 'r'.
+
+=item tiefield
+
+Name of the field to use for breaking ties in the sort order.
+Default: ties aren't broken.  If this is set to a field name then that
+field will be used to break ties in sorting by the primary sort field.
+
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=cut
index 0ee5b65..045cbcd 100644 (file)
@@ -250,6 +250,8 @@ sub tag_date {
 sub iter_get_repeat {
   my ($args, $acts, $name, $templater) = @_;
 
+  print STDERR "iter_get_repeat $args\n";
+
   my @args = __PACKAGE__->get_parms($args, $acts, $templater);
 
   my @values;
diff --git a/site/cgi-bin/modules/DevHelp/Tags/Iterate.pm b/site/cgi-bin/modules/DevHelp/Tags/Iterate.pm
new file mode 100644 (file)
index 0000000..6ec4027
--- /dev/null
@@ -0,0 +1,206 @@
+package DevHelp::Tags::Iterate;
+use strict;
+use Carp qw(confess);
+
+sub new {
+  my ($class, %opts) = @_;
+
+  return bless \%opts, $class;
+}
+
+sub escape {
+  return $_[1];
+}
+
+sub _iter_reset_paged {
+  my ($self, $rdata, $rindex) = @_;
+
+  $$rindex = -1;
+
+  1;
+}
+
+sub _iter_iterate {
+  my ($self, $rdata, $rindex) = @_;
+
+  return ++$$rindex < @$rdata;
+}
+
+sub _iter_item {
+  my ($self, $rdata, $rindex, $single, $plural, $args) = @_;
+
+  $$rindex >= 0 && $$rindex < @$rdata
+    or return "** $single should only be used inside iterator $plural **";
+  return $self->escape($rdata->[$$rindex]{$args});
+}
+
+sub _iter_number_paged {
+  my ($self, $rindex, $baseindex) = @_;
+
+  $$rindex + $baseindex + 1;
+}
+
+sub _iter_reset_page_counter {
+  my ($self, $rpage) = @_;
+
+  $$rpage = 1;
+}
+
+sub _iter_iterate_page_counter {
+  my ($self, $rpage, $page_count) = @_;
+
+  ++$$rpage <= $page_count;
+}
+
+sub _iter_page_counter {
+  my ($self, $rpage) = @_;
+
+  $$rpage;
+}
+
+sub _iter_index {
+  my ($self, $rindex) = @_;
+
+  $$rindex;
+}
+
+sub _iter_if_first {
+  my ($self, $rindex) = @_;
+
+  $$rindex == 0;
+}
+
+sub _iter_if_last {
+  my ($self, $rdata, $rindex) = @_;
+
+  $$rindex == $#$rdata;
+}
+
+sub make_paged_iterator {
+  my ($self, $single, $plural, $rdata, $rindex, $cgi, $pagename,
+      $perpage) = @_;
+
+  my $index;
+  defined $rindex or $rindex = \$index;
+  $$rindex = -1;
+  $rdata or die;
+  my $loaded = 0;
+  my $max;
+  unless ($perpage =~ /^\d+$/) {
+    my ($name, $count) = $perpage =~ /^(\w+)=(\d+)$/
+      or confess "Invalid perpage '$perpage'";
+    $name ||= 'pp';
+    $count ||= 10;
+    my $work = $cgi->param($name);
+    if (defined $work && $work =~ /^\d+$/ && $work >= 1 && $work <= 1000) {
+      $perpage = $work;
+    }
+    else {
+      $perpage = $count;
+    }
+  }
+  my $page_count = int((@$rdata + $perpage - 1) / $perpage);
+  $page_count = 1 unless $page_count;
+  $pagename ||= 'p';
+  my $page_num = $cgi->param($pagename);
+  unless (defined($page_num) && $page_num =~ /^\d+$/
+         && $page_num >= 1 && $page_num <= $page_count) {
+    $page_num = 1;
+  }
+  my $base_index = $perpage * ($page_num - 1);
+  my $end_index = $base_index + $perpage - 1;
+  $end_index <= $#$rdata or $end_index = $#$rdata;
+  my @data;
+  @data = @$rdata[$base_index .. $end_index] if @$rdata;
+
+  my $page_counter;
+
+  return
+    (
+     "iterate_${plural}_reset" => 
+     [ _iter_reset_paged=>$self, \@data, $rindex ],
+     "iterate_${plural}" =>
+     [ _iter_iterate=>$self, \@data, $rindex, $single ],
+     $single => [ _iter_item => $self, \@data, $rindex, $single, $plural ],
+     "if\u$plural" => scalar(@data),
+     "${single}_index" => [ _iter_index=>$self, $rindex ],
+     "${single}_number" => [ _iter_number_paged=>$self, $rindex, $base_index ],
+     "ifLast\u$single" => [ _iter_if_last=>$self, \@data, $rindex ],
+     "ifFirst\u$single" => [ _iter_if_first=>$self, $rindex ],
+     "ifNext\u${plural}\EPage" => $page_num < $page_count,
+     "ifPrev\u${plural}\EPage" => $page_num > 1,
+     "ifFirst\u${plural}\EPage" => $page_num == 1,
+     "ifLast\u${plural}\EPage" => $page_num == $page_count,
+     "next\u${plural}\EPage" => 
+     ( $page_num < $page_count ? $page_num + 1 : $page_num ),
+     "prev\u${plural}\EPage" =>
+     ( $page_num > 1 ? $page_num - 1 : 1 ),
+     "${single}_count" => scalar(@data),
+     "${plural}_pagenum" => $page_num,
+     "${plural}_pagecount" => $page_count,
+     "${single}_totalcount" => scalar(@$rdata),
+     "${plural}_firstnumber" => $base_index + 1,
+     "${plural}_lastnumber" => $end_index + 1,
+     "iterate_${single}_pages_reset" => 
+     [ _iter_reset_page_counter=>$self, \$page_counter ],
+     "iterate_${single}_pages" =>
+     [ _iter_iterate_page_counter=>$self, \$page_counter, $page_count ],
+     "${single}_pagecounter" =>
+     [ _iter_page_counter=>$self, \$page_counter ],
+     "${plural}_perpage" => $perpage,
+    );
+}
+
+sub _iter_reset {
+  my ($self, $rdata, $rindex, $code, $loaded, $nocache, 
+      $args, $acts, $name, $templater) = @_;
+
+  if (!$$loaded && !@$rdata && $code || $args || $nocache) {
+    my ($sub, @args) = $code;
+
+    if (ref $code eq 'ARRAY') {
+      ($sub, @args) = @$code;
+    }
+    @$rdata = $sub->(@args, $args, $acts, $name, $templater);
+    ++$$loaded unless $args;
+  }
+
+  $$rindex = -1;
+
+  1;
+}
+
+sub _iter_number {
+  my ($self, $rindex) = @_;
+
+  1+$$rindex;
+}
+
+sub make_iterator {
+  my ($self, $code, $single, $plural, $rdata, $rindex, $nocache) = @_;
+
+  my $index;
+  defined $rindex or $rindex = \$index;
+  $$rindex = -1;
+  $rdata ||= [];
+  my $loaded = 0;
+  return
+    (
+     "iterate_${plural}_reset" => 
+     [ _iter_reset=>$self, $rdata, $rindex, $code, \$loaded, $nocache ],
+     "iterate_${plural}" =>
+     [ _iter_iterate=>$self, $rdata, $rindex, $nocache ],
+     $single => 
+     [ _iter_item=>$self, $rdata, $rindex, $single, $plural ],
+     "${single}_index" => [ _iter_index=>$self, $rindex ],
+     "${single}_number" => [ _iter_number =>$self, $rindex ],
+     "if\u$plural" => 
+     [ _iter_count=>$self, $rdata, $code, \$loaded, $nocache ],
+     "${single}_count" => 
+     [ _iter_count=>$self, $rdata, $code, \$loaded, $nocache ],
+     "ifLast\u$single" => [ _iter_if_last=>$self, $rdata, $rindex ],
+     "ifFirst\u$single" => [ _iter_if_first=>$self, $rindex ],
+    );
+}
+
+1;
index 0b0c471..48923bb 100644 (file)
@@ -17,7 +17,8 @@ sub columns {
            filled whenFilled whoFilled paidFor paymentReceipt
            randomId cancelled userId paymentType
            customInt1 customInt2 customInt3 customInt4 customInt5
-           customStr1 customStr2 customStr3 customStr4 customStr5/;
+           customStr1 customStr2 customStr3 customStr4 customStr5
+           instructions billTelephone billFacsimile billEmail/;
 }
 
 1;
index af304a2..22d2c8b 100644 (file)
@@ -4,13 +4,21 @@ use strict;
 use Squirrel::Row;
 use vars qw/@ISA/;
 @ISA = qw/Squirrel::Row/;
+use Constants qw($SHOP_FROM);
+use BSE::Util::SQL qw/now_datetime/;
+
+use constant MAX_UNACKED_CONF_MSGS => 3;
+use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
 
 sub columns {
   return qw/id userId password email keepAddress whenRegistered lastLogon
             name1 name2 address city state postcode telephone facsimile 
             country wantLetter confirmed confirmSecret waitingForConfirmation
             textOnlyMail title organization referral otherReferral
-            prompt otherPrompt profession otherProfession previousLogon/;
+            prompt otherPrompt profession otherProfession previousLogon
+            billFirstName billLastName billStreet billSuburb billState 
+            billPostCode billCountry instructions billTelephone billFacsimile 
+            billEmail adminNotes disabled/;
 }
 
 sub removeSubscriptions {
@@ -25,4 +33,121 @@ sub removeSubscription {
   SiteUsers->doSpecial('removeSub', $self->{id}, $subid);
 }
 
+sub generic_email {
+  my ($class, $checkemail) = @_;
+
+  # Build a generic form for the email - since an attacker could
+  # include comments or extra spaces or a bunch of other stuff.
+  # this isn't strictly correct, but it's good enough
+  1 while $checkemail =~ s/\([^)]\)//g;
+  if ($checkemail =~ /<([^>]+)>/) {
+    $checkemail = $1;
+  }
+  $checkemail = lc $checkemail;
+  $checkemail =~ s/\s+//g;
+
+  $checkemail;
+}
+
+sub subscriptions {
+  my ($self) = @_;
+
+  require BSE::SubscriptionTypes;
+  return BSE::SubscriptionTypes->getSpecial(userSubscribedTo => $self->{id});
+}
+
+sub send_conf_request {
+  my ($user, $cgi, $cfg, $rcode, $rmsg) = @_;
+      
+  my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
+  
+  # check for existing in-progress confirmations
+  my $checkemail = $user->generic_email($user->{email});
+  
+  # check the blacklist
+  require BSE::EmailBlacklist;
+
+  # check that the from address has been configured
+  my $from = $cfg->entry('confirmations', 'from') || 
+    $cfg->entry('basic', 'emailfrom')|| $SHOP_FROM;
+  unless ($from) {
+    $$rcode = 'config';
+    $$rmsg = "Configuration Error: The confirmations from address has not been configured";
+    return;
+  }
+
+  my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
+
+  if ($blackentry) {
+    $$rcode = "blacklist";
+    $$rmsg = $blackentry->{why};
+    return;
+  }
+  
+  unless ($user->{confirmSecret}) {
+    use BSE::Util::Secure qw/make_secret/;
+    # print STDERR "Generating secret\n";
+    $user->{confirmSecret} = make_secret($cfg);
+    $user->save;
+  }
+
+  # check for existing confirmations
+  my $confirm = BSE::EmailRequests->getBy(genEmail=>$checkemail);
+  if ($confirm) {
+    if ($confirm->{unackedConfMsgs} >= MAX_UNACKED_CONF_MSGS) {
+      $$rcode = 'toomany';
+      $$rmsg = "Too many confirmations have been sent to this email address";
+      return;
+    }
+    use BSE::Util::SQL qw/sql_datetime_to_epoch/;
+    my $lastSentEpoch = sql_datetime_to_epoch($confirm->{lastConfSent});
+    if ($lastSentEpoch + MIN_UNACKED_CONF_GAP > time) {
+      $$rcode = 'toosoon';
+      $$rmsg = "The last confirmation was sent too recently, please wait before trying again";
+      return;
+    }
+  }
+  else {
+    my %confirm;
+    my @cols = BSE::EmailRequest->columns;
+    shift @cols;
+    $confirm{email} = $user->{email};
+    $confirm{genEmail} = $checkemail;
+    # prevents silliness on error
+    use BSE::Util::SQL qw(sql_datetime);
+    $confirm{lastConfSent} = sql_datetime(time - MIN_UNACKED_CONF_GAP);
+    $confirm{unackedConfMsgs} = 0;
+    $confirm = BSE::EmailRequests->add(@confirm{@cols});
+  }
+
+  # ok, now we can send the confirmation request
+  my %confacts;
+  %confacts =
+    (
+     BSE::Util::Tags->basic(\%confacts, $cgi, $cfg),
+     user => sub { $user->{$_[0]} },
+     confirm => sub { $confirm->{$_[0]} },
+     remote_addr => sub { $ENV{REMOTE_ADDR} },
+    );
+  my $email_template = 
+    $nopassword ? 'user/email_confirm_nop' : 'user/email_confirm';
+  my $body = BSE::Template->get_page($email_template, $cfg, \%confacts);
+  
+  my $mail = BSE::Mail->new(cfg=>$cfg);
+  my $subject = $cfg->entry('confirmations', 'subject') 
+    || 'Subscription Confirmation';
+  unless ($mail->send(from=>$from, to=>$user->{email}, subject=>$subject,
+                     body=>$body)) {
+    # a problem sending the mail
+    $$rcode = "mail";
+    $$rmsg = $mail->errstr;
+    return;
+  }
+  ++$confirm->{unackedConfMsgs};
+  $confirm->{lastConfSent} = now_datetime;
+  $confirm->save;
+
+  return 1;
+}
+
 1;
index b293de8..1fb8eb7 100644 (file)
@@ -32,7 +32,14 @@ sub low_perform {
       }
       elsif (ref $action eq 'ARRAY') {
        my ($code, @params) = @$action;
-       $value = $code->(@params, $args, $acts, $func, $self);
+       if (ref $code) {
+         $value = $code->(@params, $args, $acts, $func, $self);
+       }
+       else {
+         # assume it's a method name, first param is the object/class
+         my $obj = shift @params;
+         $value = $obj->$code(@params, $args, $acts, $func, $self);
+       }
       }
       elsif (ref $action eq 'SCALAR') {
        $value = $$action;
@@ -126,9 +133,20 @@ sub iterate {
       $entryf = $entry;
     }
 
-    $resetf->(@rargs, $args, $acts, $name, $self) if $resetf;
+    if ($resetf) {
+      if (ref $resetf) {
+       $resetf->(@rargs, $args, $acts, $name, $self);
+      }
+      else {
+       my $obj = shift @rargs;
+       $obj->$resetf(@rargs, $args, $acts, $name, $self);
+      }
+    }
+    my $eobj;
+    ref $entryf or $eobj = shift @eargs;
     my $result = '';
-    while ($entryf->(@eargs, $name, $args)) {
+    while ($eobj ? $eobj->$entryf(@eargs, $name, $args) 
+          : $entryf->(@eargs, $name, $args)) {
       $result .= $self->replace_template($sep, $acts) if length $result;
       $result .= $self->replace_template($input, $acts);
     }
index 061751e..1dd4918 100644 (file)
@@ -10,6 +10,73 @@ Maybe I'll add some other bits here.
 
 =head1 CHANGES
 
+This is a development release and is not suitable for production use.
+
+=head2 0.14_03
+
+=over
+
+=item *
+
+in some cases registration or saving user options wasn't outputting a
+page, producing a 500 error
+
+=item *
+
+user options templating changes:
+
+=over
+
+=item *
+
+for the textOnlyEmail option to be saved, a saveTextOnlyMail hidden
+field should be present with a non-zero value
+
+=item *
+
+for the keepAddress option to be saved, a saveKeepAddress hidden field
+should be present with a non-zero value
+
+=item *
+
+for the subscriptions to be saves, a saveSubscriptions hidden field
+should be present
+
+=back
+
+=item *
+
+added site user manager, this still needs some template work
+
+=item *
+
+user registration can be disabled with the C<user_register> option in
+the C<[site users]> section of the config file (you can still create
+users using the site user manager.)
+
+=item *
+
+billing information fields have been added to the site_users table.
+
+=item *
+
+adminNotes field has been added to the site users table (editable only
+by admins.)
+
+=item *
+
+instructions field has been added to the orders table, for shipping
+instructions (or any).  This still needs some support on the admin
+templates, and the email templates.
+
+=item *
+
+added config option C<billing_on_main_opts> to control whether billing
+options are on the main user options page.  This is managed by the
+templates, not the code.
+
+=back
+
 =head2 0.14_02
 
 =over
index 550698c..743c7cf 100644 (file)
@@ -741,6 +741,16 @@ and follow the link in the confirmation email.
 You can get the I<id> of a subcription by looking at the Edit link on the
 subscriptions management page, the number after "id=" is the id.
 
+=item billing_on_main_opts
+
+If set to zero then user billing options will be managed on a separate
+page.  This is controlled by the user/options_base.tmpl template.
+
+=item user_register
+
+If set to zero then users cannot register themselves.  Default: true,
+allowing users to register themselves.
+
 =back
 
 =head1 AUTHOR
index 3247b33..f147bcb 100644 (file)
@@ -6,3 +6,6 @@ input {  font-family: "MS Sans Serif", Verdana, sans-serif; font-size: 12px}
 textarea {  font-family: "MS Sans Serif", Verdana, sans-serif; font-size: 12px}
 .mini-admin { font-family: "MS Sans Serif", Verdana, sans-serif; font-size: 10px}
 .user-buttons { font-family: "MS Sans Serif", Verdana, sans-serif; font-size: 12px ; border: 2px #FF7F00 groove; background-color: #FFFFFF}
+.useroptions th { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: x-small; font-weight: bold; white-space: nowrap; text-align: left }
+.useroptions td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: x-small }
+.useroptions .thead { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: large; font-weight: bold; white-space: nowrap; text-align: center }
diff --git a/site/templates/admin/users/add.tmpl b/site/templates/admin/users/add.tmpl
new file mode 100644 (file)
index 0000000..57dfff7
--- /dev/null
@@ -0,0 +1,229 @@
+<:wrap admin/xbase.tmpl title=>"Add Site User":>
+<h1>Add Site User</h1>
+<p>
+| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |
+<a href="/cgi-bin/admin/siteusers.pl">Site users</a> |
+<a href="mailto:<:siteuser email:>">Email</a> |</p>
+
+<:ifMessage:>
+<p><b><:message:></b></p>
+<:or:><:eif:> 
+
+<form method="POST" action="<:script:>">
+<input type="hidden" name="id" value="<:siteuser id:>" />
+  <table border="0" cellspacing="0" cellpadding="0" bgcolor="#000000" class="table">
+    <tr> 
+      <td> 
+        <table cellpadding="6" border="0" cellspacing="1">
+<:ifCfg "site users" nopassword:><:or:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Logon: </th>
+            <td bgcolor="#FFFFFF"> 
+             <input type="text" name="userId" value="<:old userId:>" />*
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser userId:> <:error_img userId:></td>
+          </tr>
+<:eif:>
+<:if Cfg "site users" nopassword:><:or Cfg:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left"> Password: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="password" name="password" />*
+              </td>
+            <td bgcolor="#FFFFFF"> <:help editsiteuser password:>  <:error_img password:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left"> Confirm: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="password" name="confirm_password" />*
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser confirm_password:> <:error_img confirm_password:> </td>
+          </tr>
+<:eif Cfg:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Email: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="email" value="<:old email :>" size="60" />*
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser email:>  <:error_img email:></td>
+          </tr>
+<:ifCfg "site users" nopassword:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Confirm Email: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="confirmemail" value="<:old confirmemail:>" size="60" />*
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser confirmemail:>  <:error_img confirmemail:></td>
+          </tr>
+<:or:><:eif:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">First Name: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="name1" value="<:old name1 :>" size="60" /><:ifRequired name1:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser name1:>  <:error_img name1:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Last Name: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="name2" value="<:old name2 :>" size="60" /><:ifRequired name2:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser name2:>  <:error_img name2:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">address: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="address" value="<:old address:>" size="60" /><:ifRequired address:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser address:>  <:error_img address:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">city: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="city" value="<:old city :>" /><:ifRequired city:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser city:>  <:error_img city:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">state: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="state" value="<:old state:>" /><:ifRequired state:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser state:>  <:error_img state:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">postcode: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="postcode" value="<:old postcode:>" /><:ifRequired postcode:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser postcode:>  <:error_img postcode:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">telephone: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="telephone" value="<:old telephone:>" /><:ifRequired telephone:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser telephone:>  <:error_img telephone:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">facsimile: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="facsimile" value="<:old facsimile:>" /><:ifRequired facsimile:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser facsimile:>  <:error_img facsimile:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">country: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="country" value="<:old country :>" size="60" /><:ifRequired country:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser country:>  <:error_img country:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">title: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="title" value="<:old title :>" /><:ifRequired title:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser title:>  <:error_img title:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">organization: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="organization" value="<:old organization:>" size="60" /><:ifRequired organization:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser organization:>  <:error_img organization:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billFirstName: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billFirstName" value="<:old billFirstName:>" size="60" /><:ifRequired billFirstName:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billFirstName:>  <:error_img billFirstName:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billLastName: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billLastName" value="<:old billLastName :>" size="60" /><:ifRequired billLastName:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billLastName:>  <:error_img billLastName:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billStreet: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billStreet" value="<:old billStreet :>" size="60" /><:ifRequired billStreet:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billStreet:>  <:error_img billStreet:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billSuburb: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billSuburb" value="<:old billSuburb :>" /><:ifRequired billSuburb:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billSuburb:>  <:error_img billSuburb:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billState: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billState" value="<:old billState :>" /><:ifRequired billState:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billState:>  <:error_img billState:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billPostCode: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billPostCode" value="<:old billPostCode:>" /><:ifRequired billPostCode:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billPostCode:>  <:error_img billPostCode:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billCountry: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billCountry" value="<:old billCountry :>" /><:ifRequired billCountry:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billCountry:>  <:error_img billCountry:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">instructions: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="instructions" value="<:old instructions:>" /><:ifRequired instructions:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser instructions:>  <:error_img instructions:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billTelephone: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billTelephone" value="<:old billTelephone:>" /><:ifRequired billTelephone:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billTelephone:>  <:error_img billTelephone:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billFacsimile: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billFacsimile" value="<:old billFacsimile :>" /><:ifRequired billFacsimile:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billFacsimile:>  <:error_img billFacsimile:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billEmail: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billEmail" value="<:old billEmail :>" /><:ifRequired billEmail:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billEmail:>  <:error_img billEmail:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left" valign="top">Admin Notes: </th>
+            <td bgcolor="#FFFFFF"> 
+              <textarea name="adminNotes" wrap="soft" rows="5" cols="60"><:old adminNotes :></textarea>
+            </td>
+            <td bgcolor="#FFFFFF" valign="top"><:help editsiteuser adminNotes:>  <:error_img adminNotes:></td>
+          </tr>
+          <tr> 
+            <td bgcolor="#FFFFFF" colspan="3" align="right"> 
+              <input type="submit" name="a_add" value="  Add User  " />
+            </td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</form>
diff --git a/site/templates/admin/users/edit.tmpl b/site/templates/admin/users/edit.tmpl
new file mode 100644 (file)
index 0000000..da0f27e
--- /dev/null
@@ -0,0 +1,238 @@
+<:wrap admin/xbase.tmpl title=>"Edit Site User":>
+<h1>Edit Site User</h1>
+<p>
+| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |
+<a href="/cgi-bin/admin/siteusers.pl">Site users</a> |
+<a href="mailto:<:siteuser email:>">Email</a> |</p>
+
+<:ifMessage:>
+<p><b><:message:></b></p>
+<:or:><:eif:> 
+
+<form method="POST" action="<:script:>">
+<input type="hidden" name="id" value="<:siteuser id:>" />
+  <table border="0" cellspacing="0" cellpadding="0" bgcolor="#000000" class="table">
+    <tr> 
+      <td> 
+        <table cellpadding="6" border="0" cellspacing="1">
+<:ifCfg "site users" nopassword:><:or:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Logon: </th>
+            <td bgcolor="#FFFFFF"> 
+             <:siteuser userId:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser userId:> <:error_img userId:></td>
+          </tr>
+<:eif:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Email: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="email" value="<:old email siteuser email:>" size="60" />
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser email:>  <:error_img email:></td>
+          </tr>
+<:ifCfg "site users" nopassword:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Confirm Email: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="confirmemail" value="<:old confirmemail:>" size="60" />
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser confirmemail:>  <:error_img confirmemail:></td>
+          </tr>
+<:or:><:eif:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Disabled: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="checkbox" name="disabled" value="1" <:ifSiteuser disabled:>checked<:or:><:eif:>  /><input type="hidden" name="saveDisabled" value="1" />
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser disabled:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">First Name: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="name1" value="<:old name1 siteuser name1:>" size="60" /><:ifRequired name1:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser name1:>  <:error_img name1:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">Last Name: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="name2" value="<:old name2 siteuser name2:>" size="60" /><:ifRequired name2:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser name2:>  <:error_img name2:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">address: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="address" value="<:old address siteuser address:>" size="60" /><:ifRequired address:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser address:>  <:error_img address:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">city: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="city" value="<:old city siteuser city:>" /><:ifRequired city:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser city:>  <:error_img city:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">state: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="state" value="<:old state siteuser state:>" /><:ifRequired state:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser state:>  <:error_img state:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">postcode: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="postcode" value="<:old postcode siteuser postcode:>" /><:ifRequired postcode:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser postcode:>  <:error_img postcode:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">telephone: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="telephone" value="<:old telephone siteuser telephone:>" /><:ifRequired telephone:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser telephone:>  <:error_img telephone:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">facsimile: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="facsimile" value="<:old facsimile siteuser facsimile:>" /><:ifRequired facsimile:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser facsimile:>  <:error_img facsimile:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">country: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="country" value="<:old country siteuser country:>" size="60" /><:ifRequired country:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser country:>  <:error_img country:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">title: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="title" value="<:old title siteuser title:>" /><:ifRequired title:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser title:>  <:error_img title:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">organization: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="organization" value="<:old organization siteuser organization:>" size="60" /><:ifRequired organization:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser organization:>  <:error_img organization:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billFirstName: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billFirstName" value="<:old billFirstName siteuser billFirstName:>" size="60" /><:ifRequired billFirstName:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billFirstName:>  <:error_img billFirstName:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billLastName: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billLastName" value="<:old billLastName siteuser billLastName:>" size="60" /><:ifRequired billLastName:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billLastName:>  <:error_img billLastName:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billStreet: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billStreet" value="<:old billStreet siteuser billStreet:>" size="60" /><:ifRequired billStreet:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billStreet:>  <:error_img billStreet:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billSuburb: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billSuburb" value="<:old billSuburb siteuser billSuburb:>" /><:ifRequired billSuburb:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billSuburb:>  <:error_img billSuburb:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billState: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billState" value="<:old billState siteuser billState:>" /><:ifRequired billState:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billState:>  <:error_img billState:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billPostCode: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billPostCode" value="<:old billPostCode siteuser billPostCode:>" /><:ifRequired billPostCode:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billPostCode:>  <:error_img billPostCode:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billCountry: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billCountry" value="<:old billCountry siteuser billCountry:>" /><:ifRequired billCountry:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billCountry:>  <:error_img billCountry:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">instructions: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="instructions" value="<:old instructions siteuser instructions:>" /><:ifRequired instructions:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser instructions:>  <:error_img instructions:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billTelephone: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billTelephone" value="<:old billTelephone siteuser billTelephone:>" /><:ifRequired billTelephone:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billTelephone:>  <:error_img billTelephone:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billFacsimile: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billFacsimile" value="<:old billFacsimile siteuser billFacsimile:>" /><:ifRequired billFacsimile:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billFacsimile:>  <:error_img billFacsimile:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left">billEmail: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="text" name="billEmail" value="<:old billEmail siteuser billEmail:>" /><:ifRequired billEmail:>*<:or:><:eif:>
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser billEmail:>  <:error_img billEmail:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left" valign="top">Admin Notes: </th>
+            <td bgcolor="#FFFFFF"> 
+              <textarea name="adminNotes" wrap="soft" rows="5" cols="60"><:old adminNotes siteuser adminNotes:></textarea>
+            </td>
+            <td bgcolor="#FFFFFF" valign="top"><:help editsiteuser adminNotes:>  <:error_img adminNotes:></td>
+          </tr>
+<:if UserCan siteuser_changepw:>
+<:if Cfg "site users" nopassword:><:or Cfg:>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left"> Password: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="password" name="password" />
+              </td>
+            <td bgcolor="#FFFFFF"> <:help editsiteuser password:>  <:error_img password:></td>
+          </tr>
+          <tr> 
+            <th bgcolor="#FFFFFF" align="left"> Confirm: </th>
+            <td bgcolor="#FFFFFF"> 
+              <input type="password" name="confirm_password" />
+            </td>
+            <td bgcolor="#FFFFFF"><:help editsiteuser confirm_password:> <:error_img confirm_password:> </td>
+          </tr>
+<:eif Cfg:>
+<:or UserCan:><:eif UserCan:>
+          <tr> 
+            <td bgcolor="#FFFFFF" colspan="3" align="right"> 
+              <input type="submit" name="a_save" value="  Save User  " />
+            </td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</form>
diff --git a/site/templates/admin/users/list.tmpl b/site/templates/admin/users/list.tmpl
new file mode 100644 (file)
index 0000000..3c37322
--- /dev/null
@@ -0,0 +1,45 @@
+<:wrap admin/xbase.tmpl title=>"Admin Site Users":>
+<h1>Admin Site Users</h1>
+<p>
+| <a href="/cgi-bin/admin/menu.pl">Admin menu</a> |
+<:if UserCan siteuser_add :>
+<a href="<:script:>?a_addform=1">Add User</a> |<:or UserCan:><:eif UserCan:>
+<:ifMessage:>
+<p><b><:message:></b></p>
+<:or:><:eif:> 
+
+<p>Page <:siteusers_pagenum:> of <:siteusers_pagecount:>
+<:ifFirstSiteusersPage:>&lt;&lt;&lt;&lt; &lt;&lt;<:or:><a href="<:script:>?s=<:sortby:>&r=<:reverse:>&p=1&pp=<:siteusers_perpage:>">&lt;&lt;&lt;&lt;</a> <a href="<:script:>?s=<:sortby:>&r=<:reverse:>&p=<:prevSiteusersPage:>&pp=<:siteusers_perpage:>">&lt;&lt;</a><:eif:>
+<:iterator begin repeats [siteusers_pagecount]:>
+<:if Eq [repeat value] [siteusers_pagenum]:><:repeat value:><:or Eq:><a href="<:script:>?s=<:sortby:>&r=<:reverse:>&p=<:repeat value:>&pp=<:siteusers_perpage:>"><:repeat value:></a><:eif Eq:>
+<:iterator end repeats:>
+<:ifLastSiteusersPage:>&gt;&gt; &gt;&gt;&gt;&gt;<:or:><a href="<:script:>?s=<:sortby:>&r=<:reverse:>&p=<:nextSiteusersPage:>&pp=<:siteusers_perpage:>">&gt;&gt;</a> <a href="<:script:>?s=<:sortby:>&r=<:reverse:>&p=<:siteusers_pagecount:>&pp=<:siteusers_perpage:>">&gt;&gt;&gt;&gt;</a><:eif:>
+</p>
+<form method="post" action="<:script:>">
+<table border="0" cellspacing="0" cellpadding="0" bgcolor="#000000" class="table">
+    <tr>
+      <td>
+        <table cellpadding="6" border="0" cellspacing="1">
+          <tr> 
+            <th bgcolor="#FFFFFF" nowrap> <a href="<:script:>?<:sorthelp userId:>&p=<:siteusers_pagenum:>&pp=<:siteusers_perpage:>">Logon</a></th>
+            <th bgcolor="#FFFFFF" nowrap> <a href="<:script:>?<:sorthelp name1:>&p=<:siteusers_pagenum:>&pp=<:siteusers_perpage:>">First Name</a></th>
+            <th bgcolor="#FFFFFF" nowrap> <a href="<:script:>?<:sorthelp name2:>&p=<:siteusers_pagenum:>&pp=<:siteusers_perpage:>">Last Name</a></th>
+          </tr>
+          <:if Siteusers:> <: iterator begin siteusers :> 
+          <tr bgcolor="#FFFFFF"> 
+            <td nowrap> <a href="<:script:>?a_edit=1&id=<:siteuser id:>"><:siteuser userId:></a></td>
+            <td valign="top"><:siteuser name1:></td>
+            <td valign="top"><:siteuser name2:></td>
+          </tr>
+          <: iterator end siteusers :> 
+          <:or Siteusers:> 
+          <tr bgcolor="#FFFFFF"> 
+            <td colspan="4" align="center">Your system has no users.</td>
+          </tr>
+          <:eif Siteusers:> 
+        </table>
+</td>
+</tr>
+</table>
+</form>
+
index 42d990f..f9876c8 100644 (file)
@@ -226,6 +226,11 @@ else {
         <input type="Text" name="email" size=34 value="<:old email:>">
         *</font></td>
     </tr>
+    <tr> 
+      <td valign="top"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Special<br />Instructions:</font></td>
+      <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> 
+        <textarea name="instructions" rows="5" cols="40" wrap="virtual"><:old instructions:></textarea></font></td>
+    </tr>
     <tr> 
       <td colspan="2"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> 
         * Required information for order to be shipped</font></td>
index aae0217..1cc0792 100644 (file)
@@ -1,5 +1,5 @@
 <:wrap base.tmpl:>
-<div align=center
+<div align="center" class="useroptions"
 <:if Cfg "site users" nopassword:><:or Cfg:>
   <table border="0" cellspacing="0" cellpadding="0">
     <tr> 
@@ -20,9 +20,9 @@
 <:eif Cfg:>
   <table width="250">
     <tr> 
-      <th colspan="2" align="center" height="20"> 
-        <p><font face="Verdana, Arial, Helvetica, sans-serif" size="3"><b>User 
-          Options</b></font></p>
+      <th colspan="2" height="20" class="thead"> 
+        <p>User 
+          Options</p>
       </th>
     </tr>
     <tr> 
@@ -44,6 +44,7 @@
     </tr>
     <:or Message:><:eif Message:> 
     <form action="/cgi-bin/user.pl" method="post">
+    <input type="hidden" name="saveSubscriptions" value="1" />
     <:if Cfg "site users" nopassword:>
     <input type="hidden" name="u" value="<:user id:>" />
     <input type="hidden" name="p" value="<:user password:>" />
@@ -75,7 +76,7 @@
       <!--//<tr> 
         <th nowrap="nowrap" align="left"><b><font face="Verdana, Arial, Helvetica, sans-serif" size="-2">Keep 
           Details:</font></b></th>
-        <td width="100%"><input type="checkbox" name="keepAddress" value="1"<:ifLast keepAddress:> checked<:or:><:eif:> /></td>
+        <td width="100%"><input type="hidden" name="saveKeepAddress" value="1" /><input type="checkbox" name="keepAddress" value="1"<:ifLast keepAddress:> checked<:or:><:eif:> /></td>
       </tr> //-->
       <:if Subscriptions:> 
       <tr> 
             your subscription. If you change your email address you will need 
             to reconfirm your subscription.</font></div>
           <:eif User:> <:eif AnySubs:> 
-          <p> <input type="checkbox" name="textOnlyMail" value="1"<:ifLast textOnlyMail:> 
+          <p>  <input type="hidden" name="saveTextOnlyMail" value="1" /><input type="checkbox" name="textOnlyMail" value="1"<:ifLast textOnlyMail:> 
             checked<:or:><:eif:> /> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">Text 
             only E-mail messages</font></p>
         </td>
           <input type="text" name="facsimile" value="<:last facsimile:>" size="32" maxlength="80" /><:error_img facsimile:>
         </td>
       </tr>
+<:if Cfg "site users" billing_on_main_opts 1:>
+      <tr> 
+        <td colspan="2" height="20"><br>
+          <br>
+          Billing contact information:</td>
+      </tr>
+      <tr> 
+        <th>First Name:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billFirstName" value="<:last billFirstName:>" size="40" maxlength="127" /><:error_img billFirstName:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Last Name:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billLastName" value="<:last billLastName:>" size="40" maxlength="127" /><:error_img billLastName:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Street:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billStreet" value="<:last billStreet:>" size="40" maxlength="127" /><:error_img billStreet:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Suburb:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billSuburb" value="<:last billSuburb:>" size="40" maxlength="127" /><:error_img billSuburb:>
+        </td>
+      </tr>
+      <tr> 
+        <th>State:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billState" value="<:last billState:>" size="40" maxlength="127" /><:error_img billState:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Post Code:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billPostCode" value="<:last billPostCode:>" size="40" maxlength="127" /><:error_img billPostCode:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Country:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billCountry" value="<:last billCountry:>" size="40" maxlength="127" /><:error_img billCountry:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Telephone:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billTelephone" value="<:last billTelephone:>" size="40" maxlength="127" /><:error_img billTelephone:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Facsimile:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billFacsimile" value="<:last billFacsimile:>" size="40" maxlength="127" /><:error_img billFacsimile:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Email:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billEmail" value="<:last billEmail:>" size="40" maxlength="127" /><:error_img billEmail:>
+        </td>
+      </tr>
+<:or Cfg:>
+      <tr> 
+        <td colspan="2" height="20" align="center"><br>
+          <br>
+          <a href="<:script:>?show_opts=1&_t=billing">Manage Billing Contact Information</a></td>
+      </tr>
+<:eif Cfg:>
 <:if Cfg "site users" nopassword:><:or Cfg:>
       <tr> 
         <td colspan="2" align="left" height="20"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><br>
diff --git a/site/templates/user/options_billing_base.tmpl b/site/templates/user/options_billing_base.tmpl
new file mode 100644 (file)
index 0000000..e28b333
--- /dev/null
@@ -0,0 +1,131 @@
+<:wrap base.tmpl:>
+<div align=center class="useroptions"> 
+<:if Cfg "site users" nopassword:><:or Cfg:>
+  <table border="0" cellspacing="0" cellpadding="0">
+    <tr> 
+      <td> 
+        <form name="yourorders" method="post" action="<:script:>">
+          <input type="submit" name="Submit" value="View your account" class="user-buttons" />
+          <input type="hidden" name="userpage" value="1" />
+        </form>
+      </td>
+      <:ifCfg shop enabled:><td> 
+        <form name="ff" method="post" action="/cgi-bin/shop.pl">
+          <input type="submit" name="cart" value="View shopping cart" class="user-buttons" />
+        </form>
+      </td><:or:><:eif:>
+    </tr>
+  </table>
+  <br />
+<:eif Cfg:>
+  <table width="250">
+    <tr> 
+      <th colspan="2" height="20" class="thead"> 
+        <p>User Options - Billing</p>
+      </th>
+    <tr> 
+      <td colspan="2" 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 Message:> 
+    <tr> 
+      <td colspan="2" align="center"> 
+        <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>Error: 
+          <:message:></b></font></p>
+      </td>
+    </tr>
+    <:or Message:><:eif Message:> 
+    </tr>
+      <tr> 
+        <td colspan="2" height="20" align="center"><br>
+          <br>
+          <a href="<:script:>?show_opts=1">Return to Main Options</a></td>
+      </tr>
+    <form action="/cgi-bin/user.pl" method="post">
+    <input type="hidden" name="_t" value="billing" />
+    <:if Cfg "site users" nopassword:>
+    <input type="hidden" name="u" value="<:user id:>" />
+    <input type="hidden" name="p" value="<:user password:>" />
+    <input type="hidden" name="r" value="<:script:>?show_opts=<:user password:>&amp;u=<:user id:>&_t=saved" />
+    <:or Cfg:><:eif Cfg:>
+<:if Cfg "site users" nopassword:><:or Cfg:>
+      <tr> 
+        <th>Logon Name:</th>
+        <td width="100%"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:user 
+          userId:></font></td>
+      </tr>
+<:eif Cfg:>
+      <tr> 
+        <th>First Name:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billFirstName" value="<:last billFirstName:>" size="40" maxlength="127" /><:error_img billFirstName:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Last Name:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billLastName" value="<:last billLastName:>" size="40" maxlength="127" /><:error_img billLastName:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Street:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billStreet" value="<:last billStreet:>" size="40" maxlength="127" /><:error_img billStreet:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Suburb:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billSuburb" value="<:last billSuburb:>" size="40" maxlength="127" /><:error_img billSuburb:>
+        </td>
+      </tr>
+      <tr> 
+        <th>State:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billState" value="<:last billState:>" size="40" maxlength="127" /><:error_img billState:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Post Code:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billPostCode" value="<:last billPostCode:>" size="40" maxlength="127" /><:error_img billPostCode:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Country:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billCountry" value="<:last billCountry:>" size="40" maxlength="127" /><:error_img billCountry:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Telephone:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billTelephone" value="<:last billTelephone:>" size="40" maxlength="127" /><:error_img billTelephone:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Facsimile:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billFacsimile" value="<:last billFacsimile:>" size="40" maxlength="127" /><:error_img billFacsimile:>
+        </td>
+      </tr>
+      <tr> 
+        <th>Email:</th>
+        <td width="100%" nowrap="nowrap"> 
+          <input type="text" name="billEmail" value="<:last billEmail:>" size="40" maxlength="127" /><:error_img billEmail:>
+        </td>
+      </tr>
+      <tr> 
+        <td colspan="2" align="right"> 
+          <input type="submit" name="saveopts" value="Save Options" class="user-buttons" />
+        </td>
+      </tr>
+    </form>
+  </table>
+</div>
index 7f51cc9..41676e2 100644 (file)
--- a/test.cfg
+++ b/test.cfg
@@ -44,3 +44,5 @@ site users.require_name2=1
 #site users.subscribe_all=1
 site users.subscribe_1=1
 #debug.cookies=1
+site users.billing_on_main_opts=0
+site users.user_register=0