--- loncom/interface/lonhelper.pm 2003/05/08 19:52:43 1.25 +++ loncom/interface/lonhelper.pm 2003/05/27 19:59:38 1.34 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # .helper XML handler to implement the LON-CAPA helper # -# $Id: lonhelper.pm,v 1.25 2003/05/08 19:52:43 bowersj2 Exp $ +# $Id: lonhelper.pm,v 1.34 2003/05/27 19:59:38 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -53,7 +53,10 @@ Each state contains one or more state el messages, resource selections, or date queries. The helper tag is required to have one attribute, "title", which is the name -of the helper itself, such as "Parameter helper". +of the helper itself, such as "Parameter helper". The helper tag may optionally +have a "requiredpriv" attribute, specifying the priviledge a user must have +to use the helper, or get denied access. See loncom/auth/rolesplain.tab for +useful privs. Default is full access, which is often wrong! =head2 State tags @@ -234,6 +237,7 @@ sub real_handler { my $file; read $fh, $file, 100000000; + # Send header, don't cache this page if ($r->header_only) { if ($ENV{'browser.mathml'}) { @@ -256,10 +260,17 @@ sub real_handler { # xml parsing &Apache::lonxml::xmlparse($r, 'helper', $file); + my $allowed = $helper->allowedCheck(); + if (!$allowed) { + $ENV{'user.error.msg'} = $ENV{'request.uri'}.':'.$helper->{REQUIRED_PRIV}. + ":0:0:Permission denied to access this helper."; + return HTTP_NOT_ACCEPTABLE; + } + $helper->process(); $r->print($helper->display()); - return OK; + return OK; } sub registerHelperTags { @@ -283,7 +294,7 @@ sub start_helper { registerHelperTags(); - Apache::lonhelper::helper->new($token->[2]{'title'}); + Apache::lonhelper::helper->new($token->[2]{'title'}, $token->[2]{'requiredpriv'}); return ''; } @@ -342,9 +353,8 @@ sub new { my $self = {}; $self->{TITLE} = shift; + $self->{REQUIRED_PRIV} = shift; - Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); - # If there is a state from the previous form, use that. If there is no # state, use the start state parameter. if (defined $ENV{"form.CURRENT_STATE"}) @@ -460,8 +470,22 @@ sub declareVar { my $envname = 'form.' . $var . '.forminput'; if (defined($ENV{$envname})) { - $self->{VARS}->{$var} = $ENV{$envname}; + if (ref($ENV{$envname})) { + $self->{VARS}->{$var} = join('|||', @{$ENV{$envname}}); + } else { + $self->{VARS}->{$var} = $ENV{$envname}; + } + } +} + +sub allowedCheck { + my $self = shift; + + if (!defined($self->{REQUIRED_PRIV})) { + return 1; } + + return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $ENV{'request.course.id'}); } sub changeState { @@ -544,19 +568,24 @@ sub display { $bodytag HEADER - if (!$state->overrideForm()) { $result.="
"; } + if (!$state->overrideForm()) { $result.=""; } $result .= < + @@ -688,6 +737,7 @@ sub render { for my $element (@{$self->{ELEMENTS}}) { push @results, $element->render(); } + return join("\n", @results); } @@ -700,23 +750,8 @@ package Apache::lonhelper::element; =head2 Element Base Class -The Apache::lonhelper::element base class provides support methods for -the elements to use, such as a multiple value processer. - -B: - -=over 4 - -=item * process_multiple_choices(formName, varName): Process the form -element named "formName" and place the selected items into the helper -variable named varName. This is for things like checkboxes or -multiple-selection listboxes where the user can select more then -one entry. The selected entries are delimited by triple pipes in -the helper variables, like this: - - CHOICE_1|||CHOICE_2|||CHOICE_3 - -=back +The Apache::lonhelper::element base class provides support for elements +and defines some generally useful tags for use in elements. B @@ -744,6 +779,12 @@ some setting accidentally. Again, see the course initialization helper for examples. +B + +If the element stores the name of the variable in a 'variable' member, which +the provided ones all do, you can retreive the value of the variable by calling +this method. + =cut BEGIN { @@ -830,26 +871,9 @@ sub overrideForm { return 0; } -sub process_multiple_choices { +sub getValue { my $self = shift; - my $formname = shift; - my $var = shift; - - # Must extract values from data directly, as there - # may be more then one. - my @values; - for my $formparam (split (/&/, $ENV{QUERY_STRING})) { - my ($name, $value) = split(/=/, $formparam); - if ($name ne $formname) { - next; - } - $value =~ tr/+/ /; - $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; - push @values, $value; - } - $helper->{VARS}->{$var} = join('|||', @values); - - return; + return $helper->{VARS}->{$self->{'variable'}}; } 1; @@ -960,6 +984,10 @@ the result is stored in. takes an attribute "multichoice" which, if set to a true value, will allow the user to select multiple choices. + takes an attribute "allowempty" which, if set to a true +value, will allow the user to select none of the choices without raising +an error message. + B can have the following subtags: @@ -1049,6 +1077,7 @@ sub start_choices { $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'})); $helper->declareVar($paramHash->{'variable'}); $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; + $paramHash->{'allowempty'} = $token->[2]{'allowempty'}; $paramHash->{CHOICES} = []; return ''; } @@ -1131,7 +1160,7 @@ BUTTONS if (defined($self->{DEFAULT_VALUE})) { $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE}); die 'Error in default value code for variable ' . - {'variable'} . ', Perl said:' . $@ if $@; + $self->{'variable'} . ', Perl said: ' . $@ if $@; } else { $checkedChoicesFunc = sub { return ''; }; } @@ -1193,15 +1222,14 @@ sub postprocess { my $self = shift; my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'}; - if (!defined($chosenValue)) { + if (!defined($chosenValue) && !$self->{'allowempty'}) { $self->{ERROR_MSG} = "You must choose one or more choices to" . " continue."; return 0; } - if ($self->{'multichoice'}) { - $self->process_multiple_choices($self->{'variable'}.'.forminput', - $self->{'variable'}); + if (ref($chosenValue)) { + $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue); } if (defined($self->{NEXTSTATE})) { @@ -1447,7 +1475,10 @@ variable stores the results. It also tak which controls whether the user can select more then one resource. The "toponly" attribute controls whether the resource display shows just the resources in that sequence, or recurses into all sub-sequences, defaulting -to false. +to false. The "suppressEmptySequences" attribute reflects the +suppressEmptySequences argument to the render routine, which will cause +folders that have all of their contained resources filtered out to also +be filtered out. B @@ -1508,6 +1539,7 @@ sub start_resource { $paramHash->{'variable'} = $token->[2]{'variable'}; $helper->declareVar($paramHash->{'variable'}); $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; + $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'}; $paramHash->{'toponly'} = $token->[2]{'toponly'}; return ''; } @@ -1676,6 +1708,7 @@ BUTTONS 'showParts' => 0, 'filterFunc' => $filterFunc, 'resource_no_folder_link' => 1, + 'suppressEmptySequences' => $self->{'suppressEmptySequences'}, 'iterator_map' => $mapUrl } ); @@ -1687,11 +1720,6 @@ BUTTONS sub postprocess { my $self = shift; - if ($self->{'multichoice'}) { - $self->process_multiple_choices($self->{'variable'}.'.forminput', - $self->{'variable'}); - } - if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) { $self->{ERROR_MSG} = 'You must choose at least one resource to continue.'; return 0; @@ -1849,10 +1877,6 @@ sub postprocess { return 0; } - if ($self->{'multichoice'}) { - $self->process_multiple_choices($self->{'variable'}.'.forminput', - $self->{'variable'}); - } if (defined($self->{NEXTSTATE})) { $helper->changeState($self->{NEXTSTATE}); } @@ -1898,6 +1922,8 @@ no strict; @ISA = ("Apache::lonhelper::element"); use strict; +use Apache::lonpubdir; # for getTitleString + BEGIN { &Apache::lonhelper::register('Apache::lonhelper::files', ('files', 'filechoice', 'filefilter')); @@ -2054,6 +2080,9 @@ BUTTONS $color = ''; } + # Get the title + my $title = Apache::lonpubdir::getTitleString($fileName); + # Netscape 4 is stupid and there's nowhere to put the # information on the input tag that the file is Published, # Unpublished, etc. In *real* browsers we can just say @@ -2080,8 +2109,9 @@ BUTTONS if (!$self->{'multichoice'} && $choices == 0) { $result .= ' checked'; } - $result .= "/>\n"; + $result .= "/>" . + "" . + "" . "\n"; $choices++; } } @@ -2137,10 +2167,6 @@ sub postprocess { return 0; } - if ($self->{'multichoice'}) { - $self->process_multiple_choices($self->{'variable'}.'.forminput', - $self->{'variable'}); - } if (defined($self->{NEXTSTATE})) { $helper->changeState($self->{NEXTSTATE}); } @@ -2224,6 +2250,96 @@ sub end_section { } 1; +package Apache::lonhelper::string; + +=pod + +=head2 Element: string + +string elements provide a string entry field for the user. string elements +take the usual 'variable' and 'nextstate' parameters. string elements +also pass through 'maxlength' and 'size' attributes to the input tag. + +string honors the defaultvalue tag, if given. + +=cut + +no strict; +@ISA = ("Apache::lonhelper::element"); +use strict; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::string', + ('string')); +} + +sub new { + my $ref = Apache::lonhelper::element->new(); + bless($ref); +} + +# CONSTRUCTION: Construct the message element from the XML +sub start_string { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + $paramHash->{'variable'} = $token->[2]{'variable'}; + $helper->declareVar($paramHash->{'variable'}); + $paramHash->{'nextstate'} = $token->[2]{'nextstate'}; + $paramHash->{'maxlength'} = $token->[2]{'maxlength'}; + $paramHash->{'size'} = $token->[2]{'size'}; + + return ''; +} + +sub end_string { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::string->new(); + return ''; +} + +sub render { + my $self = shift; + my $result = '{'size'})) { + $result .= ' size="' . $self->{'size'} . '"'; + } + if (defined($self->{'maxlength'})) { + $result .= ' maxlength="' . $self->{'maxlength'} . '"'; + } + + if (defined($self->{DEFAULT_VALUE})) { + my $valueFunc = eval($self->{DEFAULT_VALUE}); + die 'Error in default value code for variable ' . + $self->{'variable'} . ', Perl said: ' . $@ if $@; + $result .= ' value="' . &$valueFunc($helper, $self) . '"'; + } + + $result .= ' />'; + + return $result; +} + +# If a NEXTSTATE was given, switch to it +sub postprocess { + my $self = shift; + if (defined($self->{NEXTSTATE})) { + $helper->changeState($self->{NEXTSTATE}); + } + + return 1; +} + +1; + package Apache::lonhelper::general; =pod @@ -2357,6 +2473,137 @@ sub end_eval { 1; +package Apache::lonhelper::final; + +=pod + +=head2 Element: final + + is a special element that works with helpers that use the +tag. It goes through all the states and elements, executing the +snippets and collecting the results. Finally, it takes the user out of the +helper, going to a provided page. + +If the parameter "restartCourse" is true, this will override the buttons and +will make a "Finish Helper" button that will re-initialize the course for them, +which is useful for the Course Initialization helper so the users never see +the old values taking effect. + +=cut + +no strict; +@ISA = ("Apache::lonhelper::element"); +use strict; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::final', + ('final', 'exitpage')); +} + +sub new { + my $ref = Apache::lonhelper::element->new(); + bless($ref); +} + +sub start_final { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + $paramHash->{'restartCourse'} = $token->[2]{'restartCourse'}; + + return ''; +} + +sub end_final { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + Apache::lonhelper::final->new(); + + return ''; +} + +sub start_exitpage { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + $paramHash->{EXIT_PAGE} = &Apache::lonxml::get_all_text('/exitpage', + $parser); + + return ''; +} + +sub end_exitpage { return ''; } + +sub render { + my $self = shift; + + my @results; + + # Collect all the results + for my $stateName (keys %{$helper->{STATES}}) { + my $state = $helper->{STATES}->{$stateName}; + + for my $element (@{$state->{ELEMENTS}}) { + if (defined($element->{FINAL_CODE})) { + # Compile the code. + my $code = 'sub { my $helper = shift; my $element = shift; ' + . $element->{FINAL_CODE} . '}'; + $code = eval($code); + die 'Error while executing final code for element with var ' . + $element->{'variable'} . ', Perl said: ' . $@ if $@; + + my $result = &$code($helper, $element); + if ($result) { + push @results, $result; + } + } + } + } + + if (scalar(@results) == 0) { + return ''; + } + + my $result = "
    \n"; + for my $re (@results) { + $result .= '
  • ' . $re . "
  • \n"; + } + + if (!@results) { + $result .= '
  • No changes were made to current settings.
  • '; + } + + if ($self->{'restartCourse'}) { + $result .= "
    \n" . + "\n" . + "" . + "" . + "\n" . + "\n\n" . + "
    "; + } + + return $result . '
