INSTALL.html
INSTALL.txt
test-gosv.cfg
+test-tryandbyte.cfg
site/templates/1/shop_multicat.tmpl
site/templates/1/sitemap.tmpl
# site/templates/admin/add_product.tmpl
+site/templates/admin/article_custom.tmpl
site/templates/admin/article_img.tmpl
site/templates/admin/catalog.tmpl # embedded in the shopadmin catalog/product display
+site/templates/admin/catalog_custom.tmpl
site/templates/admin/edit_0.tmpl
site/templates/admin/edit_1.tmpl
site/templates/admin/edit_2.tmpl
site/templates/admin/order_list.tmpl
site/templates/admin/order_list_filled.tmpl
site/templates/admin/order_list_unfilled.tmpl
+site/templates/admin/product_custom.tmpl
site/templates/admin/product_detail.tmpl
site/templates/admin/product_list.tmpl
site/templates/admin/regenerror.tmpl
-VERSION=0.12_24
+VERSION=0.12_25
DISTNAME=bse-$(VERSION)
DISTBUILD=$(DISTNAME)
DISTTAR=../$(DISTNAME).tar
-- used by code and templates
flags varchar(80) not null default '',
+ -- custom fields for local usage
+ customDate1 datetime null,
+ customDate2 datetime null,
+
+ customStr1 varchar(255) null,
+ customStr2 varchar(255) null,
+
PRIMARY KEY (id),
-- if we keep id in the indexes MySQL will sometimes be able to
use BSE::DB;
use BSE::Request;
use BSE::Template;
+use Carp qw'verbose';
use Carp 'confess';
# $SIG{__DIE__} =
return qw/id parentid displayOrder title titleImage body
thumbImage thumbWidth thumbHeight imagePos
release expire keyword template link admin threshold
- summaryLength generator level listed lastModified flags/;
+ summaryLength generator level listed lastModified flags
+ customDate1 customDate2 customStr1 customStr2/;
}
sub step_parents {
package BSE::CustomBase;
use strict;
+sub new {
+ my ($class, %params) = @_;
+
+ exists $params{cfg} or die "No cfg parameter passed to custom class constructor";
+
+ return bless \%params, $class;
+}
+
sub enter_cart {
my ($class, $items, $products, $state, $cfg) = @_;
return ();
}
+# called to validate fields for a custom application
+# $cfg - a BSE::Cfg object
+# $new - the new data to be stored
+# $old - the existing article if any
+# $type - the type of article (Article, Product, Catalog)
+# $errors - hashref of fields to messages
+# Return non-zero if all fields are valid.
+# Set an error message in $errors for any invalid fields
+sub article_validate {
+ my ($self, $new, $old, $type, $errors) = @_;
+
+ 1;
+}
+
+sub article_fill_new {
+ my ($self, $data, $type) = @_;
+
+ $self->article_fill($data, $data, $type);
+}
+
+sub article_fill_old {
+ my ($self, $out, $in, $type) = @_;
+
+ $self->article_fill($out, $in, $type);
+}
+
+sub article_fill {
+ my ($self, $out, $in, $type) = @_;
+
+ 1;
+}
+
1;
=head1 NAME
(
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 = ?',
use BSE::Util::Tags;
use BSE::Util::SQL qw(now_sqldate);
use BSE::Permissions;
+use Util qw(custom_class);
sub article_dispatch {
my ($self, $req, $article, $articles) = @_;
return @possible;
}
-
-
sub tag_possible_stepkids {
my ($step_kids, $req, $article, $possstepkids, $articles, $cgi) = @_;
sub generator { 'Generate::Article' }
+sub typename {
+ my ($self) = @_;
+
+ my $gen = $self->generator;
+
+ ($gen =~ /(\w+)$/)[0] || 'Article';
+}
+
sub _validate_common {
my ($self, $data, $articles, $errors) = @_;
my ($self, $data, $articles, $errors) = @_;
$self->_validate_common($data, $articles, $errors);
+ custom_class($self->{cfg})
+ ->article_validate($data, undef, $self->typename, $errors);
return !keys %$errors;
}
my ($self, $article, $data, $articles, $errors) = @_;
$self->_validate_common($data, $articles, $errors);
+ custom_class($self->{cfg})
+ ->article_validate($data, $article, $self->typename, $errors);
return !keys %$errors;
}
sub fill_new_data {
my ($self, $req, $data, $articles) = @_;
+ custom_class($self->{cfg})
+ ->article_fill_new($data, $self->typename);
+
1;
}
$data->{body} =~ tr/\r/\n/;
}
for my $col (Article->columns) {
+ next if $col =~ /^custom/;
$article->{$col} = $data->{$col}
if exists $data->{$col} && $col ne 'id' && $col ne 'parentid';
}
+ custom_class($self->{cfg})
+ ->article_fill_old($article, $data, $self->typename);
return 1;
}
total shop_total load_order_fields basic_tags need_logon/;
use Constants qw/:shop/;
use BSE::Util::SQL qw(now_sqldate);
-use BSE::Custom;
use BSE::Util::Tags;
+use Util;
use Carp 'confess';
# returns a list of tags which display the cart details
option => sub { CGI::escapeHTML($options[$option_index]{$_[0]}) },
ifOptions => sub { @options },
options => sub { nice_options(@options) },
- BSE::Custom->checkout_actions($acts, $cart, $cart_prods,
- $session->{custom}, $q),
+ Util::custom_class($cfg)
+ ->checkout_actions($acts, $cart, $cart_prods, $session->{custom}, $q),
);
}
for my $item (@$cart) {
$total += $item->{units} * $item->{price};
}
- $total += BSE::Custom->total_extras($cart, $products, $state, $cfg, $stage);
+ $total += Util::custom_class($cfg)
+ ->total_extras($cart, $products, $state, $cfg, $stage);
return $total;
}
sub load_order_fields {
my ($wantcard, $q, $order, $session, $cart_prods, $error) = @_;
- my @required = BSE::Custom->required_fields($CGI::Q, $session->{custom});
+ require 'BSE/Cfg.pm';
+ my $cfg = BSE::Cfg->new;
+
+ my $cust_class = Util::custom_class($cfg);
+
+ my @required = $cust_class->required_fields($CGI::Q, $session->{custom});
push(@required, qw(cardHolder cardExpiry)) if $wantcard;
for my $field (@required) {
defined($q->param($field)) && length($q->param($field))
$order->{gst} += $item->{gst} * $item->{units};
}
$order->{orderDate} = $today;
- $order->{total} += BSE::Custom->total_extras(\@cart, \@products,
+ $order->{total} += $cust_class->total_extras(\@cart, \@products,
$session->{custom});
- require 'BSE/Cfg.pm';
- my $cfg = BSE::Cfg->new;
if (need_logon($cfg, \@cart, \@products, $session)) {
$$error = "Your cart contains some file-based products. Please register or logon";
return 0;
# check if a customizer has anything to do
eval {
- BSE::Custom->order_save($q, $order, \@cart, \@products, $session->{custom});
+ $cust_class->order_save($q, $order, \@cart, \@products, $session->{custom});
};
if ($@) {
$$error = $@;
use Articles;
use Constants qw($IMAGEDIR $LOCAL_FORMAT $BODY_EMBED
$EMBED_MAX_DEPTH $HAVE_HTML_PARSER);
-use BSE::Custom;
use DevHelp::Tags;
use DevHelp::HTML;
+use Util;
my $excerptSize = 300;
(
%extras,
- BSE::Custom->base_tags($articles, $acts, $article, $embedded, $cfg),
+ Util::custom_class($cfg)->base_tags($articles, $acts, $article, $embedded, $cfg),
BSE::Util::Tags->static($acts, $self->{cfg}),
# for embedding the content from children and other sources
ifEmbedded=> sub { $embedded },
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(generate_article generate_all generate_button
- refresh_to regen_and_refresh);
+ refresh_to regen_and_refresh custom_class);
use Constants qw($CONTENTBASE $GENERATE_BUTTON $SHOPID $AUTO_GENERATE);
use Carp qw(confess);
return 1;
}
+sub custom_class {
+ my ($cfg) = @_;
+
+ local @INC = @INC;
+
+ my $class = $cfg->entry('basic', 'custom_class', 'BSE::Custom');
+ (my $file = $class . ".pm") =~ s!::!/!g;
+
+ my $local_inc = $cfg->entry('paths', 'libraries');
+ unshift @INC, $local_inc if $local_inc;
+
+ require $file;
+
+ return $class->new(cfg=>$cfg);
+}
+
1;
use Constants qw(:shop $CGI_URI);
use BSE::Template;
use CGI::Cookie;
-use BSE::Custom;
+use Util;
use BSE::Mail;
use BSE::Shop::Util qw/shop_cart_tags cart_item_opts nice_options total
basic_tags load_order_fields need_logon/;
use BSE::Cfg;
use Util qw/refresh_to/;
-my $subject = $SHOP_MAIL_SUBJECT;
+my $cfg = BSE::Cfg->new();
+
+my $subject = $cfg->entry('shop', 'subject', $SHOP_MAIL_SUBJECT);
# our PGP passphrase
my $passphrase = $SHOP_PASSPHRASE;
my $pgp = $SHOP_PGP;
my $gpg = $SHOP_GPG;
-my $from = $SHOP_FROM;
+my $from = $cfg->entry('shop', 'from', $SHOP_FROM);
-my $toName = $SHOP_TO_NAME;
-my $toEmail= $SHOP_TO_EMAIL;
+my $toName = $cfg->entry('shop', 'to_name', $SHOP_TO_NAME);
+my $toEmail= $cfg->entry('shop', 'to_email', $SHOP_TO_EMAIL);
use constant PAYMENT_CC => 0;
use constant PAYMENT_CHEQUE => 1;
);
my %payment_names = qw(CC 0 Cheque 1 CallMe 2);
-my $cfg = BSE::Cfg->new();
my $urlbase = $cfg->entryVar('site', 'url');
my $securlbase = $cfg->entryVar('site', 'secureurl');
my %session;
$session{custom} ||= {};
my %custom_state = %{$session{custom}};
- BSE::Custom->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+ my $cust_class = Util::custom_class($cfg);
+ $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
$msg = '' unless defined $msg;
$msg = CGI::escapeHTML($msg);
my %acts;
%acts =
(
- BSE::Custom->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state,
+ $cust_class->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state,
$cfg),
shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
'cart'),
$session{cart} = \@cart;
$session{custom} ||= {};
my %custom_state = %{$session{custom}};
- BSE::Custom->recalc($CGI::Q, \@cart, [], \%custom_state, $cfg);
+ Util::custom_class($cfg)->recalc($CGI::Q, \@cart, [], \%custom_state, $cfg);
$session{custom} = \%custom_state;
}
my @cart = @{$session{cart}};
my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
my %custom_state = %{$session{custom}};
- BSE::Custom->checkout_update($CGI::Q, \@cart, \@cart_prods, \%custom_state,
- $cfg);
+ Util::custom_class($cfg)
+ ->checkout_update($CGI::Q, \@cart, \@cart_prods, \%custom_state, $cfg);
$session{custom} = \%custom_state;
checkout("", 1);
$session{custom} ||= {};
my %custom_state = %{$session{custom}};
- BSE::Custom->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+ my $cust_class = Util::custom_class($cfg);
+ $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+
my @payment_types = split /,/, $cfg->entry('shop', 'payment_types', '0');
@payment_types = grep $valid_payment_types{$_}, @payment_types;
@payment_types or @payment_types = ( 0 );
message => sub { $message },
old => sub { CGI::escapeHTML($olddata ? param($_[0]) :
$user && defined $user->{$_[0]} ? $user->{$_[0]} : '') },
- BSE::Custom->checkout_actions(\%acts, \@cart, \@cart_prods,
+ $cust_class->checkout_actions(\%acts, \@cart, \@cart_prods,
\%custom_state, $CGI::Q, $cfg),
ifMultPaymentTypes => @payment_types > 1,
);
# information
# BUG!!: this duplicates the code in purchase() a great deal
sub prePurchase {
- my @required = BSE::Custom->required_fields($CGI::Q, $session{custom}, $cfg);
+
+ my $cust_class = Util::custom_class($cfg);
+ my @required = $cust_class->required_fields($CGI::Q, $session{custom}, $cfg);
for my $field (@required) {
defined(param($field)) && length(param($field))
or return checkout("Field $field is required", 1);
return;
}
- $order{total} += BSE::Custom->total_extras(\@cart, \@products,
+ $order{total} += $cust_class->total_extras(\@cart, \@products,
$session{custom}, $cfg, 'final');
++$session{changed};
# blank anything else
# check if a customizer has anything to do
eval {
- BSE::Custom->order_save($CGI::Q, \%order, \@cart, \@products,
+ $cust_class->order_save($CGI::Q, \%order, \@cart, \@products,
$session{custom}, $cfg);
++$session{changed};
};
# the real work
sub purchase {
# some basic validation, in case the user switched off javascript
+ my $cust_class = Util::custom_class($cfg);
my @required =
- BSE::Custom->required_fields($CGI::Q, $session{custom}, $cfg);
+ $cust_class->required_fields($CGI::Q, $session{custom}, $cfg);
my @payment_types = split /,/, $cfg->entry('shop', 'payment_types', '0');
@payment_types = grep $valid_payment_types{$_}, @payment_types;
# check if a customizer has anything to do
eval {
- BSE::Custom->order_save($CGI::Q, \%order, \@cart, \@products, $cfg);
+ $cust_class->order_save($CGI::Q, \%order, \@cart, \@products, $cfg);
};
if ($@) {
return checkout($@, 1);
}
- $order{total} += BSE::Custom->total_extras(\@cart, \@products,
+ $order{total} += $cust_class->total_extras(\@cart, \@products,
$session{custom}, $cfg, 'final');
# load up the database
my %acts;
%acts =
(
- BSE::Custom->purchase_actions(\%acts, \@items, \@products,
+ $cust_class->purchase_actions(\%acts, \@items, \@products,
$session{custom}, $cfg),
iterate_items_reset => sub { $item_index = -1; },
iterate_items =>
%acts =
(
%extras,
- BSE::Custom->order_mail_actions(\%acts, $order, $items, $products,
- $session{custom}, $cfg),
+ Util::custom_class($cfg)
+ ->order_mail_actions(\%acts, $order, $items, $products,
+ $session{custom}, $cfg),
BSE::Util::Tags->static(\%acts, $cfg),
iterate_items_reset => sub { $item_index = -1; },
iterate_items =>
=head1 CHANGES
+=head2 0.12_25
+
+=over
+
+=item *
+
+admin/edit_product.tmpl had two <:error_img title:> tags
+
+=item *
+
+C<$SHOP_FROM>, C<$SHOP_TO_NAME> and C<$SHOP_TO_EMAIL> from
+Constants.pm can now be overridden in the config
+file. (C<[shop].from>, C<to_name> and C<to_email>)
+
+=item *
+
+the name of the custom class can now be specified in the config file
+(defaulting to C<BSE::Custom>) (C<[basic].custom_class>)
+
+=item *
+
+you can specify an alternate library path to search for your custom
+class (C<[paths].libraries>)
+
+=item *
+
+the custom class can now handle validate and store extra fields for
+articles (and relates types, like products)
+
+=item *
+
+two custom date fields (C<customDate1> and C<customDate2>) and two
+custom string fields (C<customStr1> and C<customStr2>) have been added
+to the article table to allow simplfied customization.
+
+=item *
+
+the default article, product and catalog editing template now include
+C<admin/custom_article.tmpl>, C<admin/custom_product.tmpl> and
+C<admin/custom_catalog.tmpl> includes to allow adding custom form
+fields without having to modify the main distributed editing templates
+
+=back
+
=head2 0.12_24
=over
Where uploaded images are stored. This is not yet completely
implemented. Default: $IMAGEDIR.
+=item libraries
+
+Local search path for BSE::Custom, or the class configured by
+C<custom_class> in [basic].
+
=back
=head2 [extensions]
security system will check for a user set by the browser before
attempting a form based logon. Default: None.
+=item custom_class
+
+The name of the custom class for your site. This is currently only
+used for article editing customizations. This class should derive
+from BSE::CustomBase. Default: BSE::Custom.
+
=back
=head2 [mail]
These are used by various shop templates to present an address that a
cheque payment should be sent to.
+=item from
+
+From email address for emails sent by the shop. Overides $SHOP_FROM
+in Constants.pm
+
+=item to_name
+
+To name for emailed orders sent by the shop. Overrides $SHOP_TO_NAME
+in Constants.pm
+
+=item to_email
+
+To email for emailed orders sent by the shop. Overrides $SHOP_TO_EMAIL
+in Constants.pm
+
=back
=head2 [fields]
<td bgcolor="#FFFFFF" width="100%"><:iterator begin flags:><:if FieldPerm flags:><input type=checkbox name=flags value="<:flag id:>" <:ifFlagSet [flag id]:>checked<:or:><:eif:>><:or FieldPerm:><:ifFlagSet [flag id]:>Yes<:or:>No<:eif:><:eif FieldPerm:><:flag desc:><:iterator end flags:></td>
<td bgcolor="#FFFFFF"><:help edit listed:></td>
</tr>
+<:include admin/product_custom.tmpl :>
<tr>
<th nowrap align="left" bgcolor="#FFFFFF" valign="top">Thumbnail image:</th>
<td nowrap bgcolor="#FFFFFF">
--- /dev/null
+<!-- admin/article_custom.tmpl -->
+<!-- replace with form fields for custom fields -->
\ No newline at end of file
--- /dev/null
+<!-- admin/catalog_custom.tmpl -->
+<!-- replace with form fields for custom fields -->
\ No newline at end of file
(comma separated)<:or:><: article threshold :><:eif:></td>
<td nowrap bgcolor="#FFFFFF"><:help edit keywords:> <:error_img keyword:></td>
</tr>
+<:include admin/article_custom.tmpl:>
<tr>
<th nowrap bgcolor="#FFFFFF" align="left" valign="top">Thumbnail image:</th>
<td width="100%" valign="top" bgcolor="#FFFFFF">
(comma separated)<:or:><: article threshold :><:eif:></td>
<td nowrap bgcolor="#FFFFFF"><:help edit keywords:> <:error_img keyword:></td>
</tr>
+<:include admin/article_custom.tmpl:>
<tr>
<th nowrap bgcolor="#FFFFFF" align="left" valign="top">Thumbnail image:</th>
<td width="100%" valign="top" bgcolor="#FFFFFF">
(comma separated) </td>
<td nowrap bgcolor="#FFFFFF"><:help catalog keywords:> <:error_img keyword:></td>
</tr>
+<:include admin/catalog_custom.tmpl:>
<tr>
<th bgcolor="#FFFFFF" nowrap align="left" valign="top">Thumbnail image:</th>
<td width="100%" valign="top" bgcolor="#FFFFFF">
<tr>
<th align="left" bgcolor="#FFFFFF">Title*:</th>
<td bgcolor="#FFFFFF"><:ifFieldPerm title:><input type="text" name="title" value="<:old title:>" size="60"><:or:><:product title:><:eif:> </td>
- <td nowrap bgcolor="#FFFFFF"><:help product title:> <:error_img title:> <:error_img
- title:></td>
+ <td nowrap bgcolor="#FFFFFF"><:help product title:> <:error_img title:></td>
</tr>
<tr>
<th nowrap align="left" bgcolor="#FFFFFF">Summary*:</th>
(<:alloptions:>)<:or:><:product options:><:eif:> </td>
<td bgcolor="#FFFFFF"><:help product options:> <:error_img options:></td>
</tr>
+<:include admin/product_custom.tmpl:>
<tr>
<th nowrap align="left" bgcolor="#FFFFFF" valign="top">Thumbnail image:</th>
<td nowrap bgcolor="#FFFFFF">
--- /dev/null
+<!-- admin/product_custom.tmpl -->
+<!-- replace with form fields for custom fields -->
\ No newline at end of file