site/cgi-bin/modules/BSE/Coupon/ProductPercent.pm
site/cgi-bin/modules/BSE/Custom.pm
site/cgi-bin/modules/BSE/CustomBase.pm
+site/cgi-bin/modules/BSE/CustomData.pm
site/cgi-bin/modules/BSE/DB.pm
site/cgi-bin/modules/BSE/DB/Mysql.pm
site/cgi-bin/modules/BSE/DummyArticle.pm
site/cgi-bin/modules/BSE/Passwords.pm
site/cgi-bin/modules/BSE/PayPal.pm
site/cgi-bin/modules/BSE/Permissions.pm
+site/cgi-bin/modules/BSE/PubSub.pm
site/cgi-bin/modules/BSE/Regen.pm
site/cgi-bin/modules/BSE/Report.pm
site/cgi-bin/modules/BSE/Request.pm
package BSE::Cart;
use strict;
use Scalar::Util;
+use BSE::PubSub;
-our $VERSION = "1.015";
+our $VERSION = "1.016";
=head1 NAME
unless (defined $self->{calc_price}) {
$self->{calc_price} = $self->product->price(user => $self->{cart}{req}->siteuser);
+
+ BSE::PubSub->customize(
+ cart_price => {
+ cartitem => $self,
+ cart => $self->{cart},
+ price => \($self->{calc_price}),
+ request => $self->{cart}{req},
+ });
}
return $self->{calc_price};
use BSE::Util::HTML;
use BSE::CfgInfo 'product_options';
use BSE::Util::Tags qw(tag_hash tag_article);
+use BSE::PubSub;
use constant PRODUCT_CUSTOM_FIELDS_CFG => "product custom fields";
-our $VERSION = "1.016";
+our $VERSION = "1.017";
=head1 NAME
my $price_tier;
my %prices;
$req->set_variable(product => $article);
+ BSE::PubSub->customize(product_edit_variables => { req => $req, product => $article, errors => \$errors });
return
(
product => [ \&tag_article, $article, $cfg ],
}
$req->validate(fields => \%work_option_fields,
errors => \%errors);
+ BSE::PubSub->customize(
+ product_option_add_validate =>
+ {
+ req => $req,
+ errors => \%errors,
+ product => $article,
+ fields => \%work_option_fields,
+ });
keys %errors
and return $self->_service_error($req, $article, $articles, undef,
\%errors);
$option->save;
}
+ BSE::PubSub->customize(
+ product_option_add =>
+ {
+ req => $req,
+ product => $article,
+ option => $option,
+ values => \%value_keys
+ });
+
$req->is_ajax
and return $req->json_content
(
keys %errors
and return $self->_service_error($req, $article, $articles, undef, \%errors);
+ if ($template =~ /edit/) {
+ BSE::PubSub->customize(
+ product_edit_option_edit => {
+ req => $req,
+ product => $article,
+ option => $option
+ });
+ }
$req->set_variable(option => $option);
$req->messages($errors);
+
my $it = BSE::Util::Iterate->new;
my %acts;
%acts =
$req->user_can(bse_edit_prodopt_edit => $article)
or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
+
return $self->_common_option('admin/prodopt_edit', $req, $article,
$articles, $msg, $errors);
}
and return $self->_service_error($req, $article, $articles, undef, \%errors, 'FIELD', "req_edit_option");
$req->validate(fields => \%option_name,
errors => \%errors);
+ BSE::PubSub->customize(
+ product_option_edit_validate =>
+ {
+ req => $req,
+ errors => \%errors,
+ product => $article,
+ option => $option,
+ });
my @values = $option->values;
my %fields = map {; "value$_->{id}" => \%option_value } @values;
$req->validate(fields => \%fields,
or $errors{default_value} = "Unknown value selected as default";
}
- $DB::single = 1;
my @new_values;
my $index = 1;
while ($index < 10 && defined $cgi->param("newvalue$index")) {
my $value = $cgi->param($field);
$req->validate(fields => { $field => \%option_value },
errors => \%errors);
- push @new_values, $value;
+ push @new_values, [ $field, $value ];
++$index;
}
}
}
my $order = @values ? $values[-1]->display_order : time;
- for my $value (@new_values) {
- BSE::TB::ProductOptionValues->make
+ my %newvalues;
+ for my $new (@new_values) {
+ my ($name, $value) = @$new;
+ $newvalues{$name} = BSE::TB::ProductOptionValues->make
(
product_option_id => $option->id,
value => $value,
display_order => ++$order,
);
}
+ BSE::PubSub->customize(
+ product_option_edit_save =>
+ {
+ req => $req,
+ product => $article,
+ option => $option,
+ newvalues => \%newvalues,
+ });
$req->is_ajax
and return $req->json_content
--- /dev/null
+package BSE::PubSub;
+use strict;
+use BSE::CfgInfo qw(load_class);
+
+our $VERSION = "1.000";
+
+my %subscribers;
+
+my %customizers;
+
+sub _class_desc {
+ my ($class) = @_;
+
+ ref($class) ? "Object of " . ref($class) . " type" : $class;
+}
+
+sub _load_handlers {
+ my ($name, $cache) = @_;
+
+ $cache->{_} and return;
+
+ my $cfg = BSE::Cfg->single;
+
+ my @entries = $cfg->entries($name);
+ while (@entries) {
+ my ($key, $val) = splice @entries, 0, 2;
+ $key =~ s/\d+$//;
+ $key =~ s/-.*//;
+ push @{$cache->{$key}}, [ split /,/, $val ];
+ }
+ $cache->{_} = 1;
+
+ # preloads
+ if ($cache->{preload}) {
+ for my $handler (@{$cache->{preload}}) {
+ my ($class, $method) = @$handler;
+
+ if (ref $class || eval { load_class($class); 1 }) {
+ if (!eval { $class->$method(); 1 }) {
+ _log_error("pubsub::preload", "Call to method $method of $class for preload threw an exception",
+ { class => $class, method => $method, message => "preload", error => $@ });
+ }
+ }
+ else {
+ _log_error("pubsub::preload", "Loading $class for preload threw an exception",
+ { class => $class, method => $method, message => "preload", error => $@ });
+ }
+ }
+ }
+}
+
+sub _sub_info {
+ my ($code) = @_;
+
+ ref $code eq "CODE"
+ or return +{ type => ref($code) };
+
+ my $info = eval {
+ require B;
+ my $cv = B::svref_2object($code);
+ +{
+ file => scalar($cv->FILE),
+ stash => scalar($cv->STASH->NAME),
+ };
+ };
+
+ $info ||= { error => $@ };
+
+ return $info;
+}
+
+sub _log_error {
+ my ($comp, $msg, $dump) = @_;
+
+ require BSE::TB::AuditLog;
+ BSE::TB::AuditLog->log
+ (
+ component => $comp,
+ msg => $msg,
+ dump => $dump,
+ actor => "S",
+ level => "error",
+ );
+}
+
+sub _publish {
+ my ($self, $section, $hash, $comp, $message, $param) = @_;
+
+ _load_handlers($section => $hash);
+ $hash->{$message}
+ or return;
+
+ for my $handler (@{$hash->{$message}}) {
+ if (ref($handler) eq "CODE") {
+ eval { $handler->($param); 1 }
+ or _log_error("pubsub::publish", "Internal handler for $message threw an exception", _sub_info($handler));
+ }
+ elsif (ref($handler) eq "ARRAY") {
+ my ($class, $method) = @$handler;
+ my $cls = _class_desc($class);
+ if (ref $class || eval { load_class($class); 1 }) {
+ if (!eval { $class->$method($param); 1 }) {
+ _log_error($comp, "Call to method $method of $class for message $message threw an exception",
+ { class => _class_desc($class), method => $method, message => $message, error => $@ });
+ }
+ }
+ else {
+ _log_error($comp, "Loading $class for message $message threw an exception",
+ { class => _class_desc($class), method => $method, message => $message, error => $@ });
+ }
+ }
+ }
+
+ return;
+}
+
+sub publish {
+ my ($self, $message, $param) = @_;
+
+ $self->_publish(subscribers => \%subscribers, "pubsub::publish", $message, $param);
+}
+
+sub customize {
+ my ($self, $message, $param) = @_;
+
+ $self->_publish(customizers => \%customizers, "pubsub::customize", $message, $param);
+
+ if ($customizers{"*"}) {
+ for my $class (@{$customizers{"*"}}) {
+ if ($class->can($message)) {
+ if (!eval { $class->$message($param); 1 }) {
+ _log_error("pubsub::customize", "Call to method $message for " . _class_desc($class) . " threw an exception", { class => _class_desc($class), method => $message, message => $message, error => $@, preload => 1 });
+ }
+ }
+ }
+ }
+}
+
+sub handle {
+ my ($class, $message, $handler) = @_;
+
+ $customizers{$message} = $handler;
+}
+
+sub subscribe {
+ my ($class, $message, $handler) = @_;
+
+ $subscribers{$message} = $handler;
+}
+
+1;
+
+=head1 NAME
+
+BSE::Notify - BSE's publish/subcribe system.
+
+=head1 SYNOPSIS
+
+ use BSE::PubSub;
+
+ BSE::PubSub->publish(message_name => \%parameters);
+
+ BSE::PubSub->subscribe(message_name => \&code);
+ BSE::PubSub->subscribe(message_name => [ $class_object, $method ]);
+
+ BSE::PubSub->customize(custom_type => \%parameters);
+
+ BSE::PubSub->handle(custom_type => \&code);
+ BSE::PubSub->handle(custom_type => [ $class_object, $method ]);
+ BSE::PubSub->handle("*" => $class_object);
+
+ # in a config file somewhere
+ [subscribers]
+ messagename=classname,methodname,reserved
+
+ [customizers]
+ custom_type=classname,methodname,reserved
+
+=head1 DESCRIPTION
+
+A less messy, more extensible version of custom classes.
+
+Advantages over the custom class interface:
+
+=over
+
+=item *
+
+we're not passing a long list of positional arguments.
+
+=item *
+
+multiple handlers are better defined.
+
+=back
+
+The two methods are distinguished based on their purpose.
+
+=over
+
+=item *
+
+C<publish> - sends off a notification, no response is expected.
+Under some circumstances the message might be retained by the receiver
+or passed onto another system.
+
+=item *
+
+C<customize> - requests customization of a data structure. This is
+intended for in-process use only.
+
+=back
+
+The return value of either method is unspecified and may change.
+
+Typically handlers for messages will be configured in the
+configuration file, but other parts of the system may request to
+handle specific messages (or customizations), but this will be rare.
+
+=cut
for my $opt (@all_options) {
my @opt_values = $opt->values;
my %opt_values = map { $_->id => $_->value } @opt_values;
+ my %opt_valueobjs = map { $_->id => $_ } @opt_values;
my $result_opt =
{
id => $opt->key,
name => $opt->key,
desc => $opt->name,
value => $values[$index],
+ valueobj => $opt_valueobjs{$values[$index]},
type => $opt->type,
labels => \%opt_values,
default => $opt->default_value,
use BSE::Util::Secure qw(make_secret);
use BSE::Template;
-our $VERSION = "1.051";
+our $VERSION = "1.052";
=head1 NAME
$order_values->{paidFor} = 0;
my @items = $class->_build_items($req);
+ my @cartitems = $cart->items;
@products = $cart->products;
if ($session->{order_work}) {
my @option_descs = $product->option_descs($cfg, $item->{options});
my $display_order = 1;
for my $option (@option_descs) {
- BSE::TB::OrderItemOptions->make
+ my $optionitem = BSE::TB::OrderItemOptions->make
(
order_item_id => $dbitem->{id},
original_id => $option->{id},
display => $option->{display},
display_order => $display_order++,
);
+ BSE::PubSub->customize(
+ order_item_option =>
+ {
+ cartitem => $cartitems[$row_num],
+ cartoption => $option->{valueobj},
+ cart => $cart,
+ orderitem => $dbitem,
+ orderitemoption => $optionitem,
+ });
}
}
-
+
my $sub = $product->subscription;
if ($sub) {
$subscribing_to{$sub->{text_id}} = $sub;
$work{product_discount} = 0;
$work{product_discount_units} = 0;
}
-
+ BSE::PubSub->customize(
+ order_build_item => {
+ cartitem => $item,
+ cart => $cart,
+ orderitem => \%work,
+ });
+
push @newcart, \%work;
}
}
<span id="prodoptvaluemove<:= loop.current.id :>"><:.call "make_arrows", down_url: downurl, up_url: upurl :></span>
<:.end define :>
+<:.define addform_value_entry:>
+<div class="valueentry<:= index mod 2 == 0 ? "" : " odd":>">
+ <span><input type="text" name="value<:= index :>" value="<:= cgi.param("value" _ index) :>" maxlength="255" class="editor_field" title="Enter some values here" /></span>
+ <span><input type="radio" name="default" value="value<:= index :>"></span>
+ <span><:.call "error_img", field: "value" _ index:></span>
+</div>
+<:.end define:>
+<:.define addform_value_head:>
+<div class="valueentry"><span>Values</span><span>Default</span></div>
+<:.end define:>
+<:.define value_entry_menu :>
+<:.if request.user_can("bse_edit_prodopt_edit", article):>
+<div class="valueentrymenu">
+<a href="<:script:>?id=<:= product.id:>&value_id=<:= dboptionvalue.id:>&a_edit_option_value=1">Edit</a>
+<a href="<:script:>?id=<:= product.id:>&value_id=<:= dboptionvalue.id:>&a_confdel_option_value=1">Delete</a>
+<:.call "dboptionvalue_move":>
+<:.end if :>
+</div>
+<:.end define :>
+<:.define value_entry:>
+<div id="valentry<:= dboptionvalue.id:>" class="valueentry<:= loop.index mod 2 == 1 ? " odd" : "":>">
+<span id="prodoptvalue<:= dboptionvalue.id:>"><:= dboptionvalue.value:></span>
+<:.if dboptionvalue.id == dboption.default_value:>(default)<:.end if:>
+<:.call "value_entry_menu":>
+</div>
+<:.end define:>
+<:include admin/edit_prodopt_custom.tmpl optional:>
+
<:.set dboptions = [ product.db_options ] :>
<:.if dboptions.size:>
<h2>Product options</h2>
<div id="vallist<:= dboption.id:>" class="prodoptvalues">
<:.for dboptionvalue in [ dboption.values ] :>
<:.set dbovloop = loop :>
-<div id="valentry<:= dboptionvalue.id:>" class="valueentry<:= dbovloop.index mod 2 == 1 ? " odd" : "":>"><span id="prodoptvalue<:= dboptionvalue.id:>"><:= dboptionvalue.value:></span>
-<:.if dboptionvalue.id == dboption.default_value:>(default)<:.end if:>
-<:.if request.user_can("bse_edit_prodopt_edit", article):>
-<div class="valueentrymenu">
-<a href="<:script:>?id=<:= product.id:>&value_id=<:= dboptionvalue.id:>&a_edit_option_value=1">Edit</a>
-<a href="<:script:>?id=<:= product.id:>&value_id=<:= dboptionvalue.id:>&a_confdel_option_value=1">Delete</a>
-<:.call "dboptionvalue_move", loop:dbovloop:>
-</div>
-<:.end if :>
-</div>
+<:.call "value_entry", "loop":dbovloop:>
<:.end for :>
</div>
<:.if request.user_can("bse_edit_prodopt_edit", article) :>
</div>
<:.end if:>
<:.if request.user_can("bse_edit_prodopt_add", article) :>
-<:.define addform_value_entry:>
-<div class="valueentry<:= index mod 2 == 0 ? "" : " odd":>"><span><input type="text" name="value<:= index :>" value="<:= cgi.param("value" _ index) :>" maxlength="255" class="editor_field" title="Enter some values here" /></span><span><input type="radio" name="default" value="value<:= index :>"></span><span><:.call "error_img", field: "value" _ index:></span></div>
-<:.end define:>
-<:.define addform_value_head:>
-<div class="valueentry"><span>Values</span><span>Default</span></div>
-<:.end define:>
-<:include admin/edit_prodopt_custom.tmpl optional:>
<div id="addoptionform" class="prodopt">
<form action="<:script:>" method="post">
<:csrfp admin_add_option hidden:>
<:wrap admin/base.tmpl title => "Edit Product Option", menuitem=>"none", showtitle=>"1", js => "admin_editprodopt.js" :>
<:include admin/product_menu.tmpl:>
+<:.define value_head :>
+<tr>
+ <td colspan="2"></td>
+ <th>Default<:.call "error_img", field:"default_value":></th>
+</tr>
+<:.end define:>
+<:.define value_entry:>
+<tr>
+ <th>Value:</th>
+ <td><input type="text" name="value<:= value.id:>" value="<:= cgi.param("save_enabled") ? cgi.param("value" _ value.id) : value.value:>" /><:.call "error_img", field:"value" _ value.id:></td>
+ <td class="check"><input type="radio" name="default_value" value="<:= value.id:>" <:.if value.id == option.default_value:>checked="checked"<:.end if:> /></td>
+</tr>
+<:.end define:>
+<:-.define newvalue_entry -:>
+<div><label for="newvalue<:= index:>">Value:</label>
+<input type="text" name="newvalue<:= index:>" value="<:=cgi.param("newvalue" _ index) :>:>" /><:.call "error_img", field:"newvalue" _ index :></div>
+<:.end define-:>
+<:include admin/prodopt_edit_custom.tmpl optional:>
<form action="<:script:>" method="post">
<input type="hidden" name="id" value="<:=article.id:>" />
<input type="hidden" name="option_id" value="<:=option.id:>" />
<th>Values:</th>
<td id="product_option_values">
<table class="editform">
-<tr>
- <td colspan="2"></td>
- <th>Default<:.call "error_img", field:"default_value":></th>
-</tr>
+<:-.call "value_head" -:>
<:.for value in [ option.values ] :>
-<tr>
- <th>Value:</th>
- <td><input type="text" name="value<:= value.id:>" value="<:= cgi.param("save_enabled") ? cgi.param("value" _ value.id) : value.value:>" /><:.call "error_img", field:"value" _ value.id:></td>
- <td class="check"><input type="radio" name="default_value" value="<:= value.id:>" <:.if value.id == option.default_value:>checked="checked"<:.end if:> /></td>
-</tr>
+<:-.call "value_entry", "value": value -:>
<:.end for:>
</table>
<:.if cgi.param("newvaluecount"):>
<:.for i in [ 1 .. cgi.param("newvaluecount") ] :>
-<div><label for="newvalue<:= i:>">Value:</label>
-<input type="text" name="newvalue<:= i:>" value="<:=cgi.param("newvalue" _ i) :>:>" /><:.call "error_img", field:"newvalue" _ i :></div>
+<:.call "newvalue_entry", "index":i :>
<:.end for :>
<:.end if :>
</td>
</td>
<td><img src="/images/store/left_bottom_corner_line.gif" width="26" height="31"></td>
<td align="center" bgcolor="#FFFFFF" height="100%" NOWRAP> <font size="3" face="Verdana, Arial, Helvetica, sans-serif">
- <b>$<:money total:></b></font></td>
+ <b>$<:= bse.number("money", cart.total) :></b></font></td>
<td><img src="/images/store/right_bottom_corner_line.gif" width="26" height="31"></td>
</tr>
<tr>