'; +} + +sub overrideForm { + my $self = shift; + return $self->{'restartCourse'}; +} + +1; + package Apache::lonhelper::parmwizfinal; # This is the final state for the parmwizard. It is not generally useful, @@ -2404,60 +2651,72 @@ sub render { 'due_date' => "0_duedate", 'answer_date' => "0_answerdate"); - my $result = "
\n"; - $result .= '

Confirm that this information is correct, then click "Finish Wizard" to complete setting the parameter.

    '; my $affectedResourceId = ""; my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}}; my $level = ""; - - # Print the type of manipulation: - $result .= '
  • Setting the ' . $dateTypeHash{$vars->{ACTION_TYPE}} - . "
  • \n"; - if ($vars->{ACTION_TYPE} eq 'due_date' || - $vars->{ACTION_TYPE} eq 'answer_date') { - # for due dates, we default to "date end" type entries - $result .= "\n"; - $result .= "\n"; - $result .= "\n"; - } elsif ($vars->{ACTION_TYPE} eq 'open_date') { - $result .= "\n"; - $result .= "\n"; - $result .= "\n"; - } - + my $resourceString; + my $symb; + my $paramlevel; + # Print the granularity, depending on the action if ($vars->{GRANULARITY} eq 'whole_course') { - $result .= '
  • for all resources in the course
  • '; + $resourceString .= '
  • for all resources in the course
  • '; $level = 9; # general course, see lonparmset.pm perldoc $affectedResourceId = "0.0"; + $symb = 'a'; + $paramlevel = 'general'; } elsif ($vars->{GRANULARITY} eq 'map') { my $navmap = Apache::lonnavmaps::navmap->new( $ENV{"request.course.fn"}.".db", $ENV{"request.course.fn"}."_parms.db", 0, 0); my $res = $navmap->getById($vars->{RESOURCE_ID}); my $title = $res->compTitle(); + $symb = $res->symb(); $navmap->untieHashes(); - $result .= "
  • for the map named $title
  • "; + $resourceString .= "
  • for the map named $title
  • "; $level = 8; $affectedResourceId = $vars->{RESOURCE_ID}; + $paramlevel = 'map'; } else { my $navmap = Apache::lonnavmaps::navmap->new( $ENV{"request.course.fn"}.".db", $ENV{"request.course.fn"}."_parms.db", 0, 0); my $res = $navmap->getById($vars->{RESOURCE_ID}); + $symb = $res->symb(); my $title = $res->compTitle(); $navmap->untieHashes(); - $result .= "
  • for the resource named $title
  • "; + $resourceString .= "
  • for the resource named $title
  • "; $level = 7; $affectedResourceId = $vars->{RESOURCE_ID}; + $paramlevel = 'full'; } + my $result = "\n"; + $result .= '

    Confirm that this information is correct, then click "Finish Wizard" to complete setting the parameter.

      '; + + # Print the type of manipulation: + $result .= '
    • Setting the ' . $dateTypeHash{$vars->{ACTION_TYPE}} + . "
    • \n"; + if ($vars->{ACTION_TYPE} eq 'due_date' || + $vars->{ACTION_TYPE} eq 'answer_date') { + # for due dates, we default to "date end" type entries + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; + } elsif ($vars->{ACTION_TYPE} eq 'open_date') { + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; + } + + $result .= $resourceString; + # Print targets if ($vars->{TARGETS} eq 'course') { $result .= '
    • for all students in course
    • '; @@ -2470,7 +2729,10 @@ sub render { } else { # FIXME: This is probably wasteful! Store the name! my $classlist = Apache::loncoursedata::get_classlist(); - my $name = $classlist->{$vars->{USER_NAME}}->[6]; + my $username = $vars->{USER_NAME}; + # Chop off everything after the last colon (section) + $username = substr($username, 0, rindex($username, ':')); + my $name = $classlist->{$username}->[6]; $result .= "
    • for $name
    • "; $level -= 6; my ($uname, $udom) = split /:/, $vars->{USER_NAME}; @@ -2488,6 +2750,12 @@ sub render { # print pres_marker $result .= "\n\n"; + + # Make the table appear + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; $result .= "

      \n";

$stateTitle

HEADER + $result .= "
"; + if (!$state->overrideForm()) { $result .= $self->_saveVars(); } - $result .= $state->render() . "

 

"; + $result .= $state->render(); + + $result .= "
"; + # Warning: Copy and pasted from below, because it's too much trouble to + # turn this into a subroutine if (!$state->overrideForm()) { - $result .= '
'; if ($self->{STATE} ne $self->{START_STATE}) { #$result .= '  '; } @@ -565,17 +594,37 @@ HEADER $result .= "End Helper"; } else { - $result .= 'overrideForm()) { + if ($self->{STATE} ne $self->{START_STATE}) { + #$result .= '  '; + } + if ($self->{DONE}) { + my $returnPage = $self->{RETURN_PAGE}; + $result .= "End Helper"; + } + else { + $result .= '{VARS}}) { # $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; #} + $result .= "
"; + $result .= <
" . $file . - "$status
" . $file . "$title$status