{ "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\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"; 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 .= "