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   159695 use warnings;
  5         12  
  5         254  
4 5     5   28 use strict;
  5         9  
  5         176  
5              
6 5     5   26 use vars qw($VERSION);
  5         11  
  5         439  
7             $VERSION = '0.05';
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         9466  
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             }
312              
313             sub CheckOpenTimes {
314             for my $dt (qw(survey_start course_start talks_start survey_end)) {
315             #LogDebug("CheckOpenTimes: $dt=$tvars{$dt}");
316             if($tvars{$dt} && $tvars{$dt} =~ /(\d{4})\W(\d{2})\W(\d{2})\W(\d{2})\W(\d{2})\W(\d{2})/) {
317             my $t = timelocal(int($6),int($5),int($4),int($3),int($2-1),int($1-1900));
318             my $n = time + $settings{timezone_offset};
319              
320             LogDebug("CheckOpenTimes: dt=$dt, $tvars{$dt}, t=$t, n=$n");
321             $tvars{$dt} = $t < $n ? 1 : 0
322             } else {
323             $tvars{$dt} ||= 0;
324             }
325             #LogDebug("CheckOpenTimes: $dt=$tvars{$dt}");
326             }
327             }
328              
329             =head2 Admin Interface Methods
330              
331             =over 4
332              
333             =item Admin
334              
335             Loads the data when presenting the survey management pages.
336              
337             =back
338              
339             =cut
340              
341             sub Admin {
342             return unless AccessUser(ADMIN);
343             my @surveys = $dbi->GetQuery('hash','AdminSurveys',{'sort' => 'ORDER BY u.realname'});
344             my @courses = $dbi->GetQuery('hash','AdminCourses');
345             my @talks = $dbi->GetQuery('hash','AdminTalks');
346              
347             if(@surveys) {
348             $tvars{surveys} = \@surveys;
349             $tvars{scount} = scalar(@surveys);
350             }
351             if(@courses) {
352             $tvars{courses} = \@courses;
353             $tvars{ccount} = scalar(@courses);
354             }
355             if(@talks) {
356             $tvars{talks} = \@talks;
357             $tvars{tcount} = scalar(@talks);
358             }
359             }
360              
361             =head2 Internal Object Methods
362              
363             Both the following methods are defined by configuration settings.
364              
365             =over 4
366              
367             =item CourseFixes
368              
369             Specific configuration to fix course or talk titles.
370              
371             Use a list of key/value pairs in the configuration file:
372              
373             title_fixes=<
374             Mishpselt=Misspelt
375             LIST
376              
377             Note that for title_fixes, the values should be the full replacement string.
378              
379             =item TutorFixes
380              
381             Specific configuration to fix names used by tutors.
382              
383             Two configurable lists used here. The first is a simple list, while the second
384             uses a list of key/value pairs in the configuration file:
385              
386             single_names=<
387             Barbie
388             LIST
389              
390             tutor_fixes=<
391             Mishpselt=Misspelt
392             LIST
393              
394             Note that for tutor_fixes, the values should be the full replacement string.
395              
396             =item ConfigureFixes
397              
398             Creates the internal hashes for fixes from loaded settings.
399              
400             =back
401              
402             =cut
403              
404             sub CourseFixes {
405             my ($self,$title) = @_;
406             for my $fix (keys %title_fixes) {
407             return $title_fixes{$fix} if($title =~ /$fix/);
408             }
409             return $title;
410             }
411              
412             sub TutorFixes {
413             my ($self,$tutor) = @_;
414             for my $fix (keys %single_names) {
415             return $1 if($tutor =~ /^($fix)[^a-z]*$/);
416             }
417             for my $fix (keys %tutor_fixes) {
418             return $tutor_fixes{$fix} if($tutor =~ /$fix/);
419             }
420             return $tutor;
421             }
422              
423             sub ConfigureFixes {
424             my $self = shift;
425              
426             if($settings{title_fixes}) {
427             for my $fix (@{ $settings{title_fixes} }) {
428             my ($key,$value) = split('=',$fix,2);
429             $title_fixes{$key} = $value if($key);
430             }
431             }
432              
433             if($settings{single_names}) {
434             for my $fix (@{ $settings{single_names} }) {
435             $single_names{$fix} = 1;
436             }
437             }
438              
439             if($settings{tutor_fixes}) {
440             for my $fix (@{ $settings{tutor_fixes} }) {
441             my ($key,$value) = split('=',$fix,2);
442             $tutor_fixes{$key} = $value if($key);
443             }
444             }
445             }
446              
447             1;
448              
449             __END__