File Coverage

blib/lib/HTML/Ballot/Trusting.pm
Criterion Covered Total %
statement 13 33 39.3
branch 0 10 0.0
condition 0 6 0.0
subroutine 5 6 83.3
pod n/a
total 18 55 32.7


line stmt bran cond sub pod time code
1             package HTML::Ballot::Trusting;
2             our $VERSION = 0.2; # Thu Jul 26 15:03:11 2001
3 1     1   12495 use strict;
  1         2  
  1         40  
4 1     1   7 use warnings;
  1         1  
  1         32  
5 1     1   5 use Carp;
  1         13  
  1         72  
6 1     1   937 use HTML::Entities ();
  1         8427  
  1         594  
7            
8             # Later: CGI and use HTML::EasyTemplate 0.985;
9            
10            
11             our $CHAT = undef; # Set for reports to STDERR.
12            
13            
14             =head1 NAME
15            
16             HTML::Ballot::Trusting - HTML-template-based insercure multiple-choice ballot
17            
18             =head1 SYNOPSIS
19            
20             # Create the poll
21            
22             use HTML::Ballot::Trusting;
23             $HTML::Ballot::Trusting::CHAT = 3;
24             my $p = new HTML::Ballot::Trusting {
25             ARTICLE_ROOT => 'E:/www/leegoddard_com',
26             URL_ROOT => 'http://localhost/leegoddard_com',
27             RPATH => 'E:/www/leegoddard_com/vote/results.html',
28             TPATH => 'E:/www/leegoddard_com/vote/template.html',
29             QPATH => 'E:/www/leegoddard_com/vote/vote.html',
30             CPATH => 'E:/www/leegoddard_com/CGI_BIN/vote.pl',
31             ASKNAMES => 1,
32             QUESTIONS => [
33             'Why?',
34             'Why not?',
35             'Only for £300.'
36             ]
37             };
38             $p->create();
39            
40             =head1 DESCRIPTION
41            
42             A simple module for inseucre web ballots.
43            
44             This is a very beta version that will mature over the
45             next week or so. Please let me know how it breaks.
46            
47             Features:
48            
49             =over 4
50            
51             =item *
52            
53             no test is made of who is voting, so users may vote any number of
54             times, or may even vote (and surely will) thousands of times using a
55             "LWP" hack.
56            
57             =item *
58            
59             a HTML page of voting options and one of the results of votes so far
60             is generated from a single HTML template, and it is in these pages
61             that ballot status is maintained, so no additional file access is
62             required.
63            
64             =item *
65            
66             HTML output into the template is minimal, but all unique entities
67             are given a "class" attribute for easy CSS re-definitions.
68            
69             =item *
70            
71             simple bar charts of results are generated using HTML.
72            
73             =item *
74            
75             users may submit a comment with thier vote, though no connection
76             between the value of the vote and the user is recorded
77            
78             =item *
79            
80             users' IP addresses may be recorded, and displayed, and a chart
81             of the IP addresses from which communication has been received
82             the most may be displayed.
83            
84             =back
85            
86             In future these features may be added:
87            
88             =over 4
89            
90             =item *
91            
92             A more secure version is being considered, which uses
93             simple e-mail authentication of users, sending ony one voting
94             password to any e-mail address: this may appear as
95             "HTML::Ballot::MoreCynical".
96            
97             =item *
98            
99             This may be extended to include a ballot `time out'.
100            
101             =item *
102            
103             Options to have graphs based on single-pixels, or using the "GD"
104             interface will arrive some time in the future.
105            
106             =back
107            
108             =head1 USE
109            
110             =over 4
111            
112             =item 1.
113            
114             Construct an HTML template that can be used to generate the question
115             and answer pages. Where you wish the questions and answers to
116             appear, insert the following element:
117            
118            
119            
120             The template should at least define the CSS representation for
121             C and C as having a coloured background,
122             or you will not be able to view the results' bar graph.
123             See L for more details on other CSS classes
124             employed.
125            
126             Other functions may be included as below. Note that Cs
127             may require some minimal content of at least a space character, I'm
128             not sure, I'd better check.
129            
130             =over 4
131            
132             =item *
133            
134             If you wish to allow a user to submit a comment with their vote,
135             include the following element:
136            
137            
138             This is what voter's have said:
139            
140            
141             Unlike the C, any text you include in this
142             block will be reatained at the top of a list of users' comments.
143            
144             =item *
145            
146             If you wish to have the result page display a list of the names
147             entered by voters, also include:
148            
149            
150             Here is the voterlist...
151            
152            
153             This acts in the manner of the C, above.
154            
155             =item *
156            
157             If you wish to have the result page display a list of the most
158             frequently-posting IP addresses, include:
159            
160            
161            

Top IP Addresses To Post To This Ballot

162            
163            
164             To this, the module will add a C of HTML that lists the
165             top posters. Anything before that span (in this example,
166             the C

element) will remain.

167            
168             =back
169            
170             =item 2.
171            
172             Initiate the ballot by constructnig an HTML::Ballot::Trusting object and
173             calling C method upon it in a manner simillar to that described
174             in L.
175            
176             In response, you should receive a list of the locations of files used and
177             dynamically created by the process.
178            
179             =back
180            
181             =head1 GLOBAL VARIABLES
182            
183             Several global variables exist as system defaults. Most may be over-riden
184             using the constructor (see the sections C, C,
185             C, C in L.>
186            
187             =cut
188            
189             #
190             # These defaults can be over-ridden by using their
191             # names as values in the hash passed to the constructor
192             #
193             our $ARTICLE_ROOT = 'E:/www/leegoddard_com';
194             our $URL_ROOT = 'http://localhost/leegoddard_com';
195             our $STARTGRAPHIC = "STARTGRAPHICHERE__";
196             our $STARTPC = "STARTPCHERE__";
197             our $SHEBANG = '';
198             our $ASKNAMETEXT = 'Your name, please';
199             our $ASKCOMMENTTEXT = 'Optionally, your comment optional';
200             our $MAXTOTALCOMMENTLENGTH = 2000000; # Maxium size of all comment mark up permitted
201            
202             =item IPCHART
203            
204             THe number of items to include in the IP chart of frequent posters
205            
206             =cut
207            
208             our $IPCHART = 5;
209            
210             =head1 CONSTRUCTOR (new)
211            
212             Requires a reference to the class into which to bless, as well
213             as a hash (or reference to such) with the following key/value
214             content:
215            
216             =over 4
217            
218             =item ARTICLE_ROOT
219            
220             the root, in the filesystem, where these HTML pages begin - can over-ride the global constant of the same name;
221            
222             =item URL_ROOT
223            
224             the root, on the internet, where these HTML pages begin - can over-ride the global constant of the same name;
225            
226             =item QUESTIONS
227            
228             an array of questions to use in the ballot
229            
230             =item TPATH
231            
232             Path at which the HTML Template may be found
233            
234             =item QPATH
235            
236             Path at which to save the HTML ballot Questions' page
237            
238             =item RPATH
239            
240             Path at which to save the HTML Results page
241            
242             =item CPATH
243            
244             If you do not use the C attribute (below), you must use this: Path at
245             which to save a dynamically-generated perl script that processes
246             form submissions. Obviously must be CGI accessible and CHMOD appropriately.
247            
248             =item SUBMITTO
249            
250             If you do not use the C attribute (above), you must use this:
251             Path to the script that processes submission of the CGI voting form
252            
253             =item SHEBANG
254            
255             Represents the Shebang line you place at the start of your perl scrpts:
256             set this to over-ride the default value taken from the global constant scalar
257             of the same name. Could adjust this to suss the path from C or
258             even C, if it came to it, but time....
259            
260             =item COMMENTLENGTH
261            
262             Maximum acceptable length of text comments.
263            
264             =item ASKNAMES
265            
266             Set if users should supply their name when voting.
267            
268             =item NAMELENGTH
269            
270             If C (above) is defined, this value may be set to
271             limit the possible length of a name.
272            
273             =back
274            
275             =cut
276            
277             sub new {
278 0 0   0     my $class = shift or die "Called without class";
279 0           my %args;
280 0           my $self = {};
281 0           bless $self,$class;
282            
283             # Default instance variables
284 0           $self->{ARTICLE_ROOT} = $ARTICLE_ROOT;
285 0           $self->{URL_ROOT} = $URL_ROOT;
286 0           $self->{COMMENTLENGTH} = 75;
287 0           $self->{NAMELENGTH} = 30;
288            
289             # Take parameters and place in object slots/set as instance variables
290 0 0         if (ref $_[0] eq 'HASH'){ %args = %{$_[0]} }
  0 0          
  0            
291 0           elsif (not ref $_[0]){ %args = @_ }
292            
293             # Overwrite default instance variables with user's values
294 0           foreach (keys %args) { $self->{uc $_} = $args{$_} }
  0            
295 0           undef %args;
296            
297             # Calling-paramter error checking
298 0 0 0       croak "Template path TPATH does not exist" if exists $self->{TPATH} and not -e $self->{TPATH};
299 0 0 0       croak "No RPATH" if not exists $self->{RPATH} and not defined $self->{RPATH};
300            
301 0           return $self;
302             } # End sub new
303            
304            
305            
306             =head1 METHODS
307            
308             =head2 METHOD create
309            
310             Creates the HTML voting page.
311            
312             Accepts: just the calling object: all properties used should be set
313             during construction (see L).
314            
315             If the page contains a C, will include a text box
316             in the voting page, to allow users to submit comments. Setting
317             C to a value when calling the constructor will
318             restrict the length of acceptable comments.
319            
320             If the page contains a , this will be updated
321             with the name supplied by the user.
322            
323             Returns: the path to the saved HTML question document.
324            
325             See also L and L.
326            
327             =item QUESTION PAGE
328            
329             The C attribute of the C
element is set to the CGI
330             environment variable, C (that is, the location of this script).
331            
332             Form elements are simply seperated by linebreaks (C
): use CSS to control
333             the layout: the radio-button HTML elements are set to be class C;
334             the C button element is set to be class C.
335            
336             =item RESULTS PAGE
337            
338             HTML is used to create bar charts, but this should be easy to replace with
339             a C image, or a stretched single-pixel. Each question is given a
340             C element, and results will be placed within by the C
341             method (see L).
342            
343             See also L.
344            
345             =cut
346            
347             sub create { my $self = shift;
348             local *OUT;
349             my %template_items;
350             my $form_processing_url ;
351             croak "No path to HTML template" if not exists $self->{TPATH} or not defined $self->{TPATH};
352             croak "No path to save HTML at" if not exists $self->{QPATH} or not defined $self->{QPATH};
353             croak "No questions" if not exists $self->{QUESTIONS} or not defined $self->{QUESTIONS};
354             if ((not exists $self->{SUBMITTO} or not defined $self->{SUBMITTO})
355             and (not exists $self->{CPATH} or not defined $self->{CPATH})){
356             croak "No SUBMITTO or CPATH value defined - one or the other is required"
357             }
358            
359 1     1   1508 use HTML::EasyTemplate 0.985;
  0            
  0            
360            
361             # Create question poll page QPATH #############################################
362             #
363             # Create radio button HTML from questions
364             my $TEMPLATE = new HTML::EasyTemplate(
365             { ADD_TAGS => 1,
366             SOURCE_PATH => $self->{TPATH},
367             ARTICLE_ROOT => $self->{ARTICLE_ROOT},
368             URL_ROOT => $self->{URL_ROOT},
369             });
370             $TEMPLATE -> process('collect'); # Collect the values
371            
372             # Where should the form ACTION point? Set now so can use TEMPLATE methods
373             if (exists $self->{CPATH}){
374             $form_processing_url = $TEMPLATE->set_article_url($self->{CPATH});
375             } else {
376             $form_processing_url = $self->{SUBMITTO}
377             }
378            
379             # Construct form
380             my $qhtml = "
381             $qhtml .= "onSubmit=\"";
382             $qhtml .= "if (this.usrcomment.value=='$ASKCOMMENTTEXT'){this.usrcomment.value=''}";
383             $qhtml .= "if (this.usrname.value==''){alert('Please, please, please enter your name.... it will not be recorded against your vote');this.usrname.focus();return false;}";
384             $qhtml .= "if (this.usrname.value=='$ASKNAMETEXT'){alert('Please enter your name.. It will not be recorded against your vote');this.usrname.focus();return false;}";
385             $qhtml .= "return true;";
386             $qhtml .= "\">\n";
387            
388             foreach (@{$self->{QUESTIONS}}) {
389             $_ = HTML::Entities::encode($_);
390             $qhtml .= "$_
\n";
391             }
392             $qhtml.="{RPATH}\">\n";
393            
394             # Add name input field if appropriate
395             if (exists $self->{ASKNAMES}){
396             $qhtml.="{NAMELENGTH}\" SIZE=\"40\">\n";
397             }
398            
399             # Add comment input field if comment output area is defined:
400             if (exists $TEMPLATE->{TEMPLATEITEMS}->{COMMENT} and defined $TEMPLATE->{TEMPLATEITEMS}->{COMMENT}){
401             $qhtml.="{COMMENTLENGTH}\" SIZE=\"40\">\n";
402             }
403            
404             $qhtml.="\n\n";
405             $template_items{QUESTIONS} = $qhtml; # Make new values, for example:
406             $TEMPLATE -> process('fill', \%template_items ); # Add them to the page
407             $TEMPLATE -> save($self->{QPATH});
408            
409             # Create initial results page RPATH template ####################################
410             #
411             my $rhtml = "
\n\n"; \n\n\t"; \n"; \n"; \n"; \n";
412             foreach (@{$self->{QUESTIONS}}) {
413             $rhtml .= "
$_
414             $rhtml .= "0
415             $rhtml .= "0%
416             $rhtml .= "No votes yet cast.
417             $rhtml .= "
418             }
419             $rhtml .= "
\n
\n";
420            
421             $TEMPLATE = new HTML::EasyTemplate(
422             { ADD_TAGS => 1,
423             SOURCE_PATH => $self->{TPATH},
424             ARTICLE_ROOT => $self->{ARTICLE_ROOT},
425             URL_ROOT => $self->{URL_ROOT},
426             });
427             $TEMPLATE -> process('collect'); # Collect the values
428            
429             $template_items{QUESTIONS} = $rhtml; # Make new values, for example:
430            
431             if (exists $TEMPLATE->{TEMPLATEITEMS}->{COMMENT} and defined $TEMPLATE->{TEMPLATEITEMS}->{COMMENT}){
432             #die "",$TEMPLATE->{TEMPLATEITEMS}->{COMMENT},"";
433             $template_items{COMMENT} = $TEMPLATE->{TEMPLATEITEMS}->{COMMENT};
434             }
435             if (exists $TEMPLATE->{TEMPLATEITEMS}->{VOTERLIST}){
436             $template_items{VOTERLIST} = $TEMPLATE->{TEMPLATEITEMS}->{VOTERLIST};
437             }
438             # Add IP chart if requested
439             if (exists $TEMPLATE->{TEMPLATEITEMS}->{IPCHART} ){
440             $template_items{IPCHART} = $TEMPLATE->{TEMPLATEITEMS}->{IPCHART}
441             }
442             $TEMPLATE -> process('fill', \%template_items ); # Add them to the page
443             $TEMPLATE -> save($self->{RPATH});
444            
445             # Create the script to submit the form ##########################################
446             # Could have this sciprt's functionality within the module, checking for CGI
447             # param on every calling, and that may be more economical, but is less clean.
448             $_ = scalar __PACKAGE__;
449             my $Perl =<
450            
451             $SHEBANG
452             \# Caller script located at $self->{CPATH} ($form_processing_url)
453             \# Dynamically generated by and for $_ :: create
454            
455             use HTML::Ballot::Trusting;
456             use CGI;
457             our \$cgi = new CGI;
458             if (\$cgi->param() and \$cgi->param('question') and \$cgi->param('rpath') ){
459             \$v = new HTML::Ballot::Trusting ( {RPATH=>\$cgi->param('rpath')});
460             \$v->cast_vote( \$cgi->param('question'),\$cgi->param('usrcomment'),\$cgi->param('usrname') );
461             } else {print "Location: $form_processing_url\\n\\n\\n";}
462             exit;
463            
464             EOPERL
465            
466             open OUT, ">$self->{CPATH}" or croak "Could not open <$self->{CPATH}> for writing";
467             print OUT $Perl;
468             close OUT;
469            
470             # Report #######################################################################
471             print "Created poll.\n",
472             "Calling-script at: $self->{CPATH}\n",
473             "HTML template at: $self->{TPATH}\n",
474             "Qustion HTML is at: $self->{QPATH}\n",
475             "Results HTML is at: $self->{RPATH}\n\n";
476            
477             return 1;
478             }
479            
480            
481            
482            
483             =head2 METHOD cast_vote
484            
485             Casts a vote and updates the results file.
486            
487             Accepts:
488            
489             1. the question voted for, as defined in the HTML vote form's C/C.
490            
491             2. optionally, a user-submitted comment.
492            
493             3. optionally, a user-submitted name.
494            
495             =cut
496            
497             sub cast_vote { my ($self, $q_answered,$usrcomment,$usrname) = (shift,shift,shift,shift);
498             croak "No object" if not defined $self;
499             croak "No answer" if not defined $q_answered;
500             croak "No RPATH" if not exists $self->{RPATH};
501             croak "No RPATH path to save results at" if not exists $self->{RPATH};
502            
503             @_ = split/ /,(scalar localtime); # Create the date
504             my $todaydate = "$_[2] $_[1] $_[4] $_[3]";
505            
506             # Get existing results
507             my $TEMPLATE = new HTML::EasyTemplate(
508             { ADD_TAGS => 1,
509             SOURCE_PATH => $self->{RPATH},
510             ARTICLE_ROOT => $self->{ARTICLE_ROOT},
511             URL_ROOT => $self->{URL_ROOT},
512             FLOCK => 1,
513             });
514             $TEMPLATE -> process('collect'); # Collect the values
515             my %template_items = %{$TEMPLATE->{TEMPLATEITEMS}}; # Do something with them
516            
517             my %scores; # Keyed by question
518             my ($total_cast,$hi_score) = (0,0);
519             # Aquire results from template
520             foreach (keys %template_items){
521             if ($_!~/^(VOTERLIST|IPCHART|COMMENT|\Q$STARTGRAPHIC\E|\Q$STARTPC\E)/ and $_ ne 'QUESTIONS'){
522             $template_items{$_}++ if $_ eq $q_answered;
523             $scores{$_} = $template_items{$_}; # Will create a warning, not-numeric, but works...:(
524             $total_cast += $scores{$_};
525             $hi_score = $scores{$_} if $scores{$_} > $hi_score;
526             }
527             }
528             # Create new results
529             foreach (keys %scores){
530             warn "$_...$template_items{$_}\n" if $CHAT;
531             my $pc = ((100 / $total_cast) * $template_items{$_} );
532             $template_items{$_} = $scores{$_};
533             $template_items{"$STARTGRAPHIC$_"} = '
534             if ($scores{$_} == $hi_score){
535             $template_items{"$STARTGRAPHIC$_"}.= 'class="votehighscorebar" ';
536             } elsif ($scores{$_}>0) {
537             $template_items{"$STARTGRAPHIC$_"}.= 'class="votebar" ';
538             }
539             $template_items{"$STARTGRAPHIC$_"}.= 'width="';
540             if ($scores{$_}==0){
541             $template_items{"$STARTGRAPHIC$_"}.='0%">';
542             } else {
543             $template_items{"$STARTGRAPHIC$_"} .= $pc;
544             $template_items{"$STARTGRAPHIC$_"}.= '%" ';
545             $template_items{"$STARTGRAPHIC$_"}.= 'bgcolor="red"' if exists $self->{NOCSS};
546             $template_items{"$STARTGRAPHIC$_"}.= '> ';
547             }
548             $template_items{"$STARTPC$_"} = sprintf("%.2f", $pc)."%";
549             $template_items{"$STARTGRAPHIC$_"}.= '
'."\n";
550             }
551             # Include user's comments
552             if (defined $usrcomment and $usrcomment!~/^\s*$/g
553             and length $template_items{COMMENT}<$MAXTOTALCOMMENTLENGTH # No overstuffing of the file
554             ){
555             $usrcomment = substr $usrcomment,$self->{COMMENTLENGTH} if length $usrcomment>$self->{COMMENTLENGTH};
556             $usrcomment = HTML::Entities::encode($usrcomment);
557             $template_items{COMMENT} .= "
$todaydate$usrname$usrcomment
\n";
558             }
559            
560             # Include user's name
561             if (exists $template_items{VOTERLIST}){
562             $template_items{VOTERLIST}.="$usrname ";
563             }
564             # Include IP?
565             if (exists $template_items{VOTERLIST}){
566             $template_items{VOTERLIST}.="($ENV{REMOTE_HOST})";
567             }
568             # Finish user's name list
569             if (exists $template_items{VOTERLIST}){
570             $template_items{VOTERLIST}.="\n";
571             }
572             # Top-X IPs
573             if (exists $template_items{IPCHART}){
574             # Collect IP addresses from VOTERLIST
575             my %ips;
576             while ($template_items{VOTERLIST} =~ m/\QSPAN class="voteusrip">(\E(127.0.0.1)\Q)<\/SPAN>\E/g){
577             $ips{$1}++;
578             }
579             # Remove previous chart (as defined below) from page
580             $template_items{IPCHART} =~ s/.*//s;
581             # Add the chart
582             $template_items{IPCHART} .= '';
583             my @ips = sort { $ips{$b} <=> $ips{$a} } keys %ips;
584             for (0..$IPCHART-1){
585             $template_items{IPCHART} .= "".($_+1).": $ips[$_]\n";
586             }
587             $template_items{IPCHART} .= '';
588             }
589            
590             $TEMPLATE -> process('fill', \%template_items ); # Add them to the page
591             $TEMPLATE -> save($self->{RPATH});
592             # Redirect
593             print "Location: $TEMPLATE->{ARTICLE_PATH}\n\n";
594             return 1;
595             }
596            
597            
598            
599             1;
600             __END__