package PgCommitFest::Request; require CGI::Fast; require PgCommitFest::DB; require PgCommitFest::WebControl; require Template; require Template::Plugin::HTML; use strict; use warnings; die "Must set PGCOMMITFEST_ROOT to root directory of installation!\n" if !defined $ENV{'PGCOMMITFEST_ROOT'} || ! -d $ENV{'PGCOMMITFEST_ROOT'}; our $ROOT = $ENV{'PGCOMMITFEST_ROOT'}; our $template = Template->new({ 'INCLUDE_PATH' => $ROOT . '/template', 'FILTERS' => { 'htmlsafe' => \&PgCommitFest::WebControl::escape } }); our $PG = defined $ENV{'PGCOMMITFEST_DB'} ? $ENV{'PGCOMMITFEST_DB'} : 'dbi:Pg:dbname=pgcommitfest user=pgcommitfest'; our $PGUSERNAME = ''; our $PGPASSWORD = ''; $CGI::POST_MAX = 65536; $CGI::DISABLE_UPLOADS = 1; # No need for uploads at present. sub new { my ($class, $db) = @_; my $cgi = CGI::Fast->new(); return undef if !defined $cgi; bless { 'cgi' => $cgi, 'control' => {}, 'control_list' => [], 'db' => undef, 'error_list' => [], 'header' => { 'Content-type' => 'text/html; charset=utf-8', 'Cache-Control' => 'no-cache', 'Pragma' => 'no-cache', }, 'link' => [], 'response_sent' => 0, 'rss_alternate' => undef, 'title' => '', }, $class; } sub add_link { my ($self, $url, $name, $confirm) = @_; push @{$self->{'link'}}, [ $url, $name, $confirm ]; } sub add_control { my ($self, $name, $type, $display_name, %args) = @_; my $control = PgCommitFest::WebControl->new($name, $type, $display_name, %args); push @{$self->{'control_list'}}, $control; $self->{'control'}{$name} = $control; } sub authenticate { my ($self, %option) = @_; if (!defined $self->{'authenticate'} && defined $self->cookie('session')) { $self->{'authenticate'} = $self->db->select_one(<cookie('session')); SELECT s.*, p.is_administrator FROM session s LEFT JOIN user_privilege p ON s.userid = p.userid WHERE s.id = ? EOM } if (!defined $self->{'authenticate'} && $option{'require_login'}) { if ($ENV{'REQUEST_METHOD'} eq 'GET') { my $uri = $ENV{'REQUEST_URI'}; $uri =~ s/[^A-Za-z0-9]/sprintf "%%%x", ord($&)/ge; $self->redirect('/action/login?uri=' . $uri); } $self->redirect('/action/login'); } if (defined $self->{'authenticate'} && $option{'require_administrator'} && ! $self->{'authenticate'}{'is_administrator'}) { $self->error_exit(<{'authenticate'}; } sub cgi { my ($self, $n) = @_; return scalar($self->{'cgi'}->param($n)); } sub cgi_id { my ($self, $n) = @_; $n = 'id' if !defined $n; my $v = $self->cgi($n); if (defined $v && $v !~ /^\d+$/) { $self->error_exit('Invalid parameter.'); } return $v; } sub cgi_required_id { my ($self, $n) = @_; $n = 'id' if !defined $n; my $v = $self->cgi($n); if (!defined $v || $v !~ /^\d+$/) { $self->error_exit('Invalid parameter.'); } return $v; } sub cookie { my ($self, $n) = @_; return scalar($self->{'cgi'}->cookie($n)); } sub control { my ($self, $n) = @_; return $self->{'control'}{$n}; } sub db { my ($self) = @_; return $self->{'db'} if ref $self && defined $self->{'db'}; my $db = PgCommitFest::DB->connect($PG, $PGUSERNAME, $PGPASSWORD); $self->{'db'} = $db if ref $self; return $db; } sub db_is_connected { my ($self) = @_; defined $self->{'db'}; } sub error { my ($self, $fmt, @arg) = @_; push @{$self->{'error_list'}}, sprintf($fmt, @arg); } sub error_exit { my ($self, $error) = @_; $self->render_template('error', { 'error_list' => [ $error ] }); die "DONE\n"; } sub eval_template { my ($self, $name, $stash) = @_; my $content; $template->process($name . '.tt2', $stash, \$content) || die $template->error(); return $content; } sub generate_headers { my ($self) = @_; my @header; while (my ($header, $value) = each %{$self->{'header'}}) { push @header, "$header: $value"; } return join("\r\n", @header) . "\r\n\r\n"; } sub header { my ($self, $header, $value) = @_; $self->{'header'}{$header} = $value; } sub is_error { my ($self) = @_; return (@{$self->{'error_list'}} != 0); } sub initialize_controls { my ($self, $default) = @_; my %value; # It's important that we process these controls in the same order that they # were added, so that the user doesn't see the error messages in a funny # order. for my $control (@{$self->{'control_list'}}) { my $n = $control->name; $n .= '_id' if $control->istype('select'); $value{$n} = $control->set($self, $default); } return %value; } sub redirect { my ($self, $url) = @_; $self->header('Status', 302); $self->header('Location', $url); print $self->generate_headers(); $self->{'response_sent'} = 1; die "DONE\n"; } sub render_template { my ($self, $file, $vars) = @_; # Generate data. We generate the whole response before sending it, # because an error at this stage is possible, but it's hard to do anything # reasonable if we've already begun sending the response back to Apache. my %stash; my $content = $self->generate_headers(); my $uri = ''; my $scriptname = $ENV{'REQUEST_URI'}; $scriptname =~ s/\?.*//; if ($ENV{'REQUEST_METHOD'} eq 'GET') { if ($scriptname ne '/action/login') { my $req = $ENV{'REQUEST_URI'}; $req =~ s/[^A-Za-z0-9]/sprintf "%%%x", ord($&)/ge;; $uri = '?uri=' . $req; } else { $uri = '?' . $ENV{'QUERY_STRING'}; } } %stash = %$vars if defined $vars; $stash{'authenticate'} = $self->authenticate(); $stash{'control'} = $self->{'control'}; $template->process('header.tt2', { 'authenticate' => $self->authenticate(), 'link' => $self->{'link'}, 'rss_alternate' => $self->{'rss_alternate'}, 'title' => $self->{'title'}, 'error_list' => $self->{'error_list'}, 'script_name' => $scriptname, 'uri' => $uri, }, \$content) || die $template->error(); $template->process($file . '.tt2', \%stash, \$content) || die $template->error(); $template->process('footer.tt2', {}, \$content) || die $template->error(); # Send the results, unless we already did. die 'response already sent' if $self->{'response_sent'}; $self->{'response_sent'} = 1; print $content; } sub response_sent { my ($self) = @_; $self->{'response_sent'}; } sub set_rss_alternate { my ($self, $name, $url) = @_; $self->{'rss_alternate'} = { 'name' => $name, 'url' => $url }; } sub set_title { my ($self, $fmt, @args) = @_; $self->{'title'} = sprintf($fmt, @args); } 1;