Diff for /loncom/interface/lonhelper.pm between versions 1.4 and 1.21

version 1.4, 2003/03/28 20:25:19 version 1.21, 2003/05/07 18:22:43
Line 46  directory and having the .helper file ex Line 46  directory and having the .helper file ex
   
 All classes are in the Apache::lonhelper namespace.  All classes are in the Apache::lonhelper namespace.
   
 =head2 lonxml  
   
 The helper uses the lonxml XML parsing support. The following capabilities  
 are directly imported from lonxml:  
   
 =over 4  
   
 =item * <startouttext> and <endouttext>: These tags may be used, as in problems,  
         to directly output text to the user.  
   
 =back  
   
 =head2 lonhelper XML file format  =head2 lonhelper XML file format
   
 A helper consists of a top-level <helper> tag which contains a series of states.  A helper consists of a top-level <helper> tag which contains a series of states.
Line 70  of the helper itself, such as "Parameter Line 58  of the helper itself, such as "Parameter
 =head2 State tags  =head2 State tags
   
 State tags are required to have an attribute "name", which is the symbolic  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  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 wizard  required to have one state named "START", which is the state the helper
 will start with. by convention, this state should clearly describe what  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  the helper will do for the user, and may also include the first information
 entry the user needs to do for the helper.  entry the user needs to do for the helper.
   
Line 96  Of course this does nothing. In order fo Line 84  Of course this does nothing. In order fo
 necessary to put actual elements into the wizard. Documentation for each  necessary to put actual elements into the wizard. Documentation for each
 of these elements follows.  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 <condition>
   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 <exec> or 
   <eval> 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<Apache::lonhelper::registerHelperTags()>
   before parsing XML fragments and B<Apache::lonhelper::unregisterHelperTags()>
   when you are done. See lonprintout.pm for examples of this usage in the
   printHelper subroutine.
   
 =cut  =cut
   
 package Apache::lonhelper;  package Apache::lonhelper;
