webgui/lib/WebGUI/Asset/Wobject/Poll.pm
2011-12-28 11:30:38 -08:00

570 lines
16 KiB
Perl

package WebGUI::Asset::Wobject::Poll;
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2012 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use strict;
use List::Util;
use WebGUI::Form;
use WebGUI::International;
use WebGUI::SQL;
use WebGUI::User;
use WebGUI::Asset::Wobject;
use WebGUI::Image::Graph;
use WebGUI::Storage;
use JSON;
use Try::Tiny;
use Moose;
use WebGUI::Definition::Asset;
extends 'WebGUI::Asset::Wobject';
define assetName => ['assetName', 'Asset_Poll'];
define tableName => 'Poll';
define icon => 'poll.gif';
property templateId => (
tab => 'display',
fieldType => "template",
default => 'PBtmpl0000000000000055',
label => [73, 'Asset_Poll'],
hoverHelp => ['73 description', 'Asset_Poll'],
namespace => "Poll",
);
property active => (
tab => 'properties',
fieldType => "yesNo",
default => 1,
label => [3, 'Asset_Poll'],
hoverHelp => ['3 description', 'Asset_Poll'],
);
property karmaPerVote => (
fieldType => 'integer',
noFormPost => \&_karmaPerVote_noFormPost,
default => 0,
label => [20, 'Asset_Poll'],
hoverHelp => ['20 description', 'Asset_Poll'],
);
sub _karmaPerVote_noFormPost {
my $self = shift;
return ! $self->session->setting->get('useKarma');
}
property graphWidth => (
fieldType => "integer",
default => 150,
label => [5, 'Asset_Poll'],
hoverHelp => ['5 description', 'Asset_Poll'],
);
property voteGroup => (
tab => 'security',
fieldType => "group",
default => 7,
label => [4, 'Asset_Poll'],
hoverHelp => ['4 description', 'Asset_Poll'],
);
property question => (
tab => 'properties',
fieldType => "text",
default => undef,
label => [6, 'Asset_Poll'],
hoverHelp => ['6 description', 'Asset_Poll'],
);
property randomizeAnswers => (
tab => 'properties',
fieldType => "yesNo",
default => 1,
label => [72, 'Asset_Poll'],
hoverHelp => ['72 description', 'Asset_Poll'],
);
property a1 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a2 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a3 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a4 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a5 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a6 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a7 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a8 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a9 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a10 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a11 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a12 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a13 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a14 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a15 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a16 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a17 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a18 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a19 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property a20 => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property graphConfiguration => (
fieldType => "hidden",
default => undef,
noFormPost => 1,
);
property generateGraph => (
noFormPost => \&_generateGraph_noFormPost,
fieldType => 'yesNo',
default => 0,
label => ['generate graph', 'Asset_Poll'],
hoverHelp => ['generate graph description', 'Asset_Poll'],
);
sub _generateGraph_noFormPost {
my $self = shift;
return WebGUI::Image::Graph->getPluginList($self->session) ? 1 : 0;
}
#-------------------------------------------------------------------
sub _hasVoted {
my $self = shift;
my ($hasVoted) = $self->session->db->quickArray("select count(*) from Poll_answer
where assetId=".$self->session->db->quote($self->getId)." and ((userId=".$self->session->db->quote($self->session->user->userId)."
and userId<>'1') or (userId=".$self->session->db->quote($self->session->user->userId)." and ipAddress='".$self->session->request->address."'))");
return $hasVoted;
}
#-------------------------------------------------------------------
=head2 duplicate
Extend the base method to handle copying Poll answer data.
=cut
override duplicate => sub {
my $self = shift;
my $newAsset = super();
my $sth = $self->session->db->read("select * from Poll_answer where assetId=?", [$self->getId]);
while (my $data = $sth->hashRef) {
$newAsset->setVote($data->{answer}, $data->{userId}, $data->{ipAddress});
}
$sth->finish;
return $newAsset;
};
#----------------------------------------------------------------------------
=head2 freezeGraphConfig
Serializes graph configuration. Returns a scalar containing the serialized
structure.
=cut
sub freezeGraphConfig {
my $self = shift;
my $obj = shift;
return JSON::to_json($obj);
}
#-------------------------------------------------------------------
=head2 getEditForm
Extend the base class to handle the answers and graphing plugins.
=cut
##TODO: Pull out all form elements which can come from the definition sub
##and only have hand code in here.
override getEditForm => sub {
my $self = shift;
my $fb = super();
my $i18n = WebGUI::International->new($self->session,"Asset_Poll");
my ($i, $answers);
for ($i=1; $i<=20; $i++) {
if ($self->get('a'.$i) =~ /\C/) {
$answers .= $self->get("a".$i)."\n";
}
}
$fb->getTab("properties")->addField( "textarea",
name=>"answers",
label=>$i18n->get(7),
hoverHelp=>$i18n->get('7 description'),
subtext=>('<span class="formSubtext"><br />'.$i18n->get(8).'</span>'),
value=>$answers
);
$fb->getTab("properties")->addField( "YesNo",
name=>"resetVotes",
label=>$i18n->get(10),
hoverHelp=>$i18n->get('10 description')
) if $self->session->form->process("func") ne 'add';
if (WebGUI::Image::Graph->getPluginList($self->session)) {
my $config = $self->getGraphConfig;
my $graphTab = $fb->addTab(name => 'graph', label => $i18n->get('Graphing','Image_Graph'));
$graphTab->addField( "yesNo",
name => 'generateGraph',
label => $i18n->get('generate graph'),
hoverHelp => $i18n->get('generate graph description'),
value => $self->generateGraph,
);
WebGUI::Image::Graph->getGraphingTab($graphTab, $config)
}
return $fb;
};
#----------------------------------------------------------------------------
=head2 getGraphConfig
Gets and thaws the graph configuration. Returns a reference to the original
data structure.
=cut
sub getGraphConfig {
my $self = shift;
my $config = $self->get("graphConfiguration");
return undef unless $config;
return $self->thawGraphConfig($config);
}
#-------------------------------------------------------------------
=head2 indexContent ( )
Indexing question and answers. See WebGUI::Asset::indexContent() for additonal details.
=cut
around indexContent => sub {
my $orig = shift;
my $self = shift;
my $indexer = $self->$orig(@_);
$indexer->addKeywords($self->get("question")." ".$self->get("answers"));
};
#-------------------------------------------------------------------
=head2 prepareView ( )
See WebGUI::Asset::prepareView() for details.
=cut
override prepareView => sub {
my $self = shift;
super();
my $template = WebGUI::Asset::Template->newById($self->session, $self->get("templateId"));
if (!$template) {
WebGUI::Error::ObjectNotFound::Template->throw(
error => qq{Template not found},
templateId => $self->get("templateId"),
assetId => $self->getId,
);
}
$template->prepare($self->getMetaDataAsTemplateVariables);
$self->{_viewTemplate} = $template;
};
#-------------------------------------------------------------------
=head2 processEditForm
Extend the base method to handle the answers and the Graphing plugin.
=cut
override processEditForm => sub {
my $self = shift;
super();
my $property = {};
my $answers = $self->session->form->process("answers");
$answers =~ s{\r}{}xmsg;
my @answer = split("\n",$answers);
for (my $i=1; $i<=20; $i++) {
$property->{'a'.$i} = $answer[($i-1)] || "";
}
if (WebGUI::Image::Graph->getPluginList($self->session)) {
my $graph;
try {
$graph = WebGUI::Image::Graph->processConfigurationForm($self->session);
} catch {
$self->session->log->error( "Graph plugin not available or not functional: Error: ``$_''" );
};
if( $graph ) {
$self->setGraphConfig( $graph->getConfiguration );
}
}
$self->update($property);
$self->session->db->write("delete from Poll_answer where assetId=".$self->session->db->quote($self->getId)) if ($self->session->form->process("resetVotes"));
};
#-------------------------------------------------------------------
=head2 purge
Extend the base method to handle Poll answers.
=cut
override purge => sub {
my $self = shift;
$self->session->db->write("delete from Poll_answer where assetId=".$self->session->db->quote($self->getId));
super();
};
#----------------------------------------------------------------------------
=head2 setGraphConfig
Freezes and stores the configuration for the graphing of this poll.
=cut
sub setGraphConfig {
my $self = shift;
my $obj = shift;
$self->update({
graphConfiguration => $self->freezeGraphConfig($obj),
});
}
#-------------------------------------------------------------------
=head2 setVote ($answer, $userId, $ip)
Accumulates a vote into the database so that it can be counted.
=head3 $answer
The answer selected by the user.
=head3 $userid
The userId of the person who voted.
=head3 $ip
The IP address of the user who voted.
=cut
sub setVote {
my $self = shift;
my $answer = shift;
my $userId = shift;
my $ip = shift;
$self->session->db->write("insert into Poll_answer (assetId, answer, userId, ipAddress) values (?,?,?,?)",
[$self->getId, $answer, $userId, $ip] );
}
#----------------------------------------------------------------------------
=head2 thawGraphConfig
Deserializes the graph configuration and returns the data structure.
=cut
sub thawGraphConfig {
my $self = shift;
my $string = shift;
return unless $string;
return JSON::from_json($string);
}
#-------------------------------------------------------------------
=head2 view
Generate the poll results with graph if configured to do so. Display the form
for the user to vote.
=cut
sub view {
my $self = shift;
my (%var, $answer, @answers, $showPoll, $f, @dataset, @labels);
$var{question} = $self->get("question");
if ($self->get("active") eq "0") {
$showPoll = 0;
} elsif ($self->session->user->isInGroup($self->get("voteGroup"))) {
if ($self->_hasVoted()) {
$showPoll = 0;
} else {
$showPoll = 1;
}
} else {
$showPoll = 0;
}
$var{canVote} = $showPoll;
my ($totalResponses) = $self->session->db->quickArray("select count(*) from Poll_answer where assetId=".$self->session->db->quote($self->getId));
my $i18n = WebGUI::International->new($self->session,"Asset_Poll");
$var{"responses.label"} = $i18n->get(12);
$var{"responses.total"} = $totalResponses;
$var{"form.start"} = WebGUI::Form::formHeader($self->session,{action=>$self->getUrl});
$var{"form.start"} .= WebGUI::Form::hidden($self->session,{name=>'func',value=>'vote'});
$var{"form.submit"} = WebGUI::Form::submit($self->session,{value=>$i18n->get(11)});
$var{"form.end"} = WebGUI::Form::formFooter($self->session,);
$totalResponses = 1 if ($totalResponses < 1);
for (my $i=1; $i<=20; $i++) {
if ($self->get('a'.$i) =~ /\C/) {
my ($tally) = $self->session->db->quickArray("select count(*) from Poll_answer where answer='a"
.$i."' and assetId=".$self->session->db->quote($self->getId)." group by answer");
push(@answers,{
"answer.form"=>WebGUI::Form::radio($self->session,{name=>"answer",value=>"a".$i}),
"answer.text"=>$self->get('a'.$i),
"answer.graphWidth"=>sprintf('%.0f', $self->get("graphWidth")*$tally/$totalResponses),
"answer.number"=>$i,
"answer.percent"=>sprintf('%0.f', 100*$tally/$totalResponses),
"answer.total"=>($tally+0)
});
push(@dataset, ($tally+0));
push(@labels, $self->get('a'.$i));
}
}
@answers = List::Util::shuffle(@answers) if ($self->get("randomizeAnswers"));
$var{answer_loop} = \@answers;
if ($self->generateGraph) {
my $config = $self->getGraphConfig;
if ($config) {
my $graph = WebGUI::Image::Graph->loadByConfiguration($self->session, $config);
$graph->addDataset(\@dataset);
$graph->setLabels(\@labels);
$graph->draw;
my $storage = WebGUI::Storage->createTemp($self->session);
my $filename = 'poll'.$self->session->id->generate.".png";
$graph->saveToStorageLocation($storage, $filename);
$var{graphUrl} = $storage->getUrl($filename);
$var{hasImageGraph} = 1;
} else {
$self->session->log->error('The graph configuration hash of the Poll ('.$self->getUrl.') is corrupt.');
}
}
return $self->processTemplate(\%var,undef,$self->{_viewTemplate});
}
#-------------------------------------------------------------------
=head2 www_vote
Web method for the user to add their vote. If so configured, gives karma
to the user.
=cut
sub www_vote {
my $self = shift;
my $u;
if ($self->session->form->process("answer") ne "" && $self->session->user->isInGroup($self->get("voteGroup")) && !($self->_hasVoted())) {
$self->setVote($self->session->form->process("answer"),$self->session->user->userId,$self->session->request->address);
if ($self->session->setting->get("useKarma")) {
$self->session->user->karma($self->get("karmaPerVote"),"Poll (".$self->getId.")","Voted on this poll.");
}
$self->getContainer->purgeCache;
}
return $self->session->asset($self->getContainer)->www_view;
}
__PACKAGE__->meta->make_immutable;
1;