{ "max" => 15, # maximum total number of elements allowed }, "items" => { "min" => 1, # minimum number of options "start" => 5, # number of items shown at start "max" => 15, # max number of options "maxlength" => 500, # max length of an option's textual value, min is implicitly 0 "more" => 5, # number of items to add when requesting more }, "question" => { "maxlength" => 1000, # maximum length of question allowed }, "pollname" => { "maxlength" => 1000, # maximum length of poll name allowed }, "text" => { "size" => 30, # default size of a text element "maxlength" => 50, # default maxlength of a text element }, "size" => { "min" => 1, # minimum allowed size value for a text element "max" => 100, # maximum allowed size value for a text element }, "maxlength" => { "min" => 1, # minimum allowed maxlength value for a text element "max" => 255, # maximum allowed maxlength value for a text element }, "scale" => { "from" => 1, # default from value for a scale "to" => 10, # default to value for a scale "by" => 1, # default by value for a scale "maxitems" => 20, # maximum number of items allowed in a scale }, ); # type name mappings %TYPENAME = ( "radio" => $ML{'.type.radio'}, "check" => $ML{'.type.check'}, "drop" => $ML{'.type.drop'}, "text" => $ML{'.type.text'}, "scale" => $ML{'.type.scale'}, ); ####################################################### # # Generate page for the user # # authenticate - but bypass db hit if we think they're already in. my $remote = LJ::get_remote(); unless ($remote) { $body = ""; return; } my $authas = $GET{'authas'} || $remote->{'user'}; my $u = LJ::get_authas_user($authas); unless ($u) { $body = ""; return; } # first pageview, show authas and pregeneration hook if (! LJ::did_post() || $POST{'start_over'}) { # authas switcher form $body .= "
\n"; $body .= LJ::make_authas_select($remote, { 'authas' => $GET{'authas'}, 'cap' => 'makepoll' }) . "\n"; $body .= "
\n\n"; # show pregenerate options $body .= LJ::run_hook('poll_pregeneration_html', $u, $authas); } # does the selected user have the 'makepoll' cap? unless (LJ::get_cap($u, "makepoll")) { $body .= ""; return; } # extra arguments for get requests my $getextra = $authas ne $remote->{'user'} ? "?authas=$authas" : ''; ####################################################### # # Function definitions # # builds a %poll object my $build_poll = sub { my $err = shift; # initialize the hash my $poll = { "name" => "", "count" => "0", "whoview" => "all", "whovote" => "all", "pq" => [], }; # make sure they don't plug in an outrageous count $POST{'count'} = 0 if $POST{'count'} < 0; $POST{'count'} = $RULES{'elements'}->{'max'} if $POST{'count'} > $RULES{'elements'}->{'max'}; # form properties foreach (qw(count name whoview whovote)) { $poll->{$_} = $POST{$_} if $POST{$_}; } # go through the count to build our hash foreach my $q (0..$poll->{'count'}-1) { # sanify 'opts' form elements at this level # so we don't have to do it later my $opts = "pq_${q}_opts"; $POST{$opts} = 0 if $POST{$opts} && $POST{$opts} < 0; $POST{$opts} = $RULES{'items'}->{'max'} if $POST{$opts} > $RULES{'items'}->{'max'}; # question record my $qrec = {}; # validate question attributes foreach my $atr (qw(type question opts size maxlength from to by)) { my $val = $POST{"pq_${q}_$atr"}; next unless defined $val; # ignore invalid types? next if $atr eq 'type' && $val !~ /^(radio|check|drop|text|scale)$/; # question too long/nonexistant if ($atr eq 'question') { if (! $val) { $qrec->{$atr} = $val; $err->{$q}->{$atr} = $ML{'.error.notext'}; } elsif (length($val) > $RULES{$atr}->{'maxlength'}) { $qrec->{$atr} = substr($val, 0, $RULES{$atr}->{'maxlength'}); } else { $qrec->{$atr} = $val; } next; } # opts too long? if ($atr eq 'opts') { $qrec->{$atr} = int($val); next; } # size too short/long? if ($atr eq 'size') { $qrec->{$atr} = int($val); if ($qrec->{$atr} > $RULES{$atr}->{'max'} || $qrec->{$atr} < $RULES{$atr}->{'min'}) { $err->{$q}->{$atr} = BML::ml('.error.pqsizeinvalid', { 'min' => $RULES{$atr}->{'min'}, 'max' => $RULES{$atr}->{'max'} }); } next; } # maxlength too short/long? if ($atr eq 'maxlength') { $qrec->{$atr} = int($val); if ($qrec->{$atr} > $RULES{$atr}->{'max'} || $qrec->{$atr} < $RULES{$atr}->{'min'}) { $err->{$q}->{$atr} = BML::ml('.error.pqmaxlengthinvalid', { 'min' => $RULES{'maxlength'}->{'min'}, 'max' => $RULES{'maxlength'}->{'max'} }); } next; } # from/to/by -- scale if ($atr eq 'from') { $qrec->{'to'} = int($POST{"pq_${q}_to"}) || 0; $qrec->{'from'} = int($POST{"pq_${q}_from"}) || 0; $qrec->{'by'} = int($POST{"pq_${q}_by"}) >= 1 ? int($POST{"pq_${q}_by"}) : 1; if ($qrec->{'by'} < $RULES{'by'}->{'min'}) { $err->{$q}->{'by'} = BML::ml('.error.scalemininvalid', { 'min' => $RULES{'by'}->{'min'} }); } if ($qrec->{'from'} >= $qrec->{'to'}) { $err->{$q}->{'from'} = $ML{'.error.scalemaxlessmin'}; } if ((($qrec->{'to'}-$qrec->{'from'})/$qrec->{'by'}) > $RULES{'scale'}->{'maxitems'}) { $err->{$q}->{'to'} = BML::ml('.error.scaletoobig', { 'max' => $RULES{'scale'}->{'maxitems'} }); } next; } # otherwise, let it by. $qrec->{$atr} = $val; } # insert record into poll structure $poll->{'pq'}->[$q] = $qrec; my $num_opts = 0; foreach my $o (0..$qrec->{'opts'}-1) { next unless $POST{"pq_${q}_opt_$o"}; if (length($POST{"pq_${q}_opt_$o"}) > $RULES{'items'}->{'maxlength'}) { $qrec->{'opt'}->[$o] = substr($POST{"pq_${q}_opt_$o"}, 0, $RULES{'items'}->{'maxlength'}); $err->{$q}->{$o}->{'items'} = $ML{'.error.texttoobig'}; } else { # no change necessary $qrec->{'opt'}->[$o] = $POST{"pq_${q}_opt_$o"}; } $num_opts++; } # too few options specified? if ($num_opts < $RULES{'items'}->{'min'} && $qrec->{'type'} =~ /^(drop|check|radio)$/) { $err->{$q}->{'items'} = $ML{'.error.allitemsblank'}; } } # closure to apply action to poll object, given 'type', 'item', and 'val' my $do_action = sub { my ($type, $item, $val) = @_; return unless $type && defined $item && defined $val; # move action if ($type eq "move") { # up or down? my $adj = undef; if ($val eq 'up' && $item-1 >= 0) { $adj = $item - 1; } elsif ($val eq 'dn' && $item+1 <= $poll->{'count'}) { $adj = $item + 1; } # invalid action return unless $adj; # swap poll items and error references my $swap = sub { return (@_[1], @_[0]) }; ($poll->{'pq'}->[$adj], $poll->{'pq'}->[$item]) = $swap->($poll->{'pq'}->[$adj], $poll->{'pq'}->[$item]); ($err->{$adj}, $err->{$item}) = $swap->($err->{$adj}, $err->{$item}); return; } # delete action if ($type eq "delete") { # delete from poll and decrement question count splice(@{$poll->{"pq"}}, $item, 1); $poll->{'count'}--; delete $err->{$item}; return; } # request more options if ($type eq "request") { # add more items $poll->{"pq"}->[$item]->{'opts'} += $RULES{'items'}->{'more'}; $poll->{'pq'}->[$item]->{'opts'} = $RULES{'items'}->{'max'} if @{$poll->{'pq'}}[$item]->{'opts'} > $RULES{'items'}->{'max'}; return; } # insert if ($type eq "insert") { # increase poll count $poll->{'count'}++; # splice new item in splice ( @{$poll->{'pq'}}, $item, 0, { "question" => '', "type" => $val, "opts" => ($val =~ /^(radio|drop|check)$/) ? $RULES{'items'}->{'start'} : 0, "opt" => [], } ); return; } }; # go through the count again, this time apply requested actions foreach my $q (0..$poll->{'count'}) { # if there is an action, perform the action foreach my $act (qw(move delete insert request)) { # images stick an .x and .y on inputs my $do = $POST{"$act:$q:do.x"} ? "$act:$q:do.x" : "$act:$q:do"; # catches everything but move if ($POST{$do}) { # catches deletes, requests, etc if ($act ne 'insert') { $do_action->($act, $q, $act); next; } # catches inserts if ($POST{"$act:$q"} =~ /^(radio|check|drop|text|scale)$/) { $do_action->($act, $q, $1); next; } } # catches moves if ($POST{"$act:$q:up.x"} =~ /\d+/ || $POST{"$act:$q:dn.x"} =~ /\d+/) { $do_action->($act, $q, $POST{"$act:$q:up.x"} ? 'up' : 'dn'); next; } } } # all arguments are refs, nothing to return return $poll; }; # generate edit form to be displayed to user my $make_form = sub { my ($poll, $err) = @_; my $ret; ### Form Header $ret .= "
\n"; $ret .= LJ::html_hidden('count', $poll->{'count'}); $ret .= "
$ML{'.haserrors'}
\n" if %$err; ### Poll Properties -- name, whovote, whoview $ret .= "\n"; $ret .= "
\n"; $ret .= "

$ML{'.whoview'}

\n"; $ret .= "

$ML{'.whovote'}

\n"; $ret .= "$ML{'.pollname'}
"; $ret .= LJ::html_text({ 'name' => 'name', 'size' => '50', 'maxlength' => $RULES{'pollname'}->{'maxlength'}, 'value' => $poll->{'name'} }) . "\n"; $ret .= "
\n\n"; ### Poll Questions $ret .= "\n"; # closure for an html select box to insert element my $insert_element_html = sub { my $after = shift; my $ret; $ret .= "
= $RULES{"elements"}->{"max"}) { $ret .= "\n"; } else { $ret .= "$ML{'.insertquestion'} \n"; $ret .= LJ::html_submit("insert:$after:do", $ML{'.button.insert'}) . "\n"; } $ret .= " standout?>
\n"; return $ret; }; # if they have no elements, we need to manually give them an insert option $ret .= $insert_element_html->(0); # go through our elements in order foreach my $q (0..$poll->{'count'}-1) { my $elem = $poll->{'pq'}->[$q]; $ret .= " ($q + 1) }) . " - $TYPENAME{$elem->{'type'}} h2?>\n"; $ret .= "\n"; $ret .= "\n"; $ret .= "\n
\n"; # can't move the first element up if ($q > 0) { $ret .= LJ::img('btn_up', 'input', "move:$q:up"); } # delete button $ret .= "
" . LJ::img('btn_del', 'input', "delete:$q:do"); # can't move the last element down if ($q < ($poll->{'count'} - 1)) { $ret .= "
" . LJ::img('btn_down', 'input', "move:$q:dn"); } $ret .= "
\n"; # question text and hidden fields $ret .= LJ::html_hidden("pq_${q}_type", $elem->{'type'}, "pq_${q}_opts", $elem->{'opts'}); $ret .= "
Question:
"; $ret .= LJ::html_text({ 'name' => "pq_${q}_question", 'size' => '50', 'maxlength' => $RULES{'question'}->{'maxlength'}, 'value' => $elem->{"question"} }) . "\n"; $ret .= "
[$err->{$q}->{'question'}]\n" if $err->{$q}->{'question'}; $ret .= "
\n"; # spit out opts -- choices for drop-down, radio, etc questions if ($elem->{'type'} =~ /^(radio|check|drop)$/) { $ret .= "Options:\n"; foreach my $o (0..$elem->{'opts'}-1) { $ret .= "
"; $ret .= LJ::html_check({ 'type' => 'radio', 'name' => "dummy_$q", 'disabled' => 'disabled' }) if $elem->{'type'} eq 'radio'; $ret .= LJ::html_check({ 'type' => 'checkbox', 'disabled' => 'disabled' }) if $elem->{'type'} eq 'check'; $ret .= LJ::html_text({ 'type' => 'text', 'name' => "pq_${q}_opt_$o", 'size' => '35', 'maxlength' => $RULES{'items'}->{'maxlength'}, 'value' => $elem->{'opt'}->[$o] }) . "\n"; } $ret .= "
[$err->{$q}->{'items'}]\n" if $err->{$q}->{"items"}; $ret .= $elem->{'opts'} < $RULES{'items'}->{'max'} ? "
" . LJ::html_submit("request:$q:do", undef, { 'raw' => " value='More >>'" }) . "\n" : "
\n"; # text type } elsif ($elem->{'type'} eq 'text') { foreach my $atr ( qw(size maxlength) ) { $ret .= ucfirst($atr) . ": "; $ret .= LJ::html_text({ 'name' => "pq_${q}_$atr", 'value' => $elem->{$atr} || $RULES{'text'}->{$atr}, 'size' => '3', 'maxlength' => '3' }) . "\n"; $ret .= "
[$err->{$q}->{$atr}]\n" if $err->{$q}->{$atr}; } # scale type } elsif ($elem->{'type'} eq 'scale') { foreach my $atr ( qw(from to by) ) { $ret .= ' ' . ucfirst($atr) . ": "; $ret .= LJ::html_text({ 'name' => "pq_${q}_$atr", 'value' => defined $elem->{$atr} ? $elem->{$atr} : $RULES{'scale'}->{$atr}, 'size' => '3', 'maxlength' => '9' }) . "\n"; } foreach my $atr ( qw(from to by items) ) { $ret .= "
[$err->{$q}->{$atr}]\n" if $err->{$q}->{$atr}; } } $ret .= "
\n
\n\n"; # add a new element unless they're already at the max $ret .= $insert_element_html->($q+1, $poll); } if ($poll->{'count'} > 0) { $ret .= "\n"; $ret .= "
\n"; $ret .= LJ::html_submit('start_over', undef, { 'raw' => "value='← $ML{'.button.startover'}'" }) . "\n"; $ret .= LJ::html_submit('see_code', undef, { 'raw' => "value='$ML{'.button.seecode'} →'" }) . "\n"; $ret .= LJ::html_submit('see_preview', undef, { 'raw' => "value='$ML{'.button.preview'} →'" }) . "\n"; $ret .= "
\n"; } $ret .= "
\n"; return $ret; }; # preview page so the users can view a preview and decide # if they want to continue or go back for revision my $make_preview = sub { my $poll = shift; # create poll code given a %poll object my $make_code = sub { my $poll = shift; my $ret; # start out the tag $ret .= "\n"; # go through and make tags foreach my $q (0..$poll->{'count'}-1) { my $elem = $poll->{'pq'}->[$q]; $ret .= "{'type'} eq 'text') { foreach ( qw(size maxlength) ) { $ret .= " $_='" . LJ::ehtml($elem->{$_}) . "'"; } } elsif ($elem->{'type'} eq 'scale') { foreach ( qw(from to by) ) { $ret .= " $_='" . LJ::ehtml($elem->{$_}) . "'"; } } $ret .= ">\n"; $ret .= $elem->{'question'} . "\n" if $elem->{'question'}; if ($elem->{'type'} =~ /^(radio|drop|check)$/) { # make tags foreach my $o (0..$elem->{'opts'}) { $ret .= "$elem->{'opt'}->[$o]\n" if $elem->{'opt'}->[$o] ne ''; } } $ret .= "\n"; } # close off the poll $ret .= ""; # escape html on this because it'll currently be sent to user so they can copy/paste return $ret; }; # generates html for the hidden elements necessary to maintain # the state of the given poll my $poll_hidden = sub { my $poll = shift; my @elements = (); foreach my $k (keys %$poll) { # poll attributes unless (ref $poll->{$k} eq 'ARRAY') { push @elements, ($k, $poll->{$k}); next; } # poll questions my $q_idx = 0; foreach my $q (@{$poll->{$k}}) { # question attributes foreach my $atr (keys %$q) { unless (ref $q->{$atr} eq 'ARRAY') { push @elements, ("${k}_${q_idx}_$atr", $q->{$atr}); next; } # radio/text/drop options my $opt_idx = 0; foreach my $o (@{$q->{$atr}}) { push @elements, ("${k}_${q_idx}_${atr}_$opt_idx", $o); $opt_idx++; } } $q_idx++; } } return LJ::html_hidden(@elements); }; # generate code for preview my $code = $make_code->($poll); # parse code into standard poll hashref # so we can feed it into LJ::Poll::preview() my $err; my $codecopy = $code; # parse function will eat the code my $stdpoll = (LJ::Poll::parse(\$codecopy, \$err, {}))[0]; return "[$ML{'.error.parsing'} $err]\n" if $err; # display poll preview my $ret; $ret .= "\n"; $ret .= "\n\n"; $ret .= "
\n"; # edit poll $ret .= "
\n"; $ret .= $poll_hidden->($poll); $ret .= LJ::html_submit('edit_poll', undef, { 'raw' => "value='← $ML{'.button.editpoll'}'" }) . "\n"; # need one more button, depending on which page they're currently on my ($name, $val) = $POST{'see_code'} ? ("see_preview", $ML{'.button.preview'}) : ("see_code", $ML{'.button.seecode'}); $ret .= LJ::html_submit($name, $val) . "\n"; $ret .= "
\n"; # submit button / form my $usejournal = $getextra ? "?usejournal=$authas" : ''; $ret .= "
\n"; $ret .= LJ::html_hidden('event', $code) . "\n"; $ret .= LJ::html_submit('showform', undef, { 'raw' => "value='$ML{'.button.postpoll'} →'" }) . "\n"; $ret .= "
\n"; # preview code or ... preview $ret .= "
\n"; # viewing code, show preview button if ($POST{'see_code'}) { $ret .= "
\n"; $ret .= LJ::html_textarea({ 'style' => 'width: 100%', 'rows' => '16', 'cols' => '60', 'value' => $code }); $ret .= "
\n"; # seeing preview, show code button } elsif ($POST{'see_preview'}) { # this has its own form open / close $ret .= LJ::Poll::preview($stdpoll); } $ret .= "
\n"; return $ret; }; # variables to pass around my $poll = {}; my $err = {}; # should we pregenerate something? if (my $pgid = $GET{'pregen'}+0) { $poll = LJ::run_hook('pregenerate_poll', $u, $pgid); } # process post input if (LJ::did_post() && ! $POST{'start_over'}) { # load poll hash from %POST and get action and error info $poll = $build_poll->($err); # generate poll preview for them if (($POST{'see_preview'} || $POST{'see_code'}) && ! %$err) { $body .= $make_preview->($poll); return; } } # show start page $body .= $make_form->($poll, $err); return; } _code?> body=> page?> lib: LJ::img, cgi-bin/htmlcontrols.pl, cgi-bin/ljpoll.pl link: htdocs/support/faqbrowse.bml, htdocs/login.bml img: img::btn_up, img::btn_down, img::btn_del post: htdocs/poll/create.bml, htdocs/update.bml _c?>