summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-08-26 07:55:24 +0200
committerDylan William Hardison <dylan@hardison.net>2018-08-26 07:55:24 +0200
commit9263f397e701f25af395e8cdee48c87ee3327157 (patch)
treecc7f6b6beef8947090a108701ca34316a5c8edb8 /Bugzilla
parent23b94e8410d90e9e15584d3a9220b6bb214f4220 (diff)
parentd57aefa118802606ea7cc424aaa62173be9eec41 (diff)
downloadbugzilla-9263f397e701f25af395e8cdee48c87ee3327157.tar.gz
bugzilla-9263f397e701f25af395e8cdee48c87ee3327157.tar.xz
Merge remote-tracking branch 'bmo/mojo'
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/CGI.pm2
-rw-r--r--Bugzilla/Config.pm37
-rw-r--r--Bugzilla/DB/Sqlite.pm2
-rw-r--r--Bugzilla/Install/Filesystem.pm52
-rw-r--r--Bugzilla/Install/Localconfig.pm10
-rw-r--r--Bugzilla/Quantum.pm4
-rw-r--r--Bugzilla/Quantum/SES.pm79
-rw-r--r--Bugzilla/Quantum/Static.pm2
-rw-r--r--Bugzilla/Quantum/Stdout.pm3
-rw-r--r--Bugzilla/Test/MockDB.pm120
-rw-r--r--Bugzilla/Test/MockLocalconfig.pm18
-rw-r--r--Bugzilla/Test/MockParams.pm71
-rw-r--r--Bugzilla/Test/Util.pm2
-rw-r--r--Bugzilla/Types.pm27
-rw-r--r--Bugzilla/WebService/BugUserLastVisit.pm2
15 files changed, 339 insertions, 92 deletions
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index 9ac01c71e..ae997a5fe 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -39,7 +39,7 @@ sub DEFAULT_CSP {
script_src => [ 'self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com' ],
frame_src => [ 'none', ],
worker_src => [ 'none', ],
- img_src => [ 'self', 'https://secure.gravatar.com' ],
+ img_src => [ 'self', 'blob:', 'https://secure.gravatar.com' ],
style_src => [ 'self', 'unsafe-inline' ],
object_src => [ 'none' ],
connect_src => [
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index d050ff9e0..85779fa6b 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -251,28 +251,11 @@ sub write_params {
my ($param_data) = @_;
$param_data ||= Bugzilla->params;
- my $datadir = bz_locations()->{'datadir'};
- my $param_file = "$datadir/params";
-
local $Data::Dumper::Sortkeys = 1;
- my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
- DIR => $datadir );
-
my %params = %$param_data;
$params{urlbase} = Bugzilla->localconfig->{urlbase};
- print $fh (Data::Dumper->Dump([\%params], ['*param']))
- || die "Can't write param file: $!";
-
- close $fh;
-
- rename $tmpname, $param_file
- or die "Can't rename $tmpname to $param_file: $!";
-
- # It's not common to edit parameters and loading
- # Bugzilla::Install::Filesystem is slow.
- require Bugzilla::Install::Filesystem;
- Bugzilla::Install::Filesystem::fix_file_permissions($param_file);
+ __PACKAGE__->_write_file( Data::Dumper->Dump([\%params], ['*param']) );
# And now we have to reset the params cache so that Bugzilla will re-read
# them.
@@ -311,6 +294,24 @@ sub read_param_file {
return \%params;
}
+sub _write_file {
+ my ($class, $str) = @_;
+ my $datadir = bz_locations()->{'datadir'};
+ my $param_file = "$datadir/params";
+ my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
+ DIR => $datadir );
+ print $fh $str || die "Can't write param file: $!";
+ close $fh || die "Can't close param file: $!";
+
+ rename $tmpname, $param_file
+ or die "Can't rename $tmpname to $param_file: $!";
+
+ # It's not common to edit parameters and loading
+ # Bugzilla::Install::Filesystem is slow.
+ require Bugzilla::Install::Filesystem;
+ Bugzilla::Install::Filesystem::fix_file_permissions($param_file);
+}
+
1;
__END__
diff --git a/Bugzilla/DB/Sqlite.pm b/Bugzilla/DB/Sqlite.pm
index 3890d0795..81ee7d888 100644
--- a/Bugzilla/DB/Sqlite.pm
+++ b/Bugzilla/DB/Sqlite.pm
@@ -73,7 +73,7 @@ sub BUILDARGS {
my $db_name = $params->{db_name};
# Let people specify paths intead of data/ for the DB.
- if ($db_name and $db_name !~ m{[\\/]}) {
+ if ($db_name && $db_name ne ':memory:' && $db_name !~ m{[\\/]}) {
# When the DB is first created, there's a chance that the
# data directory doesn't exist at all, because the Install::Filesystem
# code happens after DB creation. So we create the directory ourselves
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index 46e121779..1da33882b 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -67,55 +67,6 @@ use constant HTTPD_ENV => qw(
NYTPROF_DIR
);
-sub HTTPD_ENV_CONF {
- my @env = (ENV_KEYS, HTTPD_ENV);
- return join( "\n", map { "PerlPassEnv " . $_ } @env ) . "\n";
-}
-
-sub _error_page {
- my ($code, $title, $description) = @_;
-
- return <<EOT;
-<!DOCTYPE HTML>
-<html>
- <head>
- <title>$title</title>
- <style>
- body {
- margin: 1em 2em;
- background-color: #455372;
- color: #ddd;
- font-family: sans-serif;
- }
- h1, h3 {
- color: #fff;
- }
- a {
- color: #fff;
- text-decoration: none;
- }
- #buggie {
- float: left;
- }
- #content {
- margin-left: 100px;
- padding-top: 20px;
- }
- </style>
- </head>
- <body>
- <img src="/images/buggie.png" id="buggie" alt="buggie" width="78" height="215">
- <div id="content">
- <h1>$title</h1>
- <p>$description</p>
- <h3>Error $code</h3>
- <p><a href="/">this site</a></p>
- </div>
- </body>
-</html>
-EOT
-}
-
###############
# Permissions #
###############
@@ -427,9 +378,6 @@ sub FILESYSTEM {
"skins/yui3.css" => { perms => CGI_READ,
overwrite => 1,
contents => $yui3_all_css },
- "$confdir/env.conf" => { perms => CGI_READ,
- overwrite => 1,
- contents => \&HTTPD_ENV_CONF },
);
# Create static error pages.
diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm
index 39063ee63..ac21a0cb7 100644
--- a/Bugzilla/Install/Localconfig.pm
+++ b/Bugzilla/Install/Localconfig.pm
@@ -186,7 +186,15 @@ use constant LOCALCONFIG_VARS => (
{
name => 'shadowdb_pass',
default => '',
- }
+ },
+ {
+ name => 'datadog_host',
+ default => '',
+ },
+ {
+ name => 'datadog_port',
+ default => 8125,
+ },
);
diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm
index 8d46833c4..03dfcf0d0 100644
--- a/Bugzilla/Quantum.pm
+++ b/Bugzilla/Quantum.pm
@@ -61,10 +61,12 @@ sub startup {
Bugzilla::WebService::Server::REST->preload;
$r->any('/')->to('CGI#index_cgi');
+ $r->any('/bug/<id:num>')->to('CGI#show_bug_cgi');
+ $r->any('/<id:num>')->to('CGI#show_bug_cgi');
+
$r->any('/rest')->to('CGI#rest_cgi');
$r->any('/rest.cgi/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
$r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
- $r->any('/bug/:id')->to('CGI#show_bug_cgi');
$r->any('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi');
$r->get(
diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm
index 47c591fb5..03916075d 100644
--- a/Bugzilla/Quantum/SES.pm
+++ b/Bugzilla/Quantum/SES.pm
@@ -18,8 +18,25 @@ use JSON::MaybeXS qw(decode_json);
use LWP::UserAgent ();
use Try::Tiny qw(catch try);
+use Types::Standard qw( :all );
+use Type::Utils;
+use Type::Params qw( compile );
+
+my $Invocant = class_type { class => __PACKAGE__ };
+
sub main {
my ($self) = @_;
+ try {
+ $self->_main;
+ }
+ catch {
+ FATAL("Error in SES Handler: ", $_);
+ $self->_respond( 400 => 'Bad Request' );
+ };
+}
+
+sub _main {
+ my ($self) = @_;
Bugzilla->error_mode(ERROR_MODE_DIE);
my $message = $self->_decode_json_wrapper( $self->req->body ) // return;
my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') // '(missing)';
@@ -50,7 +67,8 @@ sub main {
}
sub _confirm_subscription {
- my ($self, $message) = @_;
+ state $check = compile($Invocant, Dict[SubscribeURL => Str, slurpy Any]);
+ my ($self, $message) = $check->(@_);
my $subscribe_url = $message->{SubscribeURL};
if ( !$subscribe_url ) {
@@ -70,8 +88,17 @@ sub _confirm_subscription {
$self->_respond( 200 => 'OK' );
}
+my $NotificationType = Enum [qw( Bounce Complaint )];
+my $TypeField = Enum [qw(eventType notificationType)];
+my $Notification = Dict [
+ eventType => Optional [$NotificationType],
+ notificationType => Optional [$NotificationType],
+ slurpy Any,
+];
+
sub _handle_notification {
- my ( $self, $notification, $type_field ) = @_;
+ state $check = compile($Invocant, $Notification, $TypeField );
+ my ( $self, $notification, $type_field ) = $check->(@_);
if ( !exists $notification->{$type_field} ) {
return 0;
@@ -91,8 +118,28 @@ sub _handle_notification {
return 1;
}
+my $BouncedRecipients = ArrayRef[
+ Dict[
+ emailAddress => Str,
+ action => Str,
+ diagnosticCode => Str,
+ slurpy Any,
+ ],
+];
+my $BounceNotification = Dict [
+ bounce => Dict [
+ bouncedRecipients => $BouncedRecipients,
+ reportingMTA => Str,
+ bounceSubType => Str,
+ bounceType => Str,
+ slurpy Any,
+ ],
+ slurpy Any,
+];
+
sub _process_bounce {
- my ($self, $notification) = @_;
+ state $check = compile($Invocant, $BounceNotification);
+ my ($self, $notification) = $check->(@_);
# disable each account that is bouncing
foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
@@ -132,11 +179,19 @@ sub _process_bounce {
$self->_respond( 200 => 'OK' );
}
-sub _process_complaint {
- my ($self) = @_;
+my $ComplainedRecipients = ArrayRef[Dict[ emailAddress => Str, slurpy Any ]];
+my $ComplaintNotification = Dict[
+ complaint => Dict [
+ complainedRecipients => $ComplainedRecipients,
+ complaintFeedbackType => Str,
+ slurpy Any,
+ ],
+ slurpy Any,
+];
- # email notification to bugzilla admin
- my ($notification) = @_;
+sub _process_complaint {
+ state $check = compile($Invocant, $ComplaintNotification);
+ my ($self, $notification) = $check->(@_);
my $template = Bugzilla->template_inner();
my $json = JSON::MaybeXS->new(
pretty => 1,
@@ -169,13 +224,9 @@ sub _respond {
}
sub _decode_json_wrapper {
- my ($self, $json) = @_;
+ state $check = compile($Invocant, Str);
+ my ($self, $json) = $check->(@_);
my $result;
- if ( !defined $json ) {
- WARN( 'Missing JSON from ' . $self->tx->remote_address );
- $self->_respond( 400 => 'Bad Request' );
- return undef;
- }
my $ok = try {
$result = decode_json($json);
}
@@ -200,4 +251,4 @@ sub ua {
return $ua;
}
-1; \ No newline at end of file
+1;
diff --git a/Bugzilla/Quantum/Static.pm b/Bugzilla/Quantum/Static.pm
index d687873ab..c01f062a4 100644
--- a/Bugzilla/Quantum/Static.pm
+++ b/Bugzilla/Quantum/Static.pm
@@ -11,7 +11,7 @@ use Bugzilla::Constants qw(bz_locations);
my $LEGACY_RE = qr{
^ (?:static/v[0-9]+\.[0-9]+/) ?
- ( (?:extensions/[^/]+/web|(?:image|skin|j)s)/.+)
+ ( (?:extensions/[^/]+/web|(?:image|graph|skin|j)s)/.+)
$
}xs;
diff --git a/Bugzilla/Quantum/Stdout.pm b/Bugzilla/Quantum/Stdout.pm
index be7b546ea..9cf19992c 100644
--- a/Bugzilla/Quantum/Stdout.pm
+++ b/Bugzilla/Quantum/Stdout.pm
@@ -11,6 +11,7 @@ use Moo;
use Bugzilla::Logging;
use Encode;
+use English qw(-no_match_vars);
has 'controller' => (
is => 'ro',
@@ -41,7 +42,7 @@ sub PRINT { ## no critic (unpack)
if ( $self->_encoding ) {
$bytes = encode( $self->_encoding, $bytes );
}
- $c->write($bytes.$\);
+ $c->write($bytes . ( $OUTPUT_RECORD_SEPARATOR // '' ) );
}
sub BINMODE {
diff --git a/Bugzilla/Test/MockDB.pm b/Bugzilla/Test/MockDB.pm
new file mode 100644
index 000000000..fb7873ccf
--- /dev/null
+++ b/Bugzilla/Test/MockDB.pm
@@ -0,0 +1,120 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+package Bugzilla::Test::MockDB;
+use 5.10.1;
+use strict;
+use warnings;
+use Try::Tiny;
+use Capture::Tiny qw(capture_merged);
+
+use Bugzilla::Test::MockLocalconfig (
+ db_driver => 'sqlite',
+ db_name => ':memory:',
+);
+use Bugzilla;
+BEGIN { Bugzilla->extensions };
+use Bugzilla::Test::MockParams (
+ emailsuffix => '',
+ emailregexp => '.+',
+);
+
+sub import {
+ require Bugzilla::Install;
+ require Bugzilla::Install::DB;
+ require Bugzilla::Field;;
+
+ state $first_time = 0;
+
+ return undef if $first_time++;
+
+ return capture_merged {
+ Bugzilla->dbh->bz_setup_database();
+
+ # Populate the tables that hold the values for the <select> fields.
+ Bugzilla->dbh->bz_populate_enum_tables();
+
+ Bugzilla::Install::DB::update_fielddefs_definition();
+ Bugzilla::Field::populate_field_definitions();
+ Bugzilla::Install::init_workflow();
+ Bugzilla::Install::DB->update_table_definitions({});
+ Bugzilla::Install::update_system_groups();
+
+ Bugzilla->set_user(Bugzilla::User->super_user);
+
+ Bugzilla::Install::update_settings();
+
+ my $dbh = Bugzilla->dbh;
+ if ( !$dbh->selectrow_array("SELECT 1 FROM priority WHERE value = 'P1'") ) {
+ $dbh->do("DELETE FROM priority");
+ my $count = 100;
+ foreach my $priority (map { "P$_" } 1..5) {
+ $dbh->do( "INSERT INTO priority (value, sortkey) VALUES (?, ?)", undef, ( $priority, $count + 100 ) );
+ }
+ }
+ my @flagtypes = (
+ {
+ name => 'review',
+ desc => 'The patch has passed review by a module owner or peer.',
+ is_requestable => 1,
+ is_requesteeble => 1,
+ is_multiplicable => 1,
+ grant_group => '',
+ target_type => 'a',
+ cc_list => '',
+ inclusions => ['']
+ },
+ {
+ name => 'feedback',
+ desc => 'A particular person\'s input is requested for a patch, ' .
+ 'but that input does not amount to an official review.',
+ is_requestable => 1,
+ is_requesteeble => 1,
+ is_multiplicable => 1,
+ grant_group => '',
+ target_type => 'a',
+ cc_list => '',
+ inclusions => ['']
+ }
+ );
+
+ foreach my $flag (@flagtypes) {
+ next if Bugzilla::FlagType->new({ name => $flag->{name} });
+ my $grant_group_id = $flag->{grant_group}
+ ? Bugzilla::Group->new({ name => $flag->{grant_group} })->id
+ : undef;
+ my $request_group_id = $flag->{request_group}
+ ? Bugzilla::Group->new({ name => $flag->{request_group} })->id
+ : undef;
+
+ $dbh->do('INSERT INTO flagtypes (name, description, cc_list, target_type, is_requestable,
+ is_requesteeble, is_multiplicable, grant_group_id, request_group_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
+ undef, ($flag->{name}, $flag->{desc}, $flag->{cc_list}, $flag->{target_type},
+ $flag->{is_requestable}, $flag->{is_requesteeble}, $flag->{is_multiplicable},
+ $grant_group_id, $request_group_id));
+
+ my $type_id = $dbh->bz_last_key('flagtypes', 'id');
+
+ foreach my $inclusion (@{$flag->{inclusions}}) {
+ my ($product, $component) = split(':', $inclusion);
+ my ($prod_id, $comp_id);
+ if ($product) {
+ my $prod_obj = Bugzilla::Product->new({ name => $product });
+ $prod_id = $prod_obj->id;
+ if ($component) {
+ $comp_id = Bugzilla::Component->new({ name => $component, product => $prod_obj})->id;
+ }
+ }
+ $dbh->do('INSERT INTO flaginclusions (type_id, product_id, component_id)
+ VALUES (?, ?, ?)',
+ undef, ($type_id, $prod_id, $comp_id));
+ }
+ }
+ };
+}
+
+1;
diff --git a/Bugzilla/Test/MockLocalconfig.pm b/Bugzilla/Test/MockLocalconfig.pm
new file mode 100644
index 000000000..a32aea0d4
--- /dev/null
+++ b/Bugzilla/Test/MockLocalconfig.pm
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+package Bugzilla::Test::MockLocalconfig;
+use 5.10.1;
+use strict;
+use warnings;
+
+sub import {
+ my ($class, %lc) = @_;
+ $ENV{LOCALCONFIG_ENV} = 'BMO';
+ $ENV{"BMO_$_"} = $lc{$_} for keys %lc;
+}
+
+1;
diff --git a/Bugzilla/Test/MockParams.pm b/Bugzilla/Test/MockParams.pm
new file mode 100644
index 000000000..2d064c616
--- /dev/null
+++ b/Bugzilla/Test/MockParams.pm
@@ -0,0 +1,71 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+package Bugzilla::Test::MockParams;
+use 5.10.1;
+use strict;
+use warnings;
+use Try::Tiny;
+use Capture::Tiny qw(capture_merged);
+use Test2::Tools::Mock qw(mock);
+
+use Bugzilla::Config;
+use Safe;
+
+our $Params;
+BEGIN {
+ our $Mock = mock 'Bugzilla::Config' => (
+ override => [
+ 'read_param_file' => sub {
+ my ($class) = @_;
+ return {} unless $Params;
+ my $s = Safe->new;
+ $s->reval($Params);
+ die "Error evaluating params: $@" if $@;
+ return { %{ $s->varglob('param') } };
+ },
+ '_write_file' => sub {
+ my ($class, $str) = @_;
+ $Params = $str;
+ },
+ ],
+ );
+}
+
+sub import {
+ my ($self, %answers) = @_;
+ state $first_time = 0;
+
+ require Bugzilla::Field;
+ require Bugzilla::Status;
+ require Bugzilla;
+ my $Bugzilla = mock 'Bugzilla' => (
+ override => [
+ installation_answers => sub { \%answers },
+ ],
+ );
+ my $BugzillaField = mock 'Bugzilla::Field' => (
+ override => [
+ get_legal_field_values => sub { [] },
+ ],
+ );
+ my $BugzillaStatus = mock 'Bugzilla::Status' => (
+ override => [
+ closed_bug_statuses => sub { die "no database" },
+ ],
+ );
+
+ if ($first_time++) {
+ capture_merged {
+ Bugzilla::Config::update_params();
+ };
+ }
+ else {
+ Bugzilla::Config::SetParam($_, $answers{$_}) for keys %answers;
+ }
+}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Test/Util.pm b/Bugzilla/Test/Util.pm
index 4c9981e52..02c842658 100644
--- a/Bugzilla/Test/Util.pm
+++ b/Bugzilla/Test/Util.pm
@@ -24,7 +24,7 @@ sub create_user {
cryptpassword => $password,
disabledtext => "",
disable_mail => 0,
- extern_id => 0,
+ extern_id => undef,
%extra,
});
}
diff --git a/Bugzilla/Types.pm b/Bugzilla/Types.pm
new file mode 100644
index 000000000..93d699f49
--- /dev/null
+++ b/Bugzilla/Types.pm
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Types;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use Type::Library
+ -base,
+ -declare => qw( Bug User Group Attachment Comment JSONBool );
+use Type::Utils -all;
+use Types::Standard -types;
+
+class_type Bug, { class => 'Bugzilla::Bug' };
+class_type User, { class => 'Bugzilla::User' };
+class_type Group, { class => 'Bugzilla::Group' };
+class_type Attachment, { class => 'Bugzilla::Attachment' };
+class_type Comment, { class => 'Bugzilla::Comment' };
+class_type JSONBool, { class => 'JSON::PP::Boolean' };
+
+1;
diff --git a/Bugzilla/WebService/BugUserLastVisit.pm b/Bugzilla/WebService/BugUserLastVisit.pm
index 7b729c6c8..5e4c0d2ba 100644
--- a/Bugzilla/WebService/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/BugUserLastVisit.pm
@@ -52,7 +52,7 @@ sub update {
push(
@results,
$self->_bug_user_last_visit_to_hash(
- $bug, $last_visit_ts, $params
+ $bug_id, $last_visit_ts, $params
));
}
$dbh->bz_commit_transaction();