File Coverage

blib/lib/Labyrinth/Plugin/Survey.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package Labyrinth::Plugin::Survey;
2              
3 5     5   125480 use warnings;
  5         11  
  5         180  
4 5     5   26 use strict;
  5         11  
  5         201  
5              
6 5     5   25 use vars qw($VERSION);
  5         15  
  5         347  
7             $VERSION = '0.06';
8              
9             =head1 NAME
10              
11             Labyrinth::Plugin::Survey - YAPC Surveys plugin for the Labyrinth framework
12              
13             =head1 DESCRIPTION
14              
15             Provides all the core survey management functionality for YAPC Surveys.
16              
17             =cut
18              
19             # -------------------------------------
20             # Library Modules
21              
22 5     5   26 use base qw(Labyrinth::Plugin::Base);
  5         9  
  5         7930  
23              
24             use Crypt::Lite;
25             use Session::Token;
26             use Time::Local;
27              
28             use Labyrinth::Audit;
29             use Labyrinth::DBUtils;
30             use Labyrinth::DTUtils;
31             use Labyrinth::MLUtils;
32             use Labyrinth::Session;
33             use Labyrinth::Support;
34             use Labyrinth::Users;
35             use Labyrinth::Variables;
36              
37             # -------------------------------------
38             # Load Parser
39              
40             BEGIN {
41             my $loaded = 0;
42              
43             eval {
44             require YAML::Syck;
45             eval "use YAML::Syck qw(Load LoadFile)";
46             $loaded = 1;
47             };
48              
49             if(!$loaded){
50             eval {
51             require YAML;
52             eval "use YAML qw(Load LoadFile)";
53             $loaded = 1;
54             };
55             }
56              
57             if(!$loaded){
58             die "Cannot load a YAML parser!";
59             }
60             }
61              
62             # -------------------------------------
63             # Variables
64              
65             my (%title_fixes,%single_names,%tutor_fixes);
66              
67             # -------------------------------------
68             # The Subs
69              
70             =head1 PUBLIC INTERFACE METHODS
71              
72             =head2 General Survey Methods
73              
74             =over 4
75              
76             =item LoadSurvey
77              
78             Load the current survey configuration.
79              
80             =item AnalyseSurvey
81              
82             Analyses the survey configuration, to determine the mandatory and optional
83             fields to be applied when the survey is submitted.
84              
85             =item CheckQuestion
86              
87             Check the configuration of a specific survey question set.
88              
89             =item CheckParam
90              
91             Check the specific question parameter.
92              
93             =item CreateID
94              
95             Create a unique ID to be used when collating questions.
96              
97             =back
98              
99             =cut
100              
101             sub LoadSurvey {
102             my ($self,$file) = @_;
103             my $result;
104              
105             eval {
106             $result = LoadFile($file);
107             if(!defined $result){ # special case for YAML::Syck
108             open my $fh, '<', $file or die "Can't open $file: $!";
109             $result = do {local $/; <$fh> };
110             close $fh;
111             }
112             };
113              
114             if($@) {
115             $tvars{errmess} = "Survey parse error: $@";
116             $tvars{errcode} = 'ERROR';
117             return;
118             }
119              
120             my $index = 1;
121             for my $section (@{$result->{sections}}) {
122             for my $question (@{$section->{questions}}) {
123             if($question->{multipart}) {
124             use Data::Dumper;
125             LogDebug("multipart=".Dumper($question->{multipart}));
126             for my $multipart (@{$question->{multipart}}) {
127             $multipart->{name} = sprintf 'qu%05d', $index;
128             $multipart->{data} = $cgiparams{$multipart->{name}};
129             $index++;
130             }
131             } else {
132             $question->{name} = sprintf 'qu%05d', $index;
133             $question->{data} = $cgiparams{$question->{name}};
134             $index++;
135             }
136              
137             if($question->{choices}) {
138             for my $choice (@{$question->{choices}}) {
139             $choice =~ s!\\:\\ !: !g;
140             }
141             }
142             }
143             }
144             return $result;
145             }
146              
147             sub AnalyseSurvey {
148             my $self = shift;
149             my (@all,@man,%collate,%qu);
150              
151             # build mandatory & optional lists
152             for my $section (@{$tvars{survey}->{sections}}) {
153             for my $question (@{$section->{questions}}) {
154             next if($question->{status} && $question->{status} eq 'hidden');
155              
156             if(defined $question->{multipart}) {
157             for my $part (@{$question->{multipart}}) {
158             next if($part->{status} && $part->{status} eq 'hidden');
159              
160             if($part->{name}) {
161             my ($a,$b,$c) = $self->CheckQuestion($part,$question);
162             push @all, @$a;
163             push @man, @$b;
164             $collate{$_} = 1 for(keys %$c);
165             $qu{$part->{name}} = $part;
166             }
167             }
168             push @man, $question->{name} if($question->{mandatory});
169              
170             } elsif($question->{name}) {
171             my ($a,$b,$c) = $self->CheckQuestion($question,$question);
172             push @all, @$a;
173             push @man, @$b;
174             $collate{$_} = 1 for(keys %$c);
175             $qu{$question->{name}} = $question;
176             }
177             }
178             }
179              
180             return (\@all, \@man, \%collate, \%qu);
181             }
182              
183             sub CheckQuestion {
184             my ($self,$part,$question) = @_;
185             my (@man,@all,%collate);
186              
187             push @all, $part->{name};
188             push @all, "$part->{name}X" if($part->{default});
189             push @man, $part->{name} if($part->{mandatory} || $question->{mandatory});
190             $collate{$part->{name}} = 1 if($part->{collate} || $question->{collate});
191              
192             if($part->{type} =~ /text|count|currency/) {
193             CheckParam($part->{name},$part);
194             if($part->{default}) {
195             CheckParam("$part->{name}X",$part);
196             $collate{"$part->{name}X"} = 1 if($part->{collate} || $question->{collate});
197             }
198              
199             } elsif($part->{type} =~ /radio/) {
200             for my $opt (@{$part->{options}}) {
201             next unless($opt->{default});
202              
203             push @all, "$part->{name}X";
204             CheckParam("$part->{name}X",$part);
205             $collate{"$part->{name}X"} = 1 if($part->{collate} || $question->{collate});
206             }
207              
208             } elsif($part->{default}) {
209             CheckParam("$part->{name}X",$part);
210             $collate{"$part->{name}X"} = 1 if($part->{collate} || $question->{collate});
211              
212             }
213              
214             if($part->{choices}) {
215             my $opts = @{$part->{choices}};
216             for my $inx (1..$opts) {
217             push @all, "$part->{name}_$inx";
218             CheckParam("$part->{name}_$inx",$part);
219             $collate{"$part->{name}X"} = 1 if($part->{collate});
220             }
221             }
222              
223             return \@all,\@man,\%collate;
224             }
225              
226             sub CheckParam {
227             my ($name,$part) = @_;
228             return unless($name && $cgiparams{$name});
229             my $clean = CleanTags($cgiparams{$name});
230             $cgiparams{"${name}_err"} = ErrorSymbol() if($clean cmp $cgiparams{$name});
231             $part->{error} = ErrorSymbol() if($clean cmp $cgiparams{$name});
232             $cgiparams{$name} = $clean;
233             }
234              
235             sub CreateID {
236             my $generator = Session::Token->new(alphabet => [0..9], length => 8);
237             return $generator->get();
238             }
239              
240             =head2 User Interface Methods
241              
242             =over 4
243              
244             =item Login
245              
246             Enable an automatic login, providing the keycode is correct.
247              
248             =item Welcome
249              
250             Provides the supporting data for the initial welcome page. This includes all
251             the courses attributed to the current user, and all the talks held during the
252             conference.
253              
254             =item CheckOpenTimes
255              
256             Check the configured start and end times, and set the appropriate template
257             variables to enable or disable access to surveys and evaluations as
258             appropriate.
259              
260             =back
261              
262             =cut
263              
264             sub Login {
265             my @rows = $dbi->GetQuery('hash','SurveyLogin',$cgiparams{code});
266             unless(@rows) {
267             # force failure
268             LogDebug("SurveyLogin: keycode not found");
269             $tvars{errcode} = 'FAIL';
270             return;
271             }
272              
273             if($rows[0]->{userid} != $cgiparams{userid}) {
274             # force failure
275             LogDebug("SurveyCheck: crypt/userid look up failed");
276             $tvars{errcode} = 'FAIL';
277             return;
278             }
279              
280             Labyrinth::Session::InternalLogin($rows[0]);
281             }
282              
283             sub Welcome {
284             my $self = shift;
285              
286             $self->ConfigureFixes;
287              
288             my @survey = $dbi->GetQuery('hash','GetUserCode',$tvars{loginid});
289             $tvars{data}{survey}{completed} = $survey[0]->{completed} if(@survey);
290              
291             # list courses
292             my @courses = $dbi->GetQuery('hash','ListCourses',$tvars{loginid});
293             $tvars{data}{courses} = \@courses if(@courses);
294              
295             # list talks
296             my %talks;
297             my @rows = $dbi->GetQuery('hash','ListTalks',$tvars{loginid});
298             for my $row (@rows) {
299             next if($row->{course} =~ /Lightning Talks/);
300             $row->{course} = $self->CourseFixes($row->{course});
301             $row->{course} = "[LT] " . $row->{course} if($row->{talk} == 2); # Lightning Talks
302             $row->{tutor} = $self->TutorFixes($row->{tutor});
303             my $date = formatDate(9,$row->{datetime});
304             $talks{$date}->{date} = formatDate(19,$row->{datetime});
305             $row->{datetime} += $settings{timezone_offset};
306             push @{$talks{$date}->{talks}}, $row;
307             }
308             my @talks = map {$talks{$_}} sort keys %talks;
309             $tvars{data}{talks} = \@talks if(@talks);
310             $tvars{talks_time} = time;
311             $tvars{thanks} = $cgiparams{thanks};
312             }
313              
314             sub CheckOpenTimes {
315             for my $dt (qw(survey_start course_start talks_start survey_end)) {
316             #LogDebug("CheckOpenTimes: $dt=$tvars{$dt}");
317             if($tvars{$dt} && $tvars{$dt} =~ /(\d{4})\W(\d{2})\W(\d{2})\W(\d{2})\W(\d{2})\W(\d{2})/) {
318             my $t = timelocal(int($6),int($5),int($4),int($3),int($2-1),int($1-1900));
319             my $n = time + $settings{timezone_offset};
320              
321             LogDebug("CheckOpenTimes: dt=$dt, $tvars{$dt}, t=$t, n=$n");
322             $tvars{$dt} = $t < $n ? 1 : 0
323             } else {
324             $tvars{$dt} ||= 0;
325             }
326             #LogDebug("CheckOpenTimes: $dt=$tvars{$dt}");
327             }
328             }
329              
330             =head2 Admin Interface Methods
331              
332             =over 4
333              
334             =item Admin
335              
336             Loads the data when presenting the survey management pages.
337              
338             =back
339              
340             =cut
341              
342             sub Admin {
343             return unless AccessUser(ADMIN);
344             my @surveys = $dbi->GetQuery('hash','AdminSurveys',{'sort' => 'ORDER BY u.realname'});
345             my @courses = $dbi->GetQuery('hash','AdminCourses');
346             my @talks = $dbi->GetQuery('hash','AdminTalks');
347              
348             if(@surveys) {
349             $tvars{surveys} = \@surveys;
350             $tvars{scount} = scalar(@surveys);
351             }
352             if(@courses) {
353             $tvars{courses} = \@courses;
354             $tvars{ccount} = scalar(@courses);
355             }
356             if(@talks) {
357             $tvars{talks} = \@talks;
358             $tvars{tcount} = scalar(@talks);
359             }
360             }
361              
362             =head2 Internal Object Methods
363              
364             Both the following methods are defined by configuration settings.
365              
366             =over 4
367              
368             =item CourseFixes
369              
370             Specific configuration to fix course or talk titles.
371              
372             Use a list of key/value pairs in the configuration file:
373              
374             title_fixes=<
375             Mishpselt=Misspelt
376             LIST
377              
378             Note that for title_fixes, the values should be the full replacement string.
379              
380             =item TutorFixes
381              
382             Specific configuration to fix names used by tutors.
383              
384             Two configurable lists used here. The first is a simple list, while the second
385             uses a list of key/value pairs in the configuration file:
386              
387             single_names=<
388             Barbie
389             LIST
390              
391             tutor_fixes=<
392             Mishpselt=Misspelt
393             LIST
394              
395             Note that for tutor_fixes, the values should be the full replacement string.
396              
397             =item ConfigureFixes
398              
399             Creates the internal hashes for fixes from loaded settings.
400              
401             =back
402              
403             =cut
404              
405             sub CourseFixes {
406             my ($self,$title) = @_;
407             for my $fix (keys %title_fixes) {
408             return $title_fixes{$fix} if($title =~ /$fix/);
409             }
410             return $title;
411             }
412              
413             sub TutorFixes {
414             my ($self,$tutor) = @_;
415             for my $fix (keys %single_names) {
416             return $1 if($tutor =~ /^($fix)[^a-z]*$/);
417             }
418             for my $fix (keys %tutor_fixes) {
419             return $tutor_fixes{$fix} if($tutor =~ /$fix/);
420             }
421             return $tutor;
422             }
423              
424             sub ConfigureFixes {
425             my $self = shift;
426              
427             if($settings{title_fixes}) {
428             for my $fix (@{ $settings{title_fixes} }) {
429             my ($key,$value) = split('=',$fix,2);
430             $title_fixes{$key} = $value if($key);
431             }
432             }
433              
434             if($settings{single_names}) {
435             for my $fix (@{ $settings{single_names} }) {
436             $single_names{$fix} = 1;
437             }
438             }
439              
440             if($settings{tutor_fixes}) {
441             for my $fix (@{ $settings{tutor_fixes} }) {
442             my ($key,$value) = split('=',$fix,2);
443             $tutor_fixes{$key} = $value if($key);
444             }
445             }
446             }
447              
448             1;
449              
450             __END__