TT#75114 javascript support for prov templates

Change-Id: I6f173ba6dd814f9be84bc3f077e8595e56d993c5
changes/07/37507/3
Rene Krenn 5 years ago
parent b12b5d1d08
commit f66435c837

1
debian/control vendored

@ -77,6 +77,7 @@ Depends:
libio-socket-ip-perl,
libipc-run3-perl,
libipc-system-simple-perl,
libje-perl,
libjson-multivalueordered-perl,
libjson-perl,
libjson-pointer-perl,

@ -3,14 +3,11 @@ use warnings;
use strict;
use MRO::Compat;
use NGCP::Panel::Utils::Generic qw();
sub escape_js {
my $c = shift;
my $str = shift;
my $quote_char = shift;
$quote_char //= "'";
$str =~ s/\\/\\\\/g;
$str =~ s/$quote_char/\\$quote_char/g;
return $str;
return NGCP::Panel::Utils::Generic::escape_js(@_);
}
1;

@ -7,9 +7,9 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 1.00;
@ISA = qw(Exporter);
@EXPORT = ();
@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all hash2obj mime_type_to_extension extension_to_mime_type);
%EXPORT_TAGS = ( DEFAULT => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &mime_type_to_extension &extension_to_mime_type)],
all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all &hash2obj &mime_type_to_extension &extension_to_mime_type)]);
@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all hash2obj mime_type_to_extension extension_to_mime_type array_to_map escape_js);
%EXPORT_TAGS = ( DEFAULT => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js)],
all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all &hash2obj &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js)]);
use Hash::Merge;
use Data::Compare qw//;
@ -213,4 +213,13 @@ sub array_to_map {
}
sub escape_js {
my $str = shift // '';
my $quote_char = shift;
$quote_char //= "'";
$str =~ s/\\/\\\\/g;
$str =~ s/$quote_char/\\$quote_char/g;
return $str;
}
1;

