File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.139: download - view: text, annotated - select for diffs
Fri May 5 10:59:51 2006 UTC (18 years, 1 month ago) by foxr
Branches: MAIN
CVS tags: HEAD
Revamped helper per Felicia's desires..still need some cleanup as follows:
- Remove dead javascript
- Restrict the (de)selections of the listboxes to the segment of the form
  they live in.
- See if we can make the student picker code independent of the helper so
  non helper student pickers can use them..alternatively switch those over
  to helper.
BUG 3809

    1: # The LearningOnline Network with CAPA
    2: # .helper XML handler to implement the LON-CAPA helper
    3: #
    4: # $Id: lonhelper.pm,v 1.139 2006/05/05 10:59:51 foxr 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: 
   29: =pod
   30: 
   31: =head1 NAME
   32: 
   33: lonhelper - implements helper framework
   34: 
   35: =head1 SYNOPSIS
   36: 
   37: lonhelper implements the helper framework for LON-CAPA, and provides
   38:     many generally useful components for that framework.
   39: 
   40: Helpers are little programs which present the user with a sequence of
   41:     simple choices, instead of one monolithic multi-dimensional
   42:     choice. They are also referred to as "wizards", "druids", and
   43:     other potentially trademarked or semantically-loaded words.
   44: 
   45: =head1 OVERVIEWX<lonhelper>
   46: 
   47: Helpers are well-established UI widgets that users
   48: feel comfortable with. It can take a complicated multidimensional problem the
   49: user has and turn it into a series of bite-sized one-dimensional questions.
   50: 
   51: For developers, helpers provide an easy way to bundle little bits of functionality
   52: for the user, without having to write the tedious state-maintenence code.
   53: 
   54: Helpers are defined as XML documents, placed in the /home/httpd/html/adm/helpers 
   55: directory and having the .helper file extension. For examples, see that directory.
   56: 
   57: All classes are in the Apache::lonhelper namespace.
   58: 
   59: =head1 lonhelper XML file formatX<lonhelper, XML file format>
   60: 
   61: A helper consists of a top-level <helper> tag which contains a series of states.
   62: Each state contains one or more state elements, which are what the user sees, like
   63: messages, resource selections, or date queries.
   64: 
   65: The helper tag is required to have one attribute, "title", which is the name
   66: of the helper itself, such as "Parameter helper". The helper tag may optionally
   67: have a "requiredpriv" attribute, specifying the priviledge a user must have
   68: to use the helper, or get denied access. See loncom/auth/rolesplain.tab for
   69: useful privs. Default is full access, which is often wrong!
   70: 
   71: =head2 State tags
   72: 
   73: State tags are required to have an attribute "name", which is the symbolic
   74: name of the state and will not be directly seen by the user. The helper is
   75: required to have one state named "START", which is the state the helper
   76: will start with. By convention, this state should clearly describe what
   77: the helper will do for the user, and may also include the first information
   78: entry the user needs to do for the helper.
   79: 
   80: State tags are also required to have an attribute "title", which is the
   81: human name of the state, and will be displayed as the header on top of 
   82: the screen for the user.
   83: 
   84: =head2 Example Helper Skeleton
   85: 
   86: An example of the tags so far:
   87: 
   88:  <helper title="Example Helper">
   89:    <state name="START" title="Demonstrating the Example Helper">
   90:      <!-- notice this is the START state the wizard requires -->
   91:      </state>
   92:    <state name="GET_NAME" title="Enter Student Name">
   93:      </state>
   94:    </helper>
   95: 
   96: Of course this does nothing. In order for the wizard to do something, it is
   97: necessary to put actual elements into the wizard. Documentation for each
   98: of these elements follows.
   99: 
  100: =head1 Creating a Helper With Code, Not XML
  101: 
  102: In some situations, such as the printing wizard (see lonprintout.pm), 
  103: writing the helper in XML would be too complicated, because of scope 
  104: issues or the fact that the code actually outweighs the XML. It is
  105: possible to create a helper via code, though it is a little odd.
  106: 
  107: Creating a helper via code is more like issuing commands to create
  108: a helper then normal code writing. For instance, elements will automatically
  109: be added to the last state created, so it's important to create the 
  110: states in the correct order.
  111: 
  112: First, create a new helper:
  113: 
  114:  use Apache::lonhelper;
  115: 
  116:  my $helper = Apache::lonhelper::new->("Helper Title");
  117: 
  118: Next you'll need to manually add states to the helper:
  119: 
  120:  Apache::lonhelper::state->new("STATE_NAME", "State's Human Title");
  121: 
  122: You don't need to save a reference to it because all elements up until
  123: the next state creation will automatically be added to this state.
  124: 
  125: Elements are created by populating the $paramHash in 
  126: Apache::lonhelper::paramhash. To prevent namespace issues, retrieve 
  127: a reference to that has with getParamHash:
  128: 
  129:  my $paramHash = Apache::lonhelper::getParamHash();
  130: 
  131: You will need to do this for each state you create.
  132: 
  133: Populate the $paramHash with the parameters for the element you wish
  134: to add next; the easiest way to find out what those entries are is
  135: to read the code. Some common ones are 'variable' to record the variable
  136: to store the results in, and NEXTSTATE to record a next state transition.
  137: 
  138: Then create your element:
  139: 
  140:  $paramHash->{MESSAGETEXT} = "This is a message.";
  141:  Apache::lonhelper::message->new();
  142: 
  143: The creation will take the $paramHash and bless it into a
  144: Apache::lonhelper::message object. To create the next element, you need
  145: to get a reference to the new, empty $paramHash:
  146: 
  147:  $paramHash = Apache::lonhelper::getParamHash();
  148: 
  149: and you can repeat creating elements that way. You can add states
  150: and elements as needed.
  151: 
  152: See lonprintout.pm, subroutine printHelper for an example of this, where
  153: we dynamically add some states to prevent security problems, for instance.
  154: 
  155: Normally the machinery in the XML format is sufficient; dynamically 
  156: adding states can easily be done by wrapping the state in a <condition>
  157: tag. This should only be used when the code dominates the XML content,
  158: the code is so complicated that it is difficult to get access to
  159: all of the information you need because of scoping issues, or would-be <exec> or 
  160: <eval> blocks using the {DATA} mechanism results in hard-to-read
  161: and -maintain code. (See course.initialization.helper for a borderline
  162: case.)
  163: 
  164: It is possible to do some of the work with an XML fragment parsed by
  165: lonxml; again, see lonprintout.pm for an example. In that case it is 
  166: imperative that you call B<Apache::lonhelper::registerHelperTags()>
  167: before parsing XML fragments and B<Apache::lonhelper::unregisterHelperTags()>
  168: when you are done. See lonprintout.pm for examples of this usage in the
  169: printHelper subroutine.
  170: 
  171: =head2 Localization
  172: 
  173: The helper framework tries to handle as much localization as
  174: possible. The text is always run through
  175: Apache::lonlocal::normalize_string, so be sure to run the keys through
  176: that function for maximum usefulness and robustness.
  177: 
  178: =cut
  179: 
  180: package Apache::lonhelper;
  181: use Apache::Constants qw(:common);
  182: use Apache::File;
  183: use Apache::lonxml;
  184: use Apache::lonlocal;
  185: use Apache::lonnet;
  186: 
  187: 
  188: # Register all the tags with the helper, so the helper can 
  189: # push and pop them
  190: 
  191: my @helperTags;
  192: 
  193: sub register {
  194:     my ($namespace, @tags) = @_;
  195: 
  196:     for my $tag (@tags) {
  197:         push @helperTags, [$namespace, $tag];
  198:     }
  199: }
  200: 
  201: BEGIN {
  202:     Apache::lonxml::register('Apache::lonhelper', 
  203:                              ('helper'));
  204:       register('Apache::lonhelper', ('state'));
  205: }
  206: 
  207: # Since all helpers are only three levels deep (helper tag, state tag, 
  208: # substate type), it's easier and more readble to explicitly track 
  209: # those three things directly, rather then futz with the tag stack 
  210: # every time.
  211: my $helper;
  212: my $state;
  213: my $substate;
  214: # To collect parameters, the contents of the subtags are collected
  215: # into this paramHash, then passed to the element object when the 
  216: # end of the element tag is located.
  217: my $paramHash; 
  218: 
  219: # Note from Jeremy 5-8-2003: It is *vital* that the real handler be called
  220: # as a subroutine from the handler, or very mysterious things might happen.
  221: # I don't know exactly why, but it seems that the scope where the Apache
  222: # server enters the perl handler is treated differently from the rest of
  223: # the handler. This also seems to manifest itself in the debugger as entering
  224: # the perl handler in seemingly random places (sometimes it starts in the
  225: # compiling phase, sometimes in the handler execution phase where it runs
  226: # the code and stepping into the "1;" the module ends with goes into the handler,
  227: # sometimes starting directly with the handler); I think the cause is related.
  228: # In the debugger, this means that breakpoints are ignored until you step into
  229: # a function and get out of what must be a "faked up scope" in the Apache->
  230: # mod_perl connection. In this code, it was manifesting itself in the existence
  231: # of two separate file-scoped $helper variables, one set to the value of the
  232: # helper in the helper constructor, and one referenced by the handler on the
  233: # "$helper->process()" line. Using the debugger, one could actually
  234: # see the two different $helper variables, as hashes at completely
  235: # different addresses. The second was therefore never set, and was still
  236: # undefined when I tried to call process on it.
  237: # By pushing the "real handler" down into the "real scope", everybody except the 
  238: # actual handler function directly below this comment gets the same $helper and
  239: # everybody is happy.
  240: # The upshot of all of this is that for safety when a handler is  using 
  241: # file-scoped variables in LON-CAPA, the handler should be pushed down one 
  242: # call level, as I do here, to ensure that the top-level handler function does
  243: # not get a different file scope from the rest of the code.
  244: sub handler {
  245:     my $r = shift;
  246:     return real_handler($r);
  247: }
  248: 
  249: # For debugging purposes, one can send a second parameter into this
  250: # function, the 'uri' of the helper you wish to have rendered, and
  251: # call this from other handlers.
  252: sub real_handler {
  253:     my $r = shift;
  254:     my $uri = shift;
  255:     if (!defined($uri)) { $uri = $r->uri(); }
  256:     $env{'request.uri'} = $uri;
  257:     my $filename = '/home/httpd/html' . $uri;
  258:     my $fh = Apache::File->new($filename);
  259:     my $file;
  260:     read $fh, $file, 100000000;
  261: 
  262: 
  263:     # Send header, don't cache this page
  264:     if ($env{'browser.mathml'}) {
  265: 	&Apache::loncommon::content_type($r,'text/xml');
  266:     } else {
  267: 	&Apache::loncommon::content_type($r,'text/html');
  268:     }
  269:     $r->send_http_header;
  270:     return OK if $r->header_only;
  271:     $r->rflush();
  272: 
  273:     # Discard result, we just want the objects that get created by the
  274:     # xml parsing
  275:     &Apache::lonxml::xmlparse($r, 'helper', $file);
  276: 
  277:     my $allowed = $helper->allowedCheck();
  278:     if (!$allowed) {
  279:         $env{'user.error.msg'} = $env{'request.uri'}.':'.$helper->{REQUIRED_PRIV}.
  280:             ":0:0:Permission denied to access this helper.";
  281:         return HTTP_NOT_ACCEPTABLE;
  282:     }
  283: 
  284:     $helper->process();
  285: 
  286:     $r->print($helper->display());
  287:     return OK;
  288: }
  289: 
  290: sub registerHelperTags {
  291:     for my $tagList (@helperTags) {
  292:         Apache::lonxml::register($tagList->[0], $tagList->[1]);
  293:     }
  294: }
  295: 
  296: sub unregisterHelperTags {
  297:     for my $tagList (@helperTags) {
  298:         Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
  299:     }
  300: }
  301: 
  302: sub start_helper {
  303:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  304: 
  305:     if ($target ne 'helper') {
  306:         return '';
  307:     }
  308: 
  309:     registerHelperTags();
  310: 
  311:     Apache::lonhelper::helper->new($token->[2]{'title'}, $token->[2]{'requiredpriv'});
  312:     return '';
  313: }
  314: 
  315: sub end_helper {
  316:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  317:     
  318:     if ($target ne 'helper') {
  319:         return '';
  320:     }
  321: 
  322:     unregisterHelperTags();
  323: 
  324:     return '';
  325: }
  326: 
  327: sub start_state {
  328:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  329: 
  330:     if ($target ne 'helper') {
  331:         return '';
  332:     }
  333: 
  334:     Apache::lonhelper::state->new($token->[2]{'name'},
  335:                                   $token->[2]{'title'});
  336:     return '';
  337: }
  338: 
  339: # Use this to get the param hash from other files.
  340: sub getParamHash {
  341:     return $paramHash;
  342: }
  343: 
  344: # Use this to get the helper, if implementing elements in other files
  345: # (like lonprintout.pm)
  346: sub getHelper {
  347:     return $helper;
  348: }
  349: 
  350: # don't need this, so ignore it
  351: sub end_state {
  352:     return '';
  353: }
  354: 
  355: 1;
  356: 
  357: package Apache::lonhelper::helper;
  358: 
  359: use Digest::MD5 qw(md5_hex);
  360: use HTML::Entities();
  361: use Apache::loncommon;
  362: use Apache::File;
  363: use Apache::lonlocal;
  364: use Apache::lonnet;
  365: 
  366: sub new {
  367:     my $proto = shift;
  368:     my $class = ref($proto) || $proto;
  369:     my $self = {};
  370: 
  371:     $self->{TITLE} = shift;
  372:     $self->{REQUIRED_PRIV} = shift;
  373:     
  374:     # If there is a state from the previous form, use that. If there is no
  375:     # state, use the start state parameter.
  376:     if (defined $env{"form.CURRENT_STATE"})
  377:     {
  378: 	$self->{STATE} = $env{"form.CURRENT_STATE"};
  379:     }
  380:     else
  381:     {
  382: 	$self->{STATE} = "START";
  383:     }
  384: 
  385:     $self->{TOKEN} = $env{'form.TOKEN'};
  386:     # If a token was passed, we load that in. Otherwise, we need to create a 
  387:     # new storage file
  388:     # Tried to use standard Tie'd hashes, but you can't seem to take a 
  389:     # reference to a tied hash and write to it. I'd call that a wart.
  390:     if ($self->{TOKEN}) {
  391:         # Validate the token before trusting it
  392:         if ($self->{TOKEN} !~ /^[a-f0-9]{32}$/) {
  393:             # Not legit. Return nothing and let all hell break loose.
  394:             # User shouldn't be doing that!
  395:             return undef;
  396:         }
  397: 
  398:         # Get the hash.
  399:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); # Note the token is not the literal file
  400:         
  401:         my $file = Apache::File->new($self->{FILENAME});
  402:         my $contents = <$file>;
  403: 
  404:         # Now load in the contents
  405:         for my $value (split (/&/, $contents)) {
  406:             my ($name, $value) = split(/=/, $value);
  407:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  408:             $self->{VARS}->{$name} = $value;
  409:         }
  410: 
  411:         $file->close();
  412:     } else {
  413:         # Only valid if we're just starting.
  414:         if ($self->{STATE} ne 'START') {
  415:             return undef;
  416:         }
  417:         # Must create the storage
  418:         $self->{TOKEN} = md5_hex($env{'user.name'} . $env{'user.domain'} .
  419:                                  time() . rand());
  420:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
  421:     }
  422: 
  423:     # OK, we now have our persistent storage.
  424: 
  425:     if (defined $env{"form.RETURN_PAGE"})
  426:     {
  427: 	$self->{RETURN_PAGE} = $env{"form.RETURN_PAGE"};
  428:     }
  429:     else
  430:     {
  431: 	$self->{RETURN_PAGE} = $ENV{REFERER};
  432:     }
  433: 
  434:     $self->{STATES} = {};
  435:     $self->{DONE} = 0;
  436: 
  437:     # Used by various helpers for various things; see lonparm.helper
  438:     # for an example.
  439:     $self->{DATA} = {};
  440: 
  441:     $helper = $self;
  442: 
  443:     # Establish the $paramHash
  444:     $paramHash = {};
  445: 
  446:     bless($self, $class);
  447:     return $self;
  448: }
  449: 
  450: # Private function; returns a string to construct the hidden fields
  451: # necessary to have the helper track state.
  452: sub _saveVars {
  453:     my $self = shift;
  454:     my $result = "";
  455:     $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
  456:         HTML::Entities::encode($self->{STATE},'<>&"') . "\" />\n";
  457:     $result .= '<input type="hidden" name="TOKEN" value="' .
  458:         $self->{TOKEN} . "\" />\n";
  459:     $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
  460:         HTML::Entities::encode($self->{RETURN_PAGE},'<>&"') . "\" />\n";
  461: 
  462:     return $result;
  463: }
  464: 
  465: # Private function: Create the querystring-like representation of the stored
  466: # data to write to disk.
  467: sub _varsInFile {
  468:     my $self = shift;
  469:     my @vars = ();
  470:     for my $key (keys %{$self->{VARS}}) {
  471:         push @vars, &Apache::lonnet::escape($key) . '=' .
  472:             &Apache::lonnet::escape($self->{VARS}->{$key});
  473:     }
  474:     return join ('&', @vars);
  475: }
  476: 
  477: # Use this to declare variables.
  478: # FIXME: Document this
  479: sub declareVar {
  480:     my $self = shift;
  481:     my $var = shift;
  482: 
  483:     if (!defined($self->{VARS}->{$var})) {
  484:         $self->{VARS}->{$var} = '';
  485:     }
  486: 
  487:     my $envname = 'form.' . $var . '.forminput';
  488:     if (defined($env{$envname})) {
  489:         if (ref($env{$envname})) {
  490:             $self->{VARS}->{$var} = join('|||', @{$env{$envname}});
  491:         } else {
  492:             $self->{VARS}->{$var} = $env{$envname};
  493:         }
  494:     }
  495: }
  496: 
  497: sub allowedCheck {
  498:     my $self = shift;
  499: 
  500:     if (!defined($self->{REQUIRED_PRIV})) { 
  501:         return 1;
  502:     }
  503: 
  504:     return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $env{'request.course.id'});
  505: }
  506: 
  507: sub changeState {
  508:     my $self = shift;
  509:     $self->{STATE} = shift;
  510: }
  511: 
  512: sub registerState {
  513:     my $self = shift;
  514:     my $state = shift;
  515: 
  516:     my $stateName = $state->name();
  517:     $self->{STATES}{$stateName} = $state;
  518: }
  519: 
  520: sub process {
  521:     my $self = shift;
  522: 
  523:     # Phase 1: Post processing for state of previous screen (which is actually
  524:     # the "current state" in terms of the helper variables), if it wasn't the 
  525:     # beginning state.
  526:     if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next ->")) {
  527: 	my $prevState = $self->{STATES}{$self->{STATE}};
  528:         $prevState->postprocess();
  529:     }
  530:     
  531:     # Note, to handle errors in a state's input that a user must correct,
  532:     # do not transition in the postprocess, and force the user to correct
  533:     # the error.
  534: 
  535:     # Phase 2: Preprocess current state
  536:     my $startState = $self->{STATE};
  537:     my $state = $self->{STATES}->{$startState};
  538:     
  539:     # For debugging, print something here to determine if you're going
  540:     # to an undefined state.
  541:     if (!defined($state)) {
  542:         return;
  543:     }
  544:     $state->preprocess();
  545: 
  546:     # Phase 3: While the current state is different from the previous state,
  547:     # keep processing.
  548:     while ( $startState ne $self->{STATE} && 
  549:             defined($self->{STATES}->{$self->{STATE}}) )
  550:     {
  551: 	$startState = $self->{STATE};
  552: 	$state = $self->{STATES}->{$startState};
  553: 	$state->preprocess();
  554:     }
  555: 
  556:     return;
  557: } 
  558: 
  559: # 1: Do the post processing for the previous state.
  560: # 2: Do the preprocessing for the current state.
  561: # 3: Check to see if state changed, if so, postprocess current and move to next.
  562: #    Repeat until state stays stable.
  563: # 4: Render the current state to the screen as an HTML page.
  564: sub display {
  565:     my $self = shift;
  566: 
  567:     my $state = $self->{STATES}{$self->{STATE}};
  568: 
  569:     my $result = "";
  570: 
  571:     if (!defined($state)) {
  572:         $result = "<font color='#ff0000'>Error: state '$state' not defined!</font>";
  573:         return $result;
  574:     }
  575: 
  576:     # Phase 4: Display.
  577:     my $stateTitle=&mt($state->title());
  578:     my $browser_searcher_js = 
  579: 	'<script type="text/javascript">'."\n".
  580: 	&Apache::loncommon::browser_and_searcher_javascript().
  581: 	"\n".'</script>';
  582: 
  583:     $result .= &Apache::loncommon::start_page($self->{TITLE},
  584: 					      $browser_searcher_js);
  585:     
  586:     my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
  587:     my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
  588:     # FIXME: This should be parameterized, not concatenated - Jeremy
  589: 
  590: 
  591:     if (!$state->overrideForm()) { $result.="<form name='helpform' method='POST'>"; }
  592:     $result .= <<HEADER;
  593:         <table border="0" width='100%'><tr><td>
  594:         <h2><i>$stateTitle</i></h2>
  595: HEADER
  596: 
  597:     $result .= "<table cellpadding='10' width='100%'><tr><td rowspan='2' valign='top'>";
  598: 
  599:     if (!$state->overrideForm()) {
  600:         $result .= $self->_saveVars();
  601:     }
  602:     $result .= $state->render();
  603: 
  604:     $result .= "</td><td valign='top' align='right'>";
  605: 
  606:     # Warning: Copy and pasted from below, because it's too much trouble to 
  607:     # turn this into a subroutine
  608:     if (!$state->overrideForm()) {
  609:         if ($self->{STATE} ne $self->{START_STATE}) {
  610:             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
  611:         }
  612:         if ($self->{DONE}) {
  613:             my $returnPage = $self->{RETURN_PAGE};
  614:             $result .= "<a href=\"$returnPage\">" . &mt("End Helper") . "</a>";
  615:         }
  616:         else {
  617:             $result .= '<nobr><input name="back" type="button" ';
  618:             $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
  619:             $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></nobr>';
  620:         }
  621:     }
  622: 
  623:     $result .= "</td></tr><tr><td valign='bottom' align='right'>";
  624: 
  625:     # Warning: Copy and pasted from above, because it's too much trouble to 
  626:     # turn this into a subroutine
  627:     if (!$state->overrideForm()) {
  628:         if ($self->{STATE} ne $self->{START_STATE}) {
  629:             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
  630:         }
  631:         if ($self->{DONE}) {
  632:             my $returnPage = $self->{RETURN_PAGE};
  633:             $result .= "<a href=\"$returnPage\">" . &mt('End Helper') . "</a>";
  634:         }
  635:         else {
  636:             $result .= '<nobr><input name="back" type="button" ';
  637:             $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
  638:             $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></nobr>';
  639:         }
  640:     }
  641: 
  642:     #foreach my $key (keys %{$self->{VARS}}) {
  643:     #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
  644:     #}
  645: 
  646:     $result .= "</td></tr></table>";
  647: 
  648:     $result .= <<FOOTER;
  649:               </td>
  650:             </tr>
  651:           </table>
  652:         </form>
  653: FOOTER
  654: 
  655:     $result .= &Apache::loncommon::end_page();
  656:     # Handle writing out the vars to the file
  657:     my $file = Apache::File->new('>'.$self->{FILENAME});
  658:     print $file $self->_varsInFile();
  659: 
  660:     return $result;
  661: }
  662: 
  663: 1;
  664: 
  665: package Apache::lonhelper::state;
  666: 
  667: # States bundle things together and are responsible for compositing the
  668: # various elements together. It is not generally necessary for users to
  669: # use the state object directly, so it is not perldoc'ed.
  670: 
  671: # Basically, all the states do is pass calls to the elements and aggregate
  672: # the results.
  673: 
  674: sub new {
  675:     my $proto = shift;
  676:     my $class = ref($proto) || $proto;
  677:     my $self = {};
  678: 
  679:     $self->{NAME} = shift;
  680:     $self->{TITLE} = shift;
  681:     $self->{ELEMENTS} = [];
  682: 
  683:     bless($self, $class);
  684: 
  685:     $helper->registerState($self);
  686: 
  687:     $state = $self;
  688: 
  689:     return $self;
  690: }
  691: 
  692: sub name {
  693:     my $self = shift;
  694:     return $self->{NAME};
  695: }
  696: 
  697: sub title {
  698:     my $self = shift;
  699:     return $self->{TITLE};
  700: }
  701: 
  702: sub preprocess {
  703:     my $self = shift;
  704:     for my $element (@{$self->{ELEMENTS}}) {
  705:         $element->preprocess();
  706:     }
  707: }
  708: 
  709: # FIXME: Document that all postprocesses must return a true value or
  710: # the state transition will be overridden
  711: sub postprocess {
  712:     my $self = shift;
  713: 
  714:     # Save the state so we can roll it back if we need to.
  715:     my $originalState = $helper->{STATE};
  716:     my $everythingSuccessful = 1;
  717: 
  718:     for my $element (@{$self->{ELEMENTS}}) {
  719:         my $result = $element->postprocess();
  720:         if (!$result) { $everythingSuccessful = 0; }
  721:     }
  722: 
  723:     # If not all the postprocesses were successful, override
  724:     # any state transitions that may have occurred. It is the
  725:     # responsibility of the states to make sure they have 
  726:     # error handling in that case.
  727:     if (!$everythingSuccessful) {
  728:         $helper->{STATE} = $originalState;
  729:     }
  730: }
  731: 
  732: # Override the form if any element wants to.
  733: # two elements overriding the form will make a mess, but that should
  734: # be considered helper author error ;-)
  735: sub overrideForm {
  736:     my $self = shift;
  737:     for my $element (@{$self->{ELEMENTS}}) {
  738:         if ($element->overrideForm()) {
  739:             return 1;
  740:         }
  741:     }
  742:     return 0;
  743: }
  744: 
  745: sub addElement {
  746:     my $self = shift;
  747:     my $element = shift;
  748:     
  749:     push @{$self->{ELEMENTS}}, $element;
  750: }
  751: 
  752: sub render {
  753:     my $self = shift;
  754:     my @results = ();
  755: 
  756:     for my $element (@{$self->{ELEMENTS}}) {
  757:         push @results, $element->render();
  758:     }
  759: 
  760:     return join("\n", @results);
  761: }
  762: 
  763: 1;
  764: 
  765: package Apache::lonhelper::element;
  766: # Support code for elements
  767: 
  768: =pod
  769: 
  770: =head1 Element Base Class
  771: 
  772: The Apache::lonhelper::element base class provides support for elements
  773: and defines some generally useful tags for use in elements.
  774: 
  775: =head2 finalcode tagX<finalcode>
  776: 
  777: Each element can contain a "finalcode" tag that, when the special FINAL
  778: helper state is used, will be executed, surrounded by "sub { my $helper = shift;"
  779: and "}". It is expected to return a string describing what it did, which 
  780: may be an empty string. See course initialization helper for an example. This is
  781: generally intended for helpers like the course initialization helper, which consist
  782: of several panels, each of which is performing some sort of bite-sized functionality.
  783: 
  784: =head2 defaultvalue tagX<defaultvalue>
  785: 
  786: Each element that accepts user input can contain a "defaultvalue" tag that,
  787: when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
  788: will form a subroutine that when called will provide a default value for
  789: the element. How this value is interpreted by the element is specific to
  790: the element itself, and possibly the settings the element has (such as 
  791: multichoice vs. single choice for <choices> tags). 
  792: 
  793: This is also intended for things like the course initialization wizard, where the
  794: user is setting various parameters. By correctly grabbing current settings 
  795: and including them into the helper, it allows the user to come back to the
  796: helper later and re-execute it, without needing to worry about overwriting
  797: some setting accidentally.
  798: 
  799: Again, see the course initialization helper for examples.
  800: 
  801: =head2 validator tagX<validator>
  802: 
  803: Some elements that accepts user input can contain a "validator" tag that,
  804: when surrounded by "sub { my $helper = shift; my $state = shift; my $element = shift; my $val = shift " 
  805: and "}", where "$val" is the value the user entered, will form a subroutine 
  806: that when called will verify whether the given input is valid or not. If it 
  807: is valid, the routine will return a false value. If invalid, the routine 
  808: will return an error message to be displayed for the user.
  809: 
  810: Consult the documentation for each element to see whether it supports this 
  811: tag.
  812: 
  813: =head2 getValue methodX<getValue (helper elements)>
  814: 
  815: If the element stores the name of the variable in a 'variable' member, which
  816: the provided ones all do, you can retreive the value of the variable by calling
  817: this method.
  818: 
  819: =cut
  820: 
  821: BEGIN {
  822:     &Apache::lonhelper::register('Apache::lonhelper::element',
  823:                                  ('nextstate', 'finalcode',
  824:                                   'defaultvalue', 'validator'));
  825: }
  826: 
  827: # Because we use the param hash, this is often a sufficent
  828: # constructor
  829: sub new {
  830:     my $proto = shift;
  831:     my $class = ref($proto) || $proto;
  832:     my $self = $paramHash;
  833:     bless($self, $class);
  834: 
  835:     $self->{PARAMS} = $paramHash;
  836:     $self->{STATE} = $state;
  837:     $state->addElement($self);
  838:     
  839:     # Ensure param hash is not reused
  840:     $paramHash = {};
  841: 
  842:     return $self;
  843: }   
  844: 
  845: sub start_nextstate {
  846:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  847: 
  848:     if ($target ne 'helper') {
  849:         return '';
  850:     }
  851:     
  852:     $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
  853:                                                              $parser);
  854:     return '';
  855: }
  856: 
  857: sub end_nextstate { return ''; }
  858: 
  859: sub start_finalcode {
  860:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  861: 
  862:     if ($target ne 'helper') {
  863:         return '';
  864:     }
  865:     
  866:     $paramHash->{FINAL_CODE} = &Apache::lonxml::get_all_text('/finalcode',
  867:                                                              $parser);
  868:     return '';
  869: }
  870: 
  871: sub end_finalcode { return ''; }
  872: 
  873: sub start_defaultvalue {
  874:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  875: 
  876:     if ($target ne 'helper') {
  877:         return '';
  878:     }
  879:     
  880:     $paramHash->{DEFAULT_VALUE} = &Apache::lonxml::get_all_text('/defaultvalue',
  881:                                                              $parser);
  882:     $paramHash->{DEFAULT_VALUE} = 'sub { my $helper = shift; my $state = shift;' .
  883:         $paramHash->{DEFAULT_VALUE} . '}';
  884:     return '';
  885: }
  886: 
  887: sub end_defaultvalue { return ''; }
  888: 
  889: # Validators may need to take language specifications
  890: sub start_validator {
  891:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  892: 
  893:     if ($target ne 'helper') {
  894:         return '';
  895:     }
  896:     
  897:     $paramHash->{VALIDATOR} = &Apache::lonxml::get_all_text('/validator',
  898:                                                              $parser);
  899:     $paramHash->{VALIDATOR} = 'sub { my $helper = shift; my $state = shift; my $element = shift; my $val = shift;' .
  900:         $paramHash->{VALIDATOR} . '}';
  901:     return '';
  902: }
  903: 
  904: sub end_validator { return ''; }
  905: 
  906: sub preprocess {
  907:     return 1;
  908: }
  909: 
  910: sub postprocess {
  911:     return 1;
  912: }
  913: 
  914: sub render {
  915:     return '';
  916: }
  917: 
  918: sub overrideForm {
  919:     return 0;
  920: }
  921: 
  922: sub getValue {
  923:     my $self = shift;
  924:     return $helper->{VARS}->{$self->{'variable'}};
  925: }
  926: 
  927: 1;
  928: 
  929: package Apache::lonhelper::message;
  930: 
  931: =pod
  932: 
  933: =head1 Elements
  934: 
  935: =head2 Element: messageX<message, helper element>
  936: 
  937: Message elements display their contents, and
  938: transition directly to the state in the <nextstate> attribute. Example:
  939: 
  940:  <message nextstate='GET_NAME'>
  941:    This is the <b>message</b> the user will see, 
  942:                  <i>HTML allowed</i>.
  943:    </message>
  944: 
  945: This will display the HTML message and transition to the 'nextstate' if
  946: given. The HTML will be directly inserted into the helper, so if you don't
  947: want text to run together, you'll need to manually wrap the message text
  948: in <p> tags, or whatever is appropriate for your HTML.
  949: 
  950: Message tags do not add in whitespace, so if you want it, you'll need to add
  951: it into states. This is done so you can inline some elements, such as 
  952: the <date> element, right between two messages, giving the appearence that 
  953: the <date> element appears inline. (Note the elements can not be embedded
  954: within each other.)
  955: 
  956: This is also a good template for creating your own new states, as it has
  957: very little code beyond the state template.
  958: 
  959: =head3 Localization
  960: 
  961: The contents of the message tag will be run through the
  962: normalize_string function and that will be used as a call to &mt.
  963: 
  964: =cut
  965: 
  966: no strict;
  967: @ISA = ("Apache::lonhelper::element");
  968: use strict;
  969: use Apache::lonlocal;
  970: 
  971: BEGIN {
  972:     &Apache::lonhelper::register('Apache::lonhelper::message',
  973:                               ('message'));
  974: }
  975: 
  976: sub new {
  977:     my $ref = Apache::lonhelper::element->new();
  978:     bless($ref);
  979: }
  980: 
  981: # CONSTRUCTION: Construct the message element from the XML
  982: sub start_message {
  983:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  984: 
  985:     if ($target ne 'helper') {
  986:         return '';
  987:     }
  988: 
  989:     $paramHash->{MESSAGE_TEXT} = &mtn(&Apache::lonxml::get_all_text('/message',
  990:                                                                $parser));
  991: 
  992:     if (defined($token->[2]{'nextstate'})) {
  993:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
  994:     }
  995:     return '';
  996: }
  997: 
  998: sub end_message {
  999:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1000: 
 1001:     if ($target ne 'helper') {
 1002:         return '';
 1003:     }
 1004:     Apache::lonhelper::message->new();
 1005:     return '';
 1006: }
 1007: 
 1008: sub render {
 1009:     my $self = shift;
 1010: 
 1011:     return &mtn($self->{MESSAGE_TEXT});
 1012: }
 1013: # If a NEXTSTATE was given, switch to it
 1014: sub postprocess {
 1015:     my $self = shift;
 1016:     if (defined($self->{NEXTSTATE})) {
 1017:         $helper->changeState($self->{NEXTSTATE});
 1018:     }
 1019: 
 1020:     return 1;
 1021: }
 1022: 1;
 1023: 
 1024: package Apache::lonhelper::choices;
 1025: 
 1026: =pod
 1027: 
 1028: =head2 Element: choicesX<choices, helper element>
 1029: 
 1030: Choice states provide a single choice to the user as a text selection box.
 1031: A "choice" is two pieces of text, one which will be displayed to the user
 1032: (the "human" value), and one which will be passed back to the program
 1033: (the "computer" value). For instance, a human may choose from a list of
 1034: resources on disk by title, while your program wants the file name.
 1035: 
 1036: <choices> takes an attribute "variable" to control which helper variable
 1037: the result is stored in.
 1038: 
 1039: <choices> takes an attribute "multichoice" which, if set to a true
 1040: value, will allow the user to select multiple choices.
 1041: 
 1042: <choices> takes an attribute "allowempty" which, if set to a true 
 1043: value, will allow the user to select none of the choices without raising
 1044: an error message.
 1045: 
 1046: =head3 SUB-TAGS
 1047: 
 1048: <choices> can have the following subtags:X<choice, helper tag>
 1049: 
 1050: =over 4
 1051: 
 1052: =item * <nextstate>state_name</nextstate>: If given, this will cause the
 1053:       choice element to transition to the given state after executing.
 1054:       This will override the <nextstate> passed to <choices> (if any).
 1055: 
 1056: =item * <choice />: If the choices are static,
 1057:       this element will allow you to specify them. Each choice
 1058:       contains  attribute, "computer", as described above. The
 1059:       content of the tag will be used as the human label.
 1060:       For example,  
 1061:       <choice computer='234-12-7312'>Bobby McDormik</choice>.
 1062: 
 1063: <choice> can take a parameter "eval", which if set to
 1064: a true value, will cause the contents of the tag to be
 1065: evaluated as it would be in an <eval> tag; see <eval> tag
 1066: below.
 1067: 
 1068: <choice> may optionally contain a 'nextstate' attribute, which
 1069: will be the state transistioned to if the choice is made, if
 1070: the choice is not multichoice. This will override the nextstate
 1071: passed to the parent C<choices> tag.
 1072: 
 1073: <choice> may optionally contain a 'relatedvalue' attribute, which
 1074: if present will cause a text entry to appear to the right of the
 1075: selection.  The value of the relatedvalue attribute is a variable
 1076: into which the text entry will be stored e.g.:
 1077: <choice computer='numberprovided" relatedvalue="num">Type the number in:</choice>
 1078: 
 1079: <choice> may contain a relatededefault atribute which, if the
 1080: relatedvalue attribute is present will be the initial value of the input
 1081: box.
 1082: 
 1083: =back
 1084: 
 1085: To create the choices programmatically, either wrap the choices in 
 1086: <condition> tags (prefered), or use an <exec> block inside the <choice>
 1087: tag. Store the choices in $state->{CHOICES}, which is a list of list
 1088: references, where each list has three strings. The first is the human
 1089: name, the second is the computer name. and the third is the option
 1090: next state. For example:
 1091: 
 1092:  <exec>
 1093:     for (my $i = 65; $i < 65 + 26; $i++) {
 1094:         push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
 1095:     }
 1096:  </exec>
 1097: 
 1098: This will allow the user to select from the letters A-Z (in ASCII), while
 1099: passing the ASCII value back into the helper variables, and the state
 1100: will in all cases transition to 'next'.
 1101: 
 1102: You can mix and match methods of creating choices, as long as you always 
 1103: "push" onto the choice list, rather then wiping it out. (You can even 
 1104: remove choices programmatically, but that would probably be bad form.)
 1105: 
 1106: =head3 defaultvalue support
 1107: 
 1108: Choices supports default values both in multichoice and single choice mode.
 1109: In single choice mode, have the defaultvalue tag's function return the 
 1110: computer value of the box you want checked. If the function returns a value
 1111: that does not correspond to any of the choices, the default behavior of selecting
 1112: the first choice will be preserved.
 1113: 
 1114: For multichoice, return a string with the computer values you want checked,
 1115: delimited by triple pipes. Note this matches how the result of the <choices>
 1116: tag is stored in the {VARS} hash.
 1117: 
 1118: =cut
 1119: 
 1120: no strict;
 1121: @ISA = ("Apache::lonhelper::element");
 1122: use strict;
 1123: use Apache::lonlocal;
 1124: use Apache::lonnet;
 1125: 
 1126: BEGIN {
 1127:     &Apache::lonhelper::register('Apache::lonhelper::choices',
 1128:                               ('choice', 'choices'));
 1129: }
 1130: 
 1131: sub new {
 1132:     my $ref = Apache::lonhelper::element->new();
 1133:     bless($ref);
 1134: }
 1135: 
 1136: # CONSTRUCTION: Construct the message element from the XML
 1137: sub start_choices {
 1138:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1139: 
 1140:     if ($target ne 'helper') {
 1141:         return '';
 1142:     }
 1143: 
 1144:     # Need to initialize the choices list, so everything can assume it exists
 1145:     $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
 1146:     $helper->declareVar($paramHash->{'variable'});
 1147:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1148:     $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
 1149:     $paramHash->{CHOICES} = [];
 1150:     return '';
 1151: }
 1152: 
 1153: sub end_choices {
 1154:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1155: 
 1156:     if ($target ne 'helper') {
 1157:         return '';
 1158:     }
 1159:     Apache::lonhelper::choices->new();
 1160:     return '';
 1161: }
 1162: 
 1163: sub start_choice {
 1164:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1165: 
 1166:     if ($target ne 'helper') {
 1167:         return '';
 1168:     }
 1169: 
 1170:     my $computer = $token->[2]{'computer'};
 1171:     my $human = &mt(&Apache::lonxml::get_all_text('/choice',
 1172:                                               $parser));
 1173:     my $nextstate  = $token->[2]{'nextstate'};
 1174:     my $evalFlag   = $token->[2]{'eval'};
 1175:     my $relatedVar = $token->[2]{'relatedvalue'}; 
 1176:     my $relatedDefault = $token->[2]{'relateddefault'};
 1177:     push @{$paramHash->{CHOICES}}, [&mtn($human), $computer, $nextstate, 
 1178:                                     $evalFlag, $relatedVar, $relatedDefault];
 1179:     return '';
 1180: }
 1181: 
 1182: sub end_choice {
 1183:     return '';
 1184: }
 1185: 
 1186: {
 1187:     # used to generate unique id attributes for <input> tags. 
 1188:     # internal use only.
 1189:     my $id = 0;
 1190:     sub new_id { return $id++; }
 1191: }
 1192: 
 1193: sub render {
 1194:     my $self = shift;
 1195:     my $var = $self->{'variable'};
 1196:     my $buttons = '';
 1197:     my $result = '';
 1198: 
 1199:     if ($self->{'multichoice'}) {
 1200:         $result .= <<SCRIPT;
 1201: <script type="text/javascript">
 1202: // <!--
 1203:     function checkall(value, checkName) {
 1204: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 1205:             ele = document.forms.helpform.elements[i];
 1206:             if (ele.name == checkName + '.forminput') {
 1207:                 document.forms.helpform.elements[i].checked=value;
 1208:             }
 1209:         }
 1210:     }
 1211: // -->
 1212: </script>
 1213: SCRIPT
 1214:     }
 1215: 
 1216:     # Only print "select all" and "unselect all" if there are five or
 1217:     # more choices; fewer then that and it looks silly.
 1218:     if ($self->{'multichoice'} && scalar(@{$self->{CHOICES}}) > 4) {
 1219:         my %lt=&Apache::lonlocal::texthash(
 1220: 			'sa'  => "Select All",
 1221: 		        'ua'  => "Unselect All");
 1222:         $buttons = <<BUTTONS;
 1223: <br />
 1224: <input type="button" onclick="checkall(true, '$var')" value="$lt{'sa'}" />
 1225: <input type="button" onclick="checkall(false, '$var')" value="$lt{'ua'}" />
 1226: <br />&nbsp;
 1227: BUTTONS
 1228:     }
 1229: 
 1230:     if (defined $self->{ERROR_MSG}) {
 1231:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
 1232:     }
 1233: 
 1234:     $result .= $buttons;
 1235:     
 1236:     $result .= "<table>\n\n";
 1237: 
 1238:     my %checkedChoices;
 1239:     my $checkedChoicesFunc;
 1240: 
 1241:     if (defined($self->{DEFAULT_VALUE})) {
 1242:         $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
 1243:         die 'Error in default value code for variable ' . 
 1244:             $self->{'variable'} . ', Perl said: ' . $@ if $@;
 1245:     } else {
 1246:         $checkedChoicesFunc = sub { return ''; };
 1247:     }
 1248: 
 1249:     # Process which choices should be checked.
 1250:     if ($self->{'multichoice'}) {
 1251:         for my $selectedChoice (split(/\|\|\|/, (&$checkedChoicesFunc($helper, $self)))) {
 1252:             $checkedChoices{$selectedChoice} = 1;
 1253:         }
 1254:     } else {
 1255:         # single choice
 1256:         my $selectedChoice = &$checkedChoicesFunc($helper, $self);
 1257:         
 1258:         my $foundChoice = 0;
 1259:         
 1260:         # check that the choice is in the list of choices.
 1261:         for my $choice (@{$self->{CHOICES}}) {
 1262:             if ($choice->[1] eq $selectedChoice) {
 1263:                 $checkedChoices{$choice->[1]} = 1;
 1264:                 $foundChoice = 1;
 1265:             }
 1266:         }
 1267:         
 1268:         # If we couldn't find the choice, pick the first one 
 1269:         if (!$foundChoice) {
 1270:             $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
 1271:         }
 1272:     }
 1273: 
 1274:     my $type = "radio";
 1275:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1276:     foreach my $choice (@{$self->{CHOICES}}) {
 1277:         my $id = &new_id();
 1278:         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
 1279:         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
 1280:             . " value='" . 
 1281:             HTML::Entities::encode($choice->[1],"<>&\"'") 
 1282:             . "'";
 1283:         if ($checkedChoices{$choice->[1]}) {
 1284:             $result .= " checked='checked' ";
 1285:         }
 1286:         $result .= qq{id="id$id"};
 1287:         my $choiceLabel = $choice->[0];
 1288:         if ($choice->[3]) {  # if we need to evaluate this choice
 1289:             $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
 1290:                 $choiceLabel . "}";
 1291:             $choiceLabel = eval($choiceLabel);
 1292:             $choiceLabel = &$choiceLabel($helper, $self);
 1293:         }
 1294:         $result .= "/></td><td> ".qq{<label for="id$id">}.
 1295:             $choiceLabel. "</label></td>";
 1296: 	if ($choice->[4]) {
 1297: 	    $result .='<td><input type="text" size="5" name="'
 1298: 		.$choice->[4].'.forminput" value="'
 1299:                 .$choice->[5].'" /></td>';
 1300: 	}
 1301: 	$result .= "</tr>\n";
 1302:     }
 1303:     $result .= "</table>\n\n\n";
 1304:     $result .= $buttons;
 1305: 
 1306:     return $result;
 1307: }
 1308: 
 1309: # If a NEXTSTATE was given or a nextstate for this choice was
 1310: # given, switch to it
 1311: sub postprocess {
 1312:     my $self = shift;
 1313:     my $chosenValue = $env{'form.' . $self->{'variable'} . '.forminput'};
 1314: 
 1315:     if (!defined($chosenValue) && !$self->{'allowempty'}) {
 1316:         $self->{ERROR_MSG} = 
 1317: 	    &mt("You must choose one or more choices to continue.");
 1318:         return 0;
 1319:     }
 1320: 
 1321:     if (ref($chosenValue)) {
 1322:         $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
 1323:     }
 1324: 
 1325:     if (defined($self->{NEXTSTATE})) {
 1326:         $helper->changeState($self->{NEXTSTATE});
 1327:     }
 1328:     
 1329:     foreach my $choice (@{$self->{CHOICES}}) {
 1330:         if ($choice->[1] eq $chosenValue) {
 1331:             if (defined($choice->[2])) {
 1332:                 $helper->changeState($choice->[2]);
 1333:             }
 1334:         }
 1335: 	if ($choice->[4]) {
 1336: 	    my $varname = $choice->[4];
 1337: 	    $helper->{'VARS'}->{$varname} = $env{'form.'."$varname.forminput"};
 1338: 	}
 1339:     }
 1340:     return 1;
 1341: }
 1342: 1;
 1343: 
 1344: package Apache::lonhelper::dropdown;
 1345: 
 1346: =pod
 1347: 
 1348: =head2 Element: dropdownX<dropdown, helper tag>
 1349: 
 1350: A drop-down provides a drop-down box instead of a radio button
 1351: box. Because most people do not know how to use a multi-select
 1352: drop-down box, that option is not allowed. Otherwise, the arguments
 1353: are the same as "choices", except "allowempty" is also meaningless.
 1354: 
 1355: <dropdown> takes an attribute "variable" to control which helper variable
 1356: the result is stored in.
 1357: 
 1358: =head3 SUB-TAGS
 1359: 
 1360: <choice>, which acts just as it does in the "choices" element.
 1361: 
 1362: =cut
 1363: 
 1364: # This really ought to be a sibling class to "choice" which is itself
 1365: # a child of some abstract class.... *shrug*
 1366: 
 1367: no strict;
 1368: @ISA = ("Apache::lonhelper::element");
 1369: use strict;
 1370: use Apache::lonlocal;
 1371: use Apache::lonnet;
 1372: 
 1373: BEGIN {
 1374:     &Apache::lonhelper::register('Apache::lonhelper::dropdown',
 1375:                               ('dropdown'));
 1376: }
 1377: 
 1378: sub new {
 1379:     my $ref = Apache::lonhelper::element->new();
 1380:     bless($ref);
 1381: }
 1382: 
 1383: # CONSTRUCTION: Construct the message element from the XML
 1384: sub start_dropdown {
 1385:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1386: 
 1387:     if ($target ne 'helper') {
 1388:         return '';
 1389:     }
 1390: 
 1391:     # Need to initialize the choices list, so everything can assume it exists
 1392:     $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
 1393:     $helper->declareVar($paramHash->{'variable'});
 1394:     $paramHash->{CHOICES} = [];
 1395:     return '';
 1396: }
 1397: 
 1398: sub end_dropdown {
 1399:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1400: 
 1401:     if ($target ne 'helper') {
 1402:         return '';
 1403:     }
 1404:     Apache::lonhelper::dropdown->new();
 1405:     return '';
 1406: }
 1407: 
 1408: sub render {
 1409:     my $self = shift;
 1410:     my $var = $self->{'variable'};
 1411:     my $result = '';
 1412: 
 1413:     if (defined $self->{ERROR_MSG}) {
 1414:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
 1415:     }
 1416: 
 1417:     my %checkedChoices;
 1418:     my $checkedChoicesFunc;
 1419: 
 1420:     if (defined($self->{DEFAULT_VALUE})) {
 1421:         $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
 1422:         die 'Error in default value code for variable ' . 
 1423:             $self->{'variable'} . ', Perl said: ' . $@ if $@;
 1424:     } else {
 1425:         $checkedChoicesFunc = sub { return ''; };
 1426:     }
 1427: 
 1428:     # single choice
 1429:     my $selectedChoice = &$checkedChoicesFunc($helper, $self);
 1430:     
 1431:     my $foundChoice = 0;
 1432:     
 1433:     # check that the choice is in the list of choices.
 1434:     for my $choice (@{$self->{CHOICES}}) {
 1435: 	if ($choice->[1] eq $selectedChoice) {
 1436: 	    $checkedChoices{$choice->[1]} = 1;
 1437: 	    $foundChoice = 1;
 1438: 	}
 1439:     }
 1440:     
 1441:     # If we couldn't find the choice, pick the first one 
 1442:     if (!$foundChoice) {
 1443: 	$checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
 1444:     }
 1445: 
 1446:     $result .= "<select name='${var}.forminput'>\n";
 1447:     foreach my $choice (@{$self->{CHOICES}}) {
 1448:         $result .= "<option value='" . 
 1449:             HTML::Entities::encode($choice->[1],"<>&\"'") 
 1450:             . "'";
 1451:         if ($checkedChoices{$choice->[1]}) {
 1452:             $result .= " selected='selected' ";
 1453:         }
 1454:         my $choiceLabel = $choice->[0];
 1455:         if ($choice->[4]) {  # if we need to evaluate this choice
 1456:             $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
 1457:                 $choiceLabel . "}";
 1458:             $choiceLabel = eval($choiceLabel);
 1459:             $choiceLabel = &$choiceLabel($helper, $self);
 1460:         }
 1461:         $result .= ">" . &mtn($choiceLabel) . "</option>\n";
 1462:     }
 1463:     $result .= "</select>\n";
 1464: 
 1465:     return $result;
 1466: }
 1467: 
 1468: # If a NEXTSTATE was given or a nextstate for this choice was
 1469: # given, switch to it
 1470: sub postprocess {
 1471:     my $self = shift;
 1472:     my $chosenValue = $env{'form.' . $self->{'variable'} . '.forminput'};
 1473: 
 1474:     if (!defined($chosenValue) && !$self->{'allowempty'}) {
 1475:         $self->{ERROR_MSG} = "You must choose one or more choices to" .
 1476:             " continue.";
 1477:         return 0;
 1478:     }
 1479: 
 1480:     if (defined($self->{NEXTSTATE})) {
 1481:         $helper->changeState($self->{NEXTSTATE});
 1482:     }
 1483:     
 1484:     foreach my $choice (@{$self->{CHOICES}}) {
 1485:         if ($choice->[1] eq $chosenValue) {
 1486:             if (defined($choice->[2])) {
 1487:                 $helper->changeState($choice->[2]);
 1488:             }
 1489:         }
 1490:     }
 1491:     return 1;
 1492: }
 1493: 1;
 1494: 
 1495: package Apache::lonhelper::date;
 1496: 
 1497: =pod
 1498: 
 1499: =head2 Element: dateX<date, helper element>
 1500: 
 1501: Date elements allow the selection of a date with a drop down list.
 1502: 
 1503: Date elements can take two attributes:
 1504: 
 1505: =over 4
 1506: 
 1507: =item * B<variable>: The name of the variable to store the chosen
 1508:         date in. Required.
 1509: 
 1510: =item * B<hoursminutes>: If a true value, the date will show hours
 1511:         and minutes, as well as month/day/year. If false or missing,
 1512:         the date will only show the month, day, and year.
 1513: 
 1514: =back
 1515: 
 1516: Date elements contain only an option <nextstate> tag to determine
 1517: the next state.
 1518: 
 1519: Example:
 1520: 
 1521:  <date variable="DUE_DATE" hoursminutes="1">
 1522:    <nextstate>choose_why</nextstate>
 1523:    </date>
 1524: 
 1525: =cut
 1526: 
 1527: no strict;
 1528: @ISA = ("Apache::lonhelper::element");
 1529: use strict;
 1530: use Apache::lonlocal; # A localization nightmare
 1531: use Apache::lonnet;
 1532: use Time::localtime;
 1533: 
 1534: BEGIN {
 1535:     &Apache::lonhelper::register('Apache::lonhelper::date',
 1536:                               ('date'));
 1537: }
 1538: 
 1539: # Don't need to override the "new" from element
 1540: sub new {
 1541:     my $ref = Apache::lonhelper::element->new();
 1542:     bless($ref);
 1543: }
 1544: 
 1545: my @months = ("January", "February", "March", "April", "May", "June", "July",
 1546: 	      "August", "September", "October", "November", "December");
 1547: 
 1548: # CONSTRUCTION: Construct the message element from the XML
 1549: sub start_date {
 1550:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1551: 
 1552:     if ($target ne 'helper') {
 1553:         return '';
 1554:     }
 1555: 
 1556:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1557:     $helper->declareVar($paramHash->{'variable'});
 1558:     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
 1559:     $paramHash->{'anytime'} = $token->[2]{'anytime'};
 1560: }
 1561: 
 1562: sub end_date {
 1563:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1564: 
 1565:     if ($target ne 'helper') {
 1566:         return '';
 1567:     }
 1568:     Apache::lonhelper::date->new();
 1569:     return '';
 1570: }
 1571: 
 1572: sub render {
 1573:     my $self = shift;
 1574:     my $result = "";
 1575:     my $var = $self->{'variable'};
 1576: 
 1577:     my $date;
 1578: 
 1579:     my $time=time;
 1580:     my ($anytime,$onclick);
 1581: 
 1582: 
 1583:     # first check VARS for a valid new value from the user
 1584:     # then check DEFAULT_VALUE for a valid default time value
 1585:     # otherwise pick now as reasonably good time
 1586: 
 1587:     if (defined($helper->{VARS}{$var})
 1588: 	&&  $helper->{VARS}{$var} > 0) {
 1589: 	$date = localtime($helper->{VARS}{$var});
 1590:     } elsif (defined($self->{DEFAULT_VALUE})) {
 1591:         my $valueFunc = eval($self->{DEFAULT_VALUE});
 1592:         die('Error in default value code for variable ' . 
 1593:             $self->{'variable'} . ', Perl said: ' . $@) if $@;
 1594:         $time = &$valueFunc($helper, $self);
 1595: 	if (lc($time) eq 'anytime') {
 1596: 	    $anytime=1;
 1597: 	    $date = localtime(time);
 1598: 	    $date->min(0);
 1599: 	} elsif (defined($time) && $time ne 0) {
 1600: 	    $date = localtime($time);
 1601: 	} else {
 1602: 	    # leave date undefined so it'll default to now
 1603: 	}
 1604:     }
 1605: 
 1606:     if (!defined($date)) {
 1607: 	$date = localtime(time);
 1608: 	$date->min(0);
 1609:     }
 1610: 
 1611:     &Apache::lonnet::logthis("date mode ");
 1612: 
 1613:     if ($anytime) {
 1614: 	$onclick = "onclick=\"javascript:updateCheck(this.form,'${var}anytime',false)\"";
 1615:     }
 1616:     # Default date: The current hour.
 1617: 
 1618:     if (defined $self->{ERROR_MSG}) {
 1619:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1620:     }
 1621: 
 1622:     # Month
 1623:     my $i;
 1624:     $result .= "<select $onclick name='${var}month'>\n";
 1625:     for ($i = 0; $i < 12; $i++) {
 1626:         if ($i == $date->mon) {
 1627:             $result .= "<option value='$i' selected='selected'>";
 1628:         } else {
 1629:             $result .= "<option value='$i'>";
 1630:         }
 1631:         $result .= &mt($months[$i]) . "</option>\n";
 1632:     }
 1633:     $result .= "</select>\n";
 1634: 
 1635:     # Day
 1636:     $result .= "<select $onclick name='${var}day'>\n";
 1637:     for ($i = 1; $i < 32; $i++) {
 1638:         if ($i == $date->mday) {
 1639:             $result .= '<option selected="selected">';
 1640:         } else {
 1641:             $result .= '<option>';
 1642:         }
 1643:         $result .= "$i</option>\n";
 1644:     }
 1645:     $result .= "</select>,\n";
 1646: 
 1647:     # Year
 1648:     $result .= "<select $onclick name='${var}year'>\n";
 1649:     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
 1650:         if ($date->year + 1900 == $i) {
 1651:             $result .= "<option selected='selected'>";
 1652:         } else {
 1653:             $result .= "<option>";
 1654:         }
 1655:         $result .= "$i</option>\n";
 1656:     }
 1657:     $result .= "</select>,\n";
 1658: 
 1659:     # Display Hours and Minutes if they are called for
 1660:     if ($self->{'hoursminutes'}) {
 1661: 	# This needs parameterization for times.
 1662: 	my $am = &mt('a.m.');
 1663: 	my $pm = &mt('p.m.');
 1664:         # Build hour
 1665:         $result .= "<select $onclick name='${var}hour'>\n";
 1666:         $result .= "<option " . ($date->hour == 0 ? 'selected="selected" ':'') .
 1667:             " value='0'>" . &mt('midnight') . "</option>\n";
 1668:         for ($i = 1; $i < 12; $i++) {
 1669:             if ($date->hour == $i) {
 1670:                 $result .= "<option selected='selected' value='$i'>$i $am</option>\n";
 1671:             } else {
 1672:                 $result .= "<option value='$i'>$i $am</option>\n";
 1673:             }
 1674:         }
 1675:         $result .= "<option " . ($date->hour == 12 ? 'selected="selected" ':'') .
 1676:             " value='12'>" . &mt('noon') . "</option>\n";
 1677:         for ($i = 13; $i < 24; $i++) {
 1678:             my $printedHour = $i - 12;
 1679:             if ($date->hour == $i) {
 1680:                 $result .= "<option selected='selected' value='$i'>$printedHour $pm</option>\n";
 1681:             } else {
 1682:                 $result .= "<option value='$i'>$printedHour $pm</option>\n";
 1683:             }
 1684:         }
 1685: 
 1686:         $result .= "</select> :\n";
 1687: 
 1688:         $result .= "<select $onclick name='${var}minute'>\n";
 1689: 	my $selected=0;
 1690:         for my $i ((0,15,30,45,59,undef,0..59)) {
 1691:             my $printedMinute = $i;
 1692:             if (defined($i) && $i < 10) {
 1693:                 $printedMinute = "0" . $printedMinute;
 1694:             }
 1695:             if (!$selected && $date->min == $i) {
 1696:                 $result .= "<option selected='selected'>";
 1697: 		$selected=1;
 1698:             } else {
 1699:                 $result .= "<option>";
 1700:             }
 1701:             $result .= "$printedMinute</option>\n";
 1702:         }
 1703:         $result .= "</select>\n";
 1704:     }
 1705:     if ($self->{'anytime'}) {
 1706: 	$result.=(<<CHECK);
 1707: <script type="text/javascript">
 1708: // <!--
 1709:     function updateCheck(form,name,value) {
 1710: 	var checkbox=form[name];
 1711: 	checkbox.checked = value;
 1712:     }
 1713: // -->
 1714: </script>
 1715: CHECK
 1716: 	$result.="&nbsp;or&nbsp;<label><input type='checkbox' ";
 1717: 	if ($anytime) {
 1718: 	    $result.=' checked="checked" '
 1719: 	}
 1720: 	$result.="name='${var}anytime'/>".&mt('Any time').'</label>'
 1721:     }
 1722:     return $result;
 1723: 
 1724: }
 1725: # If a NEXTSTATE was given, switch to it
 1726: sub postprocess {
 1727:     my $self = shift;
 1728:     my $var = $self->{'variable'};
 1729:     if ($env{'form.' . $var . 'anytime'}) {
 1730: 	$helper->{VARS}->{$var} = undef;
 1731:     } else {
 1732: 	my $month = $env{'form.' . $var . 'month'}; 
 1733: 	my $day = $env{'form.' . $var . 'day'}; 
 1734: 	my $year = $env{'form.' . $var . 'year'}; 
 1735: 	my $min = 0; 
 1736: 	my $hour = 0;
 1737: 	if ($self->{'hoursminutes'}) {
 1738: 	    $min = $env{'form.' . $var . 'minute'};
 1739: 	    $hour = $env{'form.' . $var . 'hour'};
 1740: 	}
 1741: 
 1742: 	my $chosenDate;
 1743: 	eval {$chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);};
 1744: 	my $error = $@;
 1745: 
 1746: 	# Check to make sure that the date was not automatically co-erced into a 
 1747: 	# valid date, as we want to flag that as an error
 1748: 	# This happens for "Feb. 31", for instance, which is coerced to March 2 or
 1749: 	# 3, depending on if it's a leap year
 1750: 	my $checkDate = localtime($chosenDate);
 1751: 	
 1752: 	if ($error || $checkDate->mon != $month || $checkDate->mday != $day ||
 1753: 	    $checkDate->year + 1900 != $year) {
 1754: 	    unless (Apache::lonlocal::current_language()== ~/^en/) {
 1755: 		$self->{ERROR_MSG} = &mt("Invalid date entry");
 1756: 		return 0;
 1757: 	    }
 1758: 	    # LOCALIZATION FIXME: Needs to be parameterized
 1759: 	    $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
 1760: 		. "date because it doesn't exist. Please enter a valid date.";
 1761: 
 1762: 	    return 0;
 1763: 	}
 1764: 	$helper->{VARS}->{$var} = $chosenDate;
 1765:     }
 1766: 
 1767:     if (defined($self->{VALIDATOR})) {
 1768: 	my $validator = eval($self->{VALIDATOR});
 1769: 	die 'Died during evaluation of validator code; Perl said: ' . $@ if $@;
 1770: 	my $invalid = &$validator($helper, $state, $self, $self->getValue());
 1771: 	if ($invalid) {
 1772: 	    $self->{ERROR_MSG} = $invalid;
 1773: 	    return 0;
 1774: 	}
 1775:     }
 1776: 
 1777:     if (defined($self->{NEXTSTATE})) {
 1778:         $helper->changeState($self->{NEXTSTATE});
 1779:     }
 1780: 
 1781:     return 1;
 1782: }
 1783: 1;
 1784: 
 1785: package Apache::lonhelper::resource;
 1786: 
 1787: =pod
 1788: 
 1789: =head2 Element: resourceX<resource, helper element>
 1790: 
 1791: <resource> elements allow the user to select one or multiple resources
 1792: from the current course. You can filter out which resources they can view,
 1793: and filter out which resources they can select. The course will always
 1794: be displayed fully expanded, because of the difficulty of maintaining
 1795: selections across folder openings and closings. If this is fixed, then
 1796: the user can manipulate the folders.
 1797: 
 1798: <resource> takes the standard variable attribute to control what helper
 1799: variable stores the results. It also takes a "multichoice"X<multichoice> attribute,
 1800: which controls whether the user can select more then one resource. The 
 1801: "toponly" attribute controls whether the resource display shows just the
 1802: resources in that sequence, or recurses into all sub-sequences, defaulting
 1803: to false. The "suppressEmptySequences" attribute reflects the 
 1804: suppressEmptySequences argument to the render routine, which will cause
 1805: folders that have all of their contained resources filtered out to also
 1806: be filtered out. The 'addstatus' attribute, if true, will add the icon
 1807: and long status display columns to the display. The 'addparts'
 1808: attribute will add in a part selector beside problems that have more
 1809: than 1 part.
 1810: 
 1811: =head3 SUB-TAGS
 1812: 
 1813: =over 4
 1814: 
 1815: =item * <filterfunc>X<filterfunc>: If you want to filter what resources are displayed
 1816:   to the user, use a filter func. The <filterfunc> tag should contain
 1817:   Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
 1818:   a function that returns true if the resource should be displayed, 
 1819:   and false if it should be skipped. $res is a resource object. 
 1820:   (See Apache::lonnavmaps documentation for information about the 
 1821:   resource object.)
 1822: 
 1823: =item * <choicefunc>X<choicefunc>: Same as <filterfunc>, except that controls whether
 1824:   the given resource can be chosen. (It is almost always a good idea to
 1825:   show the user the folders, for instance, but you do not always want to 
 1826:   let the user select them.)
 1827: 
 1828: =item * <nextstate>: Standard nextstate behavior.
 1829: 
 1830: =item * <valuefunc>X<valuefunc>: This function controls what is returned by the resource
 1831:   when the user selects it. Like filterfunc and choicefunc, it should be
 1832:   a function fragment that when wrapped by "sub { my $res = shift; " and
 1833:   "}" returns a string representing what you want to have as the value. By
 1834:   default, the value will be the resource ID of the object ($res->{ID}).
 1835: 
 1836: =item * <mapurl>X<mapurl>: If the URL of a map is given here, only that map
 1837:   will be displayed, instead of the whole course. If the attribute
 1838:   "evaluate" is given and is true, the contents of the mapurl will be
 1839:   evaluated with "sub { my $helper = shift; my $state = shift;" and
 1840:   "}", with the return value used as the mapurl.
 1841: 
 1842: =back
 1843: 
 1844: =cut
 1845: 
 1846: no strict;
 1847: @ISA = ("Apache::lonhelper::element");
 1848: use strict;
 1849: use Apache::lonnet;
 1850: 
 1851: BEGIN {
 1852:     &Apache::lonhelper::register('Apache::lonhelper::resource',
 1853:                               ('resource', 'filterfunc', 
 1854:                                'choicefunc', 'valuefunc',
 1855:                                'mapurl','option'));
 1856: }
 1857: 
 1858: sub new {
 1859:     my $ref = Apache::lonhelper::element->new();
 1860:     bless($ref);
 1861: }
 1862: 
 1863: # CONSTRUCTION: Construct the message element from the XML
 1864: sub start_resource {
 1865:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1866: 
 1867:     if ($target ne 'helper') {
 1868:         return '';
 1869:     }
 1870: 
 1871:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1872:     $helper->declareVar($paramHash->{'variable'});
 1873:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1874:     $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'};
 1875:     $paramHash->{'toponly'} = $token->[2]{'toponly'};
 1876:     $paramHash->{'addstatus'} = $token->[2]{'addstatus'};
 1877:     $paramHash->{'addparts'} = $token->[2]{'addparts'};
 1878:     if ($paramHash->{'addparts'}) {
 1879: 	$helper->declareVar($paramHash->{'variable'}.'_part');
 1880:     }
 1881:     $paramHash->{'closeallpages'} = $token->[2]{'closeallpages'};
 1882:     return '';
 1883: }
 1884: 
 1885: sub end_resource {
 1886:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1887: 
 1888:     if ($target ne 'helper') {
 1889:         return '';
 1890:     }
 1891:     if (!defined($paramHash->{FILTER_FUNC})) {
 1892:         $paramHash->{FILTER_FUNC} = sub {return 1;};
 1893:     }
 1894:     if (!defined($paramHash->{CHOICE_FUNC})) {
 1895:         $paramHash->{CHOICE_FUNC} = sub {return 1;};
 1896:     }
 1897:     if (!defined($paramHash->{VALUE_FUNC})) {
 1898:         $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
 1899:     }
 1900:     Apache::lonhelper::resource->new();
 1901:     return '';
 1902: }
 1903: 
 1904: sub start_filterfunc {
 1905:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1906: 
 1907:     if ($target ne 'helper') {
 1908:         return '';
 1909:     }
 1910: 
 1911:     my $contents = Apache::lonxml::get_all_text('/filterfunc',
 1912:                                                 $parser);
 1913:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1914:     $paramHash->{FILTER_FUNC} = eval $contents;
 1915: }
 1916: 
 1917: sub end_filterfunc { return ''; }
 1918: 
 1919: sub start_choicefunc {
 1920:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1921: 
 1922:     if ($target ne 'helper') {
 1923:         return '';
 1924:     }
 1925: 
 1926:     my $contents = Apache::lonxml::get_all_text('/choicefunc',
 1927:                                                 $parser);
 1928:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1929:     $paramHash->{CHOICE_FUNC} = eval $contents;
 1930: }
 1931: 
 1932: sub end_choicefunc { return ''; }
 1933: 
 1934: sub start_valuefunc {
 1935:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1936: 
 1937:     if ($target ne 'helper') {
 1938:         return '';
 1939:     }
 1940: 
 1941:     my $contents = Apache::lonxml::get_all_text('/valuefunc',
 1942:                                                 $parser);
 1943:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1944:     $paramHash->{VALUE_FUNC} = eval $contents;
 1945: }
 1946: 
 1947: sub end_valuefunc { return ''; }
 1948: 
 1949: sub start_mapurl {
 1950:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1951: 
 1952:     if ($target ne 'helper') {
 1953:         return '';
 1954:     }
 1955: 
 1956:     my $contents = Apache::lonxml::get_all_text('/mapurl',
 1957:                                                 $parser);
 1958:     $paramHash->{EVAL_MAP_URL} = $token->[2]{'evaluate'};
 1959:     $paramHash->{MAP_URL} = $contents;
 1960: }
 1961: 
 1962: sub end_mapurl { return ''; }
 1963: 
 1964: 
 1965: sub start_option {
 1966:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1967:     if (!defined($paramHash->{OPTION_TEXTS})) {
 1968: 	$paramHash->{OPTION_TEXTS} = [ ];
 1969: 	$paramHash->{OPTION_VARS}  = [ ];
 1970: 
 1971:     }
 1972:     # OPTION_TEXTS is a list of the text attribute
 1973:     #               values used to create column headings.
 1974:     # OPTION_VARS is a list of the variable names, used to create the checkbox
 1975:     #             inputs.
 1976:     #  We're ok with empty elements. as place holders
 1977:     # Although the 'variable' element should really exist.
 1978:     #
 1979: 
 1980:     my $option_texts  = $paramHash->{OPTION_TEXTS};
 1981:     my $option_vars   = $paramHash->{OPTION_VARS};
 1982:     push(@$option_texts,  $token->[2]{'text'});
 1983:     push(@$option_vars,   $token->[2]{'variable'});
 1984: 
 1985:     #  Need to create and declare the option variables as well to make them
 1986:     # persistent.
 1987:     #
 1988:     my $varname = $token->[2]{'variable'};
 1989:     $helper->declareVar($varname);
 1990: 
 1991: 
 1992:     return '';
 1993: }
 1994: 
 1995: sub end_option {
 1996:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1997:     return '';
 1998: }
 1999: 
 2000: # A note, in case I don't get to this before I leave.
 2001: # If someone complains about the "Back" button returning them
 2002: # to the previous folder state, instead of returning them to
 2003: # the previous helper state, the *correct* answer is for the helper
 2004: # to keep track of how many times the user has manipulated the folders,
 2005: # and feed that to the history.go() call in the helper rendering routines.
 2006: # If done correctly, the helper itself can keep track of how many times
 2007: # it renders the same states, so it doesn't go in just this state, and
 2008: # you can lean on the browser back button to make sure it all chains
 2009: # correctly.
 2010: # Right now, though, I'm just forcing all folders open.
 2011: 
 2012: sub render {
 2013:     my $self = shift;
 2014:     my $result = "";
 2015:     my $var = $self->{'variable'};
 2016:     my $curVal = $helper->{VARS}->{$var};
 2017: 
 2018:     my $buttons = '';
 2019: 
 2020:     if ($self->{'multichoice'}) {
 2021:         $result = <<SCRIPT;
 2022: <script type="text/javascript">
 2023: // <!--
 2024:     function checkall(value, checkName) {
 2025: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2026:             ele = document.forms.helpform.elements[i];
 2027:             if (ele.name == checkName + '.forminput') {
 2028:                 document.forms.helpform.elements[i].checked=value;
 2029:             }
 2030:         }
 2031:     }
 2032: // -->
 2033: </script>
 2034: SCRIPT
 2035:         my %lt=&Apache::lonlocal::texthash(
 2036: 			'sar'  => "Select All Resources",
 2037: 		        'uar'  => "Unselect All Resources");
 2038: 
 2039:         $buttons = <<BUTTONS;
 2040: <br /> &nbsp;
 2041: <input type="button" onclick="checkall(true, '$var')" value="$lt{'sar'}" />
 2042: <input type="button" onclick="checkall(false, '$var')" value="$lt{'uar'}" />
 2043: <br /> &nbsp;
 2044: BUTTONS
 2045:     }
 2046: 
 2047:     if (defined $self->{ERROR_MSG}) {
 2048:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 2049:     }
 2050: 
 2051:     $result .= $buttons;
 2052: 
 2053:     my $filterFunc     = $self->{FILTER_FUNC};
 2054:     my $choiceFunc     = $self->{CHOICE_FUNC};
 2055:     my $valueFunc      = $self->{VALUE_FUNC};
 2056:     my $multichoice    = $self->{'multichoice'};
 2057:     my $option_vars    = $self->{OPTION_VARS};
 2058:     my $option_texts   = $self->{OPTION_TEXTS};
 2059:     my $addparts       = $self->{'addparts'};
 2060:     my $headings_done  = 0;
 2061: 
 2062:     # Evaluate the map url as needed
 2063:     my $mapUrl;
 2064:     if ($self->{EVAL_MAP_URL}) {
 2065: 	my $mapUrlFunc = eval('sub { my $helper = shift; my $state = shift; ' . 
 2066: 	    $self->{MAP_URL} . '}');
 2067: 	$mapUrl = &$mapUrlFunc($helper, $self);
 2068:     } else {
 2069: 	$mapUrl = $self->{MAP_URL};
 2070:     }
 2071: 
 2072:     my %defaultSymbs;
 2073:     if (defined($self->{DEFAULT_VALUE})) {
 2074:         my $valueFunc = eval($self->{DEFAULT_VALUE});
 2075:         die 'Error in default value code for variable ' . 
 2076:             $self->{'variable'} . ', Perl said: ' . $@ if $@;
 2077:         my @defaultSymbs = &$valueFunc($helper, $self);
 2078: 	if (!$multichoice && @defaultSymbs) { # only allowed 1
 2079: 	    @defaultSymbs = ($defaultSymbs[0]);
 2080: 	}
 2081: 	%defaultSymbs = map { if ($_) {($_,1) } } @defaultSymbs;
 2082: 	delete($defaultSymbs{''});
 2083:     }
 2084: 
 2085:     # Create the composite function that renders the column on the nav map
 2086:     # have to admit any language that lets me do this can't be all bad
 2087:     #  - Jeremy (Pythonista) ;-)
 2088:     my $checked = 0;
 2089:     my $renderColFunc = sub {
 2090:         my ($resource, $part, $params) = @_;
 2091: 	my $result = "";
 2092: 
 2093: 	if(!$headings_done) {
 2094: 	    if ($option_texts) {
 2095: 		foreach my $text (@$option_texts) {
 2096: 		    $result .= "<th>$text</th>";
 2097: 		}
 2098: 	    }
 2099: 	    $result .= "<th>Select</th>";
 2100: 	    $result .= "</tr><tr>"; # Close off the extra row and start a new one.
 2101: 	    $headings_done = 1;
 2102: 	}
 2103: 
 2104:         my $inputType;
 2105:         if ($multichoice) { $inputType = 'checkbox'; }
 2106:         else {$inputType = 'radio'; }
 2107: 
 2108:         if (!&$choiceFunc($resource)) {
 2109: 	    $result .= '<td>&nbsp;</td>';
 2110:             return $result;
 2111:         } else {
 2112: 	    my $col = "";
 2113: 	    my $raw_name = &$valueFunc($resource);
 2114: 	    my $resource_name =   
 2115:                    HTML::Entities::encode($raw_name,"<>&\"'");
 2116: 	    if($option_vars) {
 2117: 		foreach my $option_var (@$option_vars) {
 2118: 		    my $var_value = "\|\|\|" . $helper->{VARS}->{$option_var} . 
 2119: 			"\|\|\|";
 2120: 		    my $checked ="";
 2121: 		    if($var_value =~ /\Q|||$raw_name|||\E/) {
 2122: 			$checked = "checked='checked'";
 2123: 		    }
 2124: 		    $col .= 
 2125:                         "<td align='center'><input type='checkbox' name ='$option_var".
 2126: 			".forminput' value='".
 2127: 			$resource_name . "' $checked /> </td>";
 2128: 		}
 2129: 	    }
 2130: 
 2131:             $col .= "<td align='center'><input type='$inputType' name='${var}.forminput' ";
 2132: 	    if (%defaultSymbs) {
 2133: 		my $symb=$resource->symb();
 2134: 		if (exists($defaultSymbs{$symb})) {
 2135: 		    $col .= "checked='checked' ";
 2136: 		    $checked = 1;
 2137: 		}
 2138: 	    } else {
 2139: 		if (!$checked && !$multichoice) {
 2140: 		    $col .= "checked='checked' ";
 2141: 		    $checked = 1;
 2142: 		}
 2143: 		if ($multichoice) { # all resources start checked; see bug 1174
 2144: 		    $col .= "checked='checked' ";
 2145: 		    $checked = 1;
 2146: 		}
 2147: 	    }
 2148:             $col .= "value='" . $resource_name  . "' /></td>";
 2149: 
 2150:             return $result.$col;
 2151:         }
 2152:     };
 2153:     my $renderPartsFunc = sub {
 2154:         my ($resource, $part, $params) = @_;
 2155: 	my $col= "<td>";
 2156: 	my $id=$resource->{ID};
 2157: 	my $resource_name =   
 2158: 	    &HTML::Entities::encode(&$valueFunc($resource),"<>&\"'");
 2159: 	if ($addparts && (scalar(@{$resource->parts}) > 1)) {
 2160: 	    $col .= "<select onclick=\"javascript:updateRadio(this.form,'${var}.forminput','$resource_name');updateHidden(this.form,'$id','${var}');\" name='part_$id.forminput'>\n";
 2161: 	    $col .= "<option value=\"$part\">All Parts</option>\n";
 2162: 	    foreach my $part (@{$resource->parts}) {
 2163: 		$col .= "<option value=\"$part\">Part: $part</option>\n";
 2164: 	    }
 2165: 	    $col .= "</select>";
 2166: 	}
 2167: 	$col .= "</td>";
 2168:     };
 2169:     $result.=(<<RADIO);
 2170: <script type="text/javascript">
 2171: // <!--
 2172:     function updateRadio(form,name,value) {
 2173: 	var radiobutton=form[name];
 2174: 	for (var i=0; i<radiobutton.length; i++) {
 2175: 	    if (radiobutton[i].value == value) {
 2176: 		radiobutton[i].checked = true;
 2177: 		break;
 2178: 	    }
 2179: 	}
 2180:     }
 2181:     function updateHidden(form,id,name) {
 2182: 	var select=form['part_'+id+'.forminput'];
 2183: 	var hidden=form[name+'_part.forminput'];
 2184: 	var which=select.selectedIndex;
 2185: 	hidden.value=select.options[which].value;
 2186:     }
 2187: // -->
 2188: </script>
 2189: <input type="hidden" name="${var}_part.forminput" />
 2190: 
 2191: RADIO
 2192:     $env{'form.condition'} = !$self->{'toponly'};
 2193:     my $cols = [$renderColFunc];
 2194:     if ($self->{'addparts'}) { push(@$cols, $renderPartsFunc); }
 2195:     push(@$cols, Apache::lonnavmaps::resource());
 2196:     if ($self->{'addstatus'}) {
 2197: 	push @$cols, (Apache::lonnavmaps::part_status_summary());
 2198: 	
 2199:     }
 2200:     $result .= 
 2201:         &Apache::lonnavmaps::render( { 'cols' => $cols,
 2202:                                        'showParts' => 0,
 2203:                                        'filterFunc' => $filterFunc,
 2204:                                        'resource_no_folder_link' => 1,
 2205: 				       'closeAllPages' => $self->{'closeallpages'},
 2206:                                        'suppressEmptySequences' => $self->{'suppressEmptySequences'},
 2207:                                        'iterator_map' => $mapUrl }
 2208:                                        );
 2209: 
 2210:     $result .= $buttons;
 2211:                                                 
 2212:     return $result;
 2213: }
 2214:     
 2215: sub postprocess {
 2216:     my $self = shift;
 2217: 
 2218:     if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) {
 2219:         $self->{ERROR_MSG} = 'You must choose at least one resource to continue.';
 2220:         return 0;
 2221:     }
 2222: 
 2223:     if (defined($self->{NEXTSTATE})) {
 2224:         $helper->changeState($self->{NEXTSTATE});
 2225:     }
 2226: 
 2227:     return 1;
 2228: }
 2229: 
 2230: 1;
 2231: 
 2232: package Apache::lonhelper::student;
 2233: 
 2234: =pod
 2235: 
 2236: =head2 Element: studentX<student, helper element>
 2237: 
 2238: Student elements display a choice of students enrolled in the current
 2239: course. Currently it is primitive; this is expected to evolve later.
 2240: 
 2241: Student elements take the following attributes: 
 2242: 
 2243: =over 4
 2244: 
 2245: =item * B<variable>: 
 2246: 
 2247: Does what it usually does: declare which helper variable to put the
 2248: result in.
 2249: 
 2250: =item * B<multichoice>: 
 2251: 
 2252: If true allows the user to select multiple students. Defaults to false.
 2253: 
 2254: =item * B<coursepersonnel>: 
 2255: 
 2256: If true adds the course personnel to the top of the student
 2257: selection. Defaults to false.
 2258: 
 2259: =item * B<activeonly>:
 2260: 
 2261: If true, only active students and course personnel will be
 2262: shown. Defaults to false.
 2263: 
 2264: =item * B<emptyallowed>:
 2265: 
 2266: If true, the selection of no users is allowed. Defaults to false.
 2267: 
 2268: =back
 2269: 
 2270: =cut
 2271: 
 2272: no strict;
 2273: @ISA = ("Apache::lonhelper::element");
 2274: use strict;
 2275: use Apache::lonlocal;
 2276: use Apache::lonnet;
 2277: 
 2278: #
 2279: #  Utility function used when rendering the <student> tag.
 2280: #  This function renders a segment of course personel
 2281: #  Personel are broken up by the helper into past, current and
 2282: #  future...each one gets is own subpage of selection.
 2283: #  This sub renders one of these pages.
 2284: #  Parameters:
 2285: #     $sections    - Set of sections in the course (hash reference).
 2286: #     $students    - Students in the section. (ref to array of references
 2287: #                    to arrays).
 2288: #     $formprefix  - form path prefix for form element names
 2289: #                    This is used to make each form element
 2290: #                    so that the segments having to do with each
 2291: #                    set of students won't collide.
 2292: #     $defaultusers - reference to a hash containng
 2293: #                     the set of users that should be on or off.
 2294: #  Returns:
 2295: #     HTML  text to add to the rendering of the helper.
 2296: #
 2297: sub render_student_list {
 2298:     my ($self,
 2299: 	$sections, $students, $formprefix, $defaultusers) = @_;
 2300: 
 2301:     my $multiselect = $self->{'multichoice'};
 2302:     my $result = "";
 2303: 
 2304:     # If multiple selections are allowed, we have a listbox
 2305:     # at the top which allows quick selections from each section
 2306:     # as well as from categories of personnel.
 2307: 
 2308:     if ($multiselect) {
 2309: 	$result .= '<table><tr><td>';
 2310: 
 2311: 	my $size = scalar(keys %$sections);
 2312: 	$size += 3;		# We have allstudents allpersonel nosection too.
 2313: 	if ($size > 5) { 
 2314: 	    $size = 5; 
 2315: 	}
 2316: 	$result .= '<select multiple name="'.$formprefix
 2317: 	    .'.chosensections" size="'.$size.'">'."\n";
 2318: 	$result .= '<option name="allstudents">All Students</option>';
 2319: 	$result .= '<option name="allpersonnel">All Course Personnel</option>';
 2320: 	$result .= '<option name="nosection">No Section</option>';
 2321: 	$result .= "\n";
 2322: 	foreach my $sec (sort {lc($a) cmp lc($b)} (keys(%$sections))) {
 2323: 	    $result .= '<option name="'.$sec.'">'.$sec.'</option>'."\n";
 2324: 	}
 2325: 	$result .= '</td><td valign="top">';
 2326: 	$result .= '<input type="button" name="'.$formprefix.'.select" value="Select" onClick='
 2327: 	    ."'selectSections(\"$formprefix.chosensections\")'".' /></td>';
 2328: 	$result .= '<td valign="top"><input type="button" name="'.$formprefix
 2329: 	    .'.unselect" value="Unselect"  onClick='.
 2330: 	    "'unselectSections(\"$formprefix.chosensections\")' ".' /></td></tr></table>';
 2331:     }
 2332: 
 2333:     #  Now we list the students, but the form element type
 2334:     #  will depend on whether or not multiselect is true.
 2335:     #  True -> checkboxes.
 2336:     #  False -> radiobuttons.
 2337: 
 2338:     $result .= "<table border=\"2\">\n";
 2339:     $result .= '<tr><th></th><th align="center">Name</th>'."\n";
 2340:     $result .= '    <th align="center">Section</th>'."\n";
 2341:     $result .= '    <th align="center">Status</th>'."\n";
 2342:     $result .= '    <th align="center">Role</th>'."\n";
 2343:     $result .= '    <th align="center">Username : Domain</th></tr>'."\n";
 2344: 
 2345:     my $input_type;
 2346:     if ($multiselect) {
 2347: 	$input_type = "checkbox";
 2348:     } else {
 2349: 	$input_type = "radio";
 2350:     }
 2351: 
 2352:     my $checked = 0;
 2353:     for my $student (@$students) {
 2354: 	$result .= '<tr><td><input type="'.$input_type.'"  name="'.
 2355: 	    $self->{'variable'}.".forminput".'"';
 2356: 	my $user    = $student->[0];
 2357: 
 2358: 	# Figure out which students are checked by default...
 2359: 	
 2360: 	if(%$defaultusers) {
 2361: 	    if (exists ($defaultusers->{$user})) {
 2362: 		$result .= ' checked ="checked" ';
 2363: 		$checked = 1;
 2364: 	    }
 2365: 	} elsif (!$self->{'multichoice'} && !$checked) {
 2366: 	    $result .= ' checked="checked" ';
 2367: 	    $checked = 1;	# First one for radio if no default specified.
 2368: 	}
 2369: 	$result .= ' value="'. HTML::Entities::encode($user .          ':'
 2370: 						      .$student->[2] . ':'
 2371: 						      .$student->[1] . ':'
 2372: 						      .$student->[3] . ':'
 2373: 						      .$student->[4], "<>&\"'")
 2374: 	    ."\" /></td><td>\n";
 2375: 	$result .= HTML::Entities::encode($student->[1], '<>&"')
 2376: 	        . '</td><td align="center" >'."\n";
 2377: 	$result .= HTML::Entities::encode($student->[2], '<>&"')
 2378:    	        . '</td><td align="center">'."\n";
 2379: 	$result .= HTML::Entities::encode($student->[3], '<>&"')
 2380: 	        . '</td><td align="center">'."\n";
 2381: 	$result .= HTML::Entities::encode($student->[4], '<>&"')
 2382:   	        . '</td><td align="center">'."\n";
 2383: 	$result .= HTML::Entities::encode($student->[0], '<>&"')
 2384: 	        . '</td></tr>'."\n";
 2385:     }
 2386:     $result .=" </table> <br /> <hr />\n";
 2387: 
 2388:     return $result;
 2389: }
 2390: 
 2391: BEGIN {
 2392:     &Apache::lonhelper::register('Apache::lonhelper::student',
 2393:                               ('student'));
 2394: }
 2395: 
 2396: sub new {
 2397:     my $ref = Apache::lonhelper::element->new();
 2398:     bless($ref);
 2399: }
 2400: 
 2401: sub start_student {
 2402:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2403: 
 2404:     if ($target ne 'helper') {
 2405:         return '';
 2406:     }
 2407: 
 2408:     $paramHash->{'variable'} = $token->[2]{'variable'};
 2409:     $helper->declareVar($paramHash->{'variable'});
 2410:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 2411:     $paramHash->{'coursepersonnel'} = $token->[2]{'coursepersonnel'};
 2412:     $paramHash->{'activeonly'} = $token->[2]{'activeonly'};
 2413:     if (defined($token->[2]{'nextstate'})) {
 2414:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
 2415:     }
 2416:     $paramHash->{'emptyallowed'} = $token->[2]{'emptyallowed'};
 2417:     
 2418: }    
 2419: 
 2420: sub end_student {
 2421:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2422: 
 2423:     if ($target ne 'helper') {
 2424:         return '';
 2425:     }
 2426:     Apache::lonhelper::student->new();
 2427: }
 2428: 
 2429: sub render {
 2430:     my $self = shift;
 2431:     my $result = '';
 2432:     my $buttons = '';
 2433:     my $var = $self->{'variable'};
 2434: 
 2435:     if ($self->{'multichoice'}) {
 2436:         $result = <<SCRIPT;
 2437: <script type="text/javascript">
 2438: // <!--
 2439: 
 2440:     function findElement(name) {
 2441: 	var i;
 2442: 	var ele;
 2443: 	for(i =0; i < document.forms.helpform.elements.length; i++) {
 2444: 	    ele = document.forms.helpform.elements[i];
 2445: 	    if(ele.name == name) {
 2446: 		return ele;
 2447: 	    }
 2448: 	}
 2449: 	return null;
 2450:     }
 2451:     function isStudent(element) {
 2452: 	if(element.value.indexOf(":Student") != -1) {
 2453: 	    return 1;
 2454: 	}
 2455: 	return 0;
 2456:     }
 2457:     function section(element) {
 2458: 	var i;
 2459: 	var info;
 2460: 	if (element.value.indexOf(':') != -1) {
 2461: 	    info = element.value.split(':');
 2462: 	    return info[2];
 2463: 	} else {
 2464: 	    return "";
 2465: 	}
 2466:     }
 2467: 
 2468:     function setAllStudents(value) {
 2469: 	var i;
 2470: 	var ele;
 2471: 	for (i =0; i < document.forms.helpform.elements.length; i++) {
 2472: 	    ele = document.forms.helpform.elements[i];
 2473: 	    if(isStudent(ele)) {
 2474: 		ele.checked=value;
 2475: 	    }
 2476: 	}
 2477:     }
 2478:     function setAllCoursePersonnel(value) {
 2479: 	var i;
 2480: 	var ele;
 2481: 	for (i =0; i < document.forms.helpform.elements.length; i++) {
 2482: 	    ele = document.forms.helpform.elements[i];
 2483: 	    if(!isStudent(ele)) {
 2484: 		ele.checked = value;
 2485: 	    }
 2486: 	}
 2487:     }
 2488:     function setSection(which, value) {
 2489: 	var i;
 2490: 	var ele;
 2491: 	for (i =0; i < document.forms.helpform.elements.length; i++) {
 2492: 	    ele = document.forms.helpform.elements[i];
 2493: 	    if (ele.value.indexOf(':') != -1) {
 2494: 		if (section(ele) == which) {
 2495: 		    ele.checked = value;
 2496: 		}
 2497: 	    }
 2498: 	}
 2499:     }
 2500: 
 2501:     function setCheckboxes(listbox, value) {
 2502: 	var k;
 2503: 	var elem;
 2504: 	var what;
 2505:         elem = findElement(listbox);
 2506: 	if (elem != null) {
 2507: 	    for (k = 0; k < elem.length; k++) {
 2508: 		if (elem.options[k].selected) {
 2509: 		    what = elem.options[k].text;
 2510: 		    if (what == 'All Students') {
 2511: 			setAllStudents(value);
 2512: 		    } else if (what == 'All Course Personnel') {
 2513: 			setAllCoursePersonnel(value);
 2514: 		    } else if (what == 'No Section') {
 2515: 			setSection('',value);
 2516: 		    } else {
 2517: 			setSection(what, value);
 2518: 		    }
 2519: 		}
 2520: 	    }
 2521: 	}
 2522:     }
 2523:     function selectSections(listbox) {
 2524: 	setCheckboxes(listbox, true);
 2525: 
 2526:     }
 2527:     function unselectSections(listbox) {
 2528: 	setCheckboxes(listbox, false);
 2529:     }
 2530:     /* ----------------------------- */
 2531:     
 2532:     function checkall(value, checkName) {
 2533: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2534:             ele = document.forms.helpform.elements[i];
 2535:             if (ele.name == checkName + '.forminput') {
 2536:                 document.forms.helpform.elements[i].checked=value;
 2537:             }
 2538:         }
 2539:     }
 2540:     function checksec(value) {
 2541: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2542: 	    comp = document.forms.helpform.elements.chksec.value;
 2543:             if (document.forms.helpform.elements[i].value.indexOf(':'+comp+':') != -1) {
 2544: 		if (document.forms.helpform.elements[i].value.indexOf(':Active') != -1) {
 2545: 		    document.forms.helpform.elements[i].checked=value;
 2546: 		}
 2547:             }
 2548:         }
 2549:     }
 2550:     function checkactive() {
 2551: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2552:             if (document.forms.helpform.elements[i].value.indexOf(':Active') != -1) {
 2553:                 document.forms.helpform.elements[i].checked=true;
 2554:             } 
 2555:         }
 2556:     }
 2557:     function checkexpired()  {
 2558: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2559:             if (document.forms.helpform.elements[i].value.indexOf(':Expired') != -1) {
 2560:                 document.forms.helpform.elements[i].checked=true;
 2561:             } 
 2562:         }
 2563:     }
 2564:     function uncheckexpired() {
 2565: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2566:             if (document.forms.helpform.elements[i].value.indexOf(':Expired') != -1) {
 2567:                 document.forms.helpform.elements[i].checked=false;
 2568:             } 
 2569:         }
 2570:     }
 2571:     function getDesiredState() {     // Return desired person state radio value.
 2572:         numRadio = document.forms.helpform.personstate.length;
 2573:         for (i =0; i < numRadio; i++) {
 2574: 	    if (document.forms.helpform.personstate[i].checked) {
 2575:                 return document.forms.helpform.personstate[i].value;
 2576:             }
 2577:         }
 2578:         return "";
 2579:     }
 2580: 
 2581:     function checksections(value) {    // Check selected sections.
 2582:         numSections  = document.forms.helpform.chosensections.length;
 2583: 	desiredState = getDesiredState();
 2584: 
 2585: 	for (var option = 0; option < numSections; option++) {
 2586: 	    if(document.forms.helpform.chosensections.options[option].selected) {
 2587: 		section = document.forms.helpform.chosensections.options[option].text;
 2588: 		if (section == "none") {
 2589: 		    section ="";
 2590: 		}
 2591: 		for (i = 0; i < document.forms.helpform.elements.length; i++ ) {
 2592: 		    if (document.forms.helpform.elements[i].value.indexOf(':') != -1) {
 2593: 			info = document.forms.helpform.elements[i].value.split(':');
 2594: 			hisSection = info[2];
 2595: 			hisState   = info[4];
 2596: 			if (desiredState == hisState ||
 2597: 			    desiredState == "All") {
 2598: 			    if(hisSection == section ||
 2599: 			       section =="" ) {
 2600: 				document.forms.helpform.elements[i].checked = value;
 2601: 			    }
 2602: 			}
 2603: 		    }
 2604: 		}
 2605:             }
 2606: 	}
 2607: 				   }
 2608: // -->
 2609: </script>
 2610: SCRIPT
 2611: 
 2612:         my %lt=&Apache::lonlocal::texthash(
 2613:                     'ocs'  => "Select Only Current Students",
 2614:                     'ues'  => "Unselect Expired Students",
 2615:                     'sas'  => "Select All Students",
 2616:                     'uas'  => "Unselect All Students",
 2617:                     'sfsg' => "Select Current Students for Section/Group",
 2618: 		    'ufsg' => "Unselect for Section/Group");
 2619:  
 2620:         $buttons = <<BUTTONS;
 2621: <br />
 2622: <table>
 2623:   
 2624:   <tr>
 2625:      <td><input type="button" onclick="checkall(true, '$var')" value="$lt{'sas'}" /></td>
 2626:      <td> <input type="button" onclick="checkall(false, '$var')" value="$lt{'uas'}" /><br /></td>
 2627:   </tr>
 2628:   
 2629: </table>
 2630: <br />
 2631: BUTTONS
 2632: #    $result .= $buttons;   
 2633: 
 2634: }
 2635: 
 2636:     if (defined $self->{ERROR_MSG}) {
 2637:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 2638:     }
 2639: 
 2640:     my %defaultUsers;
 2641:     if (defined($self->{DEFAULT_VALUE})) {
 2642:         my $valueFunc = eval($self->{DEFAULT_VALUE});
 2643:         die 'Error in default value code for variable ' . 
 2644:             $self->{'variable'} . ', Perl said: ' . $@ if $@;
 2645:         my @defaultUsers = &$valueFunc($helper, $self);
 2646: 	if (!$self->{'multichoice'} && @defaultUsers) { # only allowed 1
 2647: 	    @defaultUsers = ($defaultUsers[0]);
 2648: 	}
 2649: 	%defaultUsers = map { if ($_) {($_,1) } } @defaultUsers;
 2650: 	delete($defaultUsers{''});
 2651:     }
 2652: 
 2653: 
 2654: 
 2655:     # my $choices = [];
 2656: 
 2657:     #
 2658:     #  We need to parcel out the personel in to three arrays:
 2659:     #   $current_members[] - Contains those whose roles are currently active.
 2660:     #   $expired_members[] - Contains those whose roles have expired.
 2661:     #   $future_members[]  - Contains those whose roles will become active in the
 2662:     #                        future.
 2663:     #
 2664:     # Constants
 2665:     my $section    = &Apache::loncoursedata::CL_SECTION();
 2666:     my $fullname   = &Apache::loncoursedata::CL_FULLNAME();
 2667:     my $status     = &Apache::loncoursedata::CL_STATUS();
 2668:     my $start_date = &Apache::loncoursedata::CL_START();
 2669: 
 2670:     my $current_members = [];
 2671:     my $expired_members = [];
 2672:     my $future_members  = [];
 2673: 
 2674: 
 2675:     # Load up the non-students, if necessary
 2676:     if ($self->{'coursepersonnel'}) {
 2677: 	my %coursepersonnel = Apache::lonnet::get_course_adv_roles();
 2678: 	for (sort keys %coursepersonnel) {
 2679: 	    for my $role (split /,/, $coursepersonnel{$_}) {
 2680: 		# extract the names so we can sort them
 2681: 		my @people;
 2682: 		
 2683: 		for (split /,/, $role) {
 2684: 		    push @people, [split /:/, $role];
 2685: 		}
 2686: 		
 2687: 		@people = sort { $a->[0] cmp $b->[0] } @people;
 2688: 		
 2689: 		for my $person (@people) {
 2690: 		    push @$current_members, [join(':', @$person), $person->[0], '', $_];
 2691: 		}
 2692: 	    }
 2693: 	}
 2694:     }
 2695: 
 2696: 
 2697:     # Load up the students
 2698:     my $classlist = &Apache::loncoursedata::get_classlist();
 2699:     my @keys = keys %{$classlist};
 2700:     # Sort by: Section, name
 2701:     @keys = sort {
 2702:         if ($classlist->{$a}->[$section] ne $classlist->{$b}->[$section]) {
 2703:             return $classlist->{$a}->[$section] cmp $classlist->{$b}->[$section];
 2704:         }
 2705:         return $classlist->{$a}->[$fullname] cmp $classlist->{$b}->[$fullname];
 2706:     } @keys;
 2707:  
 2708: 
 2709: 
 2710: 
 2711:     for (@keys) {
 2712: 
 2713: 	if ( $classlist->{$_}->[$status] eq
 2714: 	    'Active') {
 2715: 	    push @$current_members, [$_, $classlist->{$_}->[$fullname], 
 2716: 			     $classlist->{$_}->[$section],
 2717: 			     $classlist->{$_}->[$status], 'Student'];
 2718: 	} else {
 2719: 	    #  Need to figure out if this user is future or
 2720: 	    #  Expired... If the start date is in the future
 2721: 	    #  the user is future...else expired.
 2722: 	    
 2723: 	    my $now = time;
 2724: 	    if ($classlist->{$_}->[$start_date] > $now) {
 2725: 		push @$future_members, [$_, $classlist->{$_}->[$fullname],
 2726: 					$classlist->{$_}->[$section],
 2727: 					"Future", "Student"];
 2728: 	    } else {
 2729: 		push @$expired_members, [$_, $classlist->{$_}->[$fullname],
 2730: 					$classlist->{$_}->[$section],
 2731: 					"Expired", "Student"];
 2732: 	    }
 2733: 
 2734: 	}
 2735:     }
 2736: 
 2737: 
 2738:     # Create a list of the sections that can be used to create the section 
 2739:     # selection list boxes:
 2740:     #
 2741:     my %sections;
 2742:     for my $key (@keys) {
 2743: 	my $section_name = $classlist->{$key}->[$section];
 2744: 	if ($section_name ne "") {
 2745: 	    $sections{$section_name} = 1;
 2746: 	}
 2747:     }
 2748: 
 2749: 
 2750:     if ($self->{'multichoice'}) {
 2751: 
 2752: 	#  The variable $choice_widget will have the html to make the choice 
 2753: 	#  selector.
 2754: 	my $size=5;
 2755: 	if (scalar(keys(%sections)) < 5) {
 2756: 	    $size=scalar(keys(%sections));
 2757: 	}
 2758: 	my $result = '<select multiple name="chosensections" size="'.$size.'">'."\n";
 2759: 	foreach my $sec (sort {lc($a) cmp lc($b)} (keys(%sections))) {
 2760: 	    $result .= "<option name=\"$sec\">$sec</option>\n";
 2761: 	}
 2762: 	$result .= "<option>none</option></select>\n";
 2763: 
 2764: 
 2765:     }
 2766: 
 2767:     #   Current personel
 2768: 
 2769:     $result .= $self->render_student_list(\%sections,
 2770: 					  $current_members,
 2771: 					  "current",
 2772: 					  \%defaultUsers);
 2773: 
 2774: 
 2775:     # If activeonly is not set then we can also give the expired students:
 2776:     #
 2777:     if (!$self->{'activeonly'} && ((scalar @$expired_members) > 0)) {
 2778: 
 2779: 	# Past 
 2780: 
 2781: 	$result .= $self->render_student_list(\%sections,
 2782: 					      $expired_members,
 2783: 					      "past",
 2784: 					      \%defaultUsers);
 2785: 
 2786: 	# And future.
 2787: 
 2788: 	$result .= $self->render_student_list(\%sections,
 2789: 					      $future_members,
 2790: 					      "future",
 2791: 					      \%defaultUsers);
 2792:     }
 2793: 
 2794: 
 2795: 
 2796:     return $result;
 2797: }
 2798: 
 2799: sub postprocess {
 2800:     my $self = shift;
 2801: 
 2802:     my $result = $env{'form.' . $self->{'variable'} . '.forminput'};
 2803:     if (!$result && !$self->{'emptyallowed'}) {
 2804: 	if ($self->{'coursepersonnel'}) {
 2805: 	    $self->{ERROR_MSG} = 
 2806: 		&mt('You must choose at least one user to continue.');
 2807: 	} else {
 2808: 	    $self->{ERROR_MSG} = 
 2809: 		&mt('You must choose at least one student to continue.');
 2810: 	}
 2811:         return 0;
 2812:     }
 2813: 
 2814:     if (defined($self->{NEXTSTATE})) {
 2815:         $helper->changeState($self->{NEXTSTATE});
 2816:     }
 2817: 
 2818:     return 1;
 2819: }
 2820: 
 2821: 1;
 2822: 
 2823: package Apache::lonhelper::files;
 2824: 
 2825: =pod
 2826: 
 2827: =head2 Element: filesX<files, helper element>
 2828: 
 2829: files allows the users to choose files from a given directory on the
 2830: server. It is always multichoice and stores the result as a triple-pipe
 2831: delimited entry in the helper variables. 
 2832: 
 2833: Since it is extremely unlikely that you can actually code a constant
 2834: representing the directory you wish to allow the user to search, <files>
 2835: takes a subroutine that returns the name of the directory you wish to
 2836: have the user browse.
 2837: 
 2838: files accepts the attribute "variable" to control where the files chosen
 2839: are put. It accepts the attribute "multichoice" as the other attribute,
 2840: defaulting to false, which if true will allow the user to select more
 2841: then one choice. 
 2842: 
 2843: <files> accepts three subtags: 
 2844: 
 2845: =over 4
 2846: 
 2847: =item * B<nextstate>: works as it does with the other tags. 
 2848: 
 2849: =item * B<filechoice>: When the contents of this tag are surrounded by
 2850:     "sub {" and "}", will return a string representing what directory
 2851:     on the server to allow the user to choose files from. 
 2852: 
 2853: =item * B<filefilter>: Should contain Perl code that when surrounded
 2854:     by "sub { my $filename = shift; " and "}", returns a true value if
 2855:     the user can pick that file, or false otherwise. The filename
 2856:     passed to the function will be just the name of the file, with no
 2857:     path info. By default, a filter function will be used that will
 2858:     mask out old versions of files. This function is available as
 2859:     Apache::lonhelper::files::not_old_version if you want to use it to
 2860:     composite your own filters.
 2861: 
 2862: =back
 2863: 
 2864: B<General security note>: You should ensure the user can not somehow 
 2865: pass something into your code that would allow them to look places 
 2866: they should not be able to see, like the C</etc/> directory. However,
 2867: the security impact would be minimal, since it would only expose
 2868: the existence of files, there should be no way to parlay that into
 2869: viewing the files. 
 2870: 
 2871: =cut
 2872: 
 2873: no strict;
 2874: @ISA = ("Apache::lonhelper::element");
 2875: use strict;
 2876: use Apache::lonlocal;
 2877: use Apache::lonnet;
 2878: use Apache::lonpubdir; # for getTitleString
 2879: 
 2880: BEGIN {
 2881:     &Apache::lonhelper::register('Apache::lonhelper::files',
 2882:                                  ('files', 'filechoice', 'filefilter'));
 2883: }
 2884: 
 2885: sub not_old_version {
 2886:     my $file = shift;
 2887:     
 2888:     # Given a file name, return false if it is an "old version" of a
 2889:     # file, or true if it is not.
 2890: 
 2891:     if ($file =~ /^.*\.[0-9]+\.[A-Za-z]+(\.meta)?$/) {
 2892: 	return 0;
 2893:     }
 2894:     return 1;
 2895: }
 2896: 
 2897: sub new {
 2898:     my $ref = Apache::lonhelper::element->new();
 2899:     bless($ref);
 2900: }
 2901: 
 2902: sub start_files {
 2903:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2904: 
 2905:     if ($target ne 'helper') {
 2906:         return '';
 2907:     }
 2908:     $paramHash->{'variable'} = $token->[2]{'variable'};
 2909:     $helper->declareVar($paramHash->{'variable'});
 2910:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 2911: }    
 2912: 
 2913: sub end_files {
 2914:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2915: 
 2916:     if ($target ne 'helper') {
 2917:         return '';
 2918:     }
 2919:     if (!defined($paramHash->{FILTER_FUNC})) {
 2920:         $paramHash->{FILTER_FUNC} = sub { return 1; };
 2921:     }
 2922:     Apache::lonhelper::files->new();
 2923: }    
 2924: 
 2925: sub start_filechoice {
 2926:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2927: 
 2928:     if ($target ne 'helper') {
 2929:         return '';
 2930:     }
 2931:     $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
 2932:                                                               $parser);
 2933: }
 2934: 
 2935: sub end_filechoice { return ''; }
 2936: 
 2937: sub start_filefilter {
 2938:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 2939: 
 2940:     if ($target ne 'helper') {
 2941:         return '';
 2942:     }
 2943: 
 2944:     my $contents = Apache::lonxml::get_all_text('/filefilter',
 2945:                                                 $parser);
 2946:     $contents = 'sub { my $filename = shift; ' . $contents . '}';
 2947:     $paramHash->{FILTER_FUNC} = eval $contents;
 2948: }
 2949: 
 2950: sub end_filefilter { return ''; }
 2951: 
 2952: { 
 2953:     # used to generate unique id attributes for <input> tags. 
 2954:     # internal use only.
 2955:     my $id=0;
 2956:     sub new_id { return $id++;}
 2957: }
 2958: 
 2959: sub render {
 2960:     my $self = shift;
 2961:     my $result = '';
 2962:     my $var = $self->{'variable'};
 2963:     
 2964:     my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
 2965:     die 'Error in resource filter code for variable ' . 
 2966:         {'variable'} . ', Perl said:' . $@ if $@;
 2967: 
 2968:     my $subdir = &$subdirFunc();
 2969: 
 2970:     my $filterFunc = $self->{FILTER_FUNC};
 2971:     if (!defined($filterFunc)) {
 2972: 	$filterFunc = &not_old_version;
 2973:     }
 2974:     my $buttons = '';
 2975:     my $type = 'radio';
 2976:     if ($self->{'multichoice'}) {
 2977:         $type = 'checkbox';
 2978:     }
 2979: 
 2980:     if ($self->{'multichoice'}) {
 2981:         $result = <<SCRIPT;
 2982: <script type="text/javascript">
 2983: // <!--
 2984:     function checkall(value, checkName) {
 2985: 	for (i=0; i<document.forms.helpform.elements.length; i++) {
 2986:             ele = document.forms.helpform.elements[i];
 2987:             if (ele.name == checkName + '.forminput') {
 2988:                 document.forms.helpform.elements[i].checked=value;
 2989:             }
 2990:         }
 2991:     }
 2992: 
 2993:     function checkallclass(value, className) {
 2994:         for (i=0; i<document.forms.helpform.elements.length; i++) {
 2995:             ele = document.forms.helpform.elements[i];
 2996:             if (ele.type == "$type" && ele.onclick) {
 2997:                 document.forms.helpform.elements[i].checked=value;
 2998:             }
 2999:         }
 3000:     }
 3001: // -->
 3002: </script>
 3003: SCRIPT
 3004:        my %lt=&Apache::lonlocal::texthash(
 3005: 			'saf'  => "Select All Files",
 3006: 		        'uaf'  => "Unselect All Files");
 3007:        $buttons = <<BUTTONS;
 3008: <br /> &nbsp;
 3009: <input type="button" onclick="checkall(true, '$var')" value="$lt{'saf'}" />
 3010: <input type="button" onclick="checkall(false, '$var')" value="$lt{'uaf'}" />
 3011: BUTTONS
 3012: 
 3013:        %lt=&Apache::lonlocal::texthash(
 3014: 			'sap'  => "Select All Published",
 3015: 		        'uap'  => "Unselect All Published");
 3016:         if ($helper->{VARS}->{'construction'}) {
 3017:        $buttons .= <<BUTTONS;
 3018: <input type="button" onclick="checkallclass(true, 'Published')" value="$lt{'sap'}" />
 3019: <input type="button" onclick="checkallclass(false, 'Published')" value="$lt{'uap'}" />
 3020: <br /> &nbsp;
 3021: BUTTONS
 3022:        }
 3023:     }
 3024: 
 3025:     # Get the list of files in this directory.
 3026:     my @fileList;
 3027: 
 3028:     # If the subdirectory is in local CSTR space
 3029:     my $metadir;
 3030:     if ($subdir =~ m|/home/([^/]+)/public_html/(.*)|) {
 3031: 	my ($user,$domain)= 
 3032: 	    &Apache::loncacc::constructaccess($subdir,
 3033: 				     $Apache::lonnet::perlvar{'lonDefDomain'});
 3034: 	$metadir='/res/'.$domain.'/'.$user.'/'.$2;
 3035:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 3036:     } elsif ($subdir =~ m|^~([^/]+)/(.*)$|) {
 3037: 	$subdir='/home/'.$1.'/public_html/'.$2;
 3038: 	my ($user,$domain)= 
 3039: 	    &Apache::loncacc::constructaccess($subdir,
 3040: 				     $Apache::lonnet::perlvar{'lonDefDomain'});
 3041: 	$metadir='/res/'.$domain.'/'.$user.'/'.$2;
 3042:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 3043:     } else {
 3044:         # local library server resource space
 3045:         @fileList = &Apache::lonnet::dirlist($subdir, $env{'user.domain'}, $env{'user.name'}, '');
 3046:     }
 3047: 
 3048:     # Sort the fileList into order
 3049:     @fileList = sort {lc($a) cmp lc($b)} @fileList;
 3050: 
 3051:     $result .= $buttons;
 3052: 
 3053:     if (defined $self->{ERROR_MSG}) {
 3054:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 3055:     }
 3056: 
 3057:     $result .= '<table border="0" cellpadding="2" cellspacing="0">';
 3058: 
 3059:     # Keeps track if there are no choices, prints appropriate error
 3060:     # if there are none. 
 3061:     my $choices = 0;
 3062:     # Print each legitimate file choice.
 3063:     for my $file (@fileList) {
 3064:         $file = (split(/&/, $file))[0];
 3065:         if ($file eq '.' || $file eq '..') {
 3066:             next;
 3067:         }
 3068:         my $fileName = $subdir .'/'. $file;
 3069:         if (&$filterFunc($file)) {
 3070: 	    my $status;
 3071: 	    my $color;
 3072: 	    if ($helper->{VARS}->{'construction'}) {
 3073: 		($status, $color) = @{fileState($subdir, $file)};
 3074: 	    } else {
 3075: 		$status = '';
 3076: 		$color = '';
 3077: 	    }
 3078: 
 3079:             # Get the title
 3080:             my $title = Apache::lonpubdir::getTitleString(($metadir?$metadir:$subdir) .'/'. $file);
 3081: 
 3082:             # Netscape 4 is stupid and there's nowhere to put the
 3083:             # information on the input tag that the file is Published,
 3084:             # Unpublished, etc. In *real* browsers we can just say
 3085:             # "class='Published'" and check the className attribute of
 3086:             # the input tag, but Netscape 4 is too stupid to understand
 3087:             # that attribute, and un-comprehended attributes are not
 3088:             # reflected into the object model. So instead, what I do 
 3089:             # is either have or don't have an "onclick" handler that 
 3090:             # does nothing, give Published files the onclick handler, and
 3091:             # have the checker scripts check for that. Stupid and clumsy,
 3092:             # and only gives us binary "yes/no" information (at least I
 3093:             # couldn't figure out how to reach into the event handler's
 3094:             # actual code to retreive a value), but it works well enough
 3095:             # here.
 3096:         
 3097:             my $onclick = '';
 3098:             if ($status eq 'Published' && $helper->{VARS}->{'construction'}) {
 3099:                 $onclick = 'onclick="a=1" ';
 3100:             }
 3101:             my $id = &new_id();
 3102:             $result .= '<tr><td align="right"' . " bgcolor='$color'>" .
 3103:                 "<input $onclick type='$type' name='" . $var
 3104:             . ".forminput' ".qq{id="$id"}." value='" . HTML::Entities::encode($fileName,"<>&\"'").
 3105:                 "'";
 3106:             if (!$self->{'multichoice'} && $choices == 0) {
 3107:                 $result .= ' checked="checked"';
 3108:             }
 3109:             $result .= "/></td><td bgcolor='$color'>".
 3110:                 qq{<label for="$id">}. $file . "</label></td>" .
 3111:                 "<td bgcolor='$color'>$title</td>" .
 3112:                 "<td bgcolor='$color'>$status</td>" . "</tr>\n";
 3113:             $choices++;
 3114:         }
 3115:     }
 3116: 
 3117:     $result .= "</table>\n";
 3118: 
 3119:     if (!$choices) {
 3120:         $result .= '<font color="#FF0000">There are no files available to select in this directory ('.$subdir.'). Please go back and select another option.</font><br /><br />';
 3121:     }
 3122: 
 3123:     $result .= $buttons;
 3124: 
 3125:     return $result;
 3126: }
 3127: 
 3128: # Determine the state of the file: Published, unpublished, modified.
 3129: # Return the color it should be in and a label as a two-element array
 3130: # reference.
 3131: # Logic lifted from lonpubdir.pm, even though I don't know that it's still
 3132: # the most right thing to do.
 3133: 
 3134: sub fileState {
 3135:     my $constructionSpaceDir = shift;
 3136:     my $file = shift;
 3137:     
 3138:     my ($uname,$udom)=($env{'user.name'},$env{'user.domain'});
 3139:     if ($env{'request.role'}=~/^ca\./) {
 3140: 	(undef,$udom,$uname)=split(/\//,$env{'request.role'});
 3141:     }
 3142:     my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
 3143:     my $subdirpart = $constructionSpaceDir;
 3144:     $subdirpart =~ s/^\/home\/$uname\/public_html//;
 3145:     my $resdir = $docroot . '/res/' . $udom . '/' . $uname .
 3146:         $subdirpart;
 3147: 
 3148:     my @constructionSpaceFileStat = stat($constructionSpaceDir . '/' . $file);
 3149:     my @resourceSpaceFileStat = stat($resdir . '/' . $file);
 3150:     if (!@resourceSpaceFileStat) {
 3151:         return ['Unpublished', '#FFCCCC'];
 3152:     }
 3153: 
 3154:     my $constructionSpaceFileModified = $constructionSpaceFileStat[9];
 3155:     my $resourceSpaceFileModified = $resourceSpaceFileStat[9];
 3156:     
 3157:     if ($constructionSpaceFileModified > $resourceSpaceFileModified) {
 3158:         return ['Modified', '#FFFFCC'];
 3159:     }
 3160:     return ['Published', '#CCFFCC'];
 3161: }
 3162: 
 3163: sub postprocess {
 3164:     my $self = shift;
 3165:     my $result = $env{'form.' . $self->{'variable'} . '.forminput'};
 3166:     if (!$result) {
 3167:         $self->{ERROR_MSG} = 'You must choose at least one file '.
 3168:             'to continue.';
 3169:         return 0;
 3170:     }
 3171: 
 3172:     if (defined($self->{NEXTSTATE})) {
 3173:         $helper->changeState($self->{NEXTSTATE});
 3174:     }
 3175: 
 3176:     return 1;
 3177: }
 3178: 
 3179: 1;
 3180: 
 3181: package Apache::lonhelper::section;
 3182: 
 3183: =pod
 3184: 
 3185: =head2 Element: sectionX<section, helper element>
 3186: 
 3187: <section> allows the user to choose one or more sections from the current
 3188: course.
 3189: 
 3190: It takes the standard attributes "variable", "multichoice",
 3191: "allowempty" and "nextstate", meaning what they do for most other
 3192: elements.
 3193: 
 3194: also takes a boolean 'onlysections' whcih will restrict this to only
 3195: have sections and not include groups
 3196: 
 3197: =cut
 3198: 
 3199: no strict;
 3200: @ISA = ("Apache::lonhelper::choices");
 3201: use strict;
 3202: 
 3203: BEGIN {
 3204:     &Apache::lonhelper::register('Apache::lonhelper::section',
 3205:                                  ('section'));
 3206: }
 3207: 
 3208: sub new {
 3209:     my $ref = Apache::lonhelper::choices->new();
 3210:     bless($ref);
 3211: }
 3212: 
 3213: sub start_section {
 3214:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3215: 
 3216:     if ($target ne 'helper') {
 3217:         return '';
 3218:     }
 3219: 
 3220:     $paramHash->{CHOICES} = [];
 3221: 
 3222:     $paramHash->{'variable'} = $token->[2]{'variable'};
 3223:     $helper->declareVar($paramHash->{'variable'});
 3224:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 3225:     $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
 3226:     if (defined($token->[2]{'nextstate'})) {
 3227:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
 3228:     }
 3229: 
 3230:     # Populate the CHOICES element
 3231:     my %choices;
 3232: 
 3233:     my $section = Apache::loncoursedata::CL_SECTION();
 3234:     my $classlist = Apache::loncoursedata::get_classlist();
 3235:     foreach (keys %$classlist) {
 3236:         my $sectionName = $classlist->{$_}->[$section];
 3237:         if (!$sectionName) {
 3238:             $choices{"No section assigned"} = "";
 3239:         } else {
 3240:             $choices{$sectionName} = $sectionName;
 3241:         }
 3242:     } 
 3243:    
 3244:     for my $sectionName (sort(keys(%choices))) {
 3245: 	push @{$paramHash->{CHOICES}}, [$sectionName, $sectionName];
 3246:     }
 3247:     return if ($token->[2]{'onlysections'});
 3248: 
 3249:     # add in groups to the end of the list
 3250:     my %curr_groups;
 3251:     if (&Apache::loncommon::coursegroups(\%curr_groups)) {
 3252: 	foreach my $group_name (sort(keys(%curr_groups))) {
 3253: 	    push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]);
 3254: 	}
 3255:     }
 3256: }    
 3257: 
 3258: sub end_section {
 3259:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3260: 
 3261:     if ($target ne 'helper') {
 3262:         return '';
 3263:     }
 3264:     Apache::lonhelper::section->new();
 3265: }    
 3266: 1;
 3267: 
 3268: package Apache::lonhelper::group;
 3269: 
 3270: =pod
 3271:  
 3272: =head2 Element: groupX<group, helper element>
 3273:  
 3274: <group> allows the user to choose one or more groups from the current course.
 3275: 
 3276: It takes the standard attributes "variable", "multichoice",
 3277:  "allowempty" and "nextstate", meaning what they do for most other
 3278:  elements.
 3279: 
 3280: =cut
 3281: 
 3282: no strict;
 3283: @ISA = ("Apache::lonhelper::choices");
 3284: use strict;
 3285: 
 3286: BEGIN {
 3287:     &Apache::lonhelper::register('Apache::lonhelper::group',
 3288:                                  ('group'));
 3289: }
 3290: 
 3291: sub new {
 3292:     my $ref = Apache::lonhelper::choices->new();
 3293:     bless($ref);
 3294: }
 3295:  
 3296: sub start_group {
 3297:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3298:  
 3299:     if ($target ne 'helper') {
 3300:         return '';
 3301:     }
 3302: 
 3303:     $paramHash->{CHOICES} = [];
 3304: 
 3305:     $paramHash->{'variable'} = $token->[2]{'variable'};
 3306:     $helper->declareVar($paramHash->{'variable'});
 3307:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 3308:     $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
 3309:     if (defined($token->[2]{'nextstate'})) {
 3310:         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
 3311:     }
 3312: 
 3313:     # Populate the CHOICES element
 3314:     my %choices;
 3315: 
 3316:     my %curr_groups;
 3317:     if (&Apache::loncommon::coursegroups(\%curr_groups)) {
 3318: 	foreach my $group_name (sort(keys(%curr_groups))) {
 3319: 	    push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]);
 3320: 	}
 3321:     }
 3322: }
 3323: 
 3324: sub end_group {
 3325:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3326: 
 3327:     if ($target ne 'helper') {
 3328:         return '';
 3329:     }
 3330:     Apache::lonhelper::group->new();
 3331: }
 3332: 1;
 3333: 
 3334: package Apache::lonhelper::string;
 3335: 
 3336: =pod
 3337: 
 3338: =head2 Element: stringX<string, helper element>
 3339: 
 3340: string elements provide a string entry field for the user. string elements
 3341: take the usual 'variable' and 'nextstate' parameters. string elements
 3342: also pass through 'maxlength' and 'size' attributes to the input tag.
 3343: 
 3344: string honors the defaultvalue tag, if given.
 3345: 
 3346: string honors the validation function, if given.
 3347: 
 3348: =cut
 3349: 
 3350: no strict;
 3351: @ISA = ("Apache::lonhelper::element");
 3352: use strict;
 3353: use Apache::lonlocal;
 3354: 
 3355: BEGIN {
 3356:     &Apache::lonhelper::register('Apache::lonhelper::string',
 3357:                               ('string'));
 3358: }
 3359: 
 3360: sub new {
 3361:     my $ref = Apache::lonhelper::element->new();
 3362:     bless($ref);
 3363: }
 3364: 
 3365: # CONSTRUCTION: Construct the message element from the XML
 3366: sub start_string {
 3367:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3368: 
 3369:     if ($target ne 'helper') {
 3370:         return '';
 3371:     }
 3372: 
 3373:     $paramHash->{'variable'} = $token->[2]{'variable'};
 3374:     $helper->declareVar($paramHash->{'variable'});
 3375:     $paramHash->{'nextstate'} = $token->[2]{'nextstate'};
 3376:     $paramHash->{'maxlength'} = $token->[2]{'maxlength'};
 3377:     $paramHash->{'size'} = $token->[2]{'size'};
 3378: 
 3379:     return '';
 3380: }
 3381: 
 3382: sub end_string {
 3383:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3384: 
 3385:     if ($target ne 'helper') {
 3386:         return '';
 3387:     }
 3388:     Apache::lonhelper::string->new();
 3389:     return '';
 3390: }
 3391: 
 3392: sub render {
 3393:     my $self = shift;
 3394:     my $result = '';
 3395: 
 3396:     if (defined $self->{ERROR_MSG}) {
 3397:         $result .= '<p><font color="#FF0000">' . $self->{ERROR_MSG} . '</font></p>';
 3398:     }
 3399: 
 3400:     $result .= '<input type="string" name="' . $self->{'variable'} . '.forminput"';
 3401: 
 3402:     if (defined($self->{'size'})) {
 3403:         $result .= ' size="' . $self->{'size'} . '"';
 3404:     }
 3405:     if (defined($self->{'maxlength'})) {
 3406:         $result .= ' maxlength="' . $self->{'maxlength'} . '"';
 3407:     }
 3408: 
 3409:     if (defined($self->{DEFAULT_VALUE})) {
 3410:         my $valueFunc = eval($self->{DEFAULT_VALUE});
 3411:         die 'Error in default value code for variable ' . 
 3412:             $self->{'variable'} . ', Perl said: ' . $@ if $@;
 3413:         $result .= ' value="' . &$valueFunc($helper, $self) . '"';
 3414:     }
 3415: 
 3416:     $result .= ' />';
 3417: 
 3418:     return $result;
 3419: }
 3420: 
 3421: # If a NEXTSTATE was given, switch to it
 3422: sub postprocess {
 3423:     my $self = shift;
 3424: 
 3425:     if (defined($self->{VALIDATOR})) {
 3426: 	my $validator = eval($self->{VALIDATOR});
 3427: 	die 'Died during evaluation of validator code; Perl said: ' . $@ if $@;
 3428: 	my $invalid = &$validator($helper, $state, $self, $self->getValue());
 3429: 	if ($invalid) {
 3430: 	    $self->{ERROR_MSG} = $invalid;
 3431: 	    return 0;
 3432: 	}
 3433:     }
 3434: 
 3435:     if (defined($self->{'nextstate'})) {
 3436:         $helper->changeState($self->{'nextstate'});
 3437:     }
 3438: 
 3439:     return 1;
 3440: }
 3441: 
 3442: 1;
 3443: 
 3444: package Apache::lonhelper::general;
 3445: 
 3446: =pod
 3447: 
 3448: =head2 General-purpose tag: <exec>X<exec, helper tag>
 3449: 
 3450: The contents of the exec tag are executed as Perl code, B<not> inside a 
 3451: safe space, so the full range of $env and such is available. The code
 3452: will be executed as a subroutine wrapped with the following code:
 3453: 
 3454: "sub { my $helper = shift; my $state = shift;" and
 3455: 
 3456: "}"
 3457: 
 3458: The return value is ignored.
 3459: 
 3460: $helper is the helper object. Feel free to add methods to the helper
 3461: object to support whatever manipulation you may need to do (for instance,
 3462: overriding the form location if the state is the final state; see 
 3463: parameter.helper for an example).
 3464: 
 3465: $state is the $paramHash that has currently been generated and may
 3466: be manipulated by the code in exec. Note that the $state is not yet
 3467: an actual state B<object>, it is just a hash, so do not expect to
 3468: be able to call methods on it.
 3469: 
 3470: =cut
 3471: 
 3472: use Apache::lonlocal;
 3473: use Apache::lonnet;
 3474: 
 3475: BEGIN {
 3476:     &Apache::lonhelper::register('Apache::lonhelper::general',
 3477:                                  'exec', 'condition', 'clause',
 3478:                                  'eval');
 3479: }
 3480: 
 3481: sub start_exec {
 3482:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3483: 
 3484:     if ($target ne 'helper') {
 3485:         return '';
 3486:     }
 3487:     
 3488:     my $code = &Apache::lonxml::get_all_text('/exec', $parser);
 3489:     
 3490:     $code = eval ('sub { my $helper = shift; my $state = shift; ' .
 3491:         $code . "}");
 3492:     die 'Error in <exec>, Perl said: '. $@ if $@;
 3493:     &$code($helper, $paramHash);
 3494: }
 3495: 
 3496: sub end_exec { return ''; }
 3497: 
 3498: =pod
 3499: 
 3500: =head2 General-purpose tag: <condition>
 3501: 
 3502: The <condition> tag allows you to mask out parts of the helper code
 3503: depending on some programatically determined condition. The condition
 3504: tag contains a tag <clause> which contains perl code that when wrapped
 3505: with "sub { my $helper = shift; my $state = shift; " and "}", returns
 3506: a true value if the XML in the condition should be evaluated as a normal
 3507: part of the helper, or false if it should be completely discarded.
 3508: 
 3509: The <clause> tag must be the first sub-tag of the <condition> tag or
 3510: it will not work as expected.
 3511: 
 3512: =cut
 3513: 
 3514: # The condition tag just functions as a marker, it doesn't have
 3515: # to "do" anything. Technically it doesn't even have to be registered
 3516: # with the lonxml code, but I leave this here to be explicit about it.
 3517: sub start_condition { return ''; }
 3518: sub end_condition { return ''; }
 3519: 
 3520: sub start_clause {
 3521:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3522: 
 3523:     if ($target ne 'helper') {
 3524:         return '';
 3525:     }
 3526:     
 3527:     my $clause = Apache::lonxml::get_all_text('/clause', $parser);
 3528:     $clause = eval('sub { my $helper = shift; my $state = shift; '
 3529:         . $clause . '}');
 3530:     die 'Error in clause of condition, Perl said: ' . $@ if $@;
 3531:     if (!&$clause($helper, $paramHash)) {
 3532:         # Discard all text until the /condition.
 3533:         &Apache::lonxml::get_all_text('/condition', $parser);
 3534:     }
 3535: }
 3536: 
 3537: sub end_clause { return ''; }
 3538: 
 3539: =pod
 3540: 
 3541: =head2 General-purpose tag: <eval>X<eval, helper tag>
 3542: 
 3543: The <eval> tag will be evaluated as a subroutine call passed in the
 3544: current helper object and state hash as described in <condition> above,
 3545: but is expected to return a string to be printed directly to the
 3546: screen. This is useful for dynamically generating messages. 
 3547: 
 3548: =cut
 3549: 
 3550: # This is basically a type of message.
 3551: # Programmatically setting $paramHash->{NEXTSTATE} would work, though
 3552: # it's probably bad form.
 3553: 
 3554: sub start_eval {
 3555:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3556: 
 3557:     if ($target ne 'helper') {
 3558:         return '';
 3559:     }
 3560:     
 3561:     my $program = Apache::lonxml::get_all_text('/eval', $parser);
 3562:     $program = eval('sub { my $helper = shift; my $state = shift; '
 3563:         . $program . '}');
 3564:     die 'Error in eval code, Perl said: ' . $@ if $@;
 3565:     $paramHash->{MESSAGE_TEXT} = &$program($helper, $paramHash);
 3566: }
 3567: 
 3568: sub end_eval { 
 3569:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3570: 
 3571:     if ($target ne 'helper') {
 3572:         return '';
 3573:     }
 3574: 
 3575:     Apache::lonhelper::message->new();
 3576: }
 3577: 
 3578: 1;
 3579: 
 3580: package Apache::lonhelper::final;
 3581: 
 3582: =pod
 3583: 
 3584: =head2 Element: finalX<final, helper tag>
 3585: 
 3586: <final> is a special element that works with helpers that use the <finalcode>
 3587: tagX<finalcode, helper tag>. It goes through all the states and elements, executing the <finalcode>
 3588: snippets and collecting the results. Finally, it takes the user out of the
 3589: helper, going to a provided page.
 3590: 
 3591: If the parameter "restartCourse" is true, this will override the buttons and
 3592: will make a "Finish Helper" button that will re-initialize the course for them,
 3593: which is useful for the Course Initialization helper so the users never see
 3594: the old values taking effect.
 3595: 
 3596: If the parameter "restartCourse" is not true a 'Finish' Button will be
 3597: presented that takes the user back to whatever was defined as <exitpage>
 3598: 
 3599: =cut
 3600: 
 3601: no strict;
 3602: @ISA = ("Apache::lonhelper::element");
 3603: use strict;
 3604: use Apache::lonlocal;
 3605: use Apache::lonnet;
 3606: BEGIN {
 3607:     &Apache::lonhelper::register('Apache::lonhelper::final',
 3608:                                  ('final', 'exitpage'));
 3609: }
 3610: 
 3611: sub new {
 3612:     my $ref = Apache::lonhelper::element->new();
 3613:     bless($ref);
 3614: }
 3615: 
 3616: sub start_final { 
 3617:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3618: 
 3619:     if ($target ne 'helper') {
 3620:         return '';
 3621:     }
 3622: 
 3623:     $paramHash->{'restartCourse'} = $token->[2]{'restartCourse'};
 3624: 
 3625:     return ''; 
 3626: }
 3627: 
 3628: sub end_final {
 3629:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3630: 
 3631:     if ($target ne 'helper') {
 3632:         return '';
 3633:     }
 3634: 
 3635:     Apache::lonhelper::final->new();
 3636:    
 3637:     return '';
 3638: }
 3639: 
 3640: sub start_exitpage {
 3641:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3642: 
 3643:     if ($target ne 'helper') {
 3644:         return '';
 3645:     }
 3646: 
 3647:     $paramHash->{EXIT_PAGE} = &Apache::lonxml::get_all_text('/exitpage',
 3648:                                                             $parser);
 3649: 
 3650:     return '';
 3651: }
 3652: 
 3653: sub end_exitpage { return ''; }
 3654: 
 3655: sub render {
 3656:     my $self = shift;
 3657: 
 3658:     my @results;
 3659: 
 3660:     # Collect all the results
 3661:     for my $stateName (keys %{$helper->{STATES}}) {
 3662:         my $state = $helper->{STATES}->{$stateName};
 3663:         
 3664:         for my $element (@{$state->{ELEMENTS}}) {
 3665:             if (defined($element->{FINAL_CODE})) {
 3666:                 # Compile the code.
 3667:                 my $code = 'sub { my $helper = shift; my $element = shift; ' 
 3668:                     . $element->{FINAL_CODE} . '}';
 3669:                 $code = eval($code);
 3670:                 die 'Error while executing final code for element with var ' .
 3671:                     $element->{'variable'} . ', Perl said: ' . $@ if $@;
 3672: 
 3673:                 my $result = &$code($helper, $element);
 3674:                 if ($result) {
 3675:                     push @results, $result;
 3676:                 }
 3677:             }
 3678:         }
 3679:     }
 3680: 
 3681:     my $result;
 3682: 
 3683:     if (scalar(@results) != 0) {
 3684: 	$result .= "<ul>\n";
 3685: 	for my $re (@results) {
 3686: 	    $result .= '    <li>' . $re . "</li>\n";
 3687: 	}
 3688: 	
 3689: 	if (!@results) {
 3690: 	    $result .= '    <li>' . 
 3691: 		&mt('No changes were made to current settings.') . '</li>';
 3692: 	}
 3693: 	
 3694: 	$result .= '</ul>';
 3695:     }
 3696: 
 3697:     my $actionURL = $self->{EXIT_PAGE};
 3698:     my $targetURL = '';
 3699:     my $finish=&mt('Finish');
 3700:     if ($self->{'restartCourse'}) {
 3701: 	$actionURL = '/adm/roles';
 3702: 	$targetURL = '/adm/menu';
 3703: 	if ($env{'course.'.$env{'request.course.id'}.'.url'}=~/^uploaded/) {
 3704: 	    $targetURL = '/adm/coursedocs';
 3705: 	} else {
 3706: 	    $targetURL = '/adm/navmaps';
 3707: 	}
 3708: 	if ($env{'course.'.$env{'request.course.id'}.'.clonedfrom'}) {
 3709: 	    $targetURL = '/adm/parmset?overview=1';
 3710: 	}
 3711: 	my $finish=&mt('Finish Course Initialization');
 3712:     }
 3713:     my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
 3714:     my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
 3715:     my $target = " target='loncapaclient'";
 3716:     if (($env{'browser.interface'} eq 'textual') ||
 3717:         ($env{'environment.remote'} eq 'off')) {  $target='';  }
 3718:     $result .= "<center>\n" .
 3719: 	"<form action='".$actionURL."' method='post' $target>\n" .
 3720: 	"<input type='button' onclick='history.go(-1)' value='$previous' />" .
 3721: 	"<input type='hidden' name='orgurl' value='$targetURL' />" .
 3722: 	"<input type='hidden' name='selectrole' value='1' />\n" .
 3723: 	"<input type='hidden' name='" . $env{'request.role'} . 
 3724: 	"' value='1' />\n<input type='submit' value='" . $finish . "' />\n" .
 3725: 	"</form></center>";
 3726: 
 3727:     return $result;
 3728: }
 3729: 
 3730: sub overrideForm {
 3731:     return 1;
 3732: }
 3733: 
 3734: 1;
 3735: 
 3736: package Apache::lonhelper::parmwizfinal;
 3737: 
 3738: # This is the final state for the parmwizard. It is not generally useful,
 3739: # so it is not perldoc'ed. It does its own processing.
 3740: # It is represented with <parmwizfinal />, and
 3741: # should later be moved to lonparmset.pm .
 3742: 
 3743: no strict;
 3744: @ISA = ('Apache::lonhelper::element');
 3745: use strict;
 3746: use Apache::lonlocal;
 3747: use Apache::lonnet;
 3748: 
 3749: BEGIN {
 3750:     &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal',
 3751:                                  ('parmwizfinal'));
 3752: }
 3753: 
 3754: use Time::localtime;
 3755: 
 3756: sub new {
 3757:     my $ref = Apache::lonhelper::choices->new();
 3758:     bless ($ref);
 3759: }
 3760: 
 3761: sub start_parmwizfinal { return ''; }
 3762: 
 3763: sub end_parmwizfinal {
 3764:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 3765: 
 3766:     if ($target ne 'helper') {
 3767:         return '';
 3768:     }
 3769:     Apache::lonhelper::parmwizfinal->new();
 3770: }
 3771: 
 3772: # Renders a form that, when submitted, will form the input to lonparmset.pm
 3773: sub render {
 3774:     my $self = shift;
 3775:     my $vars = $helper->{VARS};
 3776: 
 3777:     # FIXME: Unify my designators with the standard ones
 3778:     my %dateTypeHash = ('open_date' => "opening date",
 3779:                         'due_date' => "due date",
 3780:                         'answer_date' => "answer date",
 3781: 			'tries' => 'number of tries',
 3782: 			'weight' => 'problem weight'
 3783: 			);
 3784:     my %parmTypeHash = ('open_date' => "0_opendate",
 3785:                         'due_date' => "0_duedate",
 3786:                         'answer_date' => "0_answerdate",
 3787: 			'tries' => '0_maxtries',
 3788: 			'weight' => '0_weight' );
 3789:     my %realParmName = ('open_date' => "opendate",
 3790:                         'due_date' => "duedate",
 3791:                         'answer_date' => "answerdate",
 3792: 			'tries' => 'maxtries',
 3793: 			'weight' => 'weight' );
 3794:     
 3795:     my $affectedResourceId = "";
 3796:     my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};
 3797:     my $level = "";
 3798:     my $resourceString;
 3799:     my $symb;
 3800:     my $paramlevel;
 3801:     
 3802:     # Print the granularity, depending on the action
 3803:     if ($vars->{GRANULARITY} eq 'whole_course') {
 3804:         $resourceString .= '<li>'.&mt('for <b>all resources in the course</b>').'</li>';
 3805: 	if ($vars->{TARGETS} eq 'course') {
 3806: 	    $level = 11; # general course, see lonparmset.pm perldoc
 3807: 	} elsif ($vars->{TARGETS} eq 'section') {
 3808: 	    $level = 6;
 3809: 	} else {
 3810: 	    $level = 3;
 3811: 	}
 3812:         $affectedResourceId = "0.0";
 3813:         $symb = 'a';
 3814:         $paramlevel = 'general';
 3815:     } elsif ($vars->{GRANULARITY} eq 'map') {
 3816:         my $navmap = Apache::lonnavmaps::navmap->new();
 3817:         my $res = $navmap->getByMapPc($vars->{RESOURCE_ID});
 3818:         my $title = $res->compTitle();
 3819:         $symb = $res->symb();
 3820:         $resourceString .= '<li>'.&mt('for the map named [_1]',"<b>$title</b>").'</li>';
 3821: 	if ($vars->{TARGETS} eq 'course') {
 3822: 	    $level = 10; # general course, see lonparmset.pm perldoc
 3823: 	} elsif ($vars->{TARGETS} eq 'section') {
 3824: 	    $level = 5;
 3825: 	} else {
 3826: 	    $level = 2;
 3827: 	}
 3828:         $affectedResourceId = $vars->{RESOURCE_ID};
 3829:         $paramlevel = 'map';
 3830:     } else {
 3831:         my $navmap = Apache::lonnavmaps::navmap->new();
 3832:         my $res = $navmap->getById($vars->{RESOURCE_ID});
 3833:         my $part = $vars->{RESOURCE_ID_part};
 3834: 	if ($part ne 'All Parts' && $part) { $parm_name=~s/^0/$part/; } else { $part=&mt('All Parts'); }
 3835:         $symb = $res->symb();
 3836:         my $title = $res->compTitle();
 3837:         $resourceString .= '<li>'.&mt('for the resource named [_1] part [_2]',"<b>$title</b>","<b>$part</b>").'</li>';
 3838: 	if ($vars->{TARGETS} eq 'course') {
 3839: 	    $level = 7; # general course, see lonparmset.pm perldoc
 3840: 	} elsif ($vars->{TARGETS} eq 'section') {
 3841: 	    $level = 4;
 3842: 	} else {
 3843: 	    $level = 1;
 3844: 	}
 3845:         $affectedResourceId = $vars->{RESOURCE_ID};
 3846:         $paramlevel = 'full';
 3847:     }
 3848: 
 3849:     my $result = "<form name='helpform' method='POST' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
 3850:     $result .= "<input type='hidden' name='action' value='settable' />\n";
 3851:     $result .= "<input type='hidden' name='dis' value='helper' />\n";
 3852:     $result .= "<input type='hidden' name='pscat' value='".
 3853: 	$realParmName{$vars->{ACTION_TYPE}}."' />\n";
 3854:     if ($vars->{GRANULARITY} eq 'resource') {
 3855: 	$result .= "<input type='hidden' name='symb' value='".
 3856: 	    HTML::Entities::encode($symb,"'<>&\"") . "' />\n";
 3857:     } elsif ($vars->{GRANULARITY} eq 'map') {
 3858: 	$result .= "<input type='hidden' name='pschp' value='".
 3859: 	    $affectedResourceId."' />\n";
 3860:     }
 3861:     my $part = $vars->{RESOURCE_ID_part};
 3862:     if ($part eq 'All Parts' || !$part) { $part=0; }
 3863:     $result .= "<input type='hidden' name='psprt' value='".
 3864: 	HTML::Entities::encode($part,"'<>&\"") . "' />\n";
 3865: 
 3866:     $result .= '<p>'.&mt('Confirm that this information is correct, then click &quot;Finish Helper&quot; to complete setting the parameter.').'<ul>';
 3867:     
 3868:     # Print the type of manipulation:
 3869:     my $extra;
 3870:     if ($vars->{ACTION_TYPE} eq 'tries') {
 3871: 	$extra =  $vars->{TRIES};
 3872:     }
 3873:     if ($vars->{ACTION_TYPE} eq 'weight') {
 3874: 	$extra =  $vars->{WEIGHT};
 3875:     }
 3876:     $result .= "<li>";
 3877:     my $what = &mt($dateTypeHash{$vars->{ACTION_TYPE}});
 3878:     if ($extra) {
 3879: 	$result .= &mt('Setting the [_1] to [_2]',"<b>$what</b>",$extra);
 3880:     } else {
 3881: 	$result .= &mt('Setting the [_1]',"<b>$what</b>");
 3882:     }
 3883:     $result .= "</li>\n";
 3884:     if ($vars->{ACTION_TYPE} eq 'due_date' || 
 3885:         $vars->{ACTION_TYPE} eq 'answer_date') {
 3886:         # for due dates, we default to "date end" type entries
 3887:         $result .= "<input type='hidden' name='recent_date_end' " .
 3888:             "value='" . $vars->{PARM_DATE} . "' />\n";
 3889:         $result .= "<input type='hidden' name='pres_value' " . 
 3890:             "value='" . $vars->{PARM_DATE} . "' />\n";
 3891:         $result .= "<input type='hidden' name='pres_type' " .
 3892:             "value='date_end' />\n";
 3893:     } elsif ($vars->{ACTION_TYPE} eq 'open_date') {
 3894:         $result .= "<input type='hidden' name='recent_date_start' ".
 3895:             "value='" . $vars->{PARM_DATE} . "' />\n";
 3896:         $result .= "<input type='hidden' name='pres_value' " .
 3897:             "value='" . $vars->{PARM_DATE} . "' />\n";
 3898:         $result .= "<input type='hidden' name='pres_type' " .
 3899:             "value='date_start' />\n";
 3900:     } elsif ($vars->{ACTION_TYPE} eq 'tries') {
 3901: 	$result .= "<input type='hidden' name='pres_value' " .
 3902: 	    "value='" . $vars->{TRIES} . "' />\n";
 3903:         $result .= "<input type='hidden' name='pres_type' " .
 3904:             "value='int_pos' />\n";
 3905:     } elsif ($vars->{ACTION_TYPE} eq 'weight') {
 3906: 	$result .= "<input type='hidden' name='pres_value' " .
 3907: 	    "value='" . $vars->{WEIGHT} . "' />\n";
 3908:     }
 3909: 
 3910:     $result .= $resourceString;
 3911:     
 3912:     # Print targets
 3913:     if ($vars->{TARGETS} eq 'course') {
 3914:         $result .= '<li>'.&mt('for <b>all students in course</b>').'</li>';
 3915:     } elsif ($vars->{TARGETS} eq 'section') {
 3916:         my $section = $vars->{SECTION_NAME};
 3917:         $result .= '<li>'.&mt('for section [_1]',"<b>$section</b>").'</li>';
 3918: 	$result .= "<input type='hidden' name='csec' value='" .
 3919:             HTML::Entities::encode($section,"'<>&\"") . "' />\n";
 3920:     } elsif ($vars->{TARGETS} eq 'group') {
 3921:         my $group = $vars->{GROUP_NAME};
 3922:         $result .= '<li>'.&mt('for group [_1]',"<b>$group</b>").'</li>';
 3923:         $result .= "<input type='hidden' name='cgroup' value='" .
 3924:             HTML::Entities::encode($group,"'<>&\"") . "' />\n";
 3925:     } else {
 3926:         # FIXME: This is probably wasteful! Store the name!
 3927:         my $classlist = Apache::loncoursedata::get_classlist();
 3928: 	my ($uname,$udom)=split(':',$vars->{USER_NAME});
 3929:         my $name = $classlist->{$uname.':'.$udom}->[6];
 3930:         $result .= '<li>'.&mt('for [_1]',"<b>$name</b>").'</li>';
 3931:         $result .= "<input type='hidden' name='uname' value='".
 3932:             HTML::Entities::encode($uname,"'<>&\"") . "' />\n";
 3933:         $result .= "<input type='hidden' name='udom' value='".
 3934:             HTML::Entities::encode($udom,"'<>&\"") . "' />\n";
 3935:     }
 3936: 
 3937:     # Print value
 3938:     if ($vars->{ACTION_TYPE} ne 'tries' && $vars->{ACTION_TYPE} ne 'weight') {
 3939: 	$result .= '<li>'.&mt('to [_1] ([_2])',"<b>".ctime($vars->{PARM_DATE})."</b>",Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}))."</li>\n";
 3940:     }
 3941:  
 3942:     # print pres_marker
 3943:     $result .= "\n<input type='hidden' name='pres_marker'" .
 3944:         " value='$affectedResourceId&$parm_name&$level' />\n";
 3945:     
 3946:     # Make the table appear
 3947:     $result .= "\n<input type='hidden' value='true' name='prevvisit' />";
 3948:     $result .= "\n<input type='hidden' value='$symb' name='pssymb' />";
 3949:     $result .= "\n<input type='hidden' value='$paramlevel' name='parmlev' />";
 3950: 
 3951:     $result .= "<br /><br /><center><input type='submit' value='".&mt('Finish Helper')."' /></center></form>\n";
 3952: 
 3953:     return $result;
 3954: }
 3955:     
 3956: sub overrideForm {
 3957:     return 1;
 3958: }
 3959: 
 3960: 1;
 3961: 
 3962: __END__
 3963: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.