File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.27: download - view: text, annotated - select for diffs
Mon May 12 19:33:57 2003 UTC (21 years ago) by bowersj2
Branches: MAIN
CVS tags: HEAD
This commit is to fix up the parameter helper. Fixed student name
breakage. Now the parameter helper does a better job (i.e., it *does*
a job) of showing the user where they would have entered the parameter
with the advanced interface. Hopefully this won't cause too many
problems...

also included in this commit because it wasn't worth trying to mask it
out is the beginning of some more infrastructure I will be using for the
course initialization wizard, that allow the helper writer to bundle
the logic for selecting the default value, display the value, and
finally set the value all in the same geographical place in the helper,
putting all the logic for a single preference in the same place. Pretty
cool, if I do say so myself. ;-)

    1: # The LearningOnline Network with CAPA
    2: # .helper XML handler to implement the LON-CAPA helper
    3: #
    4: # $Id: lonhelper.pm,v 1.27 2003/05/12 19:33:57 bowersj2 Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: # (Page Handler
   29: #
   30: # (.helper handler
   31: #
   32: 
   33: =pod
   34: 
   35: =head1 lonhelper - HTML Helper framework for LON-CAPA
   36: 
   37: Helpers, often known as "wizards", are well-established UI widgets that users
   38: feel comfortable with. It can take a complicated multidimensional problem the
   39: user has and turn it into a series of bite-sized one-dimensional questions.
   40: 
   41: For developers, helpers provide an easy way to bundle little bits of functionality
   42: for the user, without having to write the tedious state-maintenence code.
   43: 
   44: Helpers are defined as XML documents, placed in the /home/httpd/html/adm/helpers 
   45: directory and having the .helper file extension. For examples, see that directory.
   46: 
   47: All classes are in the Apache::lonhelper namespace.
   48: 
   49: =head2 lonhelper XML file format
   50: 
   51: A helper consists of a top-level <helper> tag which contains a series of states.
   52: Each state contains one or more state elements, which are what the user sees, like
   53: messages, resource selections, or date queries.
   54: 
   55: The helper tag is required to have one attribute, "title", which is the name
   56: of the helper itself, such as "Parameter helper". 
   57: 
   58: =head2 State tags
   59: 
   60: State tags are required to have an attribute "name", which is the symbolic
   61: name of the state and will not be directly seen by the user. The helper is
   62: required to have one state named "START", which is the state the helper
   63: will start with. By convention, this state should clearly describe what
   64: the helper will do for the user, and may also include the first information
   65: entry the user needs to do for the helper.
   66: 
   67: State tags are also required to have an attribute "title", which is the
   68: human name of the state, and will be displayed as the header on top of 
   69: the screen for the user.
   70: 
   71: =head2 Example Helper Skeleton
   72: 
   73: An example of the tags so far:
   74: 
   75:  <helper title="Example Helper">
   76:    <state name="START" title="Demonstrating the Example Helper">
   77:      <!-- notice this is the START state the wizard requires -->
   78:      </state>
   79:    <state name="GET_NAME" title="Enter Student Name">
   80:      </state>
   81:    </helper>
   82: 
   83: Of course this does nothing. In order for the wizard to do something, it is
   84: necessary to put actual elements into the wizard. Documentation for each
   85: of these elements follows.
   86: 
   87: =head2 Creating a Helper With Code, Not XML
   88: 
   89: In some situations, such as the printing wizard (see lonprintout.pm), 
   90: writing the helper in XML would be too complicated, because of scope 
   91: issues or the fact that the code actually outweighs the XML. It is
   92: possible to create a helper via code, though it is a little odd.
   93: 
   94: Creating a helper via code is more like issuing commands to create
   95: a helper then normal code writing. For instance, elements will automatically
   96: be added to the last state created, so it's important to create the 
   97: states in the correct order.
   98: 
   99: First, create a new helper:
  100: 
  101:  use Apache::lonhelper;
  102: 
  103:  my $helper = Apache::lonhelper::new->("Helper Title");
  104: 
  105: Next you'll need to manually add states to the helper:
  106: 
  107:  Apache::lonhelper::state->new("STATE_NAME", "State's Human Title");
  108: 
  109: You don't need to save a reference to it because all elements up until
  110: the next state creation will automatically be added to this state.
  111: 
  112: Elements are created by populating the $paramHash in 
  113: Apache::lonhelper::paramhash. To prevent namespace issues, retrieve 
  114: a reference to that has with getParamHash:
  115: 
  116:  my $paramHash = Apache::lonhelper::getParamHash();
  117: 
  118: You will need to do this for each state you create.
  119: 
  120: Populate the $paramHash with the parameters for the element you wish
  121: to add next; the easiest way to find out what those entries are is
  122: to read the code. Some common ones are 'variable' to record the variable
  123: to store the results in, and NEXTSTATE to record a next state transition.
  124: 
  125: Then create your element:
  126: 
  127:  $paramHash->{MESSAGETEXT} = "This is a message.";
  128:  Apache::lonhelper::message->new();
  129: 
  130: The creation will take the $paramHash and bless it into a
  131: Apache::lonhelper::message object. To create the next element, you need
  132: to get a reference to the new, empty $paramHash:
  133: 
  134:  $paramHash = Apache::lonhelper::getParamHash();
  135: 
  136: and you can repeat creating elements that way. You can add states
  137: and elements as needed.
  138: 
  139: See lonprintout.pm, subroutine printHelper for an example of this, where
  140: we dynamically add some states to prevent security problems, for instance.
  141: 
  142: Normally the machinery in the XML format is sufficient; dynamically 
  143: adding states can easily be done by wrapping the state in a <condition>
  144: tag. This should only be used when the code dominates the XML content,
  145: the code is so complicated that it is difficult to get access to
  146: all of the information you need because of scoping issues, or so much
  147: of the information used is persistent because would-be <exec> or 
  148: <eval> blocks that using the {DATA} mechanism results in hard-to-read
  149: and -maintain code.
  150: 
  151: It is possible to do some of the work with an XML fragment parsed by
  152: lonxml; again, see lonprintout.pm for an example. In that case it is 
  153: imperative that you call B<Apache::lonhelper::registerHelperTags()>
  154: before parsing XML fragments and B<Apache::lonhelper::unregisterHelperTags()>
  155: when you are done. See lonprintout.pm for examples of this usage in the
  156: printHelper subroutine.
  157: 
  158: =cut
  159: 
  160: package Apache::lonhelper;
  161: use Apache::Constants qw(:common);
  162: use Apache::File;
  163: use Apache::lonxml;
  164: 
  165: # Register all the tags with the helper, so the helper can 
  166: # push and pop them
  167: 
  168: my @helperTags;
  169: 
  170: sub register {
  171:     my ($namespace, @tags) = @_;
  172: 
  173:     for my $tag (@tags) {
  174:         push @helperTags, [$namespace, $tag];
  175:     }
  176: }
  177: 
  178: BEGIN {
  179:     Apache::lonxml::register('Apache::lonhelper', 
  180:                              ('helper'));
  181:       register('Apache::lonhelper', ('state'));
  182: }
  183: 
  184: # Since all helpers are only three levels deep (helper tag, state tag, 
  185: # substate type), it's easier and more readble to explicitly track 
  186: # those three things directly, rather then futz with the tag stack 
  187: # every time.
  188: my $helper;
  189: my $state;
  190: my $substate;
  191: # To collect parameters, the contents of the subtags are collected
  192: # into this paramHash, then passed to the element object when the 
  193: # end of the element tag is located.
  194: my $paramHash; 
  195: 
  196: # Note from Jeremy 5-8-2003: It is *vital* that the real handler be called
  197: # as a subroutine from the handler, or very mysterious things might happen.
  198: # I don't know exactly why, but it seems that the scope where the Apache
  199: # server enters the perl handler is treated differently from the rest of
  200: # the handler. This also seems to manifest itself in the debugger as entering
  201: # the perl handler in seemingly random places (sometimes it starts in the
  202: # compiling phase, sometimes in the handler execution phase where it runs
  203: # the code and stepping into the "1;" the module ends with goes into the handler,
  204: # sometimes starting directly with the handler); I think the cause is related.
  205: # In the debugger, this means that breakpoints are ignored until you step into
  206: # a function and get out of what must be a "faked up scope" in the Apache->
  207: # mod_perl connection. In this code, it was manifesting itself in the existence
  208: # of two seperate file-scoped $helper variables, one set to the value of the
  209: # helper in the helper constructor, and one referenced by the handler on the
  210: # "$helper->process()" line. The second was therefore never set, and was still
  211: # undefined when I tried to call process on it.
  212: # By pushing the "real handler" down into the "real scope", everybody except the 
  213: # actual handler function directly below this comment gets the same $helper and
  214: # everybody is happy.
  215: # The upshot of all of this is that for safety when a handler is  using 
  216: # file-scoped variables in LON-CAPA, the handler should be pushed down one 
  217: # call level, as I do here, to ensure that the top-level handler function does
  218: # not get a different file scope from the rest of the code.
  219: sub handler {
  220:     my $r = shift;
  221:     return real_handler($r);
  222: }
  223: 
  224: # For debugging purposes, one can send a second parameter into this
  225: # function, the 'uri' of the helper you wish to have rendered, and
  226: # call this from other handlers.
  227: sub real_handler {
  228:     my $r = shift;
  229:     my $uri = shift;
  230:     if (!defined($uri)) { $uri = $r->uri(); }
  231:     $ENV{'request.uri'} = $uri;
  232:     my $filename = '/home/httpd/html' . $uri;
  233:     my $fh = Apache::File->new($filename);
  234:     my $file;
  235:     read $fh, $file, 100000000;
  236: 
  237: 
  238:     # Send header, don't cache this page
  239:     if ($r->header_only) {
  240:         if ($ENV{'browser.mathml'}) {
  241:             $r->content_type('text/xml');
  242:         } else {
  243:             $r->content_type('text/html');
  244:         }
  245:         $r->send_http_header;
  246:         return OK;
  247:     }
  248:     if ($ENV{'browser.mathml'}) {
  249:         $r->content_type('text/xml');
  250:     } else {
  251:         $r->content_type('text/html');
  252:     }
  253:     $r->send_http_header;
  254:     $r->rflush();
  255: 
  256:     # Discard result, we just want the objects that get created by the
  257:     # xml parsing
  258:     &Apache::lonxml::xmlparse($r, 'helper', $file);
  259: 
  260:     $helper->process();
  261: 
  262:     $r->print($helper->display());
  263:    return OK;
  264: }
  265: 
  266: sub registerHelperTags {
  267:     for my $tagList (@helperTags) {
  268:         Apache::lonxml::register($tagList->[0], $tagList->[1]);
  269:     }
  270: }
  271: 
  272: sub unregisterHelperTags {
  273:     for my $tagList (@helperTags) {
  274:         Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
  275:     }
  276: }
  277: 
  278: sub start_helper {
  279:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  280: 
  281:     if ($target ne 'helper') {
  282:         return '';
  283:     }
  284: 
  285:     registerHelperTags();
  286: 
  287:     Apache::lonhelper::helper->new($token->[2]{'title'});
  288:     return '';
  289: }
  290: 
  291: sub end_helper {
  292:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  293:     
  294:     if ($target ne 'helper') {
  295:         return '';
  296:     }
  297: 
  298:     unregisterHelperTags();
  299: 
  300:     return '';
  301: }
  302: 
  303: sub start_state {
  304:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  305: 
  306:     if ($target ne 'helper') {
  307:         return '';
  308:     }
  309: 
  310:     Apache::lonhelper::state->new($token->[2]{'name'},
  311:                                   $token->[2]{'title'});
  312:     return '';
  313: }
  314: 
  315: # Use this to get the param hash from other files.
  316: sub getParamHash {
  317:     return $paramHash;
  318: }
  319: 
  320: # Use this to get the helper, if implementing elements in other files
  321: # (like lonprintout.pm)
  322: sub getHelper {
  323:     return $helper;
  324: }
  325: 
  326: # don't need this, so ignore it
  327: sub end_state {
  328:     return '';
  329: }
  330: 
  331: 1;
  332: 
  333: package Apache::lonhelper::helper;
  334: 
  335: use Digest::MD5 qw(md5_hex);
  336: use HTML::Entities;
  337: use Apache::loncommon;
  338: use Apache::File;
  339: 
  340: sub new {
  341:     my $proto = shift;
  342:     my $class = ref($proto) || $proto;
  343:     my $self = {};
  344: 
  345:     $self->{TITLE} = shift;
  346:     
  347:     Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
  348: 
  349:     # If there is a state from the previous form, use that. If there is no
  350:     # state, use the start state parameter.
  351:     if (defined $ENV{"form.CURRENT_STATE"})
  352:     {
  353: 	$self->{STATE} = $ENV{"form.CURRENT_STATE"};
  354:     }
  355:     else
  356:     {
  357: 	$self->{STATE} = "START";
  358:     }
  359: 
  360:     $self->{TOKEN} = $ENV{'form.TOKEN'};
  361:     # If a token was passed, we load that in. Otherwise, we need to create a 
  362:     # new storage file
  363:     # Tried to use standard Tie'd hashes, but you can't seem to take a 
  364:     # reference to a tied hash and write to it. I'd call that a wart.
  365:     if ($self->{TOKEN}) {
  366:         # Validate the token before trusting it
  367:         if ($self->{TOKEN} !~ /^[a-f0-9]{32}$/) {
  368:             # Not legit. Return nothing and let all hell break loose.
  369:             # User shouldn't be doing that!
  370:             return undef;
  371:         }
  372: 
  373:         # Get the hash.
  374:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); # Note the token is not the literal file
  375:         
  376:         my $file = Apache::File->new($self->{FILENAME});
  377:         my $contents = <$file>;
  378: 
  379:         # Now load in the contents
  380:         for my $value (split (/&/, $contents)) {
  381:             my ($name, $value) = split(/=/, $value);
  382:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  383:             $self->{VARS}->{$name} = $value;
  384:         }
  385: 
  386:         $file->close();
  387:     } else {
  388:         # Only valid if we're just starting.
  389:         if ($self->{STATE} ne 'START') {
  390:             return undef;
  391:         }
  392:         # Must create the storage
  393:         $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} .
  394:                                  time() . rand());
  395:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
  396:     }
  397: 
  398:     # OK, we now have our persistent storage.
  399: 
  400:     if (defined $ENV{"form.RETURN_PAGE"})
  401:     {
  402: 	$self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};
  403:     }
  404:     else
  405:     {
  406: 	$self->{RETURN_PAGE} = $ENV{REFERER};
  407:     }
  408: 
  409:     $self->{STATES} = {};
  410:     $self->{DONE} = 0;
  411: 
  412:     # Used by various helpers for various things; see lonparm.helper
  413:     # for an example.
  414:     $self->{DATA} = {};
  415: 
  416:     $helper = $self;
  417: 
  418:     # Establish the $paramHash
  419:     $paramHash = {};
  420: 
  421:     bless($self, $class);
  422:     return $self;
  423: }
  424: 
  425: # Private function; returns a string to construct the hidden fields
  426: # necessary to have the helper track state.
  427: sub _saveVars {
  428:     my $self = shift;
  429:     my $result = "";
  430:     $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
  431:         HTML::Entities::encode($self->{STATE}) . "\" />\n";
  432:     $result .= '<input type="hidden" name="TOKEN" value="' .
  433:         $self->{TOKEN} . "\" />\n";
  434:     $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
  435:         HTML::Entities::encode($self->{RETURN_PAGE}) . "\" />\n";
  436: 
  437:     return $result;
  438: }
  439: 
  440: # Private function: Create the querystring-like representation of the stored
  441: # data to write to disk.
  442: sub _varsInFile {
  443:     my $self = shift;
  444:     my @vars = ();
  445:     for my $key (keys %{$self->{VARS}}) {
  446:         push @vars, &Apache::lonnet::escape($key) . '=' .
  447:             &Apache::lonnet::escape($self->{VARS}->{$key});
  448:     }
  449:     return join ('&', @vars);
  450: }
  451: 
  452: # Use this to declare variables.
  453: # FIXME: Document this
  454: sub declareVar {
  455:     my $self = shift;
  456:     my $var = shift;
  457: 
  458:     if (!defined($self->{VARS}->{$var})) {
  459:         $self->{VARS}->{$var} = '';
  460:     }
  461: 
  462:     my $envname = 'form.' . $var . '.forminput';
  463:     if (defined($ENV{$envname})) {
  464:         $self->{VARS}->{$var} = $ENV{$envname};
  465:     }
  466: }
  467: 
  468: sub changeState {
  469:     my $self = shift;
  470:     $self->{STATE} = shift;
  471: }
  472: 
  473: sub registerState {
  474:     my $self = shift;
  475:     my $state = shift;
  476: 
  477:     my $stateName = $state->name();
  478:     $self->{STATES}{$stateName} = $state;
  479: }
  480: 
  481: sub process {
  482:     my $self = shift;
  483: 
  484:     # Phase 1: Post processing for state of previous screen (which is actually
  485:     # the "current state" in terms of the helper variables), if it wasn't the 
  486:     # beginning state.
  487:     if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {
  488: 	my $prevState = $self->{STATES}{$self->{STATE}};
  489:         $prevState->postprocess();
  490:     }
  491:     
  492:     # Note, to handle errors in a state's input that a user must correct,
  493:     # do not transition in the postprocess, and force the user to correct
  494:     # the error.
  495: 
  496:     # Phase 2: Preprocess current state
  497:     my $startState = $self->{STATE};
  498:     my $state = $self->{STATES}->{$startState};
  499:     
  500:     # For debugging, print something here to determine if you're going
  501:     # to an undefined state.
  502:     if (!defined($state)) {
  503:         return;
  504:     }
  505:     $state->preprocess();
  506: 
  507:     # Phase 3: While the current state is different from the previous state,
  508:     # keep processing.
  509:     while ( $startState ne $self->{STATE} && 
  510:             defined($self->{STATES}->{$self->{STATE}}) )
  511:     {
  512: 	$startState = $self->{STATE};
  513: 	$state = $self->{STATES}->{$startState};
  514: 	$state->preprocess();
  515:     }
  516: 
  517:     return;
  518: } 
  519: 
  520: # 1: Do the post processing for the previous state.
  521: # 2: Do the preprocessing for the current state.
  522: # 3: Check to see if state changed, if so, postprocess current and move to next.
  523: #    Repeat until state stays stable.
  524: # 4: Render the current state to the screen as an HTML page.
  525: sub display {
  526:     my $self = shift;
  527: 
  528:     my $state = $self->{STATES}{$self->{STATE}};
  529: 
  530:     my $result = "";
  531: 
  532:     if (!defined($state)) {
  533:         $result = "<font color='#ff0000'>Error: state '$state' not defined!</font>";
  534:         return $result;
  535:     }
  536: 
  537:     # Phase 4: Display.
  538:     my $stateTitle = $state->title();
  539:     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
  540: 
  541:     $result .= <<HEADER;
  542: <html>
  543:     <head>
  544:         <title>LON-CAPA Helper: $self->{TITLE}</title>
  545:     </head>
  546:     $bodytag
  547: HEADER
  548:     if (!$state->overrideForm()) { $result.="<form name='helpform' method='GET'>"; }
  549:     $result .= <<HEADER;
  550:         <table border="0"><tr><td>
  551:         <h2><i>$stateTitle</i></h2>
  552: HEADER
  553: 
  554:     if (!$state->overrideForm()) {
  555:         $result .= $self->_saveVars();
  556:     }
  557:     $result .= $state->render() . "<p>&nbsp;</p>";
  558: 
  559:     if (!$state->overrideForm()) {
  560:         $result .= '<center>';
  561:         if ($self->{STATE} ne $self->{START_STATE}) {
  562:             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
  563:         }
  564:         if ($self->{DONE}) {
  565:             my $returnPage = $self->{RETURN_PAGE};
  566:             $result .= "<a href=\"$returnPage\">End Helper</a>";
  567:         }
  568:         else {
  569:             $result .= '<input name="back" type="button" ';
  570:             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';
  571:             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';
  572:         }
  573:         $result .= "</center>\n";
  574:     }
  575: 
  576:     #foreach my $key (keys %{$self->{VARS}}) {
  577:     #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
  578:     #}
  579: 
  580:     $result .= <<FOOTER;
  581:               </td>
  582:             </tr>
  583:           </table>
  584:         </form>
  585:     </body>
  586: </html>
  587: FOOTER
  588: 
  589:     # Handle writing out the vars to the file
  590:     my $file = Apache::File->new('>'.$self->{FILENAME});
  591:     print $file $self->_varsInFile();
  592: 
  593:     return $result;
  594: }
  595: 
  596: 1;
  597: 
  598: package Apache::lonhelper::state;
  599: 
  600: # States bundle things together and are responsible for compositing the
  601: # various elements together. It is not generally necessary for users to
  602: # use the state object directly, so it is not perldoc'ed.
  603: 
  604: # Basically, all the states do is pass calls to the elements and aggregate
  605: # the results.
  606: 
  607: sub new {
  608:     my $proto = shift;
  609:     my $class = ref($proto) || $proto;
  610:     my $self = {};
  611: 
  612:     $self->{NAME} = shift;
  613:     $self->{TITLE} = shift;
  614:     $self->{ELEMENTS} = [];
  615: 
  616:     bless($self, $class);
  617: 
  618:     $helper->registerState($self);
  619: 
  620:     $state = $self;
  621: 
  622:     return $self;
  623: }
  624: 
  625: sub name {
  626:     my $self = shift;
  627:     return $self->{NAME};
  628: }
  629: 
  630: sub title {
  631:     my $self = shift;
  632:     return $self->{TITLE};
  633: }
  634: 
  635: sub preprocess {
  636:     my $self = shift;
  637:     for my $element (@{$self->{ELEMENTS}}) {
  638:         $element->preprocess();
  639:     }
  640: }
  641: 
  642: # FIXME: Document that all postprocesses must return a true value or
  643: # the state transition will be overridden
  644: sub postprocess {
  645:     my $self = shift;
  646: 
  647:     # Save the state so we can roll it back if we need to.
  648:     my $originalState = $helper->{STATE};
  649:     my $everythingSuccessful = 1;
  650: 
  651:     for my $element (@{$self->{ELEMENTS}}) {
  652:         my $result = $element->postprocess();
  653:         if (!$result) { $everythingSuccessful = 0; }
  654:     }
  655: 
  656:     # If not all the postprocesses were successful, override
  657:     # any state transitions that may have occurred. It is the
  658:     # responsibility of the states to make sure they have 
  659:     # error handling in that case.
  660:     if (!$everythingSuccessful) {
  661:         $helper->{STATE} = $originalState;
  662:     }
  663: }
  664: 
  665: # Override the form if any element wants to.
  666: # two elements overriding the form will make a mess, but that should
  667: # be considered helper author error ;-)
  668: sub overrideForm {
  669:     my $self = shift;
  670:     for my $element (@{$self->{ELEMENTS}}) {
  671:         if ($element->overrideForm()) {
  672:             return 1;
  673:         }
  674:     }
  675:     return 0;
  676: }
  677: 
  678: sub addElement {
  679:     my $self = shift;
  680:     my $element = shift;
  681:     
  682:     push @{$self->{ELEMENTS}}, $element;
  683: }
  684: 
  685: sub render {
  686:     my $self = shift;
  687:     my @results = ();
  688: 
  689:     for my $element (@{$self->{ELEMENTS}}) {
  690:         push @results, $element->render();
  691:     }
  692:     return join("\n", @results);
  693: }
  694: 
  695: 1;
  696: 
  697: package Apache::lonhelper::element;
  698: # Support code for elements
  699: 
  700: =pod
  701: 
  702: =head2 Element Base Class
  703: 
  704: The Apache::lonhelper::element base class provides support methods for
  705: the elements to use, such as a multiple value processer.
  706: 
  707: B<Methods>:
  708: 
  709: =over 4
  710: 
  711: =item * process_multiple_choices(formName, varName): Process the form 
  712: element named "formName" and place the selected items into the helper 
  713: variable named varName. This is for things like checkboxes or 
  714: multiple-selection listboxes where the user can select more then 
  715: one entry. The selected entries are delimited by triple pipes in 
  716: the helper variables, like this:  
  717: 
  718:  CHOICE_1|||CHOICE_2|||CHOICE_3
  719: 
  720: =back
  721: 
  722: B<finalcode tag>
  723: 
  724: Each element can contain a "finalcode" tag that, when the special FINAL
  725: helper state is used, will be executed, surrounded by "sub { my $helper = shift;"
  726: and "}". It is expected to return a string describing what it did, which 
  727: may be an empty string. See course initialization helper for an example. This is
  728: generally intended for helpers like the course initialization helper, which consist
  729: of several panels, each of which is performing some sort of bite-sized functionality.
  730: 
  731: B<defaultvalue tag>
  732: 
  733: Each element that accepts user input can contain a "defaultvalue" tag that,
  734: when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
  735: will form a subroutine that when called will provide a default value for
  736: the element. How this value is interpreted by the element is specific to
  737: the element itself, and possibly the settings the element has (such as 
  738: multichoice vs. single choice for <choices> tags). 
  739: 
  740: This is also intended for things like the course initialization wizard, where the
  741: user is setting various parameters. By correctly grabbing current settings 
  742: and including them into the helper, it allows the user to come back to the
  743: helper later and re-execute it, without needing to worry about overwriting
  744: some setting accidentally.
  745: 
  746: Again, see the course initialization helper for examples.
  747: 
  748: =cut
  749: 
  750: BEGIN {
  751:     &Apache::lonhelper::register('Apache::lonhelper::element',
  752:                                  ('nextstate', 'finalcode',
  753:                                   'defaultvalue'));
  754: }
  755: 
  756: # Because we use the param hash, this is often a sufficent
  757: # constructor
  758: sub new {
  759:     my $proto = shift;
  760:     my $class = ref($proto) || $proto;
  761:     my $self = $paramHash;
  762:     bless($self, $class);
  763: 
  764:     $self->{PARAMS} = $paramHash;
  765:     $self->{STATE} = $state;
  766:     $state->addElement($self);
  767:     
  768:     # Ensure param hash is not reused
  769:     $paramHash = {};
  770: 
  771:     return $self;
  772: }   
  773: 
  774: sub start_nextstate {
  775:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  776: 
  777:     if ($target ne 'helper') {
  778:         return '';
  779:     }
  780:     
  781:     $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
  782:                                                              $parser);
  783:     return '';
  784: }
  785: 
  786: sub end_nextstate { return ''; }
  787: 
  788: sub start_finalcode {
  789:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  790: 
  791:     if ($target ne 'helper') {
  792:         return '';
  793:     }
  794:     
  795:     $paramHash->{FINAL_CODE} = &Apache::lonxml::get_all_text('/finalcode',
  796:                                                              $parser);
  797:     return '';
  798: }
  799: 
  800: sub end_finalcode { return ''; }
  801: 
  802: sub start_defaultvalue {
  803:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  804: 
  805:     if ($target ne 'helper') {
  806:         return '';
  807:     }
  808:     
  809:     $paramHash->{DEFAULT_VALUE} = &Apache::lonxml::get_all_text('/defaultvalue',
  810:                                                              $parser);
  811:     $paramHash->{DEFAULT_VALUE} = 'sub { my $helper = shift; my $state = shift;' .
  812:         $paramHash->{DEFAULT_VALUE} . '}';
  813:     return '';
  814: }
  815: 
  816: sub end_defaultvalue { return ''; }
  817: 
  818: sub preprocess {
  819:     return 1;
  820: }
  821: 
  822: sub postprocess {
  823:     return 1;
  824: }
  825: 
  826: sub render {
  827:     return '';
  828: }
  829: 
  830: sub overrideForm {
  831:     return 0;
  832: }
  833: 
  834: sub process_multiple_choices {
  835:     my $self = shift;
  836:     my $formname = shift;
  837:     my $var = shift;
  838: 
  839:     # Must extract values from data directly, as there
  840:     # may be more then one.
  841:     my @values;
  842:     for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
  843:         my ($name, $value) = split(/=/, $formparam);
  844:         if ($name ne $formname) {
  845:             next;
  846:         }
  847:         $value =~ tr/+/ /;
  848:         $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  849:         push @values, $value;
  850:     }
  851:     $helper->{VARS}->{$var} = join('|||', @values);
  852:     
  853:     return;
  854: }
  855: 
  856: 1;
  857: 
  858: package Apache::lonhelper::message;
  859: 
  860: =pod
  861: 
  862: =head2 Element: message
  863: 
  864: Message elements display the contents of their <message_text> tags, and
  865: transition directly to the state in the <nextstate> tag. Example:
  866: 
  867:  <message>
  868:    <nextstate>GET_NAME</nextstate>
  869:    <message_text>This is the <b>message</b> the user will see, 
  870:                  <i>HTML allowed</i>.</message_text>
  871:    </message>
  872: 
  873: This will display the HTML message and transition to the <nextstate> if
  874: given. The HTML will be directly inserted into the helper, so if you don't
  875: want text to run together, you'll need to manually wrap the <message_text>
  876: in <p> tags, or whatever is appropriate for your HTML.
  877: 
  878: Message tags do not add in whitespace, so if you want it, you'll need to add
  879: it into states. This is done so you can inline some elements, such as 
  880: the <date> element, right between two messages, giving the appearence that 
  881: the <date> element appears inline. (Note the elements can not be embedded
  882: within each other.)
  883: 
  884: This is also a good template for creating your own new states, as it has
  885: very little code beyond the state template.
  886: 
  887: =cut
  888: 
  889: no strict;
  890: @ISA = ("Apache::lonhelper::element");
  891: use strict;
  892: 
  893: BEGIN {
  894:     &Apache::lonhelper::register('Apache::lonhelper::message',
  895:                               ('message'));
  896: }
  897: 
  898: sub new {
  899:     my $ref = Apache::lonhelper::element->new();
  900:     bless($ref);
  901: }
  902: 
  903: # CONSTRUCTION: Construct the message element from the XML
  904: sub start_message {
  905:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  906: 
  907:     if ($target ne 'helper') {
  908:         return '';
  909:     }
  910: 
  911:     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message',
  912:                                                                $parser);
  913: 
  914:     if (defined($token->[2]{'nextstate'})) {
  915:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
  916:     }
  917:     return '';
  918: }
  919: 
  920: sub end_message {
  921:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  922: 
  923:     if ($target ne 'helper') {
  924:         return '';
  925:     }
  926:     Apache::lonhelper::message->new();
  927:     return '';
  928: }
  929: 
  930: sub render {
  931:     my $self = shift;
  932: 
  933:     return $self->{MESSAGE_TEXT};
  934: }
  935: # If a NEXTSTATE was given, switch to it
  936: sub postprocess {
  937:     my $self = shift;
  938:     if (defined($self->{NEXTSTATE})) {
  939:         $helper->changeState($self->{NEXTSTATE});
  940:     }
  941: 
  942:     return 1;
  943: }
  944: 1;
  945: 
  946: package Apache::lonhelper::choices;
  947: 
  948: =pod
  949: 
  950: =head2 Element: choices
  951: 
  952: Choice states provide a single choice to the user as a text selection box.
  953: A "choice" is two pieces of text, one which will be displayed to the user
  954: (the "human" value), and one which will be passed back to the program
  955: (the "computer" value). For instance, a human may choose from a list of
  956: resources on disk by title, while your program wants the file name.
  957: 
  958: <choices> takes an attribute "variable" to control which helper variable
  959: the result is stored in.
  960: 
  961: <choices> takes an attribute "multichoice" which, if set to a true
  962: value, will allow the user to select multiple choices.
  963: 
  964: <choices> takes an attribute "allowempty" which, if set to a true 
  965: value, will allow the user to select none of the choices without raising
  966: an error message.
  967: 
  968: B<SUB-TAGS>
  969: 
  970: <choices> can have the following subtags:
  971: 
  972: =over 4
  973: 
  974: =item * <nextstate>state_name</nextstate>: If given, this will cause the
  975:       choice element to transition to the given state after executing. If
  976:       this is used, do not pass nextstates to the <choice> tag.
  977: 
  978: =item * <choice />: If the choices are static,
  979:       this element will allow you to specify them. Each choice
  980:       contains  attribute, "computer", as described above. The
  981:       content of the tag will be used as the human label.
  982:       For example,  
  983:       <choice computer='234-12-7312'>Bobby McDormik</choice>.
  984: 
  985:       <choice> can take a parameter "eval", which if set to
  986:       a true value, will cause the contents of the tag to be
  987:       evaluated as it would be in an <eval> tag; see <eval> tag
  988:       below.
  989: 
  990: <choice> may optionally contain a 'nextstate' attribute, which
  991: will be the state transisitoned to if the choice is made, if
  992: the choice is not multichoice.
  993: 
  994: =back
  995: 
  996: To create the choices programmatically, either wrap the choices in 
  997: <condition> tags (prefered), or use an <exec> block inside the <choice>
  998: tag. Store the choices in $state->{CHOICES}, which is a list of list
  999: references, where each list has three strings. The first is the human
 1000: name, the second is the computer name. and the third is the option
 1001: next state. For example:
 1002: 
 1003:  <exec>
 1004:     for (my $i = 65; $i < 65 + 26; $i++) {
 1005:         push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
 1006:     }
 1007:  </exec>
 1008: 
 1009: This will allow the user to select from the letters A-Z (in ASCII), while
 1010: passing the ASCII value back into the helper variables, and the state
 1011: will in all cases transition to 'next'.
 1012: 
 1013: You can mix and match methods of creating choices, as long as you always 
 1014: "push" onto the choice list, rather then wiping it out. (You can even 
 1015: remove choices programmatically, but that would probably be bad form.)
 1016: 
 1017: B<defaultvalue support>
 1018: 
 1019: Choices supports default values both in multichoice and single choice mode.
 1020: In single choice mode, have the defaultvalue tag's function return the 
 1021: computer value of the box you want checked. If the function returns a value
 1022: that does not correspond to any of the choices, the default behavior of selecting
 1023: the first choice will be preserved.
 1024: 
 1025: For multichoice, return a string with the computer values you want checked,
 1026: delimited by triple pipes. Note this matches how the result of the <choices>
 1027: tag is stored in the {VARS} hash.
 1028: 
 1029: =cut
 1030: 
 1031: no strict;
 1032: @ISA = ("Apache::lonhelper::element");
 1033: use strict;
 1034: 
 1035: BEGIN {
 1036:     &Apache::lonhelper::register('Apache::lonhelper::choices',
 1037:                               ('choice', 'choices'));
 1038: }
 1039: 
 1040: sub new {
 1041:     my $ref = Apache::lonhelper::element->new();
 1042:     bless($ref);
 1043: }
 1044: 
 1045: # CONSTRUCTION: Construct the message element from the XML
 1046: sub start_choices {
 1047:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1048: 
 1049:     if ($target ne 'helper') {
 1050:         return '';
 1051:     }
 1052: 
 1053:     # Need to initialize the choices list, so everything can assume it exists
 1054:     $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
 1055:     $helper->declareVar($paramHash->{'variable'});
 1056:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1057:     $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
 1058:     $paramHash->{CHOICES} = [];
 1059:     return '';
 1060: }
 1061: 
 1062: sub end_choices {
 1063:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1064: 
 1065:     if ($target ne 'helper') {
 1066:         return '';
 1067:     }
 1068:     Apache::lonhelper::choices->new();
 1069:     return '';
 1070: }
 1071: 
 1072: sub start_choice {
 1073:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1074: 
 1075:     if ($target ne 'helper') {
 1076:         return '';
 1077:     }
 1078: 
 1079:     my $computer = $token->[2]{'computer'};
 1080:     my $human = &Apache::lonxml::get_all_text('/choice',
 1081:                                               $parser);
 1082:     my $nextstate = $token->[2]{'nextstate'};
 1083:     my $evalFlag = $token->[2]{'eval'};
 1084:     push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate, 
 1085:                                     $evalFlag];
 1086:     return '';
 1087: }
 1088: 
 1089: sub end_choice {
 1090:     return '';
 1091: }
 1092: 
 1093: sub render {
 1094:     # START HERE: Replace this with correct choices code.
 1095:     my $self = shift;
 1096:     my $var = $self->{'variable'};
 1097:     my $buttons = '';
 1098:     my $result = '';
 1099: 
 1100:     if ($self->{'multichoice'}) {
 1101:         $result .= <<SCRIPT;
 1102: <script>
 1103:     function checkall(value, checkName) {
 1104: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 1105:             ele = document.forms.helpform.elements[i];
 1106:             if (ele.name == checkName + '.forminput') {
 1107:                 document.forms.helpform.elements[i].checked=value;
 1108:             }
 1109:         }
 1110:     }
 1111: </script>
 1112: SCRIPT
 1113:     }
 1114: 
 1115:     # Only print "select all" and "unselect all" if there are five or
 1116:     # more choices; fewer then that and it looks silly.
 1117:     if ($self->{'multichoice'} && scalar(@{$self->{CHOICES}}) > 4) {
 1118:         $buttons = <<BUTTONS;
 1119: <br />
 1120: <input type="button" onclick="checkall(true, '$var')" value="Select All" />
 1121: <input type="button" onclick="checkall(false, '$var')" value="Unselect All" />
 1122: <br />&nbsp;
 1123: BUTTONS
 1124:     }
 1125: 
 1126:     if (defined $self->{ERROR_MSG}) {
 1127:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
 1128:     }
 1129: 
 1130:     $result .= $buttons;
 1131:     
 1132:     $result .= "<table>\n\n";
 1133: 
 1134:     my %checkedChoices;
 1135:     my $checkedChoicesFunc;
 1136: 
 1137:     if (defined($self->{DEFAULT_VALUE})) {
 1138:         $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
 1139:         die 'Error in default value code for variable ' . 
 1140:             {'variable'} . ', Perl said:' . $@ if $@;
 1141:     } else {
 1142:         $checkedChoicesFunc = sub { return ''; };
 1143:     }
 1144: 
 1145:     # Process which choices should be checked.
 1146:     if ($self->{'multichoice'}) {
 1147:         for my $selectedChoice (split(/\|\|\|/, (&$checkedChoicesFunc($helper, $self)))) {
 1148:             $checkedChoices{$selectedChoice} = 1;
 1149:         }
 1150:     } else {
 1151:         # single choice
 1152:         my $selectedChoice = &$checkedChoicesFunc($helper, $self);
 1153:         
 1154:         my $foundChoice = 0;
 1155:         
 1156:         # check that the choice is in the list of choices.
 1157:         for my $choice (@{$self->{CHOICES}}) {
 1158:             if ($choice->[1] eq $selectedChoice) {
 1159:                 $checkedChoices{$choice->[1]} = 1;
 1160:                 $foundChoice = 1;
 1161:             }
 1162:         }
 1163:         
 1164:         # If we couldn't find the choice, pick the first one 
 1165:         if (!$foundChoice) {
 1166:             $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
 1167:         }
 1168:     }
 1169: 
 1170:     my $type = "radio";
 1171:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1172:     foreach my $choice (@{$self->{CHOICES}}) {
 1173:         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
 1174:         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
 1175:             . "' value='" . 
 1176:             HTML::Entities::encode($choice->[1]) 
 1177:             . "'";
 1178:         if ($checkedChoices{$choice->[1]}) {
 1179:             $result .= " checked ";
 1180:         }
 1181:         my $choiceLabel = $choice->[0];
 1182:         if ($choice->[4]) {  # if we need to evaluate this choice
 1183:             $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
 1184:                 $choiceLabel . "}";
 1185:             $choiceLabel = eval($choiceLabel);
 1186:             $choiceLabel = &$choiceLabel($helper, $self);
 1187:         }
 1188:         $result .= "/></td><td> " . $choiceLabel . "</td></tr>\n";
 1189:     }
 1190:     $result .= "</table>\n\n\n";
 1191:     $result .= $buttons;
 1192: 
 1193:     return $result;
 1194: }
 1195: 
 1196: # If a NEXTSTATE was given or a nextstate for this choice was
 1197: # given, switch to it
 1198: sub postprocess {
 1199:     my $self = shift;
 1200:     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1201: 
 1202:     if (!defined($chosenValue) && !$self->{'allowempty'}) {
 1203:         $self->{ERROR_MSG} = "You must choose one or more choices to" .
 1204:             " continue.";
 1205:         return 0;
 1206:     }
 1207: 
 1208:     if ($self->{'multichoice'}) {
 1209:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1210:                                         $self->{'variable'});
 1211:     }
 1212: 
 1213:     if (defined($self->{NEXTSTATE})) {
 1214:         $helper->changeState($self->{NEXTSTATE});
 1215:     }
 1216:     
 1217:     foreach my $choice (@{$self->{CHOICES}}) {
 1218:         if ($choice->[1] eq $chosenValue) {
 1219:             if (defined($choice->[2])) {
 1220:                 $helper->changeState($choice->[2]);
 1221:             }
 1222:         }
 1223:     }
 1224:     return 1;
 1225: }
 1226: 1;
 1227: 
 1228: package Apache::lonhelper::date;
 1229: 
 1230: =pod
 1231: 
 1232: =head2 Element: date
 1233: 
 1234: Date elements allow the selection of a date with a drop down list.
 1235: 
 1236: Date elements can take two attributes:
 1237: 
 1238: =over 4
 1239: 
 1240: =item * B<variable>: The name of the variable to store the chosen
 1241:         date in. Required.
 1242: 
 1243: =item * B<hoursminutes>: If a true value, the date will show hours
 1244:         and minutes, as well as month/day/year. If false or missing,
 1245:         the date will only show the month, day, and year.
 1246: 
 1247: =back
 1248: 
 1249: Date elements contain only an option <nextstate> tag to determine
 1250: the next state.
 1251: 
 1252: Example:
 1253: 
 1254:  <date variable="DUE_DATE" hoursminutes="1">
 1255:    <nextstate>choose_why</nextstate>
 1256:    </date>
 1257: 
 1258: =cut
 1259: 
 1260: no strict;
 1261: @ISA = ("Apache::lonhelper::element");
 1262: use strict;
 1263: 
 1264: use Time::localtime;
 1265: 
 1266: BEGIN {
 1267:     &Apache::lonhelper::register('Apache::lonhelper::date',
 1268:                               ('date'));
 1269: }
 1270: 
 1271: # Don't need to override the "new" from element
 1272: sub new {
 1273:     my $ref = Apache::lonhelper::element->new();
 1274:     bless($ref);
 1275: }
 1276: 
 1277: my @months = ("January", "February", "March", "April", "May", "June", "July",
 1278: 	      "August", "September", "October", "November", "December");
 1279: 
 1280: # CONSTRUCTION: Construct the message element from the XML
 1281: sub start_date {
 1282:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1283: 
 1284:     if ($target ne 'helper') {
 1285:         return '';
 1286:     }
 1287: 
 1288:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1289:     $helper->declareVar($paramHash->{'variable'});
 1290:     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
 1291: }
 1292: 
 1293: sub end_date {
 1294:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1295: 
 1296:     if ($target ne 'helper') {
 1297:         return '';
 1298:     }
 1299:     Apache::lonhelper::date->new();
 1300:     return '';
 1301: }
 1302: 
 1303: sub render {
 1304:     my $self = shift;
 1305:     my $result = "";
 1306:     my $var = $self->{'variable'};
 1307: 
 1308:     my $date;
 1309:     
 1310:     # Default date: The current hour.
 1311:     $date = localtime();
 1312:     $date->min(0);
 1313: 
 1314:     if (defined $self->{ERROR_MSG}) {
 1315:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1316:     }
 1317: 
 1318:     # Month
 1319:     my $i;
 1320:     $result .= "<select name='${var}month'>\n";
 1321:     for ($i = 0; $i < 12; $i++) {
 1322:         if ($i == $date->mon) {
 1323:             $result .= "<option value='$i' selected>";
 1324:         } else {
 1325:             $result .= "<option value='$i'>";
 1326:         }
 1327:         $result .= $months[$i] . "</option>\n";
 1328:     }
 1329:     $result .= "</select>\n";
 1330: 
 1331:     # Day
 1332:     $result .= "<select name='${var}day'>\n";
 1333:     for ($i = 1; $i < 32; $i++) {
 1334:         if ($i == $date->mday) {
 1335:             $result .= '<option selected>';
 1336:         } else {
 1337:             $result .= '<option>';
 1338:         }
 1339:         $result .= "$i</option>\n";
 1340:     }
 1341:     $result .= "</select>,\n";
 1342: 
 1343:     # Year
 1344:     $result .= "<select name='${var}year'>\n";
 1345:     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
 1346:         if ($date->year + 1900 == $i) {
 1347:             $result .= "<option selected>";
 1348:         } else {
 1349:             $result .= "<option>";
 1350:         }
 1351:         $result .= "$i</option>\n";
 1352:     }
 1353:     $result .= "</select>,\n";
 1354: 
 1355:     # Display Hours and Minutes if they are called for
 1356:     if ($self->{'hoursminutes'}) {
 1357:         # Build hour
 1358:         $result .= "<select name='${var}hour'>\n";
 1359:         $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
 1360:             " value='0'>midnight</option>\n";
 1361:         for ($i = 1; $i < 12; $i++) {
 1362:             if ($date->hour == $i) {
 1363:                 $result .= "<option selected value='$i'>$i a.m.</option>\n";
 1364:             } else {
 1365:                 $result .= "<option value='$i'>$i a.m</option>\n";
 1366:             }
 1367:         }
 1368:         $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
 1369:             " value='12'>noon</option>\n";
 1370:         for ($i = 13; $i < 24; $i++) {
 1371:             my $printedHour = $i - 12;
 1372:             if ($date->hour == $i) {
 1373:                 $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
 1374:             } else {
 1375:                 $result .= "<option value='$i'>$printedHour p.m.</option>\n";
 1376:             }
 1377:         }
 1378: 
 1379:         $result .= "</select> :\n";
 1380: 
 1381:         $result .= "<select name='${var}minute'>\n";
 1382:         for ($i = 0; $i < 60; $i++) {
 1383:             my $printedMinute = $i;
 1384:             if ($i < 10) {
 1385:                 $printedMinute = "0" . $printedMinute;
 1386:             }
 1387:             if ($date->min == $i) {
 1388:                 $result .= "<option selected>";
 1389:             } else {
 1390:                 $result .= "<option>";
 1391:             }
 1392:             $result .= "$printedMinute</option>\n";
 1393:         }
 1394:         $result .= "</select>\n";
 1395:     }
 1396: 
 1397:     return $result;
 1398: 
 1399: }
 1400: # If a NEXTSTATE was given, switch to it
 1401: sub postprocess {
 1402:     my $self = shift;
 1403:     my $var = $self->{'variable'};
 1404:     my $month = $ENV{'form.' . $var . 'month'}; 
 1405:     my $day = $ENV{'form.' . $var . 'day'}; 
 1406:     my $year = $ENV{'form.' . $var . 'year'}; 
 1407:     my $min = 0; 
 1408:     my $hour = 0;
 1409:     if ($self->{'hoursminutes'}) {
 1410:         $min = $ENV{'form.' . $var . 'minute'};
 1411:         $hour = $ENV{'form.' . $var . 'hour'};
 1412:     }
 1413: 
 1414:     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
 1415:     # Check to make sure that the date was not automatically co-erced into a 
 1416:     # valid date, as we want to flag that as an error
 1417:     # This happens for "Feb. 31", for instance, which is coerced to March 2 or
 1418:     # 3, depending on if it's a leapyear
 1419:     my $checkDate = localtime($chosenDate);
 1420: 
 1421:     if ($checkDate->mon != $month || $checkDate->mday != $day ||
 1422:         $checkDate->year + 1900 != $year) {
 1423:         $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
 1424:             . "date because it doesn't exist. Please enter a valid date.";
 1425:         return 0;
 1426:     }
 1427: 
 1428:     $helper->{VARS}->{$var} = $chosenDate;
 1429: 
 1430:     if (defined($self->{NEXTSTATE})) {
 1431:         $helper->changeState($self->{NEXTSTATE});
 1432:     }
 1433: 
 1434:     return 1;
 1435: }
 1436: 1;
 1437: 
 1438: package Apache::lonhelper::resource;
 1439: 
 1440: =pod
 1441: 
 1442: =head2 Element: resource
 1443: 
 1444: <resource> elements allow the user to select one or multiple resources
 1445: from the current course. You can filter out which resources they can view,
 1446: and filter out which resources they can select. The course will always
 1447: be displayed fully expanded, because of the difficulty of maintaining
 1448: selections across folder openings and closings. If this is fixed, then
 1449: the user can manipulate the folders.
 1450: 
 1451: <resource> takes the standard variable attribute to control what helper
 1452: variable stores the results. It also takes a "multichoice" attribute,
 1453: which controls whether the user can select more then one resource. The 
 1454: "toponly" attribute controls whether the resource display shows just the
 1455: resources in that sequence, or recurses into all sub-sequences, defaulting
 1456: to false.
 1457: 
 1458: B<SUB-TAGS>
 1459: 
 1460: =over 4
 1461: 
 1462: =item * <filterfunc>: If you want to filter what resources are displayed
 1463:   to the user, use a filter func. The <filterfunc> tag should contain
 1464:   Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
 1465:   a function that returns true if the resource should be displayed, 
 1466:   and false if it should be skipped. $res is a resource object. 
 1467:   (See Apache::lonnavmaps documentation for information about the 
 1468:   resource object.)
 1469: 
 1470: =item * <choicefunc>: Same as <filterfunc>, except that controls whether
 1471:   the given resource can be chosen. (It is almost always a good idea to
 1472:   show the user the folders, for instance, but you do not always want to 
 1473:   let the user select them.)
 1474: 
 1475: =item * <nextstate>: Standard nextstate behavior.
 1476: 
 1477: =item * <valuefunc>: This function controls what is returned by the resource
 1478:   when the user selects it. Like filterfunc and choicefunc, it should be
 1479:   a function fragment that when wrapped by "sub { my $res = shift; " and
 1480:   "}" returns a string representing what you want to have as the value. By
 1481:   default, the value will be the resource ID of the object ($res->{ID}).
 1482: 
 1483: =item * <mapurl>: If the URL of a map is given here, only that map
 1484:   will be displayed, instead of the whole course.
 1485: 
 1486: =back
 1487: 
 1488: =cut
 1489: 
 1490: no strict;
 1491: @ISA = ("Apache::lonhelper::element");
 1492: use strict;
 1493: 
 1494: BEGIN {
 1495:     &Apache::lonhelper::register('Apache::lonhelper::resource',
 1496:                               ('resource', 'filterfunc', 
 1497:                                'choicefunc', 'valuefunc',
 1498:                                'mapurl'));
 1499: }
 1500: 
 1501: sub new {
 1502:     my $ref = Apache::lonhelper::element->new();
 1503:     bless($ref);
 1504: }
 1505: 
 1506: # CONSTRUCTION: Construct the message element from the XML
 1507: sub start_resource {
 1508:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1509: 
 1510:     if ($target ne 'helper') {
 1511:         return '';
 1512:     }
 1513: 
 1514:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1515:     $helper->declareVar($paramHash->{'variable'});
 1516:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1517:     $paramHash->{'toponly'} = $token->[2]{'toponly'};
 1518:     return '';
 1519: }
 1520: 
 1521: sub end_resource {
 1522:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1523: 
 1524:     if ($target ne 'helper') {
 1525:         return '';
 1526:     }
 1527:     if (!defined($paramHash->{FILTER_FUNC})) {
 1528:         $paramHash->{FILTER_FUNC} = sub {return 1;};
 1529:     }
 1530:     if (!defined($paramHash->{CHOICE_FUNC})) {
 1531:         $paramHash->{CHOICE_FUNC} = sub {return 1;};
 1532:     }
 1533:     if (!defined($paramHash->{VALUE_FUNC})) {
 1534:         $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
 1535:     }
 1536:     Apache::lonhelper::resource->new();
 1537:     return '';
 1538: }
 1539: 
 1540: sub start_filterfunc {
 1541:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1542: 
 1543:     if ($target ne 'helper') {
 1544:         return '';
 1545:     }
 1546: 
 1547:     my $contents = Apache::lonxml::get_all_text('/filterfunc',
 1548:                                                 $parser);
 1549:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1550:     $paramHash->{FILTER_FUNC} = eval $contents;
 1551: }
 1552: 
 1553: sub end_filterfunc { return ''; }
 1554: 
 1555: sub start_choicefunc {
 1556:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1557: 
 1558:     if ($target ne 'helper') {
 1559:         return '';
 1560:     }
 1561: 
 1562:     my $contents = Apache::lonxml::get_all_text('/choicefunc',
 1563:                                                 $parser);
 1564:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1565:     $paramHash->{CHOICE_FUNC} = eval $contents;
 1566: }
 1567: 
 1568: sub end_choicefunc { return ''; }
 1569: 
 1570: sub start_valuefunc {
 1571:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1572: 
 1573:     if ($target ne 'helper') {
 1574:         return '';
 1575:     }
 1576: 
 1577:     my $contents = Apache::lonxml::get_all_text('/valuefunc',
 1578:                                                 $parser);
 1579:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1580:     $paramHash->{VALUE_FUNC} = eval $contents;
 1581: }
 1582: 
 1583: sub end_valuefunc { return ''; }
 1584: 
 1585: sub start_mapurl {
 1586:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1587: 
 1588:     if ($target ne 'helper') {
 1589:         return '';
 1590:     }
 1591: 
 1592:     my $contents = Apache::lonxml::get_all_text('/mapurl',
 1593:                                                 $parser);
 1594:     $paramHash->{MAP_URL} = $contents;
 1595: }
 1596: 
 1597: sub end_mapurl { return ''; }
 1598: 
 1599: # A note, in case I don't get to this before I leave.
 1600: # If someone complains about the "Back" button returning them
 1601: # to the previous folder state, instead of returning them to
 1602: # the previous helper state, the *correct* answer is for the helper
 1603: # to keep track of how many times the user has manipulated the folders,
 1604: # and feed that to the history.go() call in the helper rendering routines.
 1605: # If done correctly, the helper itself can keep track of how many times
 1606: # it renders the same states, so it doesn't go in just this state, and
 1607: # you can lean on the browser back button to make sure it all chains
 1608: # correctly.
 1609: # Right now, though, I'm just forcing all folders open.
 1610: 
 1611: sub render {
 1612:     my $self = shift;
 1613:     my $result = "";
 1614:     my $var = $self->{'variable'};
 1615:     my $curVal = $helper->{VARS}->{$var};
 1616: 
 1617:     my $buttons = '';
 1618: 
 1619:     if ($self->{'multichoice'}) {
 1620:         $result = <<SCRIPT;
 1621: <script>
 1622:     function checkall(value, checkName) {
 1623: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 1624:             ele = document.forms.helpform.elements[i];
 1625:             if (ele.name == checkName + '.forminput') {
 1626:                 document.forms.helpform.elements[i].checked=value;
 1627:             }
 1628:         }
 1629:     }
 1630: </script>
 1631: SCRIPT
 1632:         $buttons = <<BUTTONS;
 1633: <br /> &nbsp;
 1634: <input type="button" onclick="checkall(true, '$var')" value="Select All Resources" />
 1635: <input type="button" onclick="checkall(false, '$var')" value="Unselect All Resources" />
 1636: <br /> &nbsp;
 1637: BUTTONS
 1638:     }
 1639: 
 1640:     if (defined $self->{ERROR_MSG}) {
 1641:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1642:     }
 1643: 
 1644:     $result .= $buttons;
 1645: 
 1646:     my $filterFunc = $self->{FILTER_FUNC};
 1647:     my $choiceFunc = $self->{CHOICE_FUNC};
 1648:     my $valueFunc = $self->{VALUE_FUNC};
 1649:     my $mapUrl = $self->{MAP_URL};
 1650:     my $multichoice = $self->{'multichoice'};
 1651: 
 1652:     # Create the composite function that renders the column on the nav map
 1653:     # have to admit any language that lets me do this can't be all bad
 1654:     #  - Jeremy (Pythonista) ;-)
 1655:     my $checked = 0;
 1656:     my $renderColFunc = sub {
 1657:         my ($resource, $part, $params) = @_;
 1658: 
 1659:         my $inputType;
 1660:         if ($multichoice) { $inputType = 'checkbox'; }
 1661:         else {$inputType = 'radio'; }
 1662: 
 1663:         if (!&$choiceFunc($resource)) {
 1664:             return '<td>&nbsp;</td>';
 1665:         } else {
 1666:             my $col = "<td><input type='$inputType' name='${var}.forminput' ";
 1667:             if (!$checked && !$multichoice) {
 1668:                 $col .= "checked ";
 1669:                 $checked = 1;
 1670:             }
 1671:             $col .= "value='" . 
 1672:                 HTML::Entities::encode(&$valueFunc($resource)) 
 1673:                 . "' /></td>";
 1674:             return $col;
 1675:         }
 1676:     };
 1677: 
 1678:     $ENV{'form.condition'} = !$self->{'toponly'};
 1679:     $result .= 
 1680:         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
 1681:                                                   Apache::lonnavmaps::resource()],
 1682:                                        'showParts' => 0,
 1683:                                        'filterFunc' => $filterFunc,
 1684:                                        'resource_no_folder_link' => 1,
 1685:                                        'iterator_map' => $mapUrl }
 1686:                                        );
 1687: 
 1688:     $result .= $buttons;
 1689:                                                 
 1690:     return $result;
 1691: }
 1692:     
 1693: sub postprocess {
 1694:     my $self = shift;
 1695: 
 1696:     if ($self->{'multichoice'}) {
 1697:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1698:                                         $self->{'variable'});
 1699:     }
 1700: 
 1701:     if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) {
 1702:         $self->{ERROR_MSG} = 'You must choose at least one resource to continue.';
 1703:         return 0;
 1704:     }
 1705: 
 1706:     if (defined($self->{NEXTSTATE})) {
 1707:         $helper->changeState($self->{NEXTSTATE});
 1708:     }
 1709: 
 1710:     return 1;
 1711: }
 1712: 
 1713: 1;
 1714: 
 1715: package Apache::lonhelper::student;
 1716: 
 1717: =pod
 1718: 
 1719: =head2 Element: student
 1720: 
 1721: Student elements display a choice of students enrolled in the current
 1722: course. Currently it is primitive; this is expected to evolve later.
 1723: 
 1724: Student elements take two attributes: "variable", which means what
 1725: it usually does, and "multichoice", which if true allows the user
 1726: to select multiple students.
 1727: 
 1728: =cut
 1729: 
 1730: no strict;
 1731: @ISA = ("Apache::lonhelper::element");
 1732: use strict;
 1733: 
 1734: 
 1735: 
 1736: BEGIN {
 1737:     &Apache::lonhelper::register('Apache::lonhelper::student',
 1738:                               ('student'));
 1739: }
 1740: 
 1741: sub new {
 1742:     my $ref = Apache::lonhelper::element->new();
 1743:     bless($ref);
 1744: }
 1745: 
 1746: sub start_student {
 1747:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1748: 
 1749:     if ($target ne 'helper') {
 1750:         return '';
 1751:     }
 1752: 
 1753:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1754:     $helper->declareVar($paramHash->{'variable'});
 1755:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1756:     if (defined($token->[2]{'nextstate'})) {
 1757:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
 1758:     }
 1759:     
 1760: }    
 1761: 
 1762: sub end_student {
 1763:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1764: 
 1765:     if ($target ne 'helper') {
 1766:         return '';
 1767:     }
 1768:     Apache::lonhelper::student->new();
 1769: }
 1770: 
 1771: sub render {
 1772:     my $self = shift;
 1773:     my $result = '';
 1774:     my $buttons = '';
 1775:     my $var = $self->{'variable'};
 1776: 
 1777:     if ($self->{'multichoice'}) {
 1778:         $result = <<SCRIPT;
 1779: <script>
 1780:     function checkall(value, checkName) {
 1781: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 1782:             ele = document.forms.helpform.elements[i];
 1783:             if (ele.name == checkName + '.forminput') {
 1784:                 document.forms.helpform.elements[i].checked=value;
 1785:             }
 1786:         }
 1787:     }
 1788: </script>
 1789: SCRIPT
 1790:         $buttons = <<BUTTONS;
 1791: <br />
 1792: <input type="button" onclick="checkall(true, '$var')" value="Select All Students" />
 1793: <input type="button" onclick="checkall(false, '$var')" value="Unselect All Students" />
 1794: <br />
 1795: BUTTONS
 1796:     }
 1797: 
 1798:     if (defined $self->{ERROR_MSG}) {
 1799:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1800:     }
 1801: 
 1802:     # Load up the students
 1803:     my $choices = &Apache::loncoursedata::get_classlist();
 1804:     my @keys = keys %{$choices};
 1805: 
 1806:     # Constants
 1807:     my $section = Apache::loncoursedata::CL_SECTION();
 1808:     my $fullname = Apache::loncoursedata::CL_FULLNAME();
 1809: 
 1810:     # Sort by: Section, name
 1811:     @keys = sort {
 1812:         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
 1813:             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
 1814:         }
 1815:         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
 1816:     } @keys;
 1817: 
 1818:     my $type = 'radio';
 1819:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1820:     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
 1821:     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
 1822:         "<td align='center'><b>Section</b></td></tr>";
 1823: 
 1824:     my $checked = 0;
 1825:     foreach (@keys) {
 1826:         $result .= "<tr><td><input type='$type' name='" .
 1827:             $self->{'variable'} . '.forminput' . "'";
 1828:             
 1829:         if (!$self->{'multichoice'} && !$checked) {
 1830:             $result .= " checked ";
 1831:             $checked = 1;
 1832:         }
 1833:         $result .=
 1834:             " value='" . HTML::Entities::encode($_ . ':' . $choices->{$_}->[$section])
 1835:             . "' /></td><td>"
 1836:             . HTML::Entities::encode($choices->{$_}->[$fullname])
 1837:             . "</td><td align='center'>" 
 1838:             . HTML::Entities::encode($choices->{$_}->[$section])
 1839:             . "</td></tr>\n";
 1840:     }
 1841: 
 1842:     $result .= "</table>\n\n";
 1843:     $result .= $buttons;    
 1844:     
 1845:     return $result;
 1846: }
 1847: 
 1848: sub postprocess {
 1849:     my $self = shift;
 1850: 
 1851:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1852:     if (!$result) {
 1853:         $self->{ERROR_MSG} = 'You must choose at least one student '.
 1854:             'to continue.';
 1855:         return 0;
 1856:     }
 1857: 
 1858:     if ($self->{'multichoice'}) {
 1859:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1860:                                         $self->{'variable'});
 1861:     }
 1862:     if (defined($self->{NEXTSTATE})) {
 1863:         $helper->changeState($self->{NEXTSTATE});
 1864:     }
 1865: 
 1866:     return 1;
 1867: }
 1868: 
 1869: 1;
 1870: 
 1871: package Apache::lonhelper::files;
 1872: 
 1873: =pod
 1874: 
 1875: =head2 Element: files
 1876: 
 1877: files allows the users to choose files from a given directory on the
 1878: server. It is always multichoice and stores the result as a triple-pipe
 1879: delimited entry in the helper variables. 
 1880: 
 1881: Since it is extremely unlikely that you can actually code a constant
 1882: representing the directory you wish to allow the user to search, <files>
 1883: takes a subroutine that returns the name of the directory you wish to
 1884: have the user browse.
 1885: 
 1886: files accepts the attribute "variable" to control where the files chosen
 1887: are put. It accepts the attribute "multichoice" as the other attribute,
 1888: defaulting to false, which if true will allow the user to select more
 1889: then one choice. 
 1890: 
 1891: <files> accepts three subtags. One is the "nextstate" sub-tag that works
 1892: as it does with the other tags. Another is a <filechoice> sub tag that
 1893: is Perl code that, when surrounded by "sub {" and "}" will return a
 1894: string representing what directory on the server to allow the user to 
 1895: choose files from. Finally, the <filefilter> subtag should contain Perl
 1896: code that when surrounded by "sub { my $filename = shift; " and "}",
 1897: returns a true value if the user can pick that file, or false otherwise.
 1898: The filename passed to the function will be just the name of the file, 
 1899: with no path info.
 1900: 
 1901: =cut
 1902: 
 1903: no strict;
 1904: @ISA = ("Apache::lonhelper::element");
 1905: use strict;
 1906: 
 1907: BEGIN {
 1908:     &Apache::lonhelper::register('Apache::lonhelper::files',
 1909:                                  ('files', 'filechoice', 'filefilter'));
 1910: }
 1911: 
 1912: sub new {
 1913:     my $ref = Apache::lonhelper::element->new();
 1914:     bless($ref);
 1915: }
 1916: 
 1917: sub start_files {
 1918:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1919: 
 1920:     if ($target ne 'helper') {
 1921:         return '';
 1922:     }
 1923:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1924:     $helper->declareVar($paramHash->{'variable'});
 1925:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1926: }    
 1927: 
 1928: sub end_files {
 1929:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1930: 
 1931:     if ($target ne 'helper') {
 1932:         return '';
 1933:     }
 1934:     if (!defined($paramHash->{FILTER_FUNC})) {
 1935:         $paramHash->{FILTER_FUNC} = sub { return 1; };
 1936:     }
 1937:     Apache::lonhelper::files->new();
 1938: }    
 1939: 
 1940: sub start_filechoice {
 1941:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1942: 
 1943:     if ($target ne 'helper') {
 1944:         return '';
 1945:     }
 1946:     $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
 1947:                                                               $parser);
 1948: }
 1949: 
 1950: sub end_filechoice { return ''; }
 1951: 
 1952: sub start_filefilter {
 1953:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1954: 
 1955:     if ($target ne 'helper') {
 1956:         return '';
 1957:     }
 1958: 
 1959:     my $contents = Apache::lonxml::get_all_text('/filefilter',
 1960:                                                 $parser);
 1961:     $contents = 'sub { my $filename = shift; ' . $contents . '}';
 1962:     $paramHash->{FILTER_FUNC} = eval $contents;
 1963: }
 1964: 
 1965: sub end_filefilter { return ''; }
 1966: 
 1967: sub render {
 1968:     my $self = shift;
 1969:     my $result = '';
 1970:     my $var = $self->{'variable'};
 1971:     
 1972:     my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
 1973:     die 'Error in resource filter code for variable ' . 
 1974:         {'variable'} . ', Perl said:' . $@ if $@;
 1975: 
 1976:     my $subdir = &$subdirFunc();
 1977: 
 1978:     my $filterFunc = $self->{FILTER_FUNC};
 1979:     my $buttons = '';
 1980:     my $type = 'radio';
 1981:     if ($self->{'multichoice'}) {
 1982:         $type = 'checkbox';
 1983:     }
 1984: 
 1985:     if ($self->{'multichoice'}) {
 1986:         $result = <<SCRIPT;
 1987: <script>
 1988:     function checkall(value, checkName) {
 1989: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 1990:             ele = document.forms.helpform.elements[i];
 1991:             if (ele.name == checkName + '.forminput') {
 1992:                 document.forms.helpform.elements[i].checked=value;
 1993:             }
 1994:         }
 1995:     }
 1996: 
 1997:     function checkallclass(value, className) {
 1998:         for (i=0; i<document.forms.helpform.elements.length; i++) {
 1999:             ele = document.forms.helpform.elements[i];
 2000:             if (ele.type == "$type" && ele.onclick) {
 2001:                 document.forms.helpform.elements[i].checked=value;
 2002:             }
 2003:         }
 2004:     }
 2005: </script>
 2006: SCRIPT
 2007:         $buttons = <<BUTTONS;
 2008: <br /> &nbsp;
 2009: <input type="button" onclick="checkall(true, '$var')" value="Select All Files" />
 2010: <input type="button" onclick="checkall(false, '$var')" value="Unselect All Files" />
 2011: BUTTONS
 2012: 
 2013:         if ($helper->{VARS}->{'construction'}) {
 2014:             $buttons .= <<BUTTONS;
 2015: <input type="button" onclick="checkallclass(true, 'Published')" value="Select All Published" />
 2016: <input type="button" onclick="checkallclass(false, 'Published')" value="Unselect All Published" />
 2017: <br /> &nbsp;
 2018: BUTTONS
 2019:        }
 2020:     }
 2021: 
 2022:     # Get the list of files in this directory.
 2023:     my @fileList;
 2024: 
 2025:     # If the subdirectory is in local CSTR space
 2026:     if ($subdir =~ m|/home/([^/]+)/public_html|) {
 2027:         my $user = $1;
 2028:         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
 2029:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 2030:     } else {
 2031:         # local library server resource space
 2032:         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
 2033:     }
 2034: 
 2035:     $result .= $buttons;
 2036: 
 2037:     if (defined $self->{ERROR_MSG}) {
 2038:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 2039:     }
 2040: 
 2041:     $result .= '<table border="0" cellpadding="2" cellspacing="0">';
 2042: 
 2043:     # Keeps track if there are no choices, prints appropriate error
 2044:     # if there are none. 
 2045:     my $choices = 0;
 2046:     # Print each legitimate file choice.
 2047:     for my $file (@fileList) {
 2048:         $file = (split(/&/, $file))[0];
 2049:         if ($file eq '.' || $file eq '..') {
 2050:             next;
 2051:         }
 2052:         my $fileName = $subdir .'/'. $file;
 2053:         if (&$filterFunc($file)) {
 2054: 	    my $status;
 2055: 	    my $color;
 2056: 	    if ($helper->{VARS}->{'construction'}) {
 2057: 		($status, $color) = @{fileState($subdir, $file)};
 2058: 	    } else {
 2059: 		$status = '';
 2060: 		$color = '';
 2061: 	    }
 2062: 
 2063:             # Netscape 4 is stupid and there's nowhere to put the
 2064:             # information on the input tag that the file is Published,
 2065:             # Unpublished, etc. In *real* browsers we can just say
 2066:             # "class='Published'" and check the className attribute of
 2067:             # the input tag, but Netscape 4 is too stupid to understand
 2068:             # that attribute, and un-comprehended attributes are not
 2069:             # reflected into the object model. So instead, what I do 
 2070:             # is either have or don't have an "onclick" handler that 
 2071:             # does nothing, give Published files the onclick handler, and
 2072:             # have the checker scripts check for that. Stupid and clumsy,
 2073:             # and only gives us binary "yes/no" information (at least I
 2074:             # couldn't figure out how to reach into the event handler's
 2075:             # actual code to retreive a value), but it works well enough
 2076:             # here.
 2077:         
 2078:             my $onclick = '';
 2079:             if ($status eq 'Published' && $helper->{VARS}->{'construction'}) {
 2080:                 $onclick = 'onclick="a=1" ';
 2081:             }
 2082:             $result .= '<tr><td align="right"' . " bgcolor='$color'>" .
 2083:                 "<input $onclick type='$type' name='" . $var
 2084:             . ".forminput' value='" . HTML::Entities::encode($fileName) .
 2085:                 "'";
 2086:             if (!$self->{'multichoice'} && $choices == 0) {
 2087:                 $result .= ' checked';
 2088:             }
 2089:             $result .= "/></td><td bgcolor='$color'>" . $file .
 2090:                  "</td><td bgcolor='$color'>$status</td></tr>\n";
 2091:             $choices++;
 2092:         }
 2093:     }
 2094: 
 2095:     $result .= "</table>\n";
 2096: 
 2097:     if (!$choices) {
 2098:         $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
 2099:     }
 2100: 
 2101:     $result .= $buttons;
 2102: 
 2103:     return $result;
 2104: }
 2105: 
 2106: # Determine the state of the file: Published, unpublished, modified.
 2107: # Return the color it should be in and a label as a two-element array
 2108: # reference.
 2109: # Logic lifted from lonpubdir.pm, even though I don't know that it's still
 2110: # the most right thing to do.
 2111: 
 2112: sub fileState {
 2113:     my $constructionSpaceDir = shift;
 2114:     my $file = shift;
 2115:     
 2116:     my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
 2117:     my $subdirpart = $constructionSpaceDir;
 2118:     $subdirpart =~ s/^\/home\/$ENV{'user.name'}\/public_html//;
 2119:     my $resdir = $docroot . '/res/' . $ENV{'user.domain'} . '/' . $ENV{'user.name'} .
 2120:         $subdirpart;
 2121: 
 2122:     my @constructionSpaceFileStat = stat($constructionSpaceDir . '/' . $file);
 2123:     my @resourceSpaceFileStat = stat($resdir . '/' . $file);
 2124:     if (!@resourceSpaceFileStat) {
 2125:         return ['Unpublished', '#FFCCCC'];
 2126:     }
 2127: 
 2128:     my $constructionSpaceFileModified = $constructionSpaceFileStat[9];
 2129:     my $resourceSpaceFileModified = $resourceSpaceFileStat[9];
 2130:     
 2131:     if ($constructionSpaceFileModified > $resourceSpaceFileModified) {
 2132:         return ['Modified', '#FFFFCC'];
 2133:     }
 2134:     return ['Published', '#CCFFCC'];
 2135: }
 2136: 
 2137: sub postprocess {
 2138:     my $self = shift;
 2139:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 2140:     if (!$result) {
 2141:         $self->{ERROR_MSG} = 'You must choose at least one file '.
 2142:             'to continue.';
 2143:         return 0;
 2144:     }
 2145: 
 2146:     if ($self->{'multichoice'}) {
 2147:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 2148:                                         $self->{'variable'});
 2149:     }
 2150:     if (defined($self->{NEXTSTATE})) {
 2151:         $helper->changeState($self->{NEXTSTATE});
 2152:     }
 2153: 
 2154:     return 1;
 2155: }
 2156: 
 2157: 1;
 2158: 
 2159: package Apache::lonhelper::section;
 2160: 
 2161: =pod
 2162: 
 2163: =head2 Element: section
 2164: 
 2165: <section> allows the user to choose one or more sections from the current
 2166: course.
 2167: 
 2168: It takes the standard attributes "variable", "multichoice", and
 2169: "nextstate", meaning what they do for most other elements.
 2170: 
 2171: =cut
 2172: 
 2173: no strict;
 2174: @ISA = ("Apache::lonhelper::choices");
 2175: use strict;
 2176: 
 2177: BEGIN {
 2178:     &Apache::lonhelper::register('Apache::lonhelper::section',
 2179:                                  ('section'));
 2180: }
 2181: 
 2182: sub new {
 2183:     my $ref = Apache::lonhelper::choices->new();
 2184:     bless($ref);
 2185: }
 2186: 
 2187: sub start_section {
 2188:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2189: 
 2190:     if ($target ne 'helper') {
 2191:         return '';
 2192:     }
 2193: 
 2194:     $paramHash->{CHOICES} = [];
 2195: 
 2196:     $paramHash->{'variable'} = $token->[2]{'variable'};
 2197:     $helper->declareVar($paramHash->{'variable'});
 2198:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 2199:     if (defined($token->[2]{'nextstate'})) {
 2200:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
 2201:     }
 2202: 
 2203:     # Populate the CHOICES element
 2204:     my %choices;
 2205: 
 2206:     my $section = Apache::loncoursedata::CL_SECTION();
 2207:     my $classlist = Apache::loncoursedata::get_classlist();
 2208:     foreach (keys %$classlist) {
 2209:         my $sectionName = $classlist->{$_}->[$section];
 2210:         if (!$sectionName) {
 2211:             $choices{"No section assigned"} = "";
 2212:         } else {
 2213:             $choices{$sectionName} = $sectionName;
 2214:         }
 2215:     } 
 2216:    
 2217:     for my $sectionName (sort(keys(%choices))) {
 2218:         
 2219:         push @{$paramHash->{CHOICES}}, [$sectionName, $sectionName];
 2220:     }
 2221: }    
 2222: 
 2223: sub end_section {
 2224:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2225: 
 2226:     if ($target ne 'helper') {
 2227:         return '';
 2228:     }
 2229:     Apache::lonhelper::section->new();
 2230: }    
 2231: 1;
 2232: 
 2233: package Apache::lonhelper::general;
 2234: 
 2235: =pod
 2236: 
 2237: =head2 General-purpose tag: <exec>
 2238: 
 2239: The contents of the exec tag are executed as Perl code, not inside a 
 2240: safe space, so the full range of $ENV and such is available. The code
 2241: will be executed as a subroutine wrapped with the following code:
 2242: 
 2243: "sub { my $helper = shift; my $state = shift;" and
 2244: 
 2245: "}"
 2246: 
 2247: The return value is ignored.
 2248: 
 2249: $helper is the helper object. Feel free to add methods to the helper
 2250: object to support whatever manipulation you may need to do (for instance,
 2251: overriding the form location if the state is the final state; see 
 2252: lonparm.helper for an example).
 2253: 
 2254: $state is the $paramHash that has currently been generated and may
 2255: be manipulated by the code in exec. Note that the $state is not yet
 2256: an actual state B<object>, it is just a hash, so do not expect to
 2257: be able to call methods on it.
 2258: 
 2259: =cut
 2260: 
 2261: BEGIN {
 2262:     &Apache::lonhelper::register('Apache::lonhelper::general',
 2263:                                  'exec', 'condition', 'clause',
 2264:                                  'eval');
 2265: }
 2266: 
 2267: sub start_exec {
 2268:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2269: 
 2270:     if ($target ne 'helper') {
 2271:         return '';
 2272:     }
 2273:     
 2274:     my $code = &Apache::lonxml::get_all_text('/exec', $parser);
 2275:     
 2276:     $code = eval ('sub { my $helper = shift; my $state = shift; ' .
 2277:         $code . "}");
 2278:     die 'Error in <exec>, Perl said: '. $@ if $@;
 2279:     &$code($helper, $paramHash);
 2280: }
 2281: 
 2282: sub end_exec { return ''; }
 2283: 
 2284: =pod
 2285: 
 2286: =head2 General-purpose tag: <condition>
 2287: 
 2288: The <condition> tag allows you to mask out parts of the helper code
 2289: depending on some programatically determined condition. The condition
 2290: tag contains a tag <clause> which contains perl code that when wrapped
 2291: with "sub { my $helper = shift; my $state = shift; " and "}", returns
 2292: a true value if the XML in the condition should be evaluated as a normal
 2293: part of the helper, or false if it should be completely discarded.
 2294: 
 2295: The <clause> tag must be the first sub-tag of the <condition> tag or
 2296: it will not work as expected.
 2297: 
 2298: =cut
 2299: 
 2300: # The condition tag just functions as a marker, it doesn't have
 2301: # to "do" anything. Technically it doesn't even have to be registered
 2302: # with the lonxml code, but I leave this here to be explicit about it.
 2303: sub start_condition { return ''; }
 2304: sub end_condition { return ''; }
 2305: 
 2306: sub start_clause {
 2307:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2308: 
 2309:     if ($target ne 'helper') {
 2310:         return '';
 2311:     }
 2312:     
 2313:     my $clause = Apache::lonxml::get_all_text('/clause', $parser);
 2314:     $clause = eval('sub { my $helper = shift; my $state = shift; '
 2315:         . $clause . '}');
 2316:     die 'Error in clause of condition, Perl said: ' . $@ if $@;
 2317:     if (!&$clause($helper, $paramHash)) {
 2318:         # Discard all text until the /condition.
 2319:         &Apache::lonxml::get_all_text('/condition', $parser);
 2320:     }
 2321: }
 2322: 
 2323: sub end_clause { return ''; }
 2324: 
 2325: =pod
 2326: 
 2327: =head2 General-purpose tag: <eval>
 2328: 
 2329: The <eval> tag will be evaluated as a subroutine call passed in the
 2330: current helper object and state hash as described in <condition> above,
 2331: but is expected to return a string to be printed directly to the
 2332: screen. This is useful for dynamically generating messages. 
 2333: 
 2334: =cut
 2335: 
 2336: # This is basically a type of message.
 2337: # Programmatically setting $paramHash->{NEXTSTATE} would work, though
 2338: # it's probably bad form.
 2339: 
 2340: sub start_eval {
 2341:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2342: 
 2343:     if ($target ne 'helper') {
 2344:         return '';
 2345:     }
 2346:     
 2347:     my $program = Apache::lonxml::get_all_text('/eval', $parser);
 2348:     $program = eval('sub { my $helper = shift; my $state = shift; '
 2349:         . $program . '}');
 2350:     die 'Error in eval code, Perl said: ' . $@ if $@;
 2351:     $paramHash->{MESSAGE_TEXT} = &$program($helper, $paramHash);
 2352: }
 2353: 
 2354: sub end_eval { 
 2355:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2356: 
 2357:     if ($target ne 'helper') {
 2358:         return '';
 2359:     }
 2360: 
 2361:     Apache::lonhelper::message->new();
 2362: }
 2363: 
 2364: 1;
 2365: 
 2366: package Apache::lonhelper::final;
 2367: 
 2368: =pod
 2369: 
 2370: =head2 Element: final
 2371: 
 2372: <final> is a special element that works with helpers that use the <finalcode>
 2373: tag. It goes through all the states and elements, executing the <finalcode>
 2374: snippets and collecting the results. Finally, it takes the user out of the
 2375: helper, going to a provided page.
 2376: 
 2377: =cut
 2378: 
 2379: no strict;
 2380: @ISA = ("Apache::lonhelper::element");
 2381: use strict;
 2382: 
 2383: BEGIN {
 2384:     &Apache::lonhelper::register('Apache::lonhelper::final',
 2385:                                  ('final', 'exitpage'));
 2386: }
 2387: 
 2388: sub new {
 2389:     my $ref = Apache::lonhelper::element->new();
 2390:     bless($ref);
 2391: }
 2392: 
 2393: sub start_final { return ''; }
 2394: 
 2395: sub end_final {
 2396:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2397: 
 2398:     if ($target ne 'helper') {
 2399:         return '';
 2400:     }
 2401: 
 2402:     Apache::lonhelper::final->new();
 2403:    
 2404:     return '';
 2405: }
 2406: 
 2407: sub start_exitpage {
 2408:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2409: 
 2410:     if ($target ne 'helper') {
 2411:         return '';
 2412:     }
 2413: 
 2414:     $paramHash->{EXIT_PAGE} = &Apache::lonxml::get_all_text('/exitpage',
 2415:                                                             $parser);
 2416: 
 2417:     return '';
 2418: }
 2419: 
 2420: sub end_exitpage { return ''; }
 2421: 
 2422: sub render {
 2423:     my $self = shift;
 2424: 
 2425:     my @results;
 2426: 
 2427:     # Collect all the results
 2428:     for my $stateName (keys %{$helper->{STATES}}) {
 2429:         my $state = $helper->{STATES}->{$stateName};
 2430:         
 2431:         for my $element (@{$state->{ELEMENTS}}) {
 2432:             if (defined($element->{FINAL_CODE})) {
 2433:                 # Compile the code.
 2434:                 my $code = 'sub { my $helper = shift; ' . $element->{FINAL_CODE} .
 2435:                     '}';
 2436:                 $code = eval($code);
 2437:                 die 'Error while executing final code for element with var ' .
 2438:                     $element->{'variable'} . ', Perl said: ' . $@ if $@;
 2439: 
 2440:                 my $result = &$code($helper);
 2441:                 if ($result) {
 2442:                     push @results, $result;
 2443:                 }
 2444:             }
 2445:         }
 2446:     }
 2447: 
 2448:     if (scalar(@results) == 0) {
 2449:         return '';
 2450:     }
 2451: 
 2452:     my $result = "<ul>\n";
 2453:     for my $re (@results) {
 2454:         $result .= '    <li>' . $re . "</li>\n";
 2455:     }
 2456:     return $result . '</ul>';
 2457: }
 2458: 
 2459: 1;
 2460: 
 2461: package Apache::lonhelper::parmwizfinal;
 2462: 
 2463: # This is the final state for the parmwizard. It is not generally useful,
 2464: # so it is not perldoc'ed. It does its own processing.
 2465: # It is represented with <parmwizfinal />, and
 2466: # should later be moved to lonparmset.pm .
 2467: 
 2468: no strict;
 2469: @ISA = ('Apache::lonhelper::element');
 2470: use strict;
 2471: 
 2472: BEGIN {
 2473:     &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal',
 2474:                                  ('parmwizfinal'));
 2475: }
 2476: 
 2477: use Time::localtime;
 2478: 
 2479: sub new {
 2480:     my $ref = Apache::lonhelper::choices->new();
 2481:     bless ($ref);
 2482: }
 2483: 
 2484: sub start_parmwizfinal { return ''; }
 2485: 
 2486: sub end_parmwizfinal {
 2487:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2488: 
 2489:     if ($target ne 'helper') {
 2490:         return '';
 2491:     }
 2492:     Apache::lonhelper::parmwizfinal->new();
 2493: }
 2494: 
 2495: # Renders a form that, when submitted, will form the input to lonparmset.pm
 2496: sub render {
 2497:     my $self = shift;
 2498:     my $vars = $helper->{VARS};
 2499: 
 2500:     # FIXME: Unify my designators with the standard ones
 2501:     my %dateTypeHash = ('open_date' => "Opening Date",
 2502:                         'due_date' => "Due Date",
 2503:                         'answer_date' => "Answer Date");
 2504:     my %parmTypeHash = ('open_date' => "0_opendate",
 2505:                         'due_date' => "0_duedate",
 2506:                         'answer_date' => "0_answerdate");
 2507:     
 2508:     my $affectedResourceId = "";
 2509:     my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};
 2510:     my $level = "";
 2511:     my $resourceString;
 2512:     my $symb;
 2513:     my $paramlevel;
 2514: 
 2515:     # Print the granularity, depending on the action
 2516:     if ($vars->{GRANULARITY} eq 'whole_course') {
 2517:         $resourceString .= '<li>for <b>all resources in the course</b></li>';
 2518:         $level = 9; # general course, see lonparmset.pm perldoc
 2519:         $affectedResourceId = "0.0";
 2520:         $symb = 'a';
 2521:         $paramlevel = 'general';
 2522:     } elsif ($vars->{GRANULARITY} eq 'map') {
 2523:         my $navmap = Apache::lonnavmaps::navmap->new(
 2524:                            $ENV{"request.course.fn"}.".db",
 2525:                            $ENV{"request.course.fn"}."_parms.db", 0, 0);
 2526:         my $res = $navmap->getById($vars->{RESOURCE_ID});
 2527:         my $title = $res->compTitle();
 2528:         $symb = $res->symb();
 2529:         $navmap->untieHashes();
 2530:         $resourceString .= "<li>for the map named <b>$title</b></li>";
 2531:         $level = 8;
 2532:         $affectedResourceId = $vars->{RESOURCE_ID};
 2533:         $paramlevel = 'map';
 2534:     } else {
 2535:         my $navmap = Apache::lonnavmaps::navmap->new(
 2536:                            $ENV{"request.course.fn"}.".db",
 2537:                            $ENV{"request.course.fn"}."_parms.db", 0, 0);
 2538:         my $res = $navmap->getById($vars->{RESOURCE_ID});
 2539:         $symb = $res->symb();
 2540:         my $title = $res->compTitle();
 2541:         $navmap->untieHashes();
 2542:         $resourceString .= "<li>for the resource named <b>$title</b></li>";
 2543:         $level = 7;
 2544:         $affectedResourceId = $vars->{RESOURCE_ID};
 2545:         $paramlevel = 'full';
 2546:     }
 2547: 
 2548:     my $result = "<form name='helpform' method='get' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
 2549:     $result .= '<p>Confirm that this information is correct, then click &quot;Finish Wizard&quot; to complete setting the parameter.<ul>';
 2550:     
 2551:     # Print the type of manipulation:
 2552:     $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}
 2553:                . "</b></li>\n";
 2554:     if ($vars->{ACTION_TYPE} eq 'due_date' || 
 2555:         $vars->{ACTION_TYPE} eq 'answer_date') {
 2556:         # for due dates, we default to "date end" type entries
 2557:         $result .= "<input type='hidden' name='recent_date_end' " .
 2558:             "value='" . $vars->{PARM_DATE} . "' />\n";
 2559:         $result .= "<input type='hidden' name='pres_value' " . 
 2560:             "value='" . $vars->{PARM_DATE} . "' />\n";
 2561:         $result .= "<input type='hidden' name='pres_type' " .
 2562:             "value='date_end' />\n";
 2563:     } elsif ($vars->{ACTION_TYPE} eq 'open_date') {
 2564:         $result .= "<input type='hidden' name='recent_date_start' ".
 2565:             "value='" . $vars->{PARM_DATE} . "' />\n";
 2566:         $result .= "<input type='hidden' name='pres_value' " .
 2567:             "value='" . $vars->{PARM_DATE} . "' />\n";
 2568:         $result .= "<input type='hidden' name='pres_type' " .
 2569:             "value='date_start' />\n";
 2570:     } 
 2571: 
 2572:     $result .= $resourceString;
 2573:     
 2574:     # Print targets
 2575:     if ($vars->{TARGETS} eq 'course') {
 2576:         $result .= '<li>for <b>all students in course</b></li>';
 2577:     } elsif ($vars->{TARGETS} eq 'section') {
 2578:         my $section = $vars->{SECTION_NAME};
 2579:         $result .= "<li>for section <b>$section</b></li>";
 2580:         $level -= 3;
 2581:         $result .= "<input type='hidden' name='csec' value='" .
 2582:             HTML::Entities::encode($section) . "' />\n";
 2583:     } else {
 2584:         # FIXME: This is probably wasteful! Store the name!
 2585:         my $classlist = Apache::loncoursedata::get_classlist();
 2586:         my $username = $vars->{USER_NAME};
 2587:         # Chop off everything after the last colon (section)
 2588:         $username = substr($username, 0, rindex($username, ':'));
 2589:         my $name = $classlist->{$username}->[6];
 2590:         $result .= "<li>for <b>$name</b></li>";
 2591:         $level -= 6;
 2592:         my ($uname, $udom) = split /:/, $vars->{USER_NAME};
 2593:         $result .= "<input type='hidden' name='uname' value='".
 2594:             HTML::Entities::encode($uname) . "' />\n";
 2595:         $result .= "<input type='hidden' name='udom' value='".
 2596:             HTML::Entities::encode($udom) . "' />\n";
 2597:     }
 2598: 
 2599:     # Print value
 2600:     $result .= "<li>to <b>" . ctime($vars->{PARM_DATE}) . "</b> (" .
 2601:         Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}) 
 2602:         . ")</li>\n";
 2603: 
 2604:     # print pres_marker
 2605:     $result .= "\n<input type='hidden' name='pres_marker'" .
 2606:         " value='$affectedResourceId&$parm_name&$level' />\n";
 2607:     
 2608:     # Make the table appear
 2609:     $result .= "\n<input type='hidden' value='true' name='prevvisit' />";
 2610:     $result .= "\n<input type='hidden' value='all' name='pschp' />";
 2611:     $result .= "\n<input type='hidden' value='$symb' name='pssymb' />";
 2612:     $result .= "\n<input type='hidden' value='$paramlevel' name='parmlev' />";
 2613: 
 2614:     $result .= "<br /><br /><center><input type='submit' value='Finish Helper' /></center></form>\n";
 2615: 
 2616:     return $result;
 2617: }
 2618:     
 2619: sub overrideForm {
 2620:     return 1;
 2621: }
 2622: 
 2623: 1;
 2624: 
 2625: __END__
 2626: 

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