@ -4,7 +4,6 @@ use Sipwise::Base;
use NGCP::Panel::Form::ProvisioningTemplate qw();
use DateTime::TimeZone qw();
#use MIME::Base64 qw(decode_base64);
use String::MkPasswd qw();
use Eval::Closure qw(eval_closure);
use Tie::IxHash;
@ -28,6 +27,11 @@ use NGCP::Panel::Utils::Subscriber qw();
use NGCP::Panel::Utils::Preferences qw();
use NGCP::Panel::Utils::Kamailio qw();
use JE::Destroyer qw();
use JE qw();
use JSON qw();
use NGCP::Panel::Utils::Generic qw(escape_js);
my $IDENTIFIER_FNAME = 'identifier';
my $CODE_SUFFIX_FNAME = '_code';
my $FIELD_TYPE_ATTRIBUTE = 'type';
@ -35,7 +39,11 @@ my $FIELD_VALUE_ATTRIBUTE = 'value';
my @INIT_FIELD_NAMES = qw(cc_ac_map default_cc);
my $PURGE_FIELD_NAME = 'purge';
my $strict_closure = 1;
my $JE_ANON_CLASS = 'je_anon';
sub je_anon::TO_JSON {
return _unbless(@_);
};
my $STRICT_CLOSURE = 1;
my @DISABLED_CORE_FUNCTIONS = qw(
binmode close closedir dbmclose dbmopen eof fileno flock format getc read
@ -63,6 +71,15 @@ my @DISABLED_CORE_FUNCTIONS = qw(
exit goto
);
my @SUPPORTED_LANGS = qw(perl js);
my $PERL_ENV = 'use subs qw(' . join(' ', @DISABLED_CORE_FUNCTIONS) . ");\n";
foreach my $sub (@DISABLED_CORE_FUNCTIONS) {
$PERL_ENV .= 'sub ' . $sub . " { die('$sub called'); }\n";
}
my $JS_ENV = '';
sub create_provisioning_template_form {
my %params = @_;
@ -110,8 +127,9 @@ sub create_provisioning_template_form {
if($posted && $form->validated) {
my %log_data = %{$c->request->params};
my $context;
try {
my $context = provision_begin(
$context = provision_begin(
c => $c,
);
provision_commit_row(
@ -129,6 +147,7 @@ sub create_provisioning_template_form {
desc => $c->loc("Provisioning template '[_1]' done: subscriber [_2] created", $template, $context->{subscriber}->{username} . '@' . $context->{domain}->{domain}),
);
} catch($e) {
_provision_cleanup($c, $context);
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
@ -216,14 +235,13 @@ sub provision_begin {
purge
/};
my $template = $c->stash->{provisioning_template_name};
my $schema = $c->model('DB');
$schema->set_transaction_isolation('READ COMMITTED');
my $context = {};
$context->{dfrd} = {};
$context->{now} = NGCP::Panel::Utils::DateTime::current_local();
$context->{schema} = $schema;
$context->{purge} = $purge // 0;
$context->{_dfrd} = {};
$context->{_purge} = $purge // 0;
my $fields = _get_fields($c,1);
my $init_values = {};
@ -233,6 +251,8 @@ sub provision_begin {
$init_values->{$k} = $v;
}
my %subs = ();
if (exists $init_values->{cc_ac_map} and not exists $context->{split_number}) {
my $cc_ac_map = $init_values->{cc_ac_map};
die("invalid cc ac map") unless ('HASH' eq ref $cc_ac_map);
@ -258,8 +278,10 @@ sub provision_begin {
}
}
}
$context->{split_number} = sub {
$subs{split_number} = sub {
my ($dn) = @_;
$dn //= '';
$dn = '' . $dn; #force JE:: unboxing
my ($cc,$ac,$sn) = ('','',$dn);
if ($default_cc) {
@ -290,16 +312,43 @@ sub provision_begin {
}
}
return {cc => $cc, ac => $ac, sn => $sn};
return bless { ac => $ac, cc => $cc, sn => $sn, }, $JE_ANON_CLASS;
};
}
foreach my $sub (qw(debug info warn error)) {
$context->{$sub} = sub {
return $c->log->$sub(@_);
$subs{$sub} = sub {
return $c->log->$sub(( map { ($_ // '') . ''; } @_));
};
}
_switch_lang(
$context,
$context->{_lang} = $c->stash->{provisioning_templates}->{$template}->{lang},
perl => sub {
$context->{now} = NGCP::Panel::Utils::DateTime::current_local();
$context->{schema} = $schema;
@{$context}{keys %subs} = values %subs;
},
js => sub {
$context->{_je} = JE->new();
$context->{_je}->eval($JS_ENV . "\nvar _func;\nvar now = new Date('" .
NGCP::Panel::Utils::DateTime::current_local() . "');\n");
$subs{'quotemeta'} = sub {
return quotemeta(_unbox_je_value(shift @_));
};
$subs{'sprintf'} = sub {
return sprintf(_unbox_je_value(shift @_), map {
_unbox_je_value($_);
} @_);
};
while (each %subs) {
$context->{_je}->new_function($_ => $subs{$_});
}
$context->{_je_env} = {};
}
);
return $context;
}
@ -353,7 +402,7 @@ sub provision_commit_row {
$schema,
);
my $purge = $context->{purge} || $values->{$PURGE_FIELD_NAME};
my $purge = $context->{_purge} || $values->{$PURGE_FIELD_NAME};
try {
_init_subscriber_context(
$c,
@ -470,16 +519,32 @@ sub provision_finish {
context
/};
if (exists $context->{dfrd}->{kamailio_trusted_reload}
and $context->{dfrd}->{kamailio_trusted_reload} > 0) {
_provision_cleanup($c, $context);
if (exists $context->{_dfrd}->{kamailio_trusted_reload}
and $context->{_dfrd}->{kamailio_trusted_reload} > 0) {
my (undef, $xmlrpc_res) = NGCP::Panel::Utils::Kamailio::trusted_reload($c);
delete $context->{dfrd}->{kamailio_trusted_reload};
delete $context->{_dfrd}->{kamailio_trusted_reload};
}
if (exists $context->{dfrd}->{kamailio_flush}
and $context->{dfrd}->{kamailio_flush} > 0) {
if (exists $context->{_dfrd}->{kamailio_flush}
and $context->{_dfrd}->{kamailio_flush} > 0) {
NGCP::Panel::Utils::Kamailio::flush($c);
delete $context->{dfrd}->{kamailio_flush};
delete $context->{_dfrd}->{kamailio_flush};
}
}
sub _provision_cleanup {
my ($c, $context) = @_;
return unless $context;
if ($context->{_je}) {
JE::Destroyer::destroy($context->{_je}); # break circular refs
undef $context->{_je};
undef $context->{_je_env};
}
}
@ -491,7 +556,6 @@ sub _init_row_context {
delete $context->{contract_contact};
delete $context->{contract};
delete $context->{contract_preferences};
delete $context->{billing_mappings};
delete $context->{subscriber};
delete $context->{subscriber_preferences};
@ -505,8 +569,9 @@ sub _init_row_context {
delete $context->{provisioning_domain};
delete $context->{product};
delete $context->{cp};
delete $context->{cs};
delete $context->{_bm};
delete $context->{_cp};
delete $context->{_cs};
delete $context->{row};
@ -549,14 +614,14 @@ sub _init_contract_context {
$contract_contact{$k} = $v;
}
if (exists $contract_contact{reseller}) {
$context->{r_c} //= {};
if (exists $context->{r_c}->{$contract_contact{reseller}}
or ($context->{r_c}->{$contract_contact{reseller}} = $schema->resultset('resellers')->search_rs({
$context->{_r_c} //= {};
if (exists $context->{_r_c}->{$contract_contact{reseller}}
or ($context->{_r_c}->{$contract_contact{reseller}} = $schema->resultset('resellers')->search_rs({
name => $contract_contact{reseller},
status => { '!=' => 'terminated' },
})->first)) {
$contract_contact{reseller_id} = $context->{r_c}->{$contract_contact{reseller}}->id;
$context->{reseller} = { $context->{r_c}->{$contract_contact{reseller}}->get_inflated_columns };
$contract_contact{reseller_id} = $context->{_r_c}->{$contract_contact{reseller}}->id;
$context->{reseller} = { $context->{_r_c}->{$contract_contact{reseller}}->get_inflated_columns };
} else {
die("unknown reseller $contract_contact{reseller}");
}
@ -592,43 +657,43 @@ sub _init_contract_context {
$contract{$k} = $v;
}
if (exists $contract{profile_package}) {
$context->{pp_c} //= {};
if (exists $context->{pp_c}->{$contract{profile_package}}
or ($context->{pp_c}->{$contract{profile_package}} = $schema->resultset('profile_packages')->search_rs({
$context->{_pp_c} //= {};
if (exists $context->{_pp_c}->{$contract{profile_package}}
or ($context->{_pp_c}->{$contract{profile_package}} = $schema->resultset('profile_packages')->search_rs({
name => $contract{profile_package},
#reseller_id
#status => { '!=' => 'terminated' },
})->first)) {
$contract{profile_package_id} = $context->{pp_c}->{$contract{profile_package}}->id;
$context->{profile_package} = { $context->{pp_c}->{$contract{profile_package}}->get_inflated_columns };
$contract{profile_package_id} = $context->{_pp_c}->{$contract{profile_package}}->id;
$context->{profile_package} = { $context->{_pp_c}->{$contract{profile_package}}->get_inflated_columns };
} else {
die("unknown profile package $contract{profile_package}");
}
delete $contract{profile_package};
}
if (exists $contract{billing_profile}) {
$context->{bp_c} //= {};
if (exists $context->{bp_c}->{$contract{billing_profile}}
or ($context->{bp_c}->{$contract{billing_profile}} = $schema->resultset('billing_profiles')->search_rs({
$context->{_bp_c} //= {};
if (exists $context->{_bp_c}->{$contract{billing_profile}}
or ($context->{_bp_c}->{$contract{billing_profile}} = $schema->resultset('billing_profiles')->search_rs({
name => $contract{billing_profile},
#todo: reseller_id
status => { '!=' => 'terminated' },
})->first)) {
$contract{billing_profile_id} = $context->{bp_c}->{$contract{billing_profile}}->id;
$context->{billing_profile} = { $context->{bp_c}->{$contract{billing_profile}}->get_inflated_columns };
$contract{billing_profile_id} = $context->{_bp_c}->{$contract{billing_profile}}->id;
$context->{billing_profile} = { $context->{_bp_c}->{$contract{billing_profile}}->get_inflated_columns };
} else {
die("unknown billing profile $contract{billing_profile}");
}
delete $contract{billing_profile};
}
if (exists $contract{product}) {
$context->{pr_c} //= {};
if (exists $context->{pr_c}->{$contract{product}}
or ($context->{pr_c}->{$contract{product}} = $schema->resultset('products')->search_rs({
$context->{_pr_c} //= {};
if (exists $context->{_pr_c}->{$contract{product}}
or ($context->{_pr_c}->{$contract{product}} = $schema->resultset('products')->search_rs({
name => $contract{product},
})->first)) {
$contract{product_id} = $context->{pr_c}->{$contract{product}}->id;
$context->{product} = { $context->{pr_c}->{$contract{product}}->get_inflated_columns };
$contract{product_id} = $context->{_pr_c}->{$contract{product}}->id;
$context->{product} = { $context->{_pr_c}->{$contract{product}}->get_inflated_columns };
} else {
die("unknown product $contract{product}");
}
@ -654,12 +719,12 @@ sub _init_contract_context {
$contract{create_timestamp} //= $context->{now};
$contract{modify_timestamp} //= $context->{now};
$context->{billing_mappings} = [];
$context->{_bm} = [];
NGCP::Panel::Utils::BillingMappings::prepare_billing_mappings(
c => $c,
resource => $context->{contract},
old_resource => undef,
mappings_to_create => $context->{billing_mappings},
mappings_to_create => $context->{_bm},
err_code => sub {
my ($err) = @_;
die($err);
@ -682,15 +747,15 @@ sub _init_subscriber_context {
$subscriber{$k} = $v;
}
if (exists $subscriber{domain}) {
$context->{bd_c} //= {};
if (exists $context->{bd_c}->{$subscriber{domain}}
or ($context->{bd_c}->{$subscriber{domain}} = $schema->resultset('domains')->search_rs({
$context->{_bd_c} //= {};
if (exists $context->{_bd_c}->{$subscriber{domain}}
or ($context->{_bd_c}->{$subscriber{domain}} = $schema->resultset('domains')->search_rs({
domain => $subscriber{domain},
#todo: reseller_id
#status => { '!=' => 'terminated' },
})->first)) {
$subscriber{domain_id} = $context->{bd_c}->{$subscriber{domain}}->id;
$context->{domain} = { $context->{bd_c}->{$subscriber{domain}}->get_inflated_columns };
$subscriber{domain_id} = $context->{_bd_c}->{$subscriber{domain}}->id;
$context->{domain} = { $context->{_bd_c}->{$subscriber{domain}}->get_inflated_columns };
$context->{provisioning_domain} = { $schema->resultset('voip_domains')->find(
{domain => $subscriber{domain}})->get_inflated_columns };
} else {
@ -705,7 +770,7 @@ sub _init_subscriber_context {
$context->{subscriber}->{customer_id} //= $context->{contract}->{id};
$context->{cs} = NGCP::Panel::Utils::Subscriber::prepare_resource(
$context->{_cs} = NGCP::Panel::Utils::Subscriber::prepare_resource(
c => $c,
schema => $schema,
resource => $context->{subscriber},
@ -732,7 +797,7 @@ sub _init_subscriber_preferences_context {
my ($c, $context, $schema, $template) = @_;
if (exists $template->{subscriber_preferences}) {
$context->{cp} = NGCP::Panel::Utils::Preferences::prepare_resource(
$context->{_cp} = NGCP::Panel::Utils::Preferences::prepare_resource(
c => $c,
schema => $schema,
item => $schema->resultset('voip_subscribers')->find({
@ -741,7 +806,7 @@ sub _init_subscriber_preferences_context {
type => 'subscribers',
);
my %subscriber_preferences = %{$context->{cp}}; #merge
my %subscriber_preferences = %{$context->{_cp}}; #merge
foreach my $col (keys %{$template->{subscriber_preferences}}) {
my ($k,$v) = _calculate($context,$col, $template->{subscriber_preferences}->{$col});
$subscriber_preferences{$k} = $v;
@ -760,7 +825,7 @@ sub _init_contract_preferences_context {
my ($c, $context, $schema, $template) = @_;
if (exists $template->{contract_preferences}) {
$context->{cp} = NGCP::Panel::Utils::Preferences::prepare_resource(
$context->{_cp} = NGCP::Panel::Utils::Preferences::prepare_resource(
c => $c,
schema => $schema,
item => $schema->resultset('contracts')->find({
@ -769,7 +834,7 @@ sub _init_contract_preferences_context {
type => 'contracts',
);
my %contract_preferences = %{$context->{cp}}; #merge
my %contract_preferences = %{$context->{_cp}}; #merge
foreach my $col (keys %{$template->{contract_preferences}}) {
my ($k,$v) = _calculate($context,$col, $template->{contract_preferences}->{$col});
$contract_preferences{$k} = $v;
@ -862,7 +927,7 @@ sub _create_contract {
$context->{contract}->{id} = $contract->id;
NGCP::Panel::Utils::BillingMappings::append_billing_mappings(c => $c,
contract => $contract,
mappings_to_create => $context->{billing_mappings},
mappings_to_create => $context->{_bm},
);
NGCP::Panel::Utils::ProfilePackages::create_initial_contract_balances(c => $c,
contract => $contract,
@ -883,19 +948,19 @@ sub _create_subscriber {
my $subscriber = NGCP::Panel::Utils::Subscriber::create_subscriber(
c => $c,
schema => $schema,
contract => $context->{cs}->{customer},
params => $context->{cs}->{resource},
preferences => $context->{cs}->{preferences},
contract => $context->{_cs}->{customer},
params => $context->{_cs}->{resource},
preferences => $context->{_cs}->{preferences},
admin_default => 0,
event_context => $event_context,
error => $error_info,
);
$context->{subscriber}->{id} = $subscriber->id;
if($context->{cs}->{resource}->{status} eq 'locked') {
if($context->{_cs}->{resource}->{status} eq 'locked') {
NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
c => $c,
prov_subscriber => $subscriber->provisioning_voip_subscriber,
level => $context->{cs}->{resource}->{lock} || 4,
level => $context->{_cs}->{resource}->{lock} || 4,
);
} else {
NGCP::Panel::Utils::ProfilePackages::underrun_lock_subscriber(c => $c, subscriber => $subscriber);
@ -903,17 +968,17 @@ sub _create_subscriber {
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
c => $c,
schema => $schema,
alias_numbers => $context->{cs}->{alias_numbers},
reseller_id => $context->{cs}->{customer}->contact->reseller_id,
alias_numbers => $context->{_cs}->{alias_numbers},
reseller_id => $context->{_cs}->{customer}->contact->reseller_id,
subscriber_id => $subscriber->id,
);
$subscriber->discard_changes; # reload row because of new number
NGCP::Panel::Utils::Subscriber::manage_pbx_groups(
c => $c,
schema => $schema,
groups => $context->{cs}->{groups},
groupmembers => $context->{cs}->{groupmembers},
customer => $context->{cs}->{customer},
groups => $context->{_cs}->{groups},
groupmembers => $context->{_cs}->{groupmembers},
customer => $context->{_cs}->{customer},
subscriber => $subscriber,
);
NGCP::Panel::Utils::Events::insert_deferred(
@ -936,7 +1001,7 @@ sub _create_subscriber_preferences {
item => $schema->resultset('voip_subscribers')->find({
id => $context->{subscriber}->{id},
}),
old_resource => $context->{cp},
old_resource => $context->{_cp},
resource => $context->{subscriber_preferences},
type => 'subscribers',
replace => 0,
@ -960,7 +1025,7 @@ sub _create_contract_preferences {
item => $schema->resultset('contracts')->find({
id => $context->{contract}->{id},
}),
old_resource => $context->{cp},
old_resource => $context->{_cp},
resource => $context->{contract_preferences},
type => 'contracts',
replace => 0,
@ -987,8 +1052,8 @@ sub _create_registrations {
$registration
);
die("failed to create registration") unless $ret->[0]->[1];
$context->{dfrd}->{kamailio_flush} //= 0;
$context->{dfrd}->{kamailio_flush} += 1;
$context->{_dfrd}->{kamailio_flush} //= 0;
$context->{_dfrd}->{kamailio_flush} += 1;
}
}
@ -999,8 +1064,8 @@ sub _create_trusted_sources {
foreach my $trusted_source (@{$context->{trusted_sources}}) {
$schema->resultset('voip_trusted_sources')->create($trusted_source);
$context->{dfrd}->{kamailio_trusted_reload} //= 0;
$context->{dfrd}->{kamailio_trusted_reload} += 1;
$context->{_dfrd}->{kamailio_trusted_reload} //= 0;
$context->{_dfrd}->{kamailio_trusted_reload} += 1;
}
}
@ -1020,52 +1085,87 @@ sub _calculate_field {
sub _calculate {
my ($context,$f,$c) = @_;
$context->{cr_c} //= {};
if ($f =~ /^([a-z0-9_]+)$CODE_SUFFIX_FNAME$/) {
my $cl;
my $env = 'use subs qw(' . join(' ', @DISABLED_CORE_FUNCTIONS) . ");\n";
foreach my $sub (@DISABLED_CORE_FUNCTIONS) {
$env .= 'sub ' . $sub . " { die('$sub called'); }\n";
}
if ($strict_closure) {
$cl = eval_closure(
source => ($env . $c),
environment => {
map { if ('ARRAY' eq ref $context->{$_}) {
('@' . $_) => $context->{$_};
} elsif ('HASH' eq ref $context->{$_}) {
('%' . $_) => $context->{$_};
} elsif ('CODE' eq ref $context->{$_}) {
('&' . $_) => $context->{$_};
} elsif (ref $context->{$_}) {
('$' . $_) => \$context->{$_};
} else {
('$' . $_) => \$context->{$_};
} } keys %$context
},
terse_error => 0,
description => $f,
alias => 0,
);
} else {
if (exists $context->{cr_c}->{$c}) {
$cl = $context->{cr_c}->{$c};
} else {
## no critic (BuiltinFunctions::ProhibitStringyEval)
#$context->{cr_c}->{$c} = eval(decode_base64($c));
$cl = eval($env . $c);
$context->{cr_c}->{$c} = $cl;
return _switch_lang(
$context,
$context->{_lang},
perl => sub {
my $cl;
if ($STRICT_CLOSURE) {
$cl = eval_closure(
source => ($PERL_ENV . $c),
environment => {
map { if ('ARRAY' eq ref $context->{$_}) {
('@' . $_) => $context->{$_};
} elsif ('HASH' eq ref $context->{$_}) {
('%' . $_) => $context->{$_};
} elsif ($JE_ANON_CLASS eq ref $context->{$_}) {
('%' . $_) => _unbless($context->{$_});
} elsif ('CODE' eq ref $context->{$_}) {
('&' . $_) => $context->{$_};
} elsif (ref $context->{$_}) {
('$' . $_) => \$context->{$_};
} else {
('$' . $_) => \$context->{$_};
} } grep { substr($_,0,1) ne '_'; } keys %$context
},
terse_error => 0,
description => $f,
alias => 0,
);
} else {
$context->{_cr_c} //= {};
if (exists $context->{_cr_c}->{$c}) {
$cl = $context->{_cr_c}->{$c};
} else {
## no critic (BuiltinFunctions::ProhibitStringyEval)
$cl = eval($PERL_ENV . $c);
$context->{_cr_c}->{$c} = $cl;
}
}
die("$f: " . $@) if $@;
my $v;
eval {
$v = $cl->($context);
$v = _unbless($v) if ($v and $JE_ANON_CLASS eq ref $v);
};
if ($@) {
die("$f: " . $@);
}
return ($1 => $v);
},
js => sub {
$context->{_je}->eval(join (";\n",
map { if ('CODE' eq ref $context->{$_}) {
die('no coderefs allowed');
} elsif (('ARRAY' eq ref $context->{$_})
or ('HASH' eq ref $context->{$_})
or ($JE_ANON_CLASS eq ref $context->{$_})) {
if ($context->{_je_env}->{$_}) {
$_ . ' = ' . _to_json($context->{$_});
} else { $context->{_je_env}->{$_} = 1;
'var ' . $_ . ' = ' . _to_json($context->{$_}); }
} elsif (ref $context->{$_}) {
die('no refs allowed');
} else { if ($context->{_je_env}->{$_}) {
$_ . " = '" . escape_js($context->{$_}) . "'";
} else { $context->{_je_env}->{$_} = 1;
'var ' . $_ . " = '" . escape_js($context->{$_}) . "'"; }
} } grep { substr($_,0,1) ne '_'; } keys %$context) .
";\n_func = $c;");
die("$f: " . $@) if $@;
my $v;
eval {
$v = _unbox_je_value($context->{_je}->eval('_func();'));
};
if ($@) {
die("$f: " . $@);
}
return ($1 => $v);
}
}
die("$f: " . $@) if $@;
my $v;
eval {
$v = $cl->($context);
};
if ($@) {
die("$f: " . $@);
}
return ($1 => $v);
);
} elsif ('HASH' eq ref $c) {
my %data = ();
foreach my $col (keys %$c) {
@ -1088,6 +1188,36 @@ sub _calculate {
}
sub _unbox_je_value {
my $v = shift;
return unless defined $v;
if ((ref $v) =~ /^JE::/) {
$v = $v->value;
} elsif ($JE_ANON_CLASS eq ref $v) {
$v = _unbless($v);
}
if ('ARRAY' eq ref $v) {
return [ map { _unbox_je_value($_); } @$v ];
} elsif ('HASH' eq ref $v) {
return { map { $_ => _unbox_je_value($v->{$_}); } keys %$v };
} else {
return $v;
}
}
sub _unbless {
my $obj = shift;
return { %$obj }; #unbless
};
sub _to_json {
return JSON::to_json(shift, {
allow_nonref => 1, allow_blessed => 1,
convert_blessed => 1, pretty => 0 });
}
sub _generate_username {
my ($length, @chars) = @_;
@ -1172,4 +1302,15 @@ sub _get_identifiers {
}
sub _switch_lang {
my ($context, $lang, %code) = @_;
die('template lang not defined') unless $lang;
die("unknown template lang '$lang'") unless exists $code{$lang};
die("template lang '$lang' not supported") unless grep { $_ eq $lang} @SUPPORTED_LANGS;
return &{$code{$lang}}($context);
}
1;

Loading…
Cancel
Save