-VERSION=0.12_06
+VERSION=0.12_07
DISTNAME=bse-$(VERSION)
DISTBUILD=$(DISTNAME)
DISTTAR=../$(DISTNAME).tar
primary key (id),
unique (email)
-);
\ No newline at end of file
+);
+
+drop table if exists admin_base;
+create table admin_base (
+ id integer not null auto_increment,
+ type char not null,
+ primary key (id)
+);
+
+drop table if exists admin_users;
+create table admin_users (
+ base_id integer not null,
+ logon varchar(60) not null,
+ name varchar(255) not null,
+ password varchar(80) not null,
+ perm_map varchar(255) not null,
+ primary key (base_id),
+ unique (logon)
+);
+
+drop table if exists admin_groups;
+create table admin_groups (
+ base_id integer not null,
+ name varchar(80) not null,
+ description varchar(255) not null,
+ perm_map varchar(255) not null,
+ primary key (base_id),
+ unique (name)
+);
+
+drop table if exists admin_membership;
+create table admin_membership (
+ user_id integer not null,
+ group_id integer not null,
+ primary key (user_id, group_id)
+);
+
+drop table if exists admin_perms;
+create table admin_perms (
+ object_id integer not null,
+ admin_id integer not null,
+ perm_map varchar(255),
+ primary key (object_id, admin_id)
+);
[articles]
shop=3
+
+[level 1]
+template=common/default.tmpl
+
+[level 2]
+template=common/default.tmpl
+
+[level 3]
+template=common/default.tmpl
+
+[level 4]
+template=common/default.tmpl
+
+[level 5]
+template=common/default.tmpl
+
+[catalogs]
+template=catalog.tmpl
+
+[products]
+template=shopitem.tmpl
return $templater->perform($acts, $func, $funcargs) ? 'Yes' : 'No';
}
+sub default_template {
+ my ($self, $article, $cfg, $templates) = @_;
+
+ if ($article->{parentid}) {
+ my $template = $cfg->entry("children of $article->{parentid}", "template");
+ return $template
+ if $template && grep $_ eq $template, @$templates;
+ }
+ if ($article->{level}) {
+ my $template = $cfg->entry("level $article->{level}", "template");
+ return $template
+ if $template && grep $_ eq $template, @$templates;
+ }
+ return $templates->[0];
+}
+
sub tag_templates {
my ($self, $article, $cfg, $cgi) = @_;
$default = $article->{template};
}
else {
- $default = $templates[0];
+ my @options;
+ $default = $self->default_template($article, $cfg, \@templates);
}
return $cgi->popup_menu(-name=>'template',
-values=>\@templates,
push @dirs, split /,/, $dirs;
}
}
+ if ($article->{level}) {
+ push @dirs, $article->{level};
+ my $dirs = $self->{cfg}->entry("level $article->{level}", 'template_dirs');
+ push @dirs, split /,/, $dirs if $dirs;
+ }
@dirs;
}
$article->setLink($self->make_link($article));
$article->save();
+ use Util 'generate_article';
+ generate_article($articles, $article) if $Constants::AUTO_GENERATE;
+
my $urlbase = $self->{cfg}->entryVar('site', 'url');
return BSE::Template->get_refresh($urlbase . $article->{admin},
$self->{cfg});
}
$article->save();
+
+ use Util 'generate_article';
+ generate_article($articles, $article) if $Constants::AUTO_GENERATE;
+
my $urlbase = $self->{cfg}->entryVar('site', 'url');
return BSE::Template->get_refresh($urlbase . $article->{admin},
$self->{cfg});
return qw(BSE::Edit::Product BSE::Edit::Catalog);
}
+sub default_template {
+ my ($self, $article, $cfg, $templates) = @_;
+
+ my $template = $cfg->entry('catalogs', 'template');
+ return $template
+ if $template && grep $_ eq $template, @$templates;
+
+ return $self->SUPER::default_template($article, $cfg, $templates);
+}
+
1;
return $self->SUPER::fill_old_data($req, $article, $src);
}
+sub default_template {
+ my ($self, $article, $cfg, $templates) = @_;
+
+ my $template = $cfg->entry('catalogs', 'template');
+ return $template
+ if $template && grep $_ eq $template, @$templates;
+
+ return $self->SUPER::default_template($article, $cfg, $templates);
+}
+
1;
package BSE::TB::AdminBase;
use strict;
-bse base qw(Squirrel::Table);
-use BSE::TB::AdminBase;
+use base qw(Squirrel::Row);
-sub rowClass {
- return 'BSE::TB::AdminBase';
+sub columns {
+ return qw/id type/;
}
1;
--- /dev/null
+package BSE::TB::AdminBases;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::AdminBase;
+
+sub rowClass {
+ return 'BSE::TB::AdminBase';
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminGroup;
+use strict;
+use base qw(BSE::TB::AdminBase);
+
+sub columns {
+ return ($_[0]->SUPER::columns,
+ qw/base_id name description perm_map/;
+}
+
+sub bases {
+ return { base_id=>{ class=>'BSE::TB::AdminBase' } };
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminGroups;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::AdminGroup;
+
+sub rowClass {
+ return 'BSE::TB::AdminGroup';
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminMembership;
+# this probably won't be used
+use strict;
+use base qw(Squirrel::Row);
+
+sub columns {
+ return qw/user_id group_id/;
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminMemberships;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::AdminMembership;
+
+sub rowClass {
+ return 'BSE::TB::AdminMembership';
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminPerm;
+# this probably won't be used
+use strict;
+use base qw(Squirrel::Row);
+
+sub columns {
+ return qw/object_id admin_id/;
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminPerms;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::AdminPerm;
+
+sub rowClass {
+ return 'BSE::TB::AdminPerm';
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminUser;
+use strict;
+use base qw(BSE::TB::AdminBase);
+
+sub columns {
+ return ($_[0]->SUPER::columns,
+ qw/base_id logon name password perm_map/;
+}
+
+sub bases {
+ return { base_id=>{ class=>'BSE::TB::AdminBase' } };
+}
+
+1;
--- /dev/null
+package BSE::TB::AdminUsers;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::AdminUser;
+
+sub rowClass {
+ return 'BSE::TB::AdminUser';
+}
+
+1;
sub add_item {
my $addid = param('id');
+ $addid ||= '';
my $quantity = param('quantity');
+ $quantity ||= 1;
my $product;
$product = Products->getByPkey($addid) if $addid;
- $product or return show_cart(); # oops
+ $product or return show_cart("Cannot find product $addid"); # oops
# collect the product options
- my @options = map scalar param($_), split /,/, $product->{options};
- grep(!defined, @options)
- and return show_cart(); # invalid parameter
+ my @options;
+ my @opt_names = split /,/, $product->{options};
+ my @not_def;
+ for my $name (@opt_names) {
+ my $value = param($name);
+ push @options, $value;
+ unless (defined $value) {
+ push @not_def, $name;
+ }
+ }
+ @not_def
+ and return show_cart("Some product options (@not_def) not supplied");
my $options = join(",", @options);
# the product must be non-expired and listed
- my $today = epoch_to_sql(time);
- $product->{release} le $today and $today le $product->{expire}
- or return show_cart();
- $product->{listed} or return show_cart();
+ use BSE::Util::SQL qw(now_sqldate);
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ my $today = now_sqldate();
+ $comp_release le $today
+ or return show_cart("Product has not been released yet");
+ $today le $comp_expire
+ or return show_cart("Product has expired");
+ $product->{listed} or return show_cart("Product not available");
# we need a natural integer quantity
$quantity =~ /^\d+$/
- or return show_cart();
+ or return show_cart("Invalid quantity");
my @cart = @{$session{cart}};
}
sub show_cart {
+ my ($msg) = @_;
my @cart = @{$session{cart}};
my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
my $item_index = -1;
my %custom_state = %{$session{custom}};
BSE::Custom->enter_cart(\@cart, \@cart_prods, \%custom_state);
+ $msg = '' unless defined $msg;
+ $msg = CGI::escapeHTML($msg);
my %acts;
%acts =
BSE::Custom->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state),
shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q),
basic_tags(\%acts),
+ msg => $msg,
);
$session{custom} = \%custom_state;
if (!$product) {
return show_cart("Product $item->{productId} not found");
}
- elsif ($product->{release} gt $today || $product->{expire} lt $today
- || !$product->{listed}) {
- return show_cart("Sorry, '$product->{title}' is no longer available");
+ else {
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ $comp_release le $today
+ or return show_cart("'$product->{title}' has not been released yet");
+ $today le $comp_expire
+ or return show_cart("'$product->{title}' has expired");
+ $product->{listed}
+ or return show_cart("'$product->{title}' not available");
}
push(@products, $product); # used in page rendering
@$item{qw/price wholesalePrice gst/} =
$order{gst} = 0;
$order{wholesale} = 0;
my @products;
- my $today = epoch_to_sql(time);
+ my $today = now_sqldate();
for my $item (@cart) {
my $product = Products->getByPkey($item->{productId});
# double check that it's still a valid product
if (!$product) {
return show_cart("Product $item->{productId} not found");
}
- elsif ($product->{release} gt $today || $product->{expire} lt $today
- || !$product->{listed}) {
- return show_cart("Sorry, '$product->{title}' is no longer available");
+ else {
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ $comp_release le $today
+ or return show_cart("'$product->{title}' has not been released yet");
+ $today le $comp_expire
+ or return show_cart("'$product->{title}' has expired");
+ $product->{listed}
+ or return show_cart("'$product->{title}' not available");
}
push(@products, $product); # used in page rendering
@$item{qw/price wholesalePrice gst/} =
Each macro will need to be described in bse.cfg, and the name of the
macro assigned an index so that it can be controlled.
+Since most normal permissions aren't going to be directly useful, the
+only permissions stored in the system will be macro based permissions.
+
=head2 Attached macros
These name one or more articles and the permissions can be different
=head1 CHANGES
+=head2 0.12_07
+
+=over
+
+=item *
+
+added handling for a default template based on parent, level and type.
+Returns to our original defaults from %LEVEL_DEFAULTS, but uses the
+config file instead.
+
+=item *
+
+now lists level specific templates in the templates drop-down for
+normal articles
+
+=item *
+
+updated templates from Adrian
+
+=item *
+
+since the default release date for new products is now the date the
+product was added, and release is a datetime, add_item()'s check to
+make sure the product had been released was broken, since it only
+compared it with a date.
+
+=item *
+
+the purchase() and prePurchase() functions in shop.pl had a similar
+problem
+
+=item *
+
+articles weren't being regenerated on save
+
+=item *
+
+report errors supplied to show_cart() in shop.pl
+
+=item *
+
+changed add_item() to report why it didn't add an item to the cart via
+show_cart() (#167)
+
+=item *
+
+t/t00smoke.t now checks add.pl for normal articles, catalogs and
+products
+
+=item *
+
+removed now unneeded onClick handler from delete in the image manager
+
+=back
+
=head2 0.12_06
This release includes some inactive files for access control.
=back
+=head2 [level I<level>]
+
+=over
+
+=item template
+
+The default template for this level of article, assuming it hasn't
+been set in the [children of I<article id>] section.
+
+=item template_dirs
+
+A comma-separated list of extra directories under $TMPLDIR to search
+for templates that can be used for articles at the given I<level>.
+
+=back
+
+=head2 [catalogs]
+
+=over
+
+=item template
+
+The default template for catalogs.
+
+=back
+
+=head2 [products]
+
+=over
+
+=item template
+
+The default template for products.
+
+=back
+
=head2 [messages]
This can be used to control translation of error messages. Each key
</form>
-<form method="POST" action="<:script:>" enctype="multipart/form-data">
+<form method="post" action="<:script:>" enctype="multipart/form-data">
<input type="hidden" name="level" value="<: level :>">
<input type="hidden" name="id" value="<: article id :>">
<input type="hidden" name="parentid" value="<: article parentid :>">
<input type="text" name="url" value="<: image url :>" size="32">
</td>
<td valign="bottom" nowrap>
- <input type="submit" name="removeimg_<: image id :>" value="Delete" onClick="return window.alert('Remember to update the article after deletion is complete')" >
- <:imgmove:> </td>
+ <b><a href="<:script:>?level=<: level :>&parentid=<: article parentid :>&id=<:article id:>&imgtype=<: articleType :>&removeimg_<: image id :>">Delete</a></b>
+<:imgmove:> </td>
</tr>
<: iterator separator images :>
<tr bgcolor="#FFFFFF">
</tr>
<:iterator end files:>
</table>
- </td></tr></table><br>
- <:or Files:><p>No files are attached to this article.<:eif Files:>
- <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p>
+ </td></tr></table>
+ <p><a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a>
+ </p>
+ <:or Files:>
+ <p>No files are attached to this article. <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p><:eif Files:>
</td>
<td nowrap bgcolor="#FFFFFF" valign="top"><:help edit files:></td>
</tr>
</tr>
<:iterator end files:>
</table>
- </td></tr></table><br>
- <:or Files:><p>No files are attached to this article.<:eif Files:>
- <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p>
+ </td></tr></table>
+ <p><a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a>
+ </p>
+ <:or Files:>
+ <p>No files are attached to this article. <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p><:eif Files:>
</td>
<td nowrap bgcolor="#FFFFFF" valign="top"><:help edit files:></td>
</tr>
<:iterator end files:>
</table>
</td></tr></table>
- <:or Files:><p>No files are attached to this article.<:eif Files:>
- <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p>
+ <p><a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a>
+ </p>
+ <:or Files:>
+ <p>No files are attached to this article. <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p><:eif Files:>
</td>
<td nowrap bgcolor="#FFFFFF" valign="top"><:help edit files:></td>
</tr>
</tr>
<tr>
<th align="left" bgcolor="#FFFFFF">Catalog:</th>
- <td bgcolor="#FFFFFF"><select name="parentid"><:list:></select></td>
+ <td bgcolor="#FFFFFF">
+<select name="parentid"><:list:></select></td>
<td nowrap bgcolor="#FFFFFF"><:help product catalog:></td>
</tr>
<tr>
</tr>
<tr>
<th nowrap align="left" bgcolor="#FFFFFF" valign="top"><a name="files"></a>Files:</th>
- <td nowrap bgcolor="#FFFFFF" width="100%"> <:if Files:>
+ <td nowrap bgcolor="#FFFFFF"> <:if Files:>
<table cellpadding="0" cellspacing="0" border="0" bgcolor="#333333">
<tr>
<td>
</table>
</td>
</tr>
- </table><br>
- <:or Files:><p>No files are attached to this article.<:eif Files:>
- <a href="<:script:>?filelist=1&id=<:product id:>"><b>Manage Files</b></a></p>
+ </table>
+ <p><a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a>
+ </p>
+ <:or Files:>
+ <p>No files are attached to this article. <a href="<:script:>?filelist=1&id=<:article id:>"><b>Manage Files</b></a></p><:eif Files:>
</td>
- <td nowrap bgcolor="#FFFFFF" width="100%" valign="top"><:help product
+ <td nowrap bgcolor="#FFFFFF" valign="top"><:help product
files:></td>
</tr>
<tr>
<th valign="top" nowrap bgcolor="#FFFFFF" align="left"> Images:
</th>
- <td align="center" bgcolor="#FFFFFF" width="100%"> <:if Images:> <:iterator begin
+ <td align="center" bgcolor="#FFFFFF"> <:if Images:> <:iterator begin
images:> <img src="/images/<: image image :>" alt="<:image alt :>" width=
<:image width:> height=<:image height:>> <:iterator separator images:>
<hr noshade size="1">
<:or Images:><p align="left">No images are attached to this article. <a href="<:script:>?id=<:article id:>&showimages=1"><b>Manage Images</b></a></p>
<:eif Images:>
</td>
- <td valign="top" bgcolor="#FFFFFF" width="100%"><:help product images:></td>
+ <td valign="top" bgcolor="#FFFFFF"><:help product images:></td>
</tr>
</table>
</td>
</td>
</tr>
</table>
+<:ifMsg:><p><font face="Verdana, Arial, Helvetica, sans-serif"><b><:msg:></b> </font></p><:or:><:eif:>
<p><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>Contains</b>
- <:count:> items</font></p>
<form name="form1" method="POST" action="/cgi-bin/shop.pl">
<form name="ff" method="POST" action="/cgi-bin/shop.pl">
<:if Options:>
-<table>
+ <table>
<tr>
- <td> <:iterator begin options:> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b><:option
- desc:>:</b></font> <:option popup:> <:iterator end options:> </td>
+ <:iterator begin options:><td valign="middle"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b><:option desc:>:</b></font></td><td> <:option popup:> </td><:iterator end options:>
</tr>
</table>
<:or Options:><:eif Options:>
-<:ifProduct leadTime:>
- <p> <b><font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FE7F00">Usually ships in: <:product leadTime:> <:if Eq [product leadTime] "1":>day<:or Eq:>days<:eif Eq:></font></b></p>
-<:or:><:eif:>
+ <:ifProduct leadTime:>
+ <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FE7F00">Usually ships in: <:product leadTime:> <:if Eq [product leadTime] "1":>day<:or Eq:>days<:eif Eq:></font></b>
+</p><:or:><br><:eif:>
<table border="0" cellspacing="0" cellpadding="0">
<tr valign="middle" align="center">
</tr>
</table>
</form>
-<:or Product:>
+<:or Product:><br>
<table border="0" cellspacing="0" cellpadding="1" bgcolor="#CCCCCC">
<tr>
<td>
<td align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:item units:></font></td>
<td align="right"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">$<:money item price:></font></td>
</tr>
- <:if Prodfiles:>
+<:iterator end items:>
</table>
+
+ <:if Prodfiles:>
<table width="100%" cellpadding="3" cellspacing="1">
<tr bgcolor="#CCCCCC">
<th colspan="3"><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#666666"><:if
"yes":>Files available when order status is ‘Complete’<:or:>Files<:eif:><:eif
Order:></font></th>
</tr>
+
<tr bgcolor="#EEEEEE">
<th width="50%" align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Description</font></th>
<th nowrap width="50%" align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">File</font></th>
<th><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Size</font></th>
</tr>
+<:iterator begin items:>
<:iterator begin prodfiles:>
<tr bgcolor="#FFFFFF">
<td width="50%"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:prodfile
<td align="right"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:kb
prodfile sizeInBytes:></font></td>
</tr>
- <:iterator end prodfiles:> <:or Prodfiles:><:eif Prodfiles:> <:iterator end items:>
+ <:iterator end prodfiles:><:iterator end items:>
</table>
+<:or Prodfiles:><:eif Prodfiles:>
<:or Items:><:eif Items:>
</td>
</tr>
use BSE::Test;
++$|;
-print "1..29\n";
+print "1..39\n";
my $baseurl = base_url;
ok($baseurl =~ /^http:/, "basic check of base url");
my $ua = make_ua;
qr!User\s+Logon!s);
fetch_ok($ua, "shop admin page", "$baseurl/cgi-bin/admin/shopadmin.pl",
qr!Shop\s+administration!s);
+fetch_ok($ua, "add article form", "$baseurl/cgi-bin/admin/add.pl",
+ qr!New\s+Subsect\sLev2!s);
+fetch_ok($ua, "add catalog form", "$baseurl/cgi-bin/admin/add.pl?type=Catalog",
+ qr!New\s+Catalog!s);
+fetch_ok($ua, "add product form", "$baseurl/cgi-bin/admin/add.pl?type=Product",
+ qr!Add\s+product!s);
+fetch_ok($ua, "edit article form", "$baseurl/cgi-bin/admin/add.pl?id=1",
+ qr!Section\sDetails!s);
+fetch_ok($ua, "edit catalog form", "$baseurl/cgi-bin/admin/add.pl?id=4",
+ qr!Catalog\sDetails!s);