package WebGUI::Asset::Wobject::Survey; #------------------------------------------------------------------- # WebGUI is Copyright 2001-2008 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 Tie::IxHash; use JSON; use WebGUI::International; use WebGUI::Form::File; use base 'WebGUI::Asset::Wobject'; use WebGUI::Asset::Wobject::Survey::SurveyJSON; use WebGUI::Asset::Wobject::Survey::ResponseJSON; use Data::Dumper; #------------------------------------------------------------------- sub definition { my $class = shift; my $session = shift; my $definition = shift; my $i18n = WebGUI::International->new( $session, 'Asset_Survey' ); my %properties; tie %properties, 'Tie::IxHash'; %properties = ( templateId => { fieldType => "template", defaultValue => 'PBtmpl0000000000000061', tab => "display", namespace => "Survey", hoverHelp => "A Survey System", label => "Template ID" }, showProgress => { fieldType => "yesNo", defaultValue => 0, tab => 'properties', label => "Show user their progress" }, showTimeLimit => { fieldType => "yesNo", defaultValue => 0, tab => 'properties', label => "Show user their time remaining" }, timeLimit => { fieldType => 'integer', defaultValue => 0, tab => 'properties', hoverHelp => $i18n->get('timelimit hoverHelp'), label => $i18n->get('timelimit') }, groupToEditSurvey => { fieldType => 'group', defaultValue => 4, label => "Group to edit survey", }, groupToTakeSurvey => { fieldType => 'group', defaultValue => 2, label => "Group to take survey", }, groupToViewReports => { fieldType => 'group', defaultValue => 4, label => "Group to view reports", }, exitURL => { fieldType => 'text', defaultValue => undef, label => "Set the URL that the survey will exit to", hoverHelp => "When the user finishes the survey, they will be sent to this URL. Leave blank if no forwarding required.", }, maxResponsesPerUser => { fieldType => 'integer', defaultValue => 1, label => "Max user reponses", }, overviewTemplateId => { tab => 'display', fieldType => 'template', defaultValue => 'PBtmpl0000000000000063', label => "Overview template id", namespace => 'Survey/Overview', }, gradebookTemplateId => { tab => 'display', fieldType => 'template', label => "Grabebook template id", defaultValue => 'PBtmpl0000000000000062', namespace => 'Survey/Gradebook', }, responseTemplateId => { tab => 'display', fieldType => 'template', label => "Response template id", defaultValue => 'PBtmpl0000000000000064', namespace => 'Survey/Response', }, surveyEditTemplateId => { tab => 'display', fieldType => 'template', label => "Survey edit template id", defaultValue => 'GRUNFctldUgop-qRLuo_DA', namespace => 'Survey/Edit', }, surveyTakeTemplateId => { tab => 'display', fieldType => 'template', label => "Take survey template id", defaultValue => 'd8jMMMRddSQ7twP4l1ZSIw', namespace => 'Survey/Take', }, surveyQuestionsId => { tab => 'display', fieldType => 'template', label => "Questions template id", defaultValue => 'CxMpE_UPauZA3p8jdrOABw', namespace => 'Survey/Take', }, sectionEditTemplateId => { tab => 'display', fieldType => 'template', label => "Section Edit Tempalte", defaultValue => '1oBRscNIcFOI-pETrCOspA', namespace => 'Survey/Edit', }, questionEditTemplateId => { tab => 'display', fieldType => 'template', label => "Question Edit Tempalte", defaultValue => 'wAc4azJViVTpo-2NYOXWvg', namespace => 'Survey/Edit', }, answerEditTemplateId => { tab => 'display', fieldType => 'template', label => "Answer Edit Tempalte", defaultValue => 'AjhlNO3wZvN5k4i4qioWcg', namespace => 'Survey/Edit', }, ); push( @{$definition}, { assetName => $i18n->get('assetName'), icon => 'survey.gif', autoGenerateForms => 1, tableName => 'Survey', className => 'WebGUI::Asset::Wobject::Survey', properties => \%properties } ); return $class->SUPER::definition( $session, $definition ); } ## end sub definition #------------------------------------------------------------------- =head2 exportAssetData ( ) Override exportAssetData so that surveyJSON is included in package exports etc.. =cut sub exportAssetData { my $self = shift; my $hash = $self->SUPER::exportAssetData(); $self->loadSurveyJSON(); $hash->{properties}{surveyJSON} = $self->survey->freeze; return $hash; } #------------------------------------------------------------------- =head2 importAssetData ( hashRef ) Override importAssetCollateralData so that surveyJSON gets imported from packages =cut sub importAssetCollateralData { my ( $self, $data ) = @_; my $surveyJSON = $data->{properties}{surveyJSON}; $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", [ $surveyJSON, $self->getId ] ); } #------------------------------------------------------------------- =head2 duplicate ( ) Override duplicate so that surveyJSON gets duplicated too =cut sub duplicate { my $self = shift; my $options = shift; my $newAsset = $self->SUPER::duplicate($options); $self->loadSurveyJSON(); $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", [ $self->survey->freeze, $newAsset->getId ] ); return $newAsset; } #------------------------------------------------------------------- =head2 loadSurveyJSON ( ) Loads the survey collateral into memory so that the survey objects can be created =cut sub loadSurveyJSON { my $self = shift; my $jsonHash = shift; if ( defined $self->survey ) { return; } #already loaded $jsonHash = $self->session->db->quickScalar( "select surveyJSON from Survey where assetId = ?", [ $self->getId ] ) if ( !defined $jsonHash ); $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $jsonHash, $self->session->errorHandler ); } #------------------------------------------------------------------- =head2 saveSurveyJSON ( ) Saves the survey collateral to the DB =cut sub survey { return shift->{survey}; } sub littleBuddy { return shift->{survey}; } sub allyourbases { return shift->{survey}; } sub helpmehelpme { return shift->{survey}; } sub saveSurveyJSON { my $self = shift; my $data = $self->survey->freeze(); $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", [ $data, $self->getId ] ); } #------------------------------------------------------------------- =head2 www_editSurvey ( ) Loads the initial edit survey page. All other edit actions are JSON calls from this page. =cut sub www_editSurvey { my $self = shift; return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup($self->get('groupToEditSurvey'))); my %var; my $out = $self->processTemplate( \%var, $self->get("surveyEditTemplateId") ); return $out; } #------------------------------------------------------------------- sub www_submitObjectEdit { my $self = shift; return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup($self->get('groupToEditSurvey'))); # my $ref = @{decode_json($self->session->form->process("data"))}; my $responses = $self->session->form->paramsHashRef(); my @address = split /-/, $responses->{id}; $self->loadSurveyJSON(); if ( $responses->{delete} ) { return $self->deleteObject( \@address ); } elsif ( $responses->{copy} ) { return $self->copyObject( \@address ); } # each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. my $message = $self->survey->update( \@address, $responses ); $self->saveSurveyJSON(); return $self->www_loadSurvey( { address => \@address } ); } ## end sub www_submitObjectEdit #------------------------------------------------------------------- sub copyObject { my ( $self, $address ) = @_; $self->loadSurveyJSON(); #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. $address = $self->survey->copy($address); $self->saveSurveyJSON(); #The parent address of the deleted object is returned. return $self->www_loadSurvey( { address => $address } ); } #------------------------------------------------------------------- sub deleteObject { my ( $self, $address ) = @_; $self->loadSurveyJSON(); my $message = $self->survey->remove($address) ; #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. $self->saveSurveyJSON(); #The parent address of the deleted object is returned. if ( @$address == 1 ) { $$address[0] = 0; } else { pop( @{$address} ); # unless @$address == 1 and $$address[0] == 0; } return $self->www_loadSurvey( { address => $address, message => $message } ); } ## end sub deleteObject #------------------------------------------------------------------- sub www_newObject { my $self = shift; return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup($self->get('groupToEditSurvey'))); my $ref; my $ids = $self->session->form->process("data"); my @inAddress = split /-/, $ids; $self->loadSurveyJSON(); #Don't save after this as the new object should not stay in the survey my $address = $self->survey->newObject( \@inAddress ); #The new temp object has an address of NEW, which means it is not a real final address. return $self->www_loadSurvey( { address => $address, message => undef } ); } ## end sub www_newObject #------------------------------------------------------------------- sub www_dragDrop { my $self = shift; return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup($self->get('groupToEditSurvey'))); my $p = decode_json( $self->session->form->process("data") ); my @tid = split /-/, $p->{target}->{id}; my @bid = split /-/, $p->{before}->{id}; $self->loadSurveyJSON(); my $target = $self->survey->getObject( \@tid ); $self->survey->remove( \@tid, 1 ); my $address = [0]; if ( @tid == 1 ) { #sections can only be inserted after another section so chop off the question and answer portion of $#bid = 0; $bid[0] = -1 if ( !defined $bid[0] ); $self->survey->insertObject( $target, [ $bid[0] ] ); } elsif ( @tid == 2 ) { #questions can be moved to any section, but a pushed to the end of a new section. if ( $bid[0] !~ /\d/ ) { $bid[0] = $tid[0]; $bid[1] = $tid[1]; } elsif ( @bid == 1 ) { #moved to a new section or head of current section if ( $bid[0] !~ /\d/ ) { $bid[0] = $tid[0]; $bid[1] = $tid[1]; } if ( $bid[0] == $tid[0] ) { #moved to top of current section $bid[1] = -1; } else { #else move to the end of the selected section $bid[1] = $#{ $self->survey->questions( [ $bid[0] ] ) }; } } ## end elsif ( @bid == 1 ) $self->survey->insertObject( $target, [ $bid[0], $bid[1] ] ); } ## end elsif ( @tid == 2 ) elsif ( @tid == 3 ) { #answers can only be rearranged in the same question if ( @bid == 2 and $bid[1] == $tid[1] ) { $bid[2] = -1; $self->survey->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } elsif ( @bid == 3 ) { $self->survey->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } else { #else put it back where it was $self->survey->insertObject( $target, \@tid ); } } $self->saveSurveyJSON(); return $self->www_loadSurvey( { address => $address } ); } ## end sub www_dragDrop #------------------------------------------------------------------- sub www_loadSurvey { my ( $self, $options ) = @_; $self->loadSurveyJSON(); my $address = defined $options->{address} ? $options->{address} : undef; if ( !defined $address ) { if ( my $inAddress = $self->session->form->process("data") ) { $address = [ split /-/, $inAddress ]; } else { $address = [0]; } } my $message = defined $options->{message} ? $options->{message} : ''; my $var = defined $options->{var} ? $options->{var} : $self->survey->getEditVars($address); my $editHtml; if ( $var->{type} eq 'section' ) { $editHtml = $self->processTemplate( $var, $self->get("sectionEditTemplateId") ); } elsif ( $var->{type} eq 'question' ) { $editHtml = $self->processTemplate( $var, $self->get("questionEditTemplateId") ); } elsif ( $var->{type} eq 'answer' ) { $editHtml = $self->processTemplate( $var, $self->get("answerEditTemplateId") ); } my %buttons; $buttons{question} = $$address[0]; if ( @$address == 2 or @$address == 3 ) { $buttons{answer} = "$$address[0]-$$address[1]"; } my $data = $self->survey->getDragDropList($address); my $html; my ( $scount, $qcount, $acount ) = ( -1, -1, -1 ); my $lastType; my %lastId; my @ids; my ( $s, $q, $a ) = ( 0, 0, 0 ); #bools on if a button has already been created foreach (@$data) { if ( $_->{type} eq 'section' ) { $lastId{section} = ++$scount; if ( $lastType eq 'answer' ) { $a = 1; } elsif ( $lastType eq 'question' ) { $q = 1; } $html .= "
"; $$q{'verte'} = "
"; } } ## end foreach my $q (@$questions) $section->{'questions'} = $questions; $section->{'questionsAnswered'} = $self->response->{questionsAnswered}; $section->{'totalQuestions'} = @{$self->response->surveyOrder}; $section->{'showProgress'} = $self->response->{showProgress}; $section->{'showTimeLimit'} = $self->response->{showTimeLimit}; $section->{'minutesLeft'} = int((($self->response->{startTime} + (60 * $self->response->{timeLimit})) - time())/60); my $out = $self->processTemplate( $section, $self->get("surveyQuestionsId") ); $self->session->http->setMimeType('application/json'); return encode_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); } ## end sub prepareShowSurveyTemplate #------------------------------------------------------------------- sub loadBothJSON { my $self = shift; my $rId = shift; if ( defined $self->survey and defined $self->response ) { return; } my $ref = $self->session->db->buildArrayRefOfHashRefs( " select s.surveyJSON,r.responseJSON from Survey s, Survey_response r where s.assetId = ? and r.Survey_responseId = ?", [ $self->getId, $rId ] ); $self->loadSurveyJSON( $ref->[0]->{surveyJSON} ); $self->loadResponseJSON( $ref->[0]->{responseJSON}, $rId ); } #------------------------------------------------------------------- sub loadResponseJSON { my $self = shift; my $jsonHash = shift; my $rId = shift; $rId = defined $rId ? $rId : $self->{responseId}; if ( defined $self->response and !defined $rId ) { return; } $jsonHash = $self->session->db->quickScalar( "select responseJSON from Survey_response where assetId = ? and Survey_responseId = ?", [ $self->getId, $rId ] ) if ( !defined $jsonHash ); $self->{response} = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $jsonHash, $self->session->errorHandler, $self->survey ); } ## end sub loadResponseJSON #------------------------------------------------------------------- sub saveResponseJSON { my $self = shift; my $data = $self->response->freeze(); $self->session->db->write( "update Survey_response set responseJSON = ? where Survey_responseId = ?", [ $data, $self->{responseId} ] ); } #------------------------------------------------------------------- sub response { my $self = shift; return $self->{response}; } #------------------------------------------------------------------- sub getResponseId { my $self = shift; return $self->{responseId} if ( defined $self->{responseId} ); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); my $anonId = $self->session->form->process("userid") || $self->session->http->getCookies->{"Survey2AnonId"} || undef; $self->session->http->setCookie( "Survey2AnonId", $anonId ) if ($anonId); my $responseId; my $string; #if there is an anonid or id is for a WG user if ( $anonId or $id != 1 ) { $string = 'userId'; if ($anonId) { $string = 'anonId'; $id = $anonId; } $responseId = $self->session->db->quickScalar( "select Survey_responseId from Survey_response where $string = ? and assetId = ? and isComplete = 0", [ $id, $self->getId() ] ); } elsif ( $id == 1 ) { $responseId = $self->session->db->quickScalar( "select Survey_responseId from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = 0", [ $id, $ip, $self->getId() ] ); } if ( !$responseId ) { my $allowedTakes = $self->session->db->quickScalar( "select maxResponsesPerUser from Survey where assetId = ? order by revisionDate desc limit 1", [ $self->getId() ] ); my $haveTaken; if ( $id == 1 ) { $haveTaken = $self->session->db->quickScalar( "select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ?", [ $id, $ip, $self->getId() ] ); } else { $haveTaken = $self->session->db->quickScalar( "select count(*) from Survey_response where $string = ? and assetId = ?", [ $id, $self->getId() ] ); } if ( $haveTaken < $allowedTakes ) { my $time = time(); $responseId = $self->session->db->setRow( "Survey_response", "Survey_responseId", { Survey_responseId => "new", userId => $id, ipAddress => $ip, username => $self->session->user->username, startDate => $time,#WebGUI::DateTime->now->toDatabase, endDate => 0,#WebGUI::DateTime->now->toDatabase, assetId => $self->getId(), anonId => $anonId } ); $self->loadBothJSON($responseId); $self->response->createSurveyOrder(); $self->{responseId} = $responseId; $self->response->{startTime} = $time; $self->response->{timeLimit} = $self->get("timeLimit"); $self->response->{showProgress} = $self->get("showProgress"); $self->response->{showTimeLimit} = $self->get("showTimeLimit"); $self->saveResponseJSON(); } ## end if ( $haveTaken < $allowedTakes) else { $self->session->log->debug("haveTaken ($haveTaken) >= allowedTakes ($allowedTakes)"); } } ## end if ( !$responseId ) $self->{responseId} = $responseId; $self->loadBothJSON($responseId); return $responseId; } ## end sub getResponseId #------------------------------------------------------------------- sub canTakeSurvey { my $self = shift; return $self->{canTake} if ( defined $self->{canTake} ); if ( !$self->session->user->isInGroup( $self->get("groupToTakeSurvey") ) ) { return 0; } #Does user have too many finished survey responses my $maxTakes = $self->getValue("maxResponsesPerUser"); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); my $takenCount = 0; if ( $id == 1 ) { $takenCount = $self->session->db->quickScalar( "select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = ?", [ $id, $ip, $self->getId(), 1 ] ); } else { $takenCount = $self->session->db->quickScalar( "select count(*) from Survey_response where userId = ? and assetId = ? and isComplete = ?", [ $id, $self->getId(), 1 ] ); } if ( $takenCount >= $maxTakes ) { $self->{canTake} = 0; } else { $self->{canTake} = 1; } return $self->{canTake}; } ## end sub canTakeSurvey #------------------------------------------------------------------- sub www_viewGradeBook{ my $self = shift; return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); $self->loadTempReportTable(); my @peoples = $self->session->db->quickArray("SELECT UNIQUE(Survey_responseId) from Survey_tempReport where assetId = ?",[$self->getId()]); for my $people(@peoples){ #my $ } } #------------------------------------------------------------------- sub www_exportSimpleResults{ my $self = shift; return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); $self->loadTempReportTable(); my $filename = $self->session->url->escape( $self->get("title") . "_results.tab" ); my $content = $self->session->db->quickTab( "select * from Survey_tempReport t where t.assetId=? order by t.Survey_responseId, t.order", [ $self->getId() ] ); return $self->export( $filename, $content ); } #------------------------------------------------------------------- sub export { my $self = shift; my $filename = shift; $filename =~ s/[^\w\d\.]/_/g; my $content = shift; #Create a temporary directory to store files if it doesn't already exist my $store = WebGUI::Storage->createTemp( $self->session ); my $tmpDir = $store->getPath(); my $filepath = $store->getPath($filename); unless ( open TEMP, ">$filepath" ) { return "Error - Could not open temporary file for writing. Please use the back button and try again"; } print TEMP $content; close TEMP; my $fileurl = $store->getUrl($filename); $self->session->http->setRedirect($fileurl); return undef; } ## end sub export sub loadTempReportTable { my $self = shift; $self->loadSurveyJSON(); my $refs = $self->session->db->buildArrayRefOfHashRefs( "select * from Survey_response where assetId = ?", [ $self->getId() ] ); $self->session->db->write( "delete from Survey_tempReport where assetId = ?", [ $self->getId() ] ); for my $ref (@$refs) { $self->loadResponseJSON( undef, $ref->{Survey_responseId} ); my $count = 1; for my $q ( @{ $self->response->returnResponseForReporting() } ) { if ( @{ $q->{answers} } == 0 and $q->{comment} =~ /\w/ ) { $self->session->db->write( "insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [ $self->getId(), $ref->{Survey_responseId}, $count++, $q->{section}, $q->{sectionName}, $q->{question}, $q->{questionName}, $q->{questionComment}, undef, undef, undef, undef, undef, undef, undef ] ); next; } for my $a ( @{ $q->{answers} } ) { $self->session->db->write( "insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [ $self->getId(), $ref->{Survey_responseId}, $count++, $q->{section}, $q->{sectionName}, $q->{question}, $q->{questionName}, $q->{questionComment}, $a->{id}, $a->{value}, $a->{comment}, $a->{time}, $a->{isCorrect}, $a->{value}, undef ] ); } } ## end for my $q ( @{ $self->response... } ## end for my $ref (@$refs) return 1; } ## end sub loadTempReportTable sub log { my $self = shift; $self->session->log->debug(shift); } 1;