File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.7: download - view: text, annotated - select for diffs
Fri Apr 11 17:45:37 2003 UTC (21 years, 1 month ago) by bowersj2
Branches: MAIN
CVS tags: HEAD
Dynamically register and unregister the helper tags when we see the
<helper> and </helper> tags, making my worries about namespace pollution
go away.

    1: # The LearningOnline Network with CAPA
    2: # .helper XML handler to implement the LON-CAPA helper
    3: #
    4: # $Id: lonhelper.pm,v 1.7 2003/04/11 17:45:37 bowersj2 Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: # (Page Handler
   29: #
   30: # (.helper handler
   31: #
   32: 
   33: # FIXME: Change register calls to register with the helper.
   34: # Then have the helper reg and unreg the tags.
   35: # This removes my concerns about breaking other code.
   36: 
   37: =pod
   38: 
   39: =head1 lonhelper - HTML Helper framework for LON-CAPA
   40: 
   41: Helpers, often known as "wizards", are well-established UI widgets that users
   42: feel comfortable with. It can take a complicated multidimensional problem the
   43: user has and turn it into a series of bite-sized one-dimensional questions.
   44: 
   45: For developers, helpers provide an easy way to bundle little bits of functionality
   46: for the user, without having to write the tedious state-maintenence code.
   47: 
   48: Helpers are defined as XML documents, placed in the /home/httpd/html/adm/helpers 
   49: directory and having the .helper file extension. For examples, see that directory.
   50: 
   51: All classes are in the Apache::lonhelper namespace.
   52: 
   53: =head2 lonhelper XML file format
   54: 
   55: A helper consists of a top-level <helper> tag which contains a series of states.
   56: Each state contains one or more state elements, which are what the user sees, like
   57: messages, resource selections, or date queries.
   58: 
   59: The helper tag is required to have one attribute, "title", which is the name
   60: of the helper itself, such as "Parameter helper". 
   61: 
   62: =head2 State tags
   63: 
   64: State tags are required to have an attribute "name", which is the symbolic
   65: name of the state and will not be directly seen by the user. The helper is
   66: required to have one state named "START", which is the state the helper
   67: will start with. By convention, this state should clearly describe what
   68: the helper will do for the user, and may also include the first information
   69: entry the user needs to do for the helper.
   70: 
   71: State tags are also required to have an attribute "title", which is the
   72: human name of the state, and will be displayed as the header on top of 
   73: the screen for the user.
   74: 
   75: =head2 Example Helper Skeleton
   76: 
   77: An example of the tags so far:
   78: 
   79:  <helper title="Example Helper">
   80:    <state name="START" title="Demonstrating the Example Helper">
   81:      <!-- notice this is the START state the wizard requires -->
   82:      </state>
   83:    <state name="GET_NAME" title="Enter Student Name">
   84:      </state>
   85:    </helper>
   86: 
   87: Of course this does nothing. In order for the wizard to do something, it is
   88: necessary to put actual elements into the wizard. Documentation for each
   89: of these elements follows.
   90: 
   91: =cut
   92: 
   93: package Apache::lonhelper;
   94: use Apache::Constants qw(:common);
   95: use Apache::File;
   96: use Apache::lonxml;
   97: 
   98: # Register all the tags with the helper, so the helper can 
   99: # push and pop them
  100: 
  101: my @helperTags;
  102: 
  103: sub register {
  104:     my ($namespace, @tags) = @_;
  105: 
  106:     for my $tag (@tags) {
  107:         push @helperTags, [$namespace, $tag];
  108:     }
  109: }
  110: 
  111: BEGIN {
  112:     Apache::lonxml::register('Apache::lonhelper', 
  113:                              ('helper'));
  114:       register('Apache::lonhelper', ('state'));
  115: }
  116: 
  117: # Since all helpers are only three levels deep (helper tag, state tag, 
  118: # substate type), it's easier and more readble to explicitly track 
  119: # those three things directly, rather then futz with the tag stack 
  120: # every time.
  121: my $helper;
  122: my $state;
  123: my $substate;
  124: # To collect parameters, the contents of the subtags are collected
  125: # into this paramHash, then passed to the element object when the 
  126: # end of the element tag is located.
  127: my $paramHash; 
  128: 
  129: sub handler {
  130:     my $r = shift;
  131:     $ENV{'request.uri'} = $r->uri();
  132:     my $filename = '/home/httpd/html' . $r->uri();
  133:     my $fh = Apache::File->new($filename);
  134:     my $file;
  135:     read $fh, $file, 100000000;
  136: 
  137:     Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
  138: 
  139:     # Send header, don't cache this page
  140:     if ($r->header_only) {
  141:         if ($ENV{'browser.mathml'}) {
  142:             $r->content_type('text/xml');
  143:         } else {
  144:             $r->content_type('text/html');
  145:         }
  146:         $r->send_http_header;
  147:         return OK;
  148:     }
  149:     if ($ENV{'browser.mathml'}) {
  150:         $r->content_type('text/xml');
  151:     } else {
  152:         $r->content_type('text/html');
  153:     }
  154:     $r->send_http_header;
  155:     $r->rflush();
  156: 
  157:     # Discard result, we just want the objects that get created by the
  158:     # xml parsing
  159:     &Apache::lonxml::xmlparse($r, 'helper', $file);
  160: 
  161:     $r->print($helper->display());
  162:    return OK;
  163: }
  164: 
  165: sub start_helper {
  166:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  167: 
  168:     if ($target ne 'helper') {
  169:         return '';
  170:     }
  171: 
  172:     for my $tagList (@helperTags) {
  173:         Apache::lonxml::register($tagList->[0], $tagList->[1]);
  174:     }
  175:     
  176:     $helper = Apache::lonhelper::helper->new($token->[2]{'title'});
  177:     return '';
  178: }
  179: 
  180: sub end_helper {
  181:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  182:     
  183:     if ($target ne 'helper') {
  184:         return '';
  185:     }
  186: 
  187:     for my $tagList (@helperTags) {
  188:         Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
  189:     }
  190: 
  191:     return '';
  192: }
  193: 
  194: sub start_state {
  195:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  196: 
  197:     if ($target ne 'helper') {
  198:         return '';
  199:     }
  200: 
  201:     $state = Apache::lonhelper::state->new($token->[2]{'name'},
  202:                                            $token->[2]{'title'});
  203:     return '';
  204: }
  205: 
  206: # don't need this, so ignore it
  207: sub end_state {
  208:     return '';
  209: }
  210: 
  211: 1;
  212: 
  213: package Apache::lonhelper::helper;
  214: 
  215: use Digest::MD5 qw(md5_hex);
  216: use HTML::Entities;
  217: use Apache::loncommon;
  218: use Apache::File;
  219: 
  220: sub new {
  221:     my $proto = shift;
  222:     my $class = ref($proto) || $proto;
  223:     my $self = {};
  224: 
  225:     $self->{TITLE} = shift;
  226:     
  227:     # If there is a state from the previous form, use that. If there is no
  228:     # state, use the start state parameter.
  229:     if (defined $ENV{"form.CURRENT_STATE"})
  230:     {
  231: 	$self->{STATE} = $ENV{"form.CURRENT_STATE"};
  232:     }
  233:     else
  234:     {
  235: 	$self->{STATE} = "START";
  236:     }
  237: 
  238:     $self->{TOKEN} = $ENV{'form.TOKEN'};
  239:     # If a token was passed, we load that in. Otherwise, we need to create a 
  240:     # new storage file
  241:     # Tried to use standard Tie'd hashes, but you can't seem to take a 
  242:     # reference to a tied hash and write to it. I'd call that a wart.
  243:     if ($self->{TOKEN}) {
  244:         # Validate the token before trusting it
  245:         if ($self->{TOKEN} !~ /^[a-f0-9]{32}$/) {
  246:             # Not legit. Return nothing and let all hell break loose.
  247:             # User shouldn't be doing that!
  248:             return undef;
  249:         }
  250: 
  251:         # Get the hash.
  252:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); # Note the token is not the literal file
  253:         
  254:         my $file = Apache::File->new($self->{FILENAME});
  255:         my $contents = <$file>;
  256: 
  257:         # Now load in the contents
  258:         for my $value (split (/&/, $contents)) {
  259:             my ($name, $value) = split(/=/, $value);
  260:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  261:             $self->{VARS}->{$name} = $value;
  262:         }
  263: 
  264:         $file->close();
  265:     } else {
  266:         # Only valid if we're just starting.
  267:         if ($self->{STATE} ne 'START') {
  268:             return undef;
  269:         }
  270:         # Must create the storage
  271:         $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} .
  272:                                  time() . rand());
  273:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
  274:     }
  275: 
  276:     # OK, we now have our persistent storage.
  277: 
  278:     if (defined $ENV{"form.RETURN_PAGE"})
  279:     {
  280: 	$self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};
  281:     }
  282:     else
  283:     {
  284: 	$self->{RETURN_PAGE} = $ENV{REFERER};
  285:     }
  286: 
  287:     $self->{STATES} = {};
  288:     $self->{DONE} = 0;
  289: 
  290:     bless($self, $class);
  291:     return $self;
  292: }
  293: 
  294: # Private function; returns a string to construct the hidden fields
  295: # necessary to have the helper track state.
  296: sub _saveVars {
  297:     my $self = shift;
  298:     my $result = "";
  299:     $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
  300:         HTML::Entities::encode($self->{STATE}) . "\" />\n";
  301:     $result .= '<input type="hidden" name="TOKEN" value="' .
  302:         $self->{TOKEN} . "\" />\n";
  303:     $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
  304:         HTML::Entities::encode($self->{RETURN_PAGE}) . "\" />\n";
  305: 
  306:     return $result;
  307: }
  308: 
  309: # Private function: Create the querystring-like representation of the stored
  310: # data to write to disk.
  311: sub _varsInFile {
  312:     my $self = shift;
  313:     my @vars = ();
  314:     for my $key (keys %{$self->{VARS}}) {
  315:         push @vars, &Apache::lonnet::escape($key) . '=' .
  316:             &Apache::lonnet::escape($self->{VARS}->{$key});
  317:     }
  318:     return join ('&', @vars);
  319: }
  320: 
  321: # Use this to declare variables.
  322: # FIXME: Document this
  323: sub declareVar {
  324:     my $self = shift;
  325:     my $var = shift;
  326: 
  327:     if (!defined($self->{VARS}->{$var})) {
  328:         $self->{VARS}->{$var} = '';
  329:     }
  330: 
  331:     my $envname = 'form.' . $var . '.forminput';
  332:     if (defined($ENV{$envname})) {
  333:         $self->{VARS}->{$var} = $ENV{$envname};
  334:     }
  335: }
  336: 
  337: sub changeState {
  338:     my $self = shift;
  339:     $self->{STATE} = shift;
  340: }
  341: 
  342: sub registerState {
  343:     my $self = shift;
  344:     my $state = shift;
  345: 
  346:     my $stateName = $state->name();
  347:     $self->{STATES}{$stateName} = $state;
  348: }
  349: 
  350: # Done in four phases
  351: # 1: Do the post processing for the previous state.
  352: # 2: Do the preprocessing for the current state.
  353: # 3: Check to see if state changed, if so, postprocess current and move to next.
  354: #    Repeat until state stays stable.
  355: # 4: Render the current state to the screen as an HTML page.
  356: sub display {
  357:     my $self = shift;
  358: 
  359:     my $result = "";
  360: 
  361:     # Phase 1: Post processing for state of previous screen (which is actually
  362:     # the "current state" in terms of the helper variables), if it wasn't the 
  363:     # beginning state.
  364:     if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {
  365: 	my $prevState = $self->{STATES}{$self->{STATE}};
  366:             $prevState->postprocess();
  367:     }
  368:     
  369:     # Note, to handle errors in a state's input that a user must correct,
  370:     # do not transition in the postprocess, and force the user to correct
  371:     # the error.
  372: 
  373:     # Phase 2: Preprocess current state
  374:     my $startState = $self->{STATE};
  375:     my $state = $self->{STATES}{$startState};
  376:     
  377:     # Error checking; it is intended that the developer will have
  378:     # checked all paths and the user can't see this!
  379:     if (!defined($state)) {
  380:         $result .="Error! The state ". $startState ." is not defined.";
  381:         return $result;
  382:     }
  383:     $state->preprocess();
  384: 
  385:     # Phase 3: While the current state is different from the previous state,
  386:     # keep processing.
  387:     while ( $startState ne $self->{STATE} )
  388:     {
  389: 	$startState = $self->{STATE};
  390: 	$state = $self->{STATES}{$startState};
  391: 	$state->preprocess();
  392:     }
  393: 
  394:     # Phase 4: Display.
  395:     my $stateTitle = $state->title();
  396:     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
  397: 
  398:     $result .= <<HEADER;
  399: <html>
  400:     <head>
  401:         <title>LON-CAPA Helper: $self->{TITLE}</title>
  402:     </head>
  403:     $bodytag
  404: HEADER
  405:     if (!$state->overrideForm()) { $result.="<form name='wizform' method='GET'>"; }
  406:     $result .= <<HEADER;
  407:         <table border="0"><tr><td>
  408:         <h2><i>$stateTitle</i></h2>
  409: HEADER
  410: 
  411:     if (!$state->overrideForm()) {
  412:         $result .= $self->_saveVars();
  413:     }
  414:     $result .= $state->render() . "<p>&nbsp;</p>";
  415: 
  416:     if (!$state->overrideForm()) {
  417:         $result .= '<center>';
  418:         if ($self->{STATE} ne $self->{START_STATE}) {
  419:             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
  420:         }
  421:         if ($self->{DONE}) {
  422:             my $returnPage = $self->{RETURN_PAGE};
  423:             $result .= "<a href=\"$returnPage\">End Helper</a>";
  424:         }
  425:         else {
  426:             $result .= '<input name="back" type="button" ';
  427:             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';
  428:             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';
  429:         }
  430:         $result .= "</center>\n";
  431:     }
  432: 
  433:     foreach my $key (keys %{$self->{VARS}}) {
  434:         $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
  435:     }
  436: 
  437:     $result .= <<FOOTER;
  438:               </td>
  439:             </tr>
  440:           </table>
  441:         </form>
  442:     </body>
  443: </html>
  444: FOOTER
  445: 
  446:     # Handle writing out the vars to the file
  447:     my $file = Apache::File->new('>'.$self->{FILENAME});
  448:     print $file $self->_varsInFile();
  449: 
  450:     return $result;
  451: }
  452: 
  453: 1;
  454: 
  455: package Apache::lonhelper::state;
  456: 
  457: # States bundle things together and are responsible for compositing the
  458: # various elements together. It is not generally necessary for users to
  459: # use the state object directly, so it is not perldoc'ed.
  460: 
  461: # Basically, all the states do is pass calls to the elements and aggregate
  462: # the results.
  463: 
  464: sub new {
  465:     my $proto = shift;
  466:     my $class = ref($proto) || $proto;
  467:     my $self = {};
  468: 
  469:     $self->{NAME} = shift;
  470:     $self->{TITLE} = shift;
  471:     $self->{ELEMENTS} = [];
  472: 
  473:     bless($self, $class);
  474: 
  475:     $helper->registerState($self);
  476: 
  477:     return $self;
  478: }
  479: 
  480: sub name {
  481:     my $self = shift;
  482:     return $self->{NAME};
  483: }
  484: 
  485: sub title {
  486:     my $self = shift;
  487:     return $self->{TITLE};
  488: }
  489: 
  490: sub preprocess {
  491:     my $self = shift;
  492:     for my $element (@{$self->{ELEMENTS}}) {
  493:         $element->preprocess();
  494:     }
  495: }
  496: 
  497: # FIXME: Document that all postprocesses must return a true value or
  498: # the state transition will be overridden
  499: sub postprocess {
  500:     my $self = shift;
  501: 
  502:     # Save the state so we can roll it back if we need to.
  503:     my $originalState = $helper->{STATE};
  504:     my $everythingSuccessful = 1;
  505: 
  506:     for my $element (@{$self->{ELEMENTS}}) {
  507:         my $result = $element->postprocess();
  508:         if (!$result) { $everythingSuccessful = 0; }
  509:     }
  510: 
  511:     # If not all the postprocesses were successful, override
  512:     # any state transitions that may have occurred. It is the
  513:     # responsibility of the states to make sure they have 
  514:     # error handling in that case.
  515:     if (!$everythingSuccessful) {
  516:         $helper->{STATE} = $originalState;
  517:     }
  518: }
  519: 
  520: sub overrideForm {
  521:     return 0;
  522: }
  523: 
  524: sub addElement {
  525:     my $self = shift;
  526:     my $element = shift;
  527:     
  528:     push @{$self->{ELEMENTS}}, $element;
  529: }
  530: 
  531: sub render {
  532:     my $self = shift;
  533:     my @results = ();
  534: 
  535:     for my $element (@{$self->{ELEMENTS}}) {
  536:         push @results, $element->render();
  537:     }
  538:     return join("\n", @results);
  539: }
  540: 
  541: 1;
  542: 
  543: package Apache::lonhelper::element;
  544: # Support code for elements
  545: 
  546: =pod
  547: 
  548: =head2 Element Base Class
  549: 
  550: The Apache::lonhelper::element base class provides support methods for
  551: the elements to use, such as a multiple value processer.
  552: 
  553: B<Methods>:
  554: 
  555: =over 4
  556: 
  557: =item * process_multiple_choices(formName, varName): Process the form 
  558: element named "formName" and place the selected items into the helper 
  559: variable named varName. This is for things like checkboxes or 
  560: multiple-selection listboxes where the user can select more then 
  561: one entry. The selected entries are delimited by triple pipes in 
  562: the helper variables, like this:  
  563: 
  564:  CHOICE_1|||CHOICE_2|||CHOICE_3
  565: 
  566: =back
  567: 
  568: =cut
  569: 
  570: BEGIN {
  571:     &Apache::lonhelper::register('Apache::lonhelper::element',
  572:                                  ('nextstate'));
  573: }
  574: 
  575: # Because we use the param hash, this is often a sufficent
  576: # constructor
  577: sub new {
  578:     my $proto = shift;
  579:     my $class = ref($proto) || $proto;
  580:     my $self = $paramHash;
  581:     bless($self, $class);
  582: 
  583:     $self->{PARAMS} = $paramHash;
  584:     $self->{STATE} = $state;
  585:     $state->addElement($self);
  586:     
  587:     # Ensure param hash is not reused
  588:     $paramHash = {};
  589: 
  590:     return $self;
  591: }   
  592: 
  593: sub start_nextstate {
  594:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  595: 
  596:     if ($target ne 'helper') {
  597:         return '';
  598:     }
  599:     
  600:     $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
  601:                                                              $parser);
  602:     return '';
  603: }
  604: 
  605: sub end_nextstate { return ''; }
  606: 
  607: sub preprocess {
  608:     return 1;
  609: }
  610: 
  611: sub postprocess {
  612:     return 1;
  613: }
  614: 
  615: sub render {
  616:     return '';
  617: }
  618: 
  619: sub process_multiple_choices {
  620:     my $self = shift;
  621:     my $formname = shift;
  622:     my $var = shift;
  623: 
  624:     my $formvalue = $ENV{'form.' . $formname};
  625:     if ($formvalue) {
  626:         # Must extract values from querystring directly, as there
  627:         # may be more then one.
  628:         my @values;
  629:         for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
  630:             my ($name, $value) = split(/=/, $formparam);
  631:             if ($name ne $formname) {
  632:                 next;
  633:             }
  634:             $value =~ tr/+/ /;
  635:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  636:             push @values, $value;
  637:         }
  638:         $helper->{VARS}->{$var} = join('|||', @values);
  639:     }
  640:     
  641:     return;
  642: }
  643: 
  644: 1;
  645: 
  646: package Apache::lonhelper::message;
  647: 
  648: =pod
  649: 
  650: =head2 Element: message
  651: 
  652: Message elements display the contents of their <message_text> tags, and
  653: transition directly to the state in the <nextstate> tag. Example:
  654: 
  655:  <message>
  656:    <nextstate>GET_NAME</nextstate>
  657:    <message_text>This is the <b>message</b> the user will see, 
  658:                  <i>HTML allowed</i>.</message_text>
  659:    </message>
  660: 
  661: This will display the HTML message and transition to the <nextstate> if
  662: given. The HTML will be directly inserted into the helper, so if you don't
  663: want text to run together, you'll need to manually wrap the <message_text>
  664: in <p> tags, or whatever is appropriate for your HTML.
  665: 
  666: Message tags do not add in whitespace, so if you want it, you'll need to add
  667: it into states. This is done so you can inline some elements, such as 
  668: the <date> element, right between two messages, giving the appearence that 
  669: the <date> element appears inline. (Note the elements can not be embedded
  670: within each other.)
  671: 
  672: This is also a good template for creating your own new states, as it has
  673: very little code beyond the state template.
  674: 
  675: =cut
  676: 
  677: no strict;
  678: @ISA = ("Apache::lonhelper::element");
  679: use strict;
  680: 
  681: BEGIN {
  682:     &Apache::lonhelper::register('Apache::lonhelper::message',
  683:                               ('message', 'message_text'));
  684: }
  685: 
  686: sub new {
  687:     my $ref = Apache::lonhelper::element->new();
  688:     bless($ref);
  689: }
  690: 
  691: # CONSTRUCTION: Construct the message element from the XML
  692: sub start_message {
  693:     return '';
  694: }
  695: 
  696: sub end_message {
  697:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  698: 
  699:     if ($target ne 'helper') {
  700:         return '';
  701:     }
  702:     Apache::lonhelper::message->new();
  703:     return '';
  704: }
  705: 
  706: sub start_message_text {
  707:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  708: 
  709:     if ($target ne 'helper') {
  710:         return '';
  711:     }
  712: 
  713:     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',
  714:                                                                $parser);
  715: }
  716:     
  717: sub end_message_text { return 1; }
  718: 
  719: sub render {
  720:     my $self = shift;
  721: 
  722:     return $self->{MESSAGE_TEXT};
  723: }
  724: # If a NEXTSTATE was given, switch to it
  725: sub postprocess {
  726:     my $self = shift;
  727:     if (defined($self->{NEXTSTATE})) {
  728:         $helper->changeState($self->{NEXTSTATE});
  729:     }
  730: 
  731:     return 1;
  732: }
  733: 1;
  734: 
  735: package Apache::lonhelper::choices;
  736: 
  737: =pod
  738: 
  739: =head2 Element: choices
  740: 
  741: Choice states provide a single choice to the user as a text selection box.
  742: A "choice" is two pieces of text, one which will be displayed to the user
  743: (the "human" value), and one which will be passed back to the program
  744: (the "computer" value). For instance, a human may choose from a list of
  745: resources on disk by title, while your program wants the file name.
  746: 
  747: <choices> takes an attribute "variable" to control which helper variable
  748: the result is stored in.
  749: 
  750: <choices> takes an attribute "multichoice" which, if set to a true
  751: value, will allow the user to select multiple choices.
  752: 
  753: B<SUB-TAGS>
  754: 
  755: <choices> can have the following subtags:
  756: 
  757: =over 4
  758: 
  759: =item * <nextstate>state_name</nextstate>: If given, this will cause the
  760:       choice element to transition to the given state after executing. If
  761:       this is used, do not pass nextstates to the <choice> tag.
  762: 
  763: =item * <choice />: If the choices are static,
  764:       this element will allow you to specify them. Each choice
  765:       contains  attribute, "computer", as described above. The
  766:       content of the tag will be used as the human label.
  767:       For example,  
  768:       <choice computer='234-12-7312'>Bobby McDormik</choice>.
  769: 
  770: <choice> may optionally contain a 'nextstate' attribute, which
  771: will be the state transisitoned to if the choice is made, if
  772: the choice is not multichoice.
  773: 
  774: =back
  775: 
  776: To create the choices programmatically, either wrap the choices in 
  777: <condition> tags (prefered), or use an <exec> block inside the <choice>
  778: tag. Store the choices in $state->{CHOICES}, which is a list of list
  779: references, where each list has three strings. The first is the human
  780: name, the second is the computer name. and the third is the option
  781: next state. For example:
  782: 
  783:  <exec>
  784:     for (my $i = 65; $i < 65 + 26; $i++) {
  785:         push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
  786:     }
  787:  </exec>
  788: 
  789: This will allow the user to select from the letters A-Z (in ASCII), while
  790: passing the ASCII value back into the helper variables, and the state
  791: will in all cases transition to 'next'.
  792: 
  793: You can mix and match methods of creating choices, as long as you always 
  794: "push" onto the choice list, rather then wiping it out. (You can even 
  795: remove choices programmatically, but that would probably be bad form.)
  796: 
  797: FIXME: Document and implement <exec> and <condition> in the element package.
  798: 
  799: =cut
  800: 
  801: no strict;
  802: @ISA = ("Apache::lonhelper::element");
  803: use strict;
  804: 
  805: BEGIN {
  806:     &Apache::lonhelper::register('Apache::lonhelper::choices',
  807:                               ('choice', 'choices'));
  808: }
  809: 
  810: sub new {
  811:     my $ref = Apache::lonhelper::element->new();
  812:     bless($ref);
  813: }
  814: 
  815: # CONSTRUCTION: Construct the message element from the XML
  816: sub start_choices {
  817:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  818: 
  819:     if ($target ne 'helper') {
  820:         return '';
  821:     }
  822: 
  823:     # Need to initialize the choices list, so everything can assume it exists
  824:     $paramHash->{'variable'} = $token->[2]{'variable'};
  825:     $helper->declareVar($paramHash->{'variable'});
  826:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
  827:     $paramHash->{CHOICES} = [];
  828:     return '';
  829: }
  830: 
  831: sub end_choices {
  832:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  833: 
  834:     if ($target ne 'helper') {
  835:         return '';
  836:     }
  837:     Apache::lonhelper::choices->new();
  838:     return '';
  839: }
  840: 
  841: sub start_choice {
  842:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  843: 
  844:     if ($target ne 'helper') {
  845:         return '';
  846:     }
  847: 
  848:     my $computer = $token->[2]{'computer'};
  849:     my $human = &Apache::lonxml::get_all_text('/choice',
  850:                                               $parser);
  851:     my $nextstate = $token->[2]{'nextstate'};
  852:     push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate];
  853:     return '';
  854: }
  855: 
  856: sub end_choice {
  857:     return '';
  858: }
  859: 
  860: sub render {
  861:     # START HERE: Replace this with correct choices code.
  862:     my $self = shift;
  863:     my $var = $self->{'variable'};
  864:     my $buttons = '';
  865:     my $result = '';
  866: 
  867:     if ($self->{'multichoice'}) {
  868:         $result .= <<SCRIPT;
  869: <script>
  870:     function checkall(value) {
  871: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
  872:             document.forms.wizform.elements[i].checked=value;
  873:         }
  874:     }
  875: </script>
  876: SCRIPT
  877:         $buttons = <<BUTTONS;
  878: <br />
  879: <input type="button" onclick="checkall(true)" value="Select All" />
  880: <input type="button" onclick="checkall(false)" value="Unselect All" />
  881: <br />&nbsp;
  882: BUTTONS
  883:     }
  884: 
  885:     if (defined $self->{ERROR_MSG}) {
  886:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
  887:     }
  888: 
  889:     $result .= $buttons;
  890:     
  891:     $result .= "<table>\n\n";
  892: 
  893:     my $type = "radio";
  894:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
  895:     my $checked = 0;
  896:     foreach my $choice (@{$self->{CHOICES}}) {
  897:         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
  898:         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
  899:             . "' value='" . 
  900:             HTML::Entities::encode($choice->[1]) 
  901:             . "'";
  902:         if (!$self->{'multichoice'} && !$checked) {
  903:             $result .= " checked ";
  904:             $checked = 1;
  905:         }
  906:         $result .= "/></td><td> " . $choice->[0] . "</td></tr>\n";
  907:     }
  908:     $result .= "</table>\n\n\n";
  909:     $result .= $buttons;
  910: 
  911:     return $result;
  912: }
  913: 
  914: # If a NEXTSTATE was given or a nextstate for this choice was
  915: # given, switch to it
  916: sub postprocess {
  917:     my $self = shift;
  918:     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
  919: 
  920:     if (!$chosenValue) {
  921:         $self->{ERROR_MSG} = "You must choose one or more choices to" .
  922:             " continue.";
  923:         return 0;
  924:     }
  925: 
  926:     if ($self->{'multichoice'}) {
  927:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
  928:                                         $self->{'variable'});
  929:     }
  930: 
  931:     if (defined($self->{NEXTSTATE})) {
  932:         $helper->changeState($self->{NEXTSTATE});
  933:     }
  934:     
  935:     foreach my $choice (@{$self->{CHOICES}}) {
  936:         if ($choice->[1] eq $chosenValue) {
  937:             if (defined($choice->[2])) {
  938:                 $helper->changeState($choice->[2]);
  939:             }
  940:         }
  941:     }
  942:     return 1;
  943: }
  944: 1;
  945: 
  946: package Apache::lonhelper::date;
  947: 
  948: =pod
  949: 
  950: =head2 Element: date
  951: 
  952: Date elements allow the selection of a date with a drop down list.
  953: 
  954: Date elements can take two attributes:
  955: 
  956: =over 4
  957: 
  958: =item * B<variable>: The name of the variable to store the chosen
  959:         date in. Required.
  960: 
  961: =item * B<hoursminutes>: If a true value, the date will show hours
  962:         and minutes, as well as month/day/year. If false or missing,
  963:         the date will only show the month, day, and year.
  964: 
  965: =back
  966: 
  967: Date elements contain only an option <nextstate> tag to determine
  968: the next state.
  969: 
  970: Example:
  971: 
  972:  <date variable="DUE_DATE" hoursminutes="1">
  973:    <nextstate>choose_why</nextstate>
  974:    </date>
  975: 
  976: =cut
  977: 
  978: no strict;
  979: @ISA = ("Apache::lonhelper::element");
  980: use strict;
  981: 
  982: use Time::localtime;
  983: 
  984: BEGIN {
  985:     &Apache::lonhelper::register('Apache::lonhelper::date',
  986:                               ('date'));
  987: }
  988: 
  989: # Don't need to override the "new" from element
  990: sub new {
  991:     my $ref = Apache::lonhelper::element->new();
  992:     bless($ref);
  993: }
  994: 
  995: my @months = ("January", "February", "March", "April", "May", "June", "July",
  996: 	      "August", "September", "October", "November", "December");
  997: 
  998: # CONSTRUCTION: Construct the message element from the XML
  999: sub start_date {
 1000:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1001: 
 1002:     if ($target ne 'helper') {
 1003:         return '';
 1004:     }
 1005: 
 1006:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1007:     $helper->declareVar($paramHash->{'variable'});
 1008:     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
 1009: }
 1010: 
 1011: sub end_date {
 1012:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1013: 
 1014:     if ($target ne 'helper') {
 1015:         return '';
 1016:     }
 1017:     Apache::lonhelper::date->new();
 1018:     return '';
 1019: }
 1020: 
 1021: sub render {
 1022:     my $self = shift;
 1023:     my $result = "";
 1024:     my $var = $self->{'variable'};
 1025: 
 1026:     my $date;
 1027:     
 1028:     # Default date: The current hour.
 1029:     $date = localtime();
 1030:     $date->min(0);
 1031: 
 1032:     if (defined $self->{ERROR_MSG}) {
 1033:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1034:     }
 1035: 
 1036:     # Month
 1037:     my $i;
 1038:     $result .= "<select name='${var}month'>\n";
 1039:     for ($i = 0; $i < 12; $i++) {
 1040:         if ($i == $date->mon) {
 1041:             $result .= "<option value='$i' selected>";
 1042:         } else {
 1043:             $result .= "<option value='$i'>";
 1044:         }
 1045:         $result .= $months[$i] . "</option>\n";
 1046:     }
 1047:     $result .= "</select>\n";
 1048: 
 1049:     # Day
 1050:     $result .= "<select name='${var}day'>\n";
 1051:     for ($i = 1; $i < 32; $i++) {
 1052:         if ($i == $date->mday) {
 1053:             $result .= '<option selected>';
 1054:         } else {
 1055:             $result .= '<option>';
 1056:         }
 1057:         $result .= "$i</option>\n";
 1058:     }
 1059:     $result .= "</select>,\n";
 1060: 
 1061:     # Year
 1062:     $result .= "<select name='${var}year'>\n";
 1063:     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
 1064:         if ($date->year + 1900 == $i) {
 1065:             $result .= "<option selected>";
 1066:         } else {
 1067:             $result .= "<option>";
 1068:         }
 1069:         $result .= "$i</option>\n";
 1070:     }
 1071:     $result .= "</select>,\n";
 1072: 
 1073:     # Display Hours and Minutes if they are called for
 1074:     if ($self->{'hoursminutes'}) {
 1075:         # Build hour
 1076:         $result .= "<select name='${var}hour'>\n";
 1077:         $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
 1078:             " value='0'>midnight</option>\n";
 1079:         for ($i = 1; $i < 12; $i++) {
 1080:             if ($date->hour == $i) {
 1081:                 $result .= "<option selected value='$i'>$i a.m.</option>\n";
 1082:             } else {
 1083:                 $result .= "<option value='$i'>$i a.m</option>\n";
 1084:             }
 1085:         }
 1086:         $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
 1087:             " value='12'>noon</option>\n";
 1088:         for ($i = 13; $i < 24; $i++) {
 1089:             my $printedHour = $i - 12;
 1090:             if ($date->hour == $i) {
 1091:                 $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
 1092:             } else {
 1093:                 $result .= "<option value='$i'>$printedHour p.m.</option>\n";
 1094:             }
 1095:         }
 1096: 
 1097:         $result .= "</select> :\n";
 1098: 
 1099:         $result .= "<select name='${var}minute'>\n";
 1100:         for ($i = 0; $i < 60; $i++) {
 1101:             my $printedMinute = $i;
 1102:             if ($i < 10) {
 1103:                 $printedMinute = "0" . $printedMinute;
 1104:             }
 1105:             if ($date->min == $i) {
 1106:                 $result .= "<option selected>";
 1107:             } else {
 1108:                 $result .= "<option>";
 1109:             }
 1110:             $result .= "$printedMinute</option>\n";
 1111:         }
 1112:         $result .= "</select>\n";
 1113:     }
 1114: 
 1115:     return $result;
 1116: 
 1117: }
 1118: # If a NEXTSTATE was given, switch to it
 1119: sub postprocess {
 1120:     my $self = shift;
 1121:     my $var = $self->{'variable'};
 1122:     my $month = $ENV{'form.' . $var . 'month'}; 
 1123:     my $day = $ENV{'form.' . $var . 'day'}; 
 1124:     my $year = $ENV{'form.' . $var . 'year'}; 
 1125:     my $min = 0; 
 1126:     my $hour = 0;
 1127:     if ($self->{'hoursminutes'}) {
 1128:         $min = $ENV{'form.' . $var . 'minute'};
 1129:         $hour = $ENV{'form.' . $var . 'hour'};
 1130:     }
 1131: 
 1132:     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
 1133:     # Check to make sure that the date was not automatically co-erced into a 
 1134:     # valid date, as we want to flag that as an error
 1135:     # This happens for "Feb. 31", for instance, which is coerced to March 2 or
 1136:     # 3, depending on if it's a leapyear
 1137:     my $checkDate = localtime($chosenDate);
 1138: 
 1139:     if ($checkDate->mon != $month || $checkDate->mday != $day ||
 1140:         $checkDate->year + 1900 != $year) {
 1141:         $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
 1142:             . "date because it doesn't exist. Please enter a valid date.";
 1143:         return 0;
 1144:     }
 1145: 
 1146:     $helper->{VARS}->{$var} = $chosenDate;
 1147: 
 1148:     if (defined($self->{NEXTSTATE})) {
 1149:         $helper->changeState($self->{NEXTSTATE});
 1150:     }
 1151: 
 1152:     return 1;
 1153: }
 1154: 1;
 1155: 
 1156: package Apache::lonhelper::resource;
 1157: 
 1158: =pod
 1159: 
 1160: =head2 Element: resource
 1161: 
 1162: <resource> elements allow the user to select one or multiple resources
 1163: from the current course. You can filter out which resources they can view,
 1164: and filter out which resources they can select. The course will always
 1165: be displayed fully expanded, because of the difficulty of maintaining
 1166: selections across folder openings and closings. If this is fixed, then
 1167: the user can manipulate the folders.
 1168: 
 1169: <resource> takes the standard variable attribute to control what helper
 1170: variable stores the results. It also takes a "multichoice" attribute,
 1171: which controls whether the user can select more then one resource.
 1172: 
 1173: B<SUB-TAGS>
 1174: 
 1175: =over 4
 1176: 
 1177: =item * <filterfunc>: If you want to filter what resources are displayed
 1178:   to the user, use a filter func. The <filterfunc> tag should contain
 1179:   Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
 1180:   a function that returns true if the resource should be displayed, 
 1181:   and false if it should be skipped. $res is a resource object. 
 1182:   (See Apache::lonnavmaps documentation for information about the 
 1183:   resource object.)
 1184: 
 1185: =item * <choicefunc>: Same as <filterfunc>, except that controls whether
 1186:   the given resource can be chosen. (It is almost always a good idea to
 1187:   show the user the folders, for instance, but you do not always want to 
 1188:   let the user select them.)
 1189: 
 1190: =item * <nextstate>: Standard nextstate behavior.
 1191: 
 1192: =item * <valuefunc>: This function controls what is returned by the resource
 1193:   when the user selects it. Like filterfunc and choicefunc, it should be
 1194:   a function fragment that when wrapped by "sub { my $res = shift; " and
 1195:   "}" returns a string representing what you want to have as the value. By
 1196:   default, the value will be the resource ID of the object ($res->{ID}).
 1197: 
 1198: =back
 1199: 
 1200: =cut
 1201: 
 1202: no strict;
 1203: @ISA = ("Apache::lonhelper::element");
 1204: use strict;
 1205: 
 1206: BEGIN {
 1207:     &Apache::lonhelper::register('Apache::lonhelper::resource',
 1208:                               ('resource', 'filterfunc', 
 1209:                                'choicefunc', 'valuefunc'));
 1210: }
 1211: 
 1212: sub new {
 1213:     my $ref = Apache::lonhelper::element->new();
 1214:     bless($ref);
 1215: }
 1216: 
 1217: # CONSTRUCTION: Construct the message element from the XML
 1218: sub start_resource {
 1219:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1220: 
 1221:     if ($target ne 'helper') {
 1222:         return '';
 1223:     }
 1224: 
 1225:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1226:     $helper->declareVar($paramHash->{'variable'});
 1227:     return '';
 1228: }
 1229: 
 1230: sub end_resource {
 1231:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1232: 
 1233:     if ($target ne 'helper') {
 1234:         return '';
 1235:     }
 1236:     if (!defined($paramHash->{FILTER_FUNC})) {
 1237:         $paramHash->{FILTER_FUNC} = sub {return 1;};
 1238:     }
 1239:     if (!defined($paramHash->{CHOICE_FUNC})) {
 1240:         $paramHash->{CHOICE_FUNC} = sub {return 1;};
 1241:     }
 1242:     if (!defined($paramHash->{VALUE_FUNC})) {
 1243:         $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
 1244:     }
 1245:     Apache::lonhelper::resource->new();
 1246:     return '';
 1247: }
 1248: 
 1249: sub start_filterfunc {
 1250:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1251: 
 1252:     if ($target ne 'helper') {
 1253:         return '';
 1254:     }
 1255: 
 1256:     my $contents = Apache::lonxml::get_all_text('/filterfunc',
 1257:                                                 $parser);
 1258:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1259:     $paramHash->{FILTER_FUNC} = eval $contents;
 1260: }
 1261: 
 1262: sub end_filterfunc { return ''; }
 1263: 
 1264: sub start_choicefunc {
 1265:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1266: 
 1267:     if ($target ne 'helper') {
 1268:         return '';
 1269:     }
 1270: 
 1271:     my $contents = Apache::lonxml::get_all_text('/choicefunc',
 1272:                                                 $parser);
 1273:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1274:     $paramHash->{CHOICE_FUNC} = eval $contents;
 1275: }
 1276: 
 1277: sub end_choicefunc { return ''; }
 1278: 
 1279: sub start_valuefunc {
 1280:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1281: 
 1282:     if ($target ne 'helper') {
 1283:         return '';
 1284:     }
 1285: 
 1286:     my $contents = Apache::lonxml::get_all_text('/valuefunc',
 1287:                                                 $parser);
 1288:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1289:     $paramHash->{VALUE_FUNC} = eval $contents;
 1290: }
 1291: 
 1292: sub end_valuefunc { return ''; }
 1293: 
 1294: # A note, in case I don't get to this before I leave.
 1295: # If someone complains about the "Back" button returning them
 1296: # to the previous folder state, instead of returning them to
 1297: # the previous helper state, the *correct* answer is for the helper
 1298: # to keep track of how many times the user has manipulated the folders,
 1299: # and feed that to the history.go() call in the helper rendering routines.
 1300: # If done correctly, the helper itself can keep track of how many times
 1301: # it renders the same states, so it doesn't go in just this state, and
 1302: # you can lean on the browser back button to make sure it all chains
 1303: # correctly.
 1304: # Right now, though, I'm just forcing all folders open.
 1305: 
 1306: sub render {
 1307:     my $self = shift;
 1308:     my $result = "";
 1309:     my $var = $self->{'variable'};
 1310:     my $curVal = $helper->{VARS}->{$var};
 1311: 
 1312:     if (defined $self->{ERROR_MSG}) {
 1313:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1314:     }
 1315: 
 1316:     my $filterFunc = $self->{FILTER_FUNC};
 1317:     my $choiceFunc = $self->{CHOICE_FUNC};
 1318:     my $valueFunc = $self->{VALUE_FUNC};
 1319: 
 1320:     # Create the composite function that renders the column on the nav map
 1321:     # have to admit any language that lets me do this can't be all bad
 1322:     #  - Jeremy (Pythonista) ;-)
 1323:     my $checked = 0;
 1324:     my $renderColFunc = sub {
 1325:         my ($resource, $part, $params) = @_;
 1326:         
 1327:         if (!&$choiceFunc($resource)) {
 1328:             return '<td>&nbsp;</td>';
 1329:         } else {
 1330:             my $col = "<td><input type='radio' name='${var}.forminput' ";
 1331:             if (!$checked) {
 1332:                 $col .= "checked ";
 1333:                 $checked = 1;
 1334:             }
 1335:             $col .= "value='" . 
 1336:                 HTML::Entities::encode(&$valueFunc($resource)) 
 1337:                 . "' /></td>";
 1338:             return $col;
 1339:         }
 1340:     };
 1341: 
 1342:     $ENV{'form.condition'} = 1;
 1343:     $result .= 
 1344:         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
 1345:                                                   Apache::lonnavmaps::resource()],
 1346:                                        'showParts' => 0,
 1347:                                        'url' => $helper->{URL},
 1348:                                        'filterFunc' => $filterFunc,
 1349:                                        'resource_no_folder_link' => 1 }
 1350:                                        );
 1351:                                                 
 1352:     return $result;
 1353: }
 1354:     
 1355: sub postprocess {
 1356:     my $self = shift;
 1357:     if (defined($self->{NEXTSTATE})) {
 1358:         $helper->changeState($self->{NEXTSTATE});
 1359:     }
 1360: 
 1361:     return 1;
 1362: }
 1363: 
 1364: 1;
 1365: 
 1366: package Apache::lonhelper::student;
 1367: 
 1368: =pod
 1369: 
 1370: =head2 Element: student
 1371: 
 1372: Student elements display a choice of students enrolled in the current
 1373: course. Currently it is primitive; this is expected to evolve later.
 1374: 
 1375: Student elements take two attributes: "variable", which means what
 1376: it usually does, and "multichoice", which if true allows the user
 1377: to select multiple students.
 1378: 
 1379: =cut
 1380: 
 1381: no strict;
 1382: @ISA = ("Apache::lonhelper::element");
 1383: use strict;
 1384: 
 1385: 
 1386: 
 1387: BEGIN {
 1388:     &Apache::lonhelper::register('Apache::lonhelper::student',
 1389:                               ('student'));
 1390: }
 1391: 
 1392: sub new {
 1393:     my $ref = Apache::lonhelper::element->new();
 1394:     bless($ref);
 1395: }
 1396: 
 1397: sub start_student {
 1398:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1399: 
 1400:     if ($target ne 'helper') {
 1401:         return '';
 1402:     }
 1403: 
 1404:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1405:     $helper->declareVar($paramHash->{'variable'});
 1406:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1407: }    
 1408: 
 1409: sub end_student {
 1410:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1411: 
 1412:     if ($target ne 'helper') {
 1413:         return '';
 1414:     }
 1415:     Apache::lonhelper::student->new();
 1416: }
 1417: 
 1418: sub render {
 1419:     my $self = shift;
 1420:     my $result = '';
 1421:     my $buttons = '';
 1422: 
 1423:     if ($self->{'multichoice'}) {
 1424:         $result = <<SCRIPT;
 1425: <script>
 1426:     function checkall(value) {
 1427: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1428:             document.forms.wizform.elements[i].checked=value;
 1429:         }
 1430:     }
 1431: </script>
 1432: SCRIPT
 1433:         $buttons = <<BUTTONS;
 1434: <br />
 1435: <input type="button" onclick="checkall(true)" value="Select All" />
 1436: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1437: <br />
 1438: BUTTONS
 1439:     }
 1440: 
 1441:     if (defined $self->{ERROR_MSG}) {
 1442:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1443:     }
 1444: 
 1445:     # Load up the students
 1446:     my $choices = &Apache::loncoursedata::get_classlist();
 1447: 
 1448:     my @keys = keys %{$choices};
 1449: 
 1450:     # Constants
 1451:     my $section = Apache::loncoursedata::CL_SECTION();
 1452:     my $fullname = Apache::loncoursedata::CL_FULLNAME();
 1453: 
 1454:     # Sort by: Section, name
 1455:     @keys = sort {
 1456:         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
 1457:             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
 1458:         }
 1459:         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
 1460:     } @keys;
 1461: 
 1462:     my $type = 'radio';
 1463:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1464:     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
 1465:     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
 1466:         "<td align='center'><b>Section</b></td></tr>";
 1467: 
 1468:     my $checked = 0;
 1469:     foreach (@keys) {
 1470:         $result .= "<tr><td><input type='$type' name='" .
 1471:             $self->{'variable'} . '.forminput' . "'";
 1472:             
 1473:         if (!$self->{'multichoice'} && !$checked) {
 1474:             $result .= " checked ";
 1475:             $checked = 1;
 1476:         }
 1477:         $result .=
 1478:             " value='" . HTML::Entities::encode($_)
 1479:             . "' /></td><td>"
 1480:             . HTML::Entities::encode($choices->{$_}->[$fullname])
 1481:             . "</td><td align='center'>" 
 1482:             . HTML::Entities::encode($choices->{$_}->[$section])
 1483:             . "</td></tr>\n";
 1484:     }
 1485: 
 1486:     $result .= "</table>\n\n";
 1487:     $result .= $buttons;    
 1488:     
 1489:     return $result;
 1490: }
 1491: 
 1492: sub postprocess {
 1493:     my $self = shift;
 1494: 
 1495:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1496:     if (!$result) {
 1497:         $self->{ERROR_MSG} = 'You must choose at least one student '.
 1498:             'to continue.';
 1499:         return 0;
 1500:     }
 1501: 
 1502:     if ($self->{'multichoice'}) {
 1503:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1504:                                         $self->{'variable'});
 1505:     }
 1506:     if (defined($self->{NEXTSTATE})) {
 1507:         $helper->changeState($self->{NEXTSTATE});
 1508:     }
 1509: 
 1510:     return 1;
 1511: }
 1512: 
 1513: 1;
 1514: 
 1515: package Apache::lonhelper::files;
 1516: 
 1517: =pod
 1518: 
 1519: =head2 Element: files
 1520: 
 1521: files allows the users to choose files from a given directory on the
 1522: server. It is always multichoice and stores the result as a triple-pipe
 1523: delimited entry in the helper variables. 
 1524: 
 1525: Since it is extremely unlikely that you can actually code a constant
 1526: representing the directory you wish to allow the user to search, <files>
 1527: takes a subroutine that returns the name of the directory you wish to
 1528: have the user browse.
 1529: 
 1530: files accepts the attribute "variable" to control where the files chosen
 1531: are put. It accepts the attribute "multichoice" as the other attribute,
 1532: defaulting to false, which if true will allow the user to select more
 1533: then one choice. 
 1534: 
 1535: <files> accepts three subtags. One is the "nextstate" sub-tag that works
 1536: as it does with the other tags. Another is a <filechoice> sub tag that
 1537: is Perl code that, when surrounded by "sub {" and "}" will return a
 1538: string representing what directory on the server to allow the user to 
 1539: choose files from. Finally, the <filefilter> subtag should contain Perl
 1540: code that when surrounded by "sub { my $filename = shift; " and "}",
 1541: returns a true value if the user can pick that file, or false otherwise.
 1542: The filename passed to the function will be just the name of the file, 
 1543: with no path info.
 1544: 
 1545: =cut
 1546: 
 1547: no strict;
 1548: @ISA = ("Apache::lonhelper::element");
 1549: use strict;
 1550: 
 1551: BEGIN {
 1552:     &Apache::lonhelper::register('Apache::lonhelper::files',
 1553:                                  ('files', 'filechoice', 'filefilter'));
 1554: }
 1555: 
 1556: sub new {
 1557:     my $ref = Apache::lonhelper::element->new();
 1558:     bless($ref);
 1559: }
 1560: 
 1561: sub start_files {
 1562:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1563: 
 1564:     if ($target ne 'helper') {
 1565:         return '';
 1566:     }
 1567:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1568:     $helper->declareVar($paramHash->{'variable'});
 1569:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1570: }    
 1571: 
 1572: sub end_files {
 1573:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1574: 
 1575:     if ($target ne 'helper') {
 1576:         return '';
 1577:     }
 1578:     if (!defined($paramHash->{FILTER_FUNC})) {
 1579:         $paramHash->{FILTER_FUNC} = sub { return 1; };
 1580:     }
 1581:     Apache::lonhelper::files->new();
 1582: }    
 1583: 
 1584: sub start_filechoice {
 1585:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1586: 
 1587:     if ($target ne 'helper') {
 1588:         return '';
 1589:     }
 1590:     $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
 1591:                                                               $parser);
 1592: }
 1593: 
 1594: sub end_filechoice { return ''; }
 1595: 
 1596: sub start_filefilter {
 1597:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1598: 
 1599:     if ($target ne 'helper') {
 1600:         return '';
 1601:     }
 1602: 
 1603:     my $contents = Apache::lonxml::get_all_text('/filefilter',
 1604:                                                 $parser);
 1605:     $contents = 'sub { my $filename = shift; ' . $contents . '}';
 1606:     $paramHash->{FILTER_FUNC} = eval $contents;
 1607: }
 1608: 
 1609: sub end_filefilter { return ''; }
 1610: 
 1611: sub render {
 1612:     my $self = shift;
 1613:     my $result = '';
 1614:     my $var = $self->{'variable'};
 1615:     
 1616:     my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
 1617:     my $subdir = &$subdirFunc();
 1618: 
 1619:     my $filterFunc = $self->{FILTER_FUNC};
 1620:     my $buttons = '';
 1621: 
 1622:     if ($self->{'multichoice'}) {
 1623:         $result = <<SCRIPT;
 1624: <script>
 1625:     function checkall(value) {
 1626: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1627:             ele = document.forms.wizform.elements[i];
 1628:             if (ele.type == "checkbox") {
 1629:                 document.forms.wizform.elements[i].checked=value;
 1630:             }
 1631:         }
 1632:     }
 1633: </script>
 1634: SCRIPT
 1635:         my $buttons = <<BUTTONS;
 1636: <br /> &nbsp;
 1637: <input type="button" onclick="checkall(true)" value="Select All" />
 1638: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1639: <br /> &nbsp;
 1640: BUTTONS
 1641:     }
 1642: 
 1643:     # Get the list of files in this directory.
 1644:     my @fileList;
 1645: 
 1646:     # If the subdirectory is in local CSTR space
 1647:     if ($subdir =~ m|/home/([^/]+)/public_html|) {
 1648:         my $user = $1;
 1649:         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
 1650:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 1651:     } else {
 1652:         # local library server resource space
 1653:         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
 1654:     }
 1655: 
 1656:     $result .= $buttons;
 1657: 
 1658:     if (defined $self->{ERROR_MSG}) {
 1659:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1660:     }
 1661: 
 1662:     $result .= '<table border="0" cellpadding="1" cellspacing="1">';
 1663: 
 1664:     # Keeps track if there are no choices, prints appropriate error
 1665:     # if there are none. 
 1666:     my $choices = 0;
 1667:     my $type = 'radio';
 1668:     if ($self->{'multichoice'}) {
 1669:         $type = 'checkbox';
 1670:     }
 1671:     # Print each legitimate file choice.
 1672:     for my $file (@fileList) {
 1673:         $file = (split(/&/, $file))[0];
 1674:         if ($file eq '.' || $file eq '..') {
 1675:             next;
 1676:         }
 1677:         my $fileName = $subdir .'/'. $file;
 1678:         if (&$filterFunc($file)) {
 1679:             $result .= '<tr><td align="right">' .
 1680:                 "<input type='$type' name='" . $var
 1681:             . ".forminput' value='" . HTML::Entities::encode($fileName) .
 1682:                 "'";
 1683:             if (!$self->{'multichoice'} && $choices == 0) {
 1684:                 $result .= ' checked';
 1685:             }
 1686:             $result .= "/></td><td>" . $file . "</td></tr>\n";
 1687:             $choices++;
 1688:         }
 1689:     }
 1690: 
 1691:     $result .= "</table>\n";
 1692: 
 1693:     if (!$choices) {
 1694:         $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
 1695:     }
 1696: 
 1697:     $result .= $buttons;
 1698: 
 1699:     return $result;
 1700: }
 1701: 
 1702: sub postprocess {
 1703:     my $self = shift;
 1704:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1705:     if (!$result) {
 1706:         $self->{ERROR_MSG} = 'You must choose at least one file '.
 1707:             'to continue.';
 1708:         return 0;
 1709:     }
 1710: 
 1711:     if ($self->{'multichoice'}) {
 1712:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1713:                                         $self->{'variable'});
 1714:     }
 1715:     if (defined($self->{NEXTSTATE})) {
 1716:         $helper->changeState($self->{NEXTSTATE});
 1717:     }
 1718: 
 1719:     return 1;
 1720: }
 1721: 
 1722: 1;
 1723: 
 1724: __END__
 1725: 

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