--- loncom/interface/lonhelper.pm 2003/04/11 17:21:18 1.6 +++ loncom/interface/lonhelper.pm 2003/05/05 15:17:25 1.18 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # .helper XML handler to implement the LON-CAPA helper # -# $Id: lonhelper.pm,v 1.6 2003/04/11 17:21:18 bowersj2 Exp $ +# $Id: lonhelper.pm,v 1.18 2003/05/05 15:17:25 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -30,10 +30,6 @@ # (.helper handler # -# FIXME: Change register calls to register with the helper. -# Then have the helper reg and unreg the tags. -# This removes my concerns about breaking other code. - =pod =head1 lonhelper - HTML Helper framework for LON-CAPA @@ -62,8 +58,8 @@ of the helper itself, such as "Parameter =head2 State tags State tags are required to have an attribute "name", which is the symbolic -name of the state and will not be directly seen by the user. The wizard is -required to have one state named "START", which is the state the wizard +name of the state and will not be directly seen by the user. The helper is +required to have one state named "START", which is the state the helper will start with. By convention, this state should clearly describe what the helper will do for the user, and may also include the first information entry the user needs to do for the helper. @@ -88,6 +84,77 @@ Of course this does nothing. In order fo necessary to put actual elements into the wizard. Documentation for each of these elements follows. +=head2 Creating a Helper With Code, Not XML + +In some situations, such as the printing wizard (see lonprintout.pm), +writing the helper in XML would be too complicated, because of scope +issues or the fact that the code actually outweighs the XML. It is +possible to create a helper via code, though it is a little odd. + +Creating a helper via code is more like issuing commands to create +a helper then normal code writing. For instance, elements will automatically +be added to the last state created, so it's important to create the +states in the correct order. + +First, create a new helper: + + use Apache::lonhelper; + + my $helper = Apache::lonhelper::new->("Helper Title"); + +Next you'll need to manually add states to the helper: + + Apache::lonhelper::state->new("STATE_NAME", "State's Human Title"); + +You don't need to save a reference to it because all elements up until +the next state creation will automatically be added to this state. + +Elements are created by populating the $paramHash in +Apache::lonhelper::paramhash. To prevent namespace issues, retrieve +a reference to that has with getParamHash: + + my $paramHash = Apache::lonhelper::getParamHash(); + +You will need to do this for each state you create. + +Populate the $paramHash with the parameters for the element you wish +to add next; the easiest way to find out what those entries are is +to read the code. Some common ones are 'variable' to record the variable +to store the results in, and NEXTSTATE to record a next state transition. + +Then create your element: + + $paramHash->{MESSAGETEXT} = "This is a message."; + Apache::lonhelper::message->new(); + +The creation will take the $paramHash and bless it into a +Apache::lonhelper::message object. To create the next element, you need +to get a reference to the new, empty $paramHash: + + $paramHash = Apache::lonhelper::getParamHash(); + +and you can repeat creating elements that way. You can add states +and elements as needed. + +See lonprintout.pm, subroutine printHelper for an example of this, where +we dynamically add some states to prevent security problems, for instance. + +Normally the machinery in the XML format is sufficient; dynamically +adding states can easily be done by wrapping the state in a +tag. This should only be used when the code dominates the XML content, +the code is so complicated that it is difficult to get access to +all of the information you need because of scoping issues, or so much +of the information used is persistent because would-be or + blocks that using the {DATA} mechanism results in hard-to-read +and -maintain code. + +It is possible to do some of the work with an XML fragment parsed by +lonxml; again, see lonprintout.pm for an example. In that case it is +imperative that you call B +before parsing XML fragments and B +when you are done. See lonprintout.pm for examples of this usage in the +printHelper subroutine. + =cut package Apache::lonhelper; @@ -95,12 +162,26 @@ use Apache::Constants qw(:common); use Apache::File; use Apache::lonxml; +# Register all the tags with the helper, so the helper can +# push and pop them + +my @helperTags; + +sub register { + my ($namespace, @tags) = @_; + + for my $tag (@tags) { + push @helperTags, [$namespace, $tag]; + } +} + BEGIN { - &Apache::lonxml::register('Apache::lonhelper', - ('helper', 'state')); + Apache::lonxml::register('Apache::lonhelper', + ('helper')); + register('Apache::lonhelper', ('state')); } -# Since all wizards are only three levels deep (wizard tag, state tag, +# Since all helpers are only three levels deep (helper tag, state tag, # substate type), it's easier and more readble to explicitly track # those three things directly, rather then futz with the tag stack # every time. @@ -112,16 +193,19 @@ my $substate; # end of the element tag is located. my $paramHash; +# For debugging purposes, one can send a second parameter into this +# function, the 'uri' of the helper you wish to have rendered, and +# call this from other handlers. sub handler { my $r = shift; - $ENV{'request.uri'} = $r->uri(); - my $filename = '/home/httpd/html' . $r->uri(); + my $uri = shift; + if (!defined($uri)) { $uri = $r->uri(); } + $ENV{'request.uri'} = $uri; + my $filename = '/home/httpd/html' . $uri; my $fh = Apache::File->new($filename); my $file; read $fh, $file, 100000000; - Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); - # Send header, don't cache this page if ($r->header_only) { if ($ENV{'browser.mathml'}) { @@ -144,8 +228,22 @@ sub handler { # xml parsing &Apache::lonxml::xmlparse($r, 'helper', $file); + $helper->process(); + $r->print($helper->display()); - return OK; + return OK; +} + +sub registerHelperTags { + for my $tagList (@helperTags) { + Apache::lonxml::register($tagList->[0], $tagList->[1]); + } +} + +sub unregisterHelperTags { + for my $tagList (@helperTags) { + Apache::lonxml::deregister($tagList->[0], $tagList->[1]); + } } sub start_helper { @@ -154,8 +252,10 @@ sub start_helper { if ($target ne 'helper') { return ''; } - - $helper = Apache::lonhelper::helper->new($token->[2]{'title'}); + + registerHelperTags(); + + Apache::lonhelper::helper->new($token->[2]{'title'}); return ''; } @@ -165,7 +265,9 @@ sub end_helper { if ($target ne 'helper') { return ''; } - + + unregisterHelperTags(); + return ''; } @@ -176,11 +278,22 @@ sub start_state { return ''; } - $state = Apache::lonhelper::state->new($token->[2]{'name'}, - $token->[2]{'title'}); + Apache::lonhelper::state->new($token->[2]{'name'}, + $token->[2]{'title'}); return ''; } +# Use this to get the param hash from other files. +sub getParamHash { + return $paramHash; +} + +# Use this to get the helper, if implementing elements in other files +# (like lonprintout.pm) +sub getHelper { + return $helper; +} + # don't need this, so ignore it sub end_state { return ''; @@ -213,6 +326,8 @@ sub new { $self->{STATE} = "START"; } + Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); + $self->{TOKEN} = $ENV{'form.TOKEN'}; # If a token was passed, we load that in. Otherwise, we need to create a # new storage file @@ -265,6 +380,15 @@ sub new { $self->{STATES} = {}; $self->{DONE} = 0; + # Used by various helpers for various things; see lonparm.helper + # for an example. + $self->{DATA} = {}; + + $helper = $self; + + # Establish the $paramHash + $paramHash = {}; + bless($self, $class); return $self; } @@ -325,23 +449,15 @@ sub registerState { $self->{STATES}{$stateName} = $state; } -# Done in four phases -# 1: Do the post processing for the previous state. -# 2: Do the preprocessing for the current state. -# 3: Check to see if state changed, if so, postprocess current and move to next. -# Repeat until state stays stable. -# 4: Render the current state to the screen as an HTML page. -sub display { +sub process { my $self = shift; - my $result = ""; - # Phase 1: Post processing for state of previous screen (which is actually # the "current state" in terms of the helper variables), if it wasn't the # beginning state. if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") { my $prevState = $self->{STATES}{$self->{STATE}}; - $prevState->postprocess(); + $prevState->postprocess(); } # Note, to handle errors in a state's input that a user must correct, @@ -350,25 +466,45 @@ sub display { # Phase 2: Preprocess current state my $startState = $self->{STATE}; - my $state = $self->{STATES}{$startState}; + my $state = $self->{STATES}->{$startState}; - # Error checking; it is intended that the developer will have - # checked all paths and the user can't see this! + # For debugging, print something here to determine if you're going + # to an undefined state. if (!defined($state)) { - $result .="Error! The state ". $startState ." is not defined."; - return $result; + return; } $state->preprocess(); # Phase 3: While the current state is different from the previous state, # keep processing. - while ( $startState ne $self->{STATE} ) + while ( $startState ne $self->{STATE} && + defined($self->{STATES}->{$self->{STATE}}) ) { $startState = $self->{STATE}; - $state = $self->{STATES}{$startState}; + $state = $self->{STATES}->{$startState}; $state->preprocess(); } + return; +} + +# 1: Do the post processing for the previous state. +# 2: Do the preprocessing for the current state. +# 3: Check to see if state changed, if so, postprocess current and move to next. +# Repeat until state stays stable. +# 4: Render the current state to the screen as an HTML page. +sub display { + my $self = shift; + + my $state = $self->{STATES}{$self->{STATE}}; + + my $result = ""; + + if (!defined($state)) { + $result = "Error: state '$state' not defined!"; + return $result; + } + # Phase 4: Display. my $stateTitle = $state->title(); my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'',''); @@ -380,7 +516,7 @@ sub display { $bodytag HEADER - if (!$state->overrideForm()) { $result.="
"; } + if (!$state->overrideForm()) { $result.=""; } $result .= <

$stateTitle

@@ -408,9 +544,9 @@ HEADER $result .= "\n"; } - foreach my $key (keys %{$self->{VARS}}) { - $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; - } + #foreach my $key (keys %{$self->{VARS}}) { + # $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; + #} $result .= < @@ -452,6 +588,8 @@ sub new { $helper->registerState($self); + $state = $self; + return $self; } @@ -495,7 +633,16 @@ sub postprocess { } } +# Override the form if any element wants to. +# two elements overriding the form will make a mess, but that should +# be considered helper author error ;-) sub overrideForm { + my $self = shift; + for my $element (@{$self->{ELEMENTS}}) { + if ($element->overrideForm()) { + return 1; + } + } return 0; } @@ -546,8 +693,8 @@ the helper variables, like this: =cut BEGIN { - &Apache::lonxml::register('Apache::lonhelper::element', - ('nextstate')); + &Apache::lonhelper::register('Apache::lonhelper::element', + ('nextstate')); } # Because we use the param hash, this is often a sufficent @@ -594,27 +741,28 @@ sub render { return ''; } +sub overrideForm { + return 0; +} + sub process_multiple_choices { my $self = shift; my $formname = shift; my $var = shift; - my $formvalue = $ENV{'form.' . $formname}; - if ($formvalue) { - # Must extract values from querystring 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; + # 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; } - $helper->{VARS}->{$var} = join('|||', @values); + $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; } @@ -637,7 +785,7 @@ transition directly to the state in the This will display the HTML message and transition to the if -given. The HTML will be directly inserted into the wizard, so if you don't +given. The HTML will be directly inserted into the helper, so if you don't want text to run together, you'll need to manually wrap the in

tags, or whatever is appropriate for your HTML. @@ -657,8 +805,8 @@ no strict; use strict; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::message', - ('message', 'message_text')); + &Apache::lonhelper::register('Apache::lonhelper::message', + ('message')); } sub new { @@ -668,31 +816,30 @@ sub new { # CONSTRUCTION: Construct the message element from the XML sub start_message { - return ''; -} - -sub end_message { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; if ($target ne 'helper') { return ''; } - Apache::lonhelper::message->new(); + + $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message', + $parser); + + if (defined($token->[2]{'nextstate'})) { + $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; + } return ''; } -sub start_message_text { +sub end_message { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; if ($target ne 'helper') { return ''; } - - $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text', - $parser); + Apache::lonhelper::message->new(); + return ''; } - -sub end_message_text { return 1; } sub render { my $self = shift; @@ -745,6 +892,11 @@ B For example, Bobby McDormik. + can take a parameter "eval", which if set to + a true value, will cause the contents of the tag to be + evaluated as it would be in an tag; see tag + below. + may optionally contain a 'nextstate' attribute, which will be the state transisitoned to if the choice is made, if the choice is not multichoice. @@ -772,8 +924,6 @@ You can mix and match methods of creatin "push" onto the choice list, rather then wiping it out. (You can even remove choices programmatically, but that would probably be bad form.) -FIXME: Document and implement and in the element package. - =cut no strict; @@ -781,7 +931,7 @@ no strict; use strict; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::choices', + &Apache::lonhelper::register('Apache::lonhelper::choices', ('choice', 'choices')); } @@ -827,7 +977,9 @@ sub start_choice { my $human = &Apache::lonxml::get_all_text('/choice', $parser); my $nextstate = $token->[2]{'nextstate'}; - push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate]; + my $evalFlag = $token->[2]{'eval'}; + push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate, + $evalFlag]; return ''; } @@ -845,17 +997,20 @@ sub render { if ($self->{'multichoice'}) { $result .= < - function checkall(value) { - for (i=0; i SCRIPT $buttons = < - - + +
  BUTTONS } @@ -881,7 +1036,14 @@ BUTTONS $result .= " checked "; $checked = 1; } - $result .= "/> " . $choice->[0] . "\n"; + my $choiceLabel = $choice->[0]; + if ($choice->[4]) { # if we need to evaluate this choice + $choiceLabel = "sub { my $helper = shift; my $state = shift;" . + $choiceLabel . "}"; + $choiceLabel = eval($choiceLabel); + $choiceLabel = &$choiceLabel($helper, $self); + } + $result .= "/> " . $choiceLabel . "\n"; } $result .= "\n\n\n"; $result .= $buttons; @@ -960,7 +1122,7 @@ use strict; use Time::localtime; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::date', + &Apache::lonhelper::register('Apache::lonhelper::date', ('date')); } @@ -1146,7 +1308,10 @@ the user can manipulate the folders. takes the standard variable attribute to control what helper variable stores the results. It also takes a "multichoice" attribute, -which controls whether the user can select more then one resource. +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. B @@ -1173,6 +1338,9 @@ B "}" returns a string representing what you want to have as the value. By default, the value will be the resource ID of the object ($res->{ID}). +=item * : If the URL of a map is given here, only that map + will be displayed, instead of the whole course. + =back =cut @@ -1182,9 +1350,10 @@ no strict; use strict; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::resource', + &Apache::lonhelper::register('Apache::lonhelper::resource', ('resource', 'filterfunc', - 'choicefunc', 'valuefunc')); + 'choicefunc', 'valuefunc', + 'mapurl')); } sub new { @@ -1202,6 +1371,8 @@ sub start_resource { $paramHash->{'variable'} = $token->[2]{'variable'}; $helper->declareVar($paramHash->{'variable'}); + $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; + $paramHash->{'toponly'} = $token->[2]{'toponly'}; return ''; } @@ -1269,6 +1440,20 @@ sub start_valuefunc { sub end_valuefunc { return ''; } +sub start_mapurl { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + my $contents = Apache::lonxml::get_all_text('/mapurl', + $parser); + $paramHash->{MAP_URL} = $contents; +} + +sub end_mapurl { return ''; } + # A note, in case I don't get to this before I leave. # If someone complains about the "Back" button returning them # to the previous folder state, instead of returning them to @@ -1287,13 +1472,40 @@ sub render { my $var = $self->{'variable'}; my $curVal = $helper->{VARS}->{$var}; + my $buttons = ''; + + if ($self->{'multichoice'}) { + $result = < + function checkall(value, checkName) { + for (i=0; i +SCRIPT + $buttons = <   + + +
  +BUTTONS + } + if (defined $self->{ERROR_MSG}) { - $result .= '' . $self->{ERROR_MSG} . '

'; + $result .= '
' . $self->{ERROR_MSG} . '

'; } + $result .= $buttons; + my $filterFunc = $self->{FILTER_FUNC}; my $choiceFunc = $self->{CHOICE_FUNC}; my $valueFunc = $self->{VALUE_FUNC}; + my $mapUrl = $self->{MAP_URL}; + my $multichoice = $self->{'multichoice'}; # Create the composite function that renders the column on the nav map # have to admit any language that lets me do this can't be all bad @@ -1301,12 +1513,16 @@ sub render { my $checked = 0; my $renderColFunc = sub { my ($resource, $part, $params) = @_; - + + my $inputType; + if ($multichoice) { $inputType = 'checkbox'; } + else {$inputType = 'radio'; } + if (!&$choiceFunc($resource)) { return ' '; } else { - my $col = "{'toponly'}; $result .= &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, Apache::lonnavmaps::resource()], 'showParts' => 0, - 'url' => $helper->{URL}, 'filterFunc' => $filterFunc, - 'resource_no_folder_link' => 1 } + 'resource_no_folder_link' => 1, + 'iterator_map' => $mapUrl } ); + + $result .= $buttons; return $result; } 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; + } + if (defined($self->{NEXTSTATE})) { $helper->changeState($self->{NEXTSTATE}); } @@ -1363,7 +1592,7 @@ use strict; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::student', + &Apache::lonhelper::register('Apache::lonhelper::student', ('student')); } @@ -1382,6 +1611,10 @@ sub start_student { $paramHash->{'variable'} = $token->[2]{'variable'}; $helper->declareVar($paramHash->{'variable'}); $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; + if (defined($token->[2]{'nextstate'})) { + $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; + } + } sub end_student { @@ -1397,21 +1630,25 @@ sub render { my $self = shift; my $result = ''; my $buttons = ''; + my $var = $self->{'variable'}; if ($self->{'multichoice'}) { $result = < - function checkall(value) { - for (i=0; i SCRIPT $buttons = < - - + +
BUTTONS } @@ -1422,7 +1659,6 @@ BUTTONS # Load up the students my $choices = &Apache::loncoursedata::get_classlist(); - my @keys = keys %{$choices}; # Constants @@ -1527,8 +1763,8 @@ no strict; use strict; BEGIN { - &Apache::lonxml::register('Apache::lonhelper::files', - ('files', 'filechoice', 'filefilter')); + &Apache::lonhelper::register('Apache::lonhelper::files', + ('files', 'filechoice', 'filefilter')); } sub new { @@ -1592,6 +1828,9 @@ sub render { my $var = $self->{'variable'}; my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}'); + die 'Error in resource filter code for variable ' . + {'variable'} . ', Perl said:' . $@ if $@; + my $subdir = &$subdirFunc(); my $filterFunc = $self->{FILTER_FUNC}; @@ -1600,20 +1839,20 @@ sub render { if ($self->{'multichoice'}) { $result = < - function checkall(value) { - for (i=0; i SCRIPT - my $buttons = <   - - + +
  BUTTONS } @@ -1697,6 +1936,356 @@ sub postprocess { return 1; } +1; + +package Apache::lonhelper::section; + +=pod + +=head2 Element: section + +

allows the user to choose one or more sections from the current +course. + +It takes the standard attributes "variable", "multichoice", and +"nextstate", meaning what they do for most other elements. + +=cut + +no strict; +@ISA = ("Apache::lonhelper::choices"); +use strict; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::section', + ('section')); +} + +sub new { + my $ref = Apache::lonhelper::choices->new(); + bless($ref); +} + +sub start_section { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + $paramHash->{CHOICES} = []; + + $paramHash->{'variable'} = $token->[2]{'variable'}; + $helper->declareVar($paramHash->{'variable'}); + $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; + if (defined($token->[2]{'nextstate'})) { + $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; + } + + # Populate the CHOICES element + my %choices; + + my $section = Apache::loncoursedata::CL_SECTION(); + my $classlist = Apache::loncoursedata::get_classlist(); + foreach (keys %$classlist) { + my $sectionName = $classlist->{$_}->[$section]; + if (!$sectionName) { + $choices{"No section assigned"} = ""; + } else { + $choices{$sectionName} = $sectionName; + } + } + + for my $sectionName (sort(keys(%choices))) { + + push @{$paramHash->{CHOICES}}, [$sectionName, $sectionName]; + } +} + +sub end_section { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::section->new(); +} +1; + +package Apache::lonhelper::general; + +=pod + +=head2 General-purpose tag: + +The contents of the exec tag are executed as Perl code, not inside a +safe space, so the full range of $ENV and such is available. The code +will be executed as a subroutine wrapped with the following code: + +"sub { my $helper = shift; my $state = shift;" and + +"}" + +The return value is ignored. + +$helper is the helper object. Feel free to add methods to the helper +object to support whatever manipulation you may need to do (for instance, +overriding the form location if the state is the final state; see +lonparm.helper for an example). + +$state is the $paramHash that has currently been generated and may +be manipulated by the code in exec. Note that the $state is not yet +an actual state B, it is just a hash, so do not expect to +be able to call methods on it. + +=cut + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::general', + 'exec', 'condition', 'clause', + 'eval'); +} + +sub start_exec { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + my $code = &Apache::lonxml::get_all_text('/exec', $parser); + + $code = eval ('sub { my $helper = shift; my $state = shift; ' . + $code . "}"); + die 'Error in , Perl said: '. $@ if $@; + &$code($helper, $paramHash); +} + +sub end_exec { return ''; } + +=pod + +=head2 General-purpose tag: + +The tag allows you to mask out parts of the helper code +depending on some programatically determined condition. The condition +tag contains a tag which contains perl code that when wrapped +with "sub { my $helper = shift; my $state = shift; " and "}", returns +a true value if the XML in the condition should be evaluated as a normal +part of the helper, or false if it should be completely discarded. + +The tag must be the first sub-tag of the tag or +it will not work as expected. + +=cut + +# The condition tag just functions as a marker, it doesn't have +# to "do" anything. Technically it doesn't even have to be registered +# with the lonxml code, but I leave this here to be explicit about it. +sub start_condition { return ''; } +sub end_condition { return ''; } + +sub start_clause { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + my $clause = Apache::lonxml::get_all_text('/clause', $parser); + $clause = eval('sub { my $helper = shift; my $state = shift; ' + . $clause . '}'); + die 'Error in clause of condition, Perl said: ' . $@ if $@; + if (!&$clause($helper, $paramHash)) { + # Discard all text until the /condition. + &Apache::lonxml::get_all_text('/condition', $parser); + } +} + +sub end_clause { return ''; } + +=pod + +=head2 General-purpose tag: + +The tag will be evaluated as a subroutine call passed in the +current helper object and state hash as described in above, +but is expected to return a string to be printed directly to the +screen. This is useful for dynamically generating messages. + +=cut + +# This is basically a type of message. +# Programmatically setting $paramHash->{NEXTSTATE} would work, though +# it's probably bad form. + +sub start_eval { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + my $program = Apache::lonxml::get_all_text('/eval', $parser); + $program = eval('sub { my $helper = shift; my $state = shift; ' + . $program . '}'); + die 'Error in eval code, Perl said: ' . $@ if $@; + $paramHash->{MESSAGE_TEXT} = &$program($helper, $paramHash); +} + +sub end_eval { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + Apache::lonhelper::message->new(); +} + +1; + +package Apache::lonhelper::parmwizfinal; + +# This is the final state for the parmwizard. It is not generally useful, +# so it is not perldoc'ed. It does its own processing. +# It is represented with , and +# should later be moved to lonparmset.pm . + +no strict; +@ISA = ('Apache::lonhelper::element'); +use strict; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal', + ('parmwizfinal')); +} + +use Time::localtime; + +sub new { + my $ref = Apache::lonhelper::choices->new(); + bless ($ref); +} + +sub start_parmwizfinal { return ''; } + +sub end_parmwizfinal { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::parmwizfinal->new(); +} + +# Renders a form that, when submitted, will form the input to lonparmset.pm +sub render { + my $self = shift; + my $vars = $helper->{VARS}; + + # FIXME: Unify my designators with the standard ones + my %dateTypeHash = ('open_date' => "Opening Date", + 'due_date' => "Due Date", + 'answer_date' => "Answer Date"); + my %parmTypeHash = ('open_date' => "0_opendate", + '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"; + } + + # Print the granularity, depending on the action + if ($vars->{GRANULARITY} eq 'whole_course') { + $result .= '
  • for all resources in the course
  • '; + $level = 9; # general course, see lonparmset.pm perldoc + $affectedResourceId = "0.0"; + } 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(); + $navmap->untieHashes(); + $result .= "
  • for the map named $title
  • "; + $level = 8; + $affectedResourceId = $vars->{RESOURCE_ID}; + } 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}); + my $title = $res->compTitle(); + $navmap->untieHashes(); + $result .= "
  • for the resource named $title
  • "; + $level = 7; + $affectedResourceId = $vars->{RESOURCE_ID}; + } + + # Print targets + if ($vars->{TARGETS} eq 'course') { + $result .= '
  • for all students in course
  • '; + } elsif ($vars->{TARGETS} eq 'section') { + my $section = $vars->{SECTION_NAME}; + $result .= "
  • for section $section
  • "; + $level -= 3; + $result .= "\n"; + } else { + # FIXME: This is probably wasteful! Store the name! + my $classlist = Apache::loncoursedata::get_classlist(); + my $name = $classlist->{$vars->{USER_NAME}}->[6]; + $result .= "
  • for $name
  • "; + $level -= 6; + my ($uname, $udom) = split /:/, $vars->{USER_NAME}; + $result .= "\n"; + $result .= "\n"; + } + + # Print value + $result .= "
  • to " . ctime($vars->{PARM_DATE}) . " (" . + Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}) + . ")
  • \n"; + + # print pres_marker + $result .= "\n\n"; + + $result .= "

    \n"; + + return $result; +} + +sub overrideForm { + return 1; +} + 1; __END__