Line 103  use Apache::Constants qw(:common); Line 162  use Apache::Constants qw(:common);
 use Apache::File;  use Apache::File;
 use Apache::lonxml;  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 {  BEGIN {
     &Apache::lonxml::register('Apache::lonhelper',       Apache::lonxml::register('Apache::lonhelper', 
                               ('helper', 'state'));                               ('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   # substate type), it's easier and more readble to explicitly track 
 # those three things directly, rather then futz with the tag stack   # those three things directly, rather then futz with the tag stack 
 # every time.  # every time.
Line 120  my $substate; Line 193  my $substate;
 # end of the element tag is located.  # end of the element tag is located.
 my $paramHash;   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 {  sub handler {
     my $r = shift;      my $r = shift;
     $ENV{'request.uri'} = $r->uri();      my $uri = shift;
     my $filename = '/home/httpd/html' . $r->uri();      if (!defined($uri)) { $uri = $r->uri(); }
       $ENV{'request.uri'} = $uri;
       my $filename = '/home/httpd/html' . $uri;
     my $fh = Apache::File->new($filename);      my $fh = Apache::File->new($filename);
     my $file;      my $file;
     read $fh, $file, 100000000;      read $fh, $file, 100000000;
   
     Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});  
   
     # Send header, don't cache this page      # Send header, don't cache this page
     if ($r->header_only) {      if ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($ENV{'browser.mathml'}) {
Line 152  sub handler { Line 228  sub handler {
     # xml parsing      # xml parsing
     &Apache::lonxml::xmlparse($r, 'helper', $file);      &Apache::lonxml::xmlparse($r, 'helper', $file);
   
       $helper->process();
   
     $r->print($helper->display());      $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 {  sub start_helper {
Line 162  sub start_helper { Line 252  sub start_helper {
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
       
     $helper = Apache::lonhelper::helper->new($token->[2]{'title'});      registerHelperTags();
   
       Apache::lonhelper::helper->new($token->[2]{'title'});
     return '';      return '';
 }  }
   
Line 173  sub end_helper { Line 265  sub end_helper {
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
       
       unregisterHelperTags();
   
     return '';      return '';
 }  }
   
Line 184  sub start_state { Line 278  sub start_state {
         return '';          return '';
     }      }
   
     $state = Apache::lonhelper::state->new($token->[2]{'name'},      Apache::lonhelper::state->new($token->[2]{'name'},
                                            $token->[2]{'title'});                                    $token->[2]{'title'});
     return '';      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  # don't need this, so ignore it
 sub end_state {  sub end_state {
     return '';      return '';
Line 221  sub new { Line 326  sub new {
  $self->{STATE} = "START";   $self->{STATE} = "START";
     }      }
   
       Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
   
     $self->{TOKEN} = $ENV{'form.TOKEN'};      $self->{TOKEN} = $ENV{'form.TOKEN'};
     # If a token was passed, we load that in. Otherwise, we need to create a       # If a token was passed, we load that in. Otherwise, we need to create a 
     # new storage file      # new storage file
Line 239  sub new { Line 346  sub new {
                   
         my $file = Apache::File->new($self->{FILENAME});          my $file = Apache::File->new($self->{FILENAME});
         my $contents = <$file>;          my $contents = <$file>;
         &Apache::loncommon::get_unprocessed_cgi($contents);  
           # Now load in the contents
           for my $value (split (/&/, $contents)) {
               my ($name, $value) = split(/=/, $value);
               $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
               $self->{VARS}->{$name} = $value;
           }
   
         $file->close();          $file->close();
     } else {      } else {
         # Only valid if we're just starting.          # Only valid if we're just starting.
Line 266  sub new { Line 380  sub new {
     $self->{STATES} = {};      $self->{STATES} = {};
     $self->{DONE} = 0;      $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);      bless($self, $class);
     return $self;      return $self;
 }  }
Line 297  sub _varsInFile { Line 420  sub _varsInFile {
     return join ('&', @vars);      return join ('&', @vars);
 }  }
   
   # Use this to declare variables.
   # FIXME: Document this
   sub declareVar {
       my $self = shift;
       my $var = shift;
   
       if (!defined($self->{VARS}->{$var})) {
           $self->{VARS}->{$var} = '';
       }
   
       my $envname = 'form.' . $var . '.forminput';
       if (defined($ENV{$envname})) {
           $self->{VARS}->{$var} = $ENV{$envname};
       }
   }
   
 sub changeState {  sub changeState {
     my $self = shift;      my $self = shift;
     $self->{STATE} = shift;      $self->{STATE} = shift;
Line 310  sub registerState { Line 449  sub registerState {
     $self->{STATES}{$stateName} = $state;      $self->{STATES}{$stateName} = $state;
 }  }
   
 # Done in four phases  sub process {
 # 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 $self = shift;
   
     my $result = "";  
   
     # Phase 1: Post processing for state of previous screen (which is actually      # 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       # the "current state" in terms of the helper variables), if it wasn't the 
     # beginning state.      # beginning state.
     if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {      if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {
  my $prevState = $self->{STATES}{$self->{STATE}};   my $prevState = $self->{STATES}{$self->{STATE}};
             $prevState->postprocess();          $prevState->postprocess();
     }      }
           
     # Note, to handle errors in a state's input that a user must correct,      # Note, to handle errors in a state's input that a user must correct,
Line 335  sub display { Line 466  sub display {
   
     # Phase 2: Preprocess current state      # Phase 2: Preprocess current state
     my $startState = $self->{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      # For debugging, print something here to determine if you're going
     # checked all paths and the user can't see this!      # to an undefined state.
     if (!defined($state)) {      if (!defined($state)) {
         $result .="Error! The state ". $startState ." is not defined.";          return;
         return $result;  
     }      }
     $state->preprocess();      $state->preprocess();
   
     # Phase 3: While the current state is different from the previous state,      # Phase 3: While the current state is different from the previous state,
     # keep processing.      # keep processing.
     while ( $startState ne $self->{STATE} )      while ( $startState ne $self->{STATE} && 
               defined($self->{STATES}->{$self->{STATE}}) )
     {      {
  $startState = $self->{STATE};   $startState = $self->{STATE};
  $state = $self->{STATES}{$startState};   $state = $self->{STATES}->{$startState};
  $state->preprocess();   $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 = "<font color='#ff0000'>Error: state '$state' not defined!</font>";
           return $result;
       }
   
     # Phase 4: Display.      # Phase 4: Display.
     my $stateTitle = $state->title();      my $stateTitle = $state->title();
     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');      my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
Line 365  sub display { Line 516  sub display {
     </head>      </head>
     $bodytag      $bodytag
 HEADER  HEADER
     if (!$state->overrideForm()) { $result.="<form name='wizform' method='GET'>"; }      if (!$state->overrideForm()) { $result.="<form name='helpform' method='GET'>"; }
     $result .= <<HEADER;      $result .= <<HEADER;
         <table border="0"><tr><td>          <table border="0"><tr><td>
         <h2><i>$stateTitle</i></h2>          <h2><i>$stateTitle</i></h2>
Line 393  HEADER Line 544  HEADER
         $result .= "</center>\n";          $result .= "</center>\n";
     }      }
   
       #foreach my $key (keys %{$self->{VARS}}) {
       #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
       #}
   
     $result .= <<FOOTER;      $result .= <<FOOTER;
               </td>                </td>
             </tr>              </tr>
Line 433  sub new { Line 588  sub new {
   
     $helper->registerState($self);      $helper->registerState($self);
   
       $state = $self;
   
     return $self;      return $self;
 }  }
   
Line 453  sub preprocess { Line 610  sub preprocess {
     }      }
 }  }
   
   # FIXME: Document that all postprocesses must return a true value or
   # the state transition will be overridden
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
       
       # Save the state so we can roll it back if we need to.
       my $originalState = $helper->{STATE};
       my $everythingSuccessful = 1;
   
     for my $element (@{$self->{ELEMENTS}}) {      for my $element (@{$self->{ELEMENTS}}) {
         $element->postprocess();          my $result = $element->postprocess();
           if (!$result) { $everythingSuccessful = 0; }
       }
   
       # If not all the postprocesses were successful, override
       # any state transitions that may have occurred. It is the
       # responsibility of the states to make sure they have 
       # error handling in that case.
       if (!$everythingSuccessful) {
           $helper->{STATE} = $originalState;
     }      }
 }  }
   
   # 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 {  sub overrideForm {
       my $self = shift;
       for my $element (@{$self->{ELEMENTS}}) {
           if ($element->overrideForm()) {
               return 1;
           }
       }
     return 0;      return 0;
 }  }
   
Line 503  element named "formName" and place the s Line 684  element named "formName" and place the s
 variable named varName. This is for things like checkboxes or   variable named varName. This is for things like checkboxes or 
 multiple-selection listboxes where the user can select more then   multiple-selection listboxes where the user can select more then 
 one entry. The selected entries are delimited by triple pipes in   one entry. The selected entries are delimited by triple pipes in 
 the helper variables, like this:  CHOICE_1|||CHOICE_2|||CHOICE_3  the helper variables, like this:  
   
    CHOICE_1|||CHOICE_2|||CHOICE_3
   
 =back  =back
   
 =cut  =cut
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::element',
                                    ('nextstate'));
   }
   
 # Because we use the param hash, this is often a sufficent  # Because we use the param hash, this is often a sufficent
 # constructor  # constructor
 sub new {  sub new {
Line 527  sub new { Line 715  sub new {
     return $self;      return $self;
 }     }   
   
   sub start_nextstate {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       
       $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
                                                                $parser);
       return '';
   }
   
   sub end_nextstate { return ''; }
   
 sub preprocess {  sub preprocess {
     return 1;      return 1;
 }  }
Line 539  sub render { Line 741  sub render {
     return '';      return '';
 }  }
   
   sub overrideForm {
       return 0;
   }
   
 sub process_multiple_choices {  sub process_multiple_choices {
     my $self = shift;      my $self = shift;
     my $formname = shift;      my $formname = shift;
     my $var = shift;      my $var = shift;
   
     my $formvalue = $ENV{'form.' . $formname};      # Must extract values from data directly, as there
     if ($formvalue) {      # may be more then one.
         # Must extract values from $wizard->{DATA} directly, as there      my @values;
         # may be more then one.      for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
         my @values;          my ($name, $value) = split(/=/, $formparam);
         for my $formparam (split (/&/, $wizard->{DATA})) {          if ($name ne $formname) {
             my ($name, $value) = split(/=/, $formparam);              next;
             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->setVar($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;      return;
 }  }
Line 573  package Apache::lonhelper::message; Line 776  package Apache::lonhelper::message;
 =head2 Element: message  =head2 Element: message
   
 Message elements display the contents of their <message_text> tags, and  Message elements display the contents of their <message_text> tags, and
 transition directly to the state in the <next_state> tag. Example:  transition directly to the state in the <nextstate> tag. Example:
   
  <message>   <message>
    <next_state>GET_NAME</next_state>     <nextstate>GET_NAME</nextstate>
    <message_text>This is the <b>message</b> the user will see,      <message_text>This is the <b>message</b> the user will see, 
                  <i>HTML allowed</i>.</message_text>                   <i>HTML allowed</i>.</message_text>
    </message>     </message>
   
 This will display the HTML message and transition to the <next_state> if  This will display the HTML message and transition to the <nextstate> 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 <message_text>  want text to run together, you'll need to manually wrap the <message_text>
 in <p> tags, or whatever is appropriate for your HTML.  in <p> tags, or whatever is appropriate for your HTML.
   
   Message tags do not add in whitespace, so if you want it, you'll need to add
   it into states. This is done so you can inline some elements, such as 
   the <date> element, right between two messages, giving the appearence that 
   the <date> element appears inline. (Note the elements can not be embedded
   within each other.)
   
 This is also a good template for creating your own new states, as it has  This is also a good template for creating your own new states, as it has
 very little code beyond the state template.  very little code beyond the state template.
   
Line 596  no strict; Line 805  no strict;
 use strict;  use strict;
   
 BEGIN {  BEGIN {
     &Apache::lonxml::register('Apache::lonhelper::message',      &Apache::lonhelper::register('Apache::lonhelper::message',
                               ('message', 'next_state', 'message_text'));                                ('message'));
 }  }
   
 # Don't need to override the "new" from element  sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
 # CONSTRUCTION: Construct the message element from the XML  # CONSTRUCTION: Construct the message element from the XML
 sub start_message {  sub start_message {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message',
                                                                  $parser);
   
       if (defined($token->[2]{'nextstate'})) {
           $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
       }
     return '';      return '';
 }  }
   
Line 617  sub end_message { Line 841  sub end_message {
     return '';      return '';
 }  }
   
 sub start_next_state {  sub render {
       my $self = shift;
   
       return $self->{MESSAGE_TEXT};
   }
   # 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::choices;
   
   =pod
   
   =head2 Element: choices
   
   Choice states provide a single choice to the user as a text selection box.
   A "choice" is two pieces of text, one which will be displayed to the user
   (the "human" value), and one which will be passed back to the program
   (the "computer" value). For instance, a human may choose from a list of
   resources on disk by title, while your program wants the file name.
   
   <choices> takes an attribute "variable" to control which helper variable
   the result is stored in.
   
   <choices> takes an attribute "multichoice" which, if set to a true
   value, will allow the user to select multiple choices.
   
   B<SUB-TAGS>
   
   <choices> can have the following subtags:
   
   =over 4
   
   =item * <nextstate>state_name</nextstate>: If given, this will cause the
         choice element to transition to the given state after executing. If
         this is used, do not pass nextstates to the <choice> tag.
   
   =item * <choice />: If the choices are static,
         this element will allow you to specify them. Each choice
         contains  attribute, "computer", as described above. The
         content of the tag will be used as the human label.
         For example,  
         <choice computer='234-12-7312'>Bobby McDormik</choice>.
   
         <choice> 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 <eval> tag; see <eval> tag
         below.
   
   <choice> may optionally contain a 'nextstate' attribute, which
   will be the state transisitoned to if the choice is made, if
   the choice is not multichoice.
   
   =back
   
   To create the choices programmatically, either wrap the choices in 
   <condition> tags (prefered), or use an <exec> block inside the <choice>
   tag. Store the choices in $state->{CHOICES}, which is a list of list
   references, where each list has three strings. The first is the human
   name, the second is the computer name. and the third is the option
   next state. For example:
   
    <exec>
       for (my $i = 65; $i < 65 + 26; $i++) {
           push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
       }
    </exec>
   
   This will allow the user to select from the letters A-Z (in ASCII), while
   passing the ASCII value back into the helper variables, and the state
   will in all cases transition to 'next'.
   
   You can mix and match methods of creating choices, as long as you always 
   "push" onto the choice list, rather then wiping it out. (You can even 
   remove choices programmatically, but that would probably be bad form.)
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::choices',
                                 ('choice', 'choices'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_choices {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       # Need to initialize the choices list, so everything can assume it exists
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{CHOICES} = [];
       return '';
   }
   
   sub end_choices {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;      my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
       Apache::lonhelper::choices->new();
       return '';
   }
   
   sub start_choice {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $computer = $token->[2]{'computer'};
       my $human = &Apache::lonxml::get_all_text('/choice',
                                                 $parser);
       my $nextstate = $token->[2]{'nextstate'};
       my $evalFlag = $token->[2]{'eval'};
       push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate, 
                                       $evalFlag];
       return '';
   }
   
   sub end_choice {
       return '';
   }
   
   sub render {
       # START HERE: Replace this with correct choices code.
       my $self = shift;
       my $var = $self->{'variable'};
       my $buttons = '';
       my $result = '';
   
       if ($self->{'multichoice'}) {
           $result .= <<SCRIPT;
   <script>
       function checkall(value, checkName) {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.name == checkName + '.forminput') {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br />
   <input type="button" onclick="checkall(true, '$var')" value="Select All" />
   <input type="button" onclick="checkall(false, '$var')" value="Unselect All" />
   <br />&nbsp;
   BUTTONS
       }
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
       }
   
       $result .= $buttons;
           
     $paramHash->{NEXT_STATE} = &Apache::lonxml::get_all_text('/next_state',      $result .= "<table>\n\n";
                                                              $parser);  
       my $type = "radio";
       if ($self->{'multichoice'}) { $type = 'checkbox'; }
       my $checked = 0;
       foreach my $choice (@{$self->{CHOICES}}) {
           $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
           $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
               . "' value='" . 
               HTML::Entities::encode($choice->[1]) 
               . "'";
           if (!$self->{'multichoice'} && !$checked) {
               $result .= " checked ";
               $checked = 1;
           }
           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 .= "/></td><td> " . $choiceLabel . "</td></tr>\n";
       }
       $result .= "</table>\n\n\n";
       $result .= $buttons;
   
       return $result;
   }
   
   # If a NEXTSTATE was given or a nextstate for this choice was
   # given, switch to it
   sub postprocess {
       my $self = shift;
       my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
   
       if (!$chosenValue) {
           $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 (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
       
       foreach my $choice (@{$self->{CHOICES}}) {
           if ($choice->[1] eq $chosenValue) {
               if (defined($choice->[2])) {
                   $helper->changeState($choice->[2]);
               }
           }
       }
       return 1;
   }
   1;
   
   package Apache::lonhelper::date;
   
   =pod
   
   =head2 Element: date
   
   Date elements allow the selection of a date with a drop down list.
   
   Date elements can take two attributes:
   
   =over 4
   
   =item * B<variable>: The name of the variable to store the chosen
           date in. Required.
   
   =item * B<hoursminutes>: If a true value, the date will show hours
           and minutes, as well as month/day/year. If false or missing,
           the date will only show the month, day, and year.
   
   =back
   
   Date elements contain only an option <nextstate> tag to determine
   the next state.
   
   Example:
   
    <date variable="DUE_DATE" hoursminutes="1">
      <nextstate>choose_why</nextstate>
      </date>
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   use Time::localtime;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::date',
                                 ('date'));
   }
   
   # Don't need to override the "new" from element
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   my @months = ("January", "February", "March", "April", "May", "June", "July",
         "August", "September", "October", "November", "December");
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_date {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
   }
   
   sub end_date {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::date->new();
     return '';      return '';
 }  }
   
 sub end_next_state { return ''; }  sub render {
       my $self = shift;
       my $result = "";
       my $var = $self->{'variable'};
   
       my $date;
       
       # Default date: The current hour.
       $date = localtime();
       $date->min(0);
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       # Month
       my $i;
       $result .= "<select name='${var}month'>\n";
       for ($i = 0; $i < 12; $i++) {
           if ($i == $date->mon) {
               $result .= "<option value='$i' selected>";
           } else {
               $result .= "<option value='$i'>";
           }
           $result .= $months[$i] . "</option>\n";
       }
       $result .= "</select>\n";
   
       # Day
       $result .= "<select name='${var}day'>\n";
       for ($i = 1; $i < 32; $i++) {
           if ($i == $date->mday) {
               $result .= '<option selected>';
           } else {
               $result .= '<option>';
           }
           $result .= "$i</option>\n";
       }
       $result .= "</select>,\n";
   
       # Year
       $result .= "<select name='${var}year'>\n";
       for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
           if ($date->year + 1900 == $i) {
               $result .= "<option selected>";
           } else {
               $result .= "<option>";
           }
           $result .= "$i</option>\n";
       }
       $result .= "</select>,\n";
   
       # Display Hours and Minutes if they are called for
       if ($self->{'hoursminutes'}) {
           # Build hour
           $result .= "<select name='${var}hour'>\n";
           $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
               " value='0'>midnight</option>\n";
           for ($i = 1; $i < 12; $i++) {
               if ($date->hour == $i) {
                   $result .= "<option selected value='$i'>$i a.m.</option>\n";
               } else {
                   $result .= "<option value='$i'>$i a.m</option>\n";
               }
           }
           $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
               " value='12'>noon</option>\n";
           for ($i = 13; $i < 24; $i++) {
               my $printedHour = $i - 12;
               if ($date->hour == $i) {
                   $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
               } else {
                   $result .= "<option value='$i'>$printedHour p.m.</option>\n";
               }
           }
   
           $result .= "</select> :\n";
   
           $result .= "<select name='${var}minute'>\n";
           for ($i = 0; $i < 60; $i++) {
               my $printedMinute = $i;
               if ($i < 10) {
                   $printedMinute = "0" . $printedMinute;
               }
               if ($date->min == $i) {
                   $result .= "<option selected>";
               } else {
                   $result .= "<option>";
               }
               $result .= "$printedMinute</option>\n";
           }
           $result .= "</select>\n";
       }
   
       return $result;
   
   }
   # If a NEXTSTATE was given, switch to it
   sub postprocess {
       my $self = shift;
       my $var = $self->{'variable'};
       my $month = $ENV{'form.' . $var . 'month'}; 
       my $day = $ENV{'form.' . $var . 'day'}; 
       my $year = $ENV{'form.' . $var . 'year'}; 
       my $min = 0; 
       my $hour = 0;
       if ($self->{'hoursminutes'}) {
           $min = $ENV{'form.' . $var . 'minute'};
           $hour = $ENV{'form.' . $var . 'hour'};
       }
   
       my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
       # Check to make sure that the date was not automatically co-erced into a 
       # valid date, as we want to flag that as an error
       # This happens for "Feb. 31", for instance, which is coerced to March 2 or
       # 3, depending on if it's a leapyear
       my $checkDate = localtime($chosenDate);
   
       if ($checkDate->mon != $month || $checkDate->mday != $day ||
           $checkDate->year + 1900 != $year) {
           $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
               . "date because it doesn't exist. Please enter a valid date.";
           return 0;
       }
   
       $helper->{VARS}->{$var} = $chosenDate;
   
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   
       return 1;
   }
   1;
   
   package Apache::lonhelper::resource;
   
   =pod
   
   =head2 Element: resource
   
   <resource> elements allow the user to select one or multiple resources
   from the current course. You can filter out which resources they can view,
   and filter out which resources they can select. The course will always
   be displayed fully expanded, because of the difficulty of maintaining
   selections across folder openings and closings. If this is fixed, then
   the user can manipulate the folders.
   
   <resource> 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. 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<SUB-TAGS>
   
   =over 4
   
   =item * <filterfunc>: If you want to filter what resources are displayed
     to the user, use a filter func. The <filterfunc> tag should contain
     Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
     a function that returns true if the resource should be displayed, 
     and false if it should be skipped. $res is a resource object. 
     (See Apache::lonnavmaps documentation for information about the 
     resource object.)
   
   =item * <choicefunc>: Same as <filterfunc>, except that controls whether
     the given resource can be chosen. (It is almost always a good idea to
     show the user the folders, for instance, but you do not always want to 
     let the user select them.)
   
   =item * <nextstate>: Standard nextstate behavior.
   
   =item * <valuefunc>: This function controls what is returned by the resource
     when the user selects it. Like filterfunc and choicefunc, it should be
     a function fragment that when wrapped by "sub { my $res = shift; " and
     "}" 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 * <mapurl>: If the URL of a map is given here, only that map
     will be displayed, instead of the whole course.
   
   =back
   
 sub start_message_text {  =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::resource',
                                 ('resource', 'filterfunc', 
                                  'choicefunc', 'valuefunc',
                                  'mapurl'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_resource {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;      my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
   
     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',      $paramHash->{'variable'} = $token->[2]{'variable'};
                                                                $parser);      $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'toponly'} = $token->[2]{'toponly'};
       return '';
   }
   
   sub end_resource {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       if (!defined($paramHash->{FILTER_FUNC})) {
           $paramHash->{FILTER_FUNC} = sub {return 1;};
       }
       if (!defined($paramHash->{CHOICE_FUNC})) {
           $paramHash->{CHOICE_FUNC} = sub {return 1;};
       }
       if (!defined($paramHash->{VALUE_FUNC})) {
           $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
       }
       Apache::lonhelper::resource->new();
       return '';
   }
   
   sub start_filterfunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/filterfunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{FILTER_FUNC} = eval $contents;
   }
   
   sub end_filterfunc { return ''; }
   
   sub start_choicefunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/choicefunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{CHOICE_FUNC} = eval $contents;
   }
   
   sub end_choicefunc { return ''; }
   
   sub start_valuefunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/valuefunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{VALUE_FUNC} = eval $contents;
   }
   
   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
   # the previous helper state, the *correct* answer is for the helper
   # to keep track of how many times the user has manipulated the folders,
   # and feed that to the history.go() call in the helper rendering routines.
   # If done correctly, the helper itself can keep track of how many times
   # it renders the same states, so it doesn't go in just this state, and
   # you can lean on the browser back button to make sure it all chains
   # correctly.
   # Right now, though, I'm just forcing all folders open.
   
   sub render {
       my $self = shift;
       my $result = "";
       my $var = $self->{'variable'};
       my $curVal = $helper->{VARS}->{$var};
   
       my $buttons = '';
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value, checkName) {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.name == checkName + '.forminput') {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br /> &nbsp;
   <input type="button" onclick="checkall(true, '$var')" value="Select All Resources" />
   <input type="button" onclick="checkall(false, '$var')" value="Unselect All Resources" />
   <br /> &nbsp;
   BUTTONS
       }
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       $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
       #  - Jeremy (Pythonista) ;-)
       my $checked = 0;
       my $renderColFunc = sub {
           my ($resource, $part, $params) = @_;
   
           my $inputType;
           if ($multichoice) { $inputType = 'checkbox'; }
           else {$inputType = 'radio'; }
   
           if (!&$choiceFunc($resource)) {
               return '<td>&nbsp;</td>';
           } else {
               my $col = "<td><input type='$inputType' name='${var}.forminput' ";
               if (!$checked && !$multichoice) {
                   $col .= "checked ";
                   $checked = 1;
               }
               $col .= "value='" . 
                   HTML::Entities::encode(&$valueFunc($resource)) 
                   . "' /></td>";
               return $col;
           }
       };
   
       $ENV{'form.condition'} = !$self->{'toponly'};
       $result .= 
           &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
                                                     Apache::lonnavmaps::resource()],
                                          'showParts' => 0,
                                          'filterFunc' => $filterFunc,
                                          'resource_no_folder_link' => 1,
                                          'iterator_map' => $mapUrl }
                                          );
   
       $result .= $buttons;
                                                   
       return $result;
 }  }
           
 sub end_message_text { return 1; }  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});
       }
   
       return 1;
   }
   
   1;
   
   package Apache::lonhelper::student;
   
   =pod
   
   =head2 Element: student
   
   Student elements display a choice of students enrolled in the current
   course. Currently it is primitive; this is expected to evolve later.
   
   Student elements take two attributes: "variable", which means what
   it usually does, and "multichoice", which if true allows the user
   to select multiple students.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::student',
                                 ('student'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_student {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $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 {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::student->new();
   }
   
 sub render {  sub render {
     my $self = shift;      my $self = shift;
       my $result = '';
       my $buttons = '';
       my $var = $self->{'variable'};
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value, checkName) {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.name == checkName + '.forminput') {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br />
   <input type="button" onclick="checkall(true, '$var')" value="Select All Students" />
   <input type="button" onclick="checkall(false, '$var')" value="Unselect All Students" />
   <br />
   BUTTONS
       }
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       # Load up the students
       my $choices = &Apache::loncoursedata::get_classlist();
       my @keys = keys %{$choices};
   
       # Constants
       my $section = Apache::loncoursedata::CL_SECTION();
       my $fullname = Apache::loncoursedata::CL_FULLNAME();
   
       # Sort by: Section, name
       @keys = sort {
           if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
               return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
           }
           return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
       } @keys;
   
     return $self->{MESSAGE_TEXT};      my $type = 'radio';
       if ($self->{'multichoice'}) { $type = 'checkbox'; }
       $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
       $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
           "<td align='center'><b>Section</b></td></tr>";
   
       my $checked = 0;
       foreach (@keys) {
           $result .= "<tr><td><input type='$type' name='" .
               $self->{'variable'} . '.forminput' . "'";
               
           if (!$self->{'multichoice'} && !$checked) {
               $result .= " checked ";
               $checked = 1;
           }
           $result .=
               " value='" . HTML::Entities::encode($_ . ':' . $choices->{$_}->[$section])
               . "' /></td><td>"
               . HTML::Entities::encode($choices->{$_}->[$fullname])
               . "</td><td align='center'>" 
               . HTML::Entities::encode($choices->{$_}->[$section])
               . "</td></tr>\n";
       }
   
       $result .= "</table>\n\n";
       $result .= $buttons;    
       
       return $result;
   }
   
   sub postprocess {
       my $self = shift;
   
       my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
       if (!$result) {
           $self->{ERROR_MSG} = 'You must choose at least one student '.
               'to continue.';
           return 0;
       }
   
       if ($self->{'multichoice'}) {
           $self->process_multiple_choices($self->{'variable'}.'.forminput',
                                           $self->{'variable'});
       }
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   
       return 1;
   }
   
   1;
   
   package Apache::lonhelper::files;
   
   =pod
   
   =head2 Element: files
   
   files allows the users to choose files from a given directory on the
   server. It is always multichoice and stores the result as a triple-pipe
   delimited entry in the helper variables. 
   
   Since it is extremely unlikely that you can actually code a constant
   representing the directory you wish to allow the user to search, <files>
   takes a subroutine that returns the name of the directory you wish to
   have the user browse.
   
   files accepts the attribute "variable" to control where the files chosen
   are put. It accepts the attribute "multichoice" as the other attribute,
   defaulting to false, which if true will allow the user to select more
   then one choice. 
   
   <files> accepts three subtags. One is the "nextstate" sub-tag that works
   as it does with the other tags. Another is a <filechoice> sub tag that
   is Perl code that, when surrounded by "sub {" and "}" will return a
   string representing what directory on the server to allow the user to 
   choose files from. Finally, the <filefilter> subtag should contain Perl
   code that when surrounded by "sub { my $filename = shift; " and "}",
   returns a true value if the user can pick that file, or false otherwise.
   The filename passed to the function will be just the name of the file, 
   with no path info.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::files',
                                    ('files', 'filechoice', 'filefilter'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_files {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
   }    
   
   sub end_files {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       if (!defined($paramHash->{FILTER_FUNC})) {
           $paramHash->{FILTER_FUNC} = sub { return 1; };
       }
       Apache::lonhelper::files->new();
   }    
   
   sub start_filechoice {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
                                                                 $parser);
   }
   
   sub end_filechoice { return ''; }
   
   sub start_filefilter {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/filefilter',
                                                   $parser);
       $contents = 'sub { my $filename = shift; ' . $contents . '}';
       $paramHash->{FILTER_FUNC} = eval $contents;
   }
   
   sub end_filefilter { return ''; }
   
   sub render {
       my $self = shift;
       my $result = '';
       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};
       my $buttons = '';
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value, checkName) {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.name == checkName + '.forminput') {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   
       function checkallid(value, idName) {
           for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.id == idName) {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br /> &nbsp;
   <input type="button" onclick="checkall(true, '$var')" value="Select All Files" />
   <input type="button" onclick="checkall(false, '$var')" value="Unselect All Files" />
   <input type="button" onclick="checkallid(true, 'Published')" value="Select All Published" />
   <input type="button" onclick="checkallid(false, 'Published')" value="Unselect All Published" />
   <br /> &nbsp;
   BUTTONS
       }
   
       # Get the list of files in this directory.
       my @fileList;
   
       # If the subdirectory is in local CSTR space
       if ($subdir =~ m|/home/([^/]+)/public_html|) {
           my $user = $1;
           my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
           @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
       } else {
           # local library server resource space
           @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
       }
   
       $result .= $buttons;
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       $result .= '<table border="0" cellpadding="2" cellspacing="0">';
   
       # Keeps track if there are no choices, prints appropriate error
       # if there are none. 
       my $choices = 0;
       my $type = 'radio';
       if ($self->{'multichoice'}) {
           $type = 'checkbox';
       }
       # Print each legitimate file choice.
       for my $file (@fileList) {
           $file = (split(/&/, $file))[0];
           if ($file eq '.' || $file eq '..') {
               next;
           }
           my $fileName = $subdir .'/'. $file;
           if (&$filterFunc($file)) {
               (my $status, my $color) = @{fileState($subdir, $file)};
               $result .= '<tr><td align="right"' . " bgcolor='$color'>" .
                   "<input id='$status' type='$type' name='" . $var
               . ".forminput' value='" . HTML::Entities::encode($fileName) .
                   "'";
               if (!$self->{'multichoice'} && $choices == 0) {
                   $result .= ' checked';
               }
               $result .= "/></td><td bgcolor='$color'>" . $file .
                    "</td><td bgcolor='$color'>$status</td></tr>\n";
               $choices++;
           }
       }
   
       $result .= "</table>\n";
   
       if (!$choices) {
           $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
       }
   
       $result .= $buttons;
   
       return $result;
 }  }
 # If a NEXT_STATE was given, switch to it  
   # Determine the state of the file: Published, unpublished, modified.
   # Return the color it should be in and a label as a two-element array
   # reference.
   # Logic lifted from lonpubdir.pm, even though I don't know that it's still
   # the most right thing to do.
   
   sub fileState {
       my $constructionSpaceDir = shift;
       my $file = shift;
       
       my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
       my $subdirpart = $constructionSpaceDir;
       $subdirpart =~ s/^\/home\/$ENV{'user.name'}\/public_html//;
       my $resdir = $docroot . '/res/' . $ENV{'user.domain'} . '/' . $ENV{'user.name'} .
           $subdirpart;
   
       my @constructionSpaceFileStat = stat($constructionSpaceDir . '/' . $file);
       my @resourceSpaceFileStat = stat($resdir . '/' . $file);
       if (!@resourceSpaceFileStat) {
           return ['Unpublished', '#FFCCCC'];
       }
   
       my $constructionSpaceFileModified = $constructionSpaceFileStat[9];
       my $resourceSpaceFileModified = $resourceSpaceFileStat[9];
       
       if ($constructionSpaceFileModified > $resourceSpaceFileModified) {
           return ['Modified', '#FFFFCC'];
       }
       return ['Published', '#CCFFCC'];
   }
   
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
     if (defined($self->{NEXT_STATE})) {      my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
         $helper->changeState($self->{NEXT_STATE});      if (!$result) {
           $self->{ERROR_MSG} = 'You must choose at least one file '.
               'to continue.';
           return 0;
       }
   
       if ($self->{'multichoice'}) {
           $self->process_multiple_choices($self->{'variable'}.'.forminput',
                                           $self->{'variable'});
       }
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
     }      }
   
       return 1;
 }  }
   
   1;
   
   package Apache::lonhelper::section;
   
   =pod
   
   =head2 Element: section
   
   <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: <exec>
   
   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<object>, 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 <exec>, Perl said: '. $@ if $@;
       &$code($helper, $paramHash);
   }
   
   sub end_exec { return ''; }
   
   =pod
   
   =head2 General-purpose tag: <condition>
   
   The <condition> tag allows you to mask out parts of the helper code
   depending on some programatically determined condition. The condition
   tag contains a tag <clause> 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 <clause> tag must be the first sub-tag of the <condition> 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: <eval>
   
   The <eval> tag will be evaluated as a subroutine call passed in the
   current helper object and state hash as described in <condition> 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 <parmwizfinal />, 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 = "<form name='helpform' method='get' action='/adm/parmset'>\n";
       $result .= '<p>Confirm that this information is correct, then click &quot;Finish Wizard&quot; to complete setting the parameter.<ul>';
       my $affectedResourceId = "";
       my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};
       my $level = "";
       
       # Print the type of manipulation:
       $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}
                  . "</b></li>\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 .= "<input type='hidden' name='recent_date_end' " .
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_value' " . 
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_type' " .
               "value='date_end' />\n";
       } elsif ($vars->{ACTION_TYPE} eq 'open_date') {
           $result .= "<input type='hidden' name='recent_date_start' ".
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_value' " .
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_type' " .
               "value='date_start' />\n";
       } 
       
       # Print the granularity, depending on the action
       if ($vars->{GRANULARITY} eq 'whole_course') {
           $result .= '<li>for <b>all resources in the course</b></li>';
           $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 .= "<li>for the map named <b>$title</b></li>";
           $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 .= "<li>for the resource named <b>$title</b></li>";
           $level = 7;
           $affectedResourceId = $vars->{RESOURCE_ID};
       }
   
       # Print targets
       if ($vars->{TARGETS} eq 'course') {
           $result .= '<li>for <b>all students in course</b></li>';
       } elsif ($vars->{TARGETS} eq 'section') {
           my $section = $vars->{SECTION_NAME};
           $result .= "<li>for section <b>$section</b></li>";
           $level -= 3;
           $result .= "<input type='hidden' name='csec' value='" .
               HTML::Entities::encode($section) . "' />\n";
       } else {
           # FIXME: This is probably wasteful! Store the name!
           my $classlist = Apache::loncoursedata::get_classlist();
           my $name = $classlist->{$vars->{USER_NAME}}->[6];
           $result .= "<li>for <b>$name</b></li>";
           $level -= 6;
           my ($uname, $udom) = split /:/, $vars->{USER_NAME};
           $result .= "<input type='hidden' name='uname' value='".
               HTML::Entities::encode($uname) . "' />\n";
           $result .= "<input type='hidden' name='udom' value='".
               HTML::Entities::encode($udom) . "' />\n";
       }
   
       # Print value
       $result .= "<li>to <b>" . ctime($vars->{PARM_DATE}) . "</b> (" .
           Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}) 
           . ")</li>\n";
   
       # print pres_marker
       $result .= "\n<input type='hidden' name='pres_marker'" .
           " value='$affectedResourceId&$parm_name&$level' />\n";
   
       $result .= "<br /><br /><center><input type='submit' value='Finish Helper' /></center></form>\n";
   
       return $result;
   }
       
   sub overrideForm {
       return 1;
   }
   
 1;  1;
   
 __END__  __END__

Removed from v.1.4  
changed lines
  Added in v.1.21


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>