File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.9: download - view: text, annotated - select for diffs
Fri Apr 11 19:01:46 2003 UTC (21 years, 2 months ago) by bowersj2
Branches: MAIN
CVS tags: HEAD
Added DATA hash to the helper, for use by helper writers.

    1: # The LearningOnline Network with CAPA
    2: # .helper XML handler to implement the LON-CAPA helper
    3: #
    4: # $Id: lonhelper.pm,v 1.9 2003/04/11 19:01:46 bowersj2 Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: # (Page Handler
   29: #
   30: # (.helper handler
   31: #
   32: 
   33: =pod
   34: 
   35: =head1 lonhelper - HTML Helper framework for LON-CAPA
   36: 
   37: Helpers, often known as "wizards", are well-established UI widgets that users
   38: feel comfortable with. It can take a complicated multidimensional problem the
   39: user has and turn it into a series of bite-sized one-dimensional questions.
   40: 
   41: For developers, helpers provide an easy way to bundle little bits of functionality
   42: for the user, without having to write the tedious state-maintenence code.
   43: 
   44: Helpers are defined as XML documents, placed in the /home/httpd/html/adm/helpers 
   45: directory and having the .helper file extension. For examples, see that directory.
   46: 
   47: All classes are in the Apache::lonhelper namespace.
   48: 
   49: =head2 lonhelper XML file format
   50: 
   51: A helper consists of a top-level <helper> tag which contains a series of states.
   52: Each state contains one or more state elements, which are what the user sees, like
   53: messages, resource selections, or date queries.
   54: 
   55: The helper tag is required to have one attribute, "title", which is the name
   56: of the helper itself, such as "Parameter helper". 
   57: 
   58: =head2 State tags
   59: 
   60: State tags are required to have an attribute "name", which is the symbolic
   61: name of the state and will not be directly seen by the user. The helper is
   62: required to have one state named "START", which is the state the helper
   63: will start with. By convention, this state should clearly describe what
   64: the helper will do for the user, and may also include the first information
   65: entry the user needs to do for the helper.
   66: 
   67: State tags are also required to have an attribute "title", which is the
   68: human name of the state, and will be displayed as the header on top of 
   69: the screen for the user.
   70: 
   71: =head2 Example Helper Skeleton
   72: 
   73: An example of the tags so far:
   74: 
   75:  <helper title="Example Helper">
   76:    <state name="START" title="Demonstrating the Example Helper">
   77:      <!-- notice this is the START state the wizard requires -->
   78:      </state>
   79:    <state name="GET_NAME" title="Enter Student Name">
   80:      </state>
   81:    </helper>
   82: 
   83: Of course this does nothing. In order for the wizard to do something, it is
   84: necessary to put actual elements into the wizard. Documentation for each
   85: of these elements follows.
   86: 
   87: =cut
   88: 
   89: package Apache::lonhelper;
   90: use Apache::Constants qw(:common);
   91: use Apache::File;
   92: use Apache::lonxml;
   93: 
   94: # Register all the tags with the helper, so the helper can 
   95: # push and pop them
   96: 
   97: my @helperTags;
   98: 
   99: sub register {
  100:     my ($namespace, @tags) = @_;
  101: 
  102:     for my $tag (@tags) {
  103:         push @helperTags, [$namespace, $tag];
  104:     }
  105: }
  106: 
  107: BEGIN {
  108:     Apache::lonxml::register('Apache::lonhelper', 
  109:                              ('helper'));
  110:       register('Apache::lonhelper', ('state'));
  111: }
  112: 
  113: # Since all helpers are only three levels deep (helper tag, state tag, 
  114: # substate type), it's easier and more readble to explicitly track 
  115: # those three things directly, rather then futz with the tag stack 
  116: # every time.
  117: my $helper;
  118: my $state;
  119: my $substate;
  120: # To collect parameters, the contents of the subtags are collected
  121: # into this paramHash, then passed to the element object when the 
  122: # end of the element tag is located.
  123: my $paramHash; 
  124: 
  125: sub handler {
  126:     my $r = shift;
  127:     $ENV{'request.uri'} = $r->uri();
  128:     my $filename = '/home/httpd/html' . $r->uri();
  129:     my $fh = Apache::File->new($filename);
  130:     my $file;
  131:     read $fh, $file, 100000000;
  132: 
  133:     Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
  134: 
  135:     # Send header, don't cache this page
  136:     if ($r->header_only) {
  137:         if ($ENV{'browser.mathml'}) {
  138:             $r->content_type('text/xml');
  139:         } else {
  140:             $r->content_type('text/html');
  141:         }
  142:         $r->send_http_header;
  143:         return OK;
  144:     }
  145:     if ($ENV{'browser.mathml'}) {
  146:         $r->content_type('text/xml');
  147:     } else {
  148:         $r->content_type('text/html');
  149:     }
  150:     $r->send_http_header;
  151:     $r->rflush();
  152: 
  153:     # Discard result, we just want the objects that get created by the
  154:     # xml parsing
  155:     &Apache::lonxml::xmlparse($r, 'helper', $file);
  156: 
  157:     $r->print($helper->display());
  158:    return OK;
  159: }
  160: 
  161: sub start_helper {
  162:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  163: 
  164:     if ($target ne 'helper') {
  165:         return '';
  166:     }
  167: 
  168:     for my $tagList (@helperTags) {
  169:         Apache::lonxml::register($tagList->[0], $tagList->[1]);
  170:     }
  171:     
  172:     $helper = Apache::lonhelper::helper->new($token->[2]{'title'});
  173:     return '';
  174: }
  175: 
  176: sub end_helper {
  177:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  178:     
  179:     if ($target ne 'helper') {
  180:         return '';
  181:     }
  182: 
  183:     for my $tagList (@helperTags) {
  184:         Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
  185:     }
  186: 
  187:     return '';
  188: }
  189: 
  190: sub start_state {
  191:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  192: 
  193:     if ($target ne 'helper') {
  194:         return '';
  195:     }
  196: 
  197:     $state = Apache::lonhelper::state->new($token->[2]{'name'},
  198:                                            $token->[2]{'title'});
  199:     return '';
  200: }
  201: 
  202: # don't need this, so ignore it
  203: sub end_state {
  204:     return '';
  205: }
  206: 
  207: 1;
  208: 
  209: package Apache::lonhelper::helper;
  210: 
  211: use Digest::MD5 qw(md5_hex);
  212: use HTML::Entities;
  213: use Apache::loncommon;
  214: use Apache::File;
  215: 
  216: sub new {
  217:     my $proto = shift;
  218:     my $class = ref($proto) || $proto;
  219:     my $self = {};
  220: 
  221:     $self->{TITLE} = shift;
  222:     
  223:     # If there is a state from the previous form, use that. If there is no
  224:     # state, use the start state parameter.
  225:     if (defined $ENV{"form.CURRENT_STATE"})
  226:     {
  227: 	$self->{STATE} = $ENV{"form.CURRENT_STATE"};
  228:     }
  229:     else
  230:     {
  231: 	$self->{STATE} = "START";
  232:     }
  233: 
  234:     $self->{TOKEN} = $ENV{'form.TOKEN'};
  235:     # If a token was passed, we load that in. Otherwise, we need to create a 
  236:     # new storage file
  237:     # Tried to use standard Tie'd hashes, but you can't seem to take a 
  238:     # reference to a tied hash and write to it. I'd call that a wart.
  239:     if ($self->{TOKEN}) {
  240:         # Validate the token before trusting it
  241:         if ($self->{TOKEN} !~ /^[a-f0-9]{32}$/) {
  242:             # Not legit. Return nothing and let all hell break loose.
  243:             # User shouldn't be doing that!
  244:             return undef;
  245:         }
  246: 
  247:         # Get the hash.
  248:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); # Note the token is not the literal file
  249:         
  250:         my $file = Apache::File->new($self->{FILENAME});
  251:         my $contents = <$file>;
  252: 
  253:         # Now load in the contents
  254:         for my $value (split (/&/, $contents)) {
  255:             my ($name, $value) = split(/=/, $value);
  256:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  257:             $self->{VARS}->{$name} = $value;
  258:         }
  259: 
  260:         $file->close();
  261:     } else {
  262:         # Only valid if we're just starting.
  263:         if ($self->{STATE} ne 'START') {
  264:             return undef;
  265:         }
  266:         # Must create the storage
  267:         $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} .
  268:                                  time() . rand());
  269:         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
  270:     }
  271: 
  272:     # OK, we now have our persistent storage.
  273: 
  274:     if (defined $ENV{"form.RETURN_PAGE"})
  275:     {
  276: 	$self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};
  277:     }
  278:     else
  279:     {
  280: 	$self->{RETURN_PAGE} = $ENV{REFERER};
  281:     }
  282: 
  283:     $self->{STATES} = {};
  284:     $self->{DONE} = 0;
  285: 
  286:     # Used by various helpers for various things; see lonparm.helper
  287:     # for an example.
  288:     $self->{DATA} = {};
  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: =cut
  798: 
  799: no strict;
  800: @ISA = ("Apache::lonhelper::element");
  801: use strict;
  802: 
  803: BEGIN {
  804:     &Apache::lonhelper::register('Apache::lonhelper::choices',
  805:                               ('choice', 'choices'));
  806: }
  807: 
  808: sub new {
  809:     my $ref = Apache::lonhelper::element->new();
  810:     bless($ref);
  811: }
  812: 
  813: # CONSTRUCTION: Construct the message element from the XML
  814: sub start_choices {
  815:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  816: 
  817:     if ($target ne 'helper') {
  818:         return '';
  819:     }
  820: 
  821:     # Need to initialize the choices list, so everything can assume it exists
  822:     $paramHash->{'variable'} = $token->[2]{'variable'};
  823:     $helper->declareVar($paramHash->{'variable'});
  824:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
  825:     $paramHash->{CHOICES} = [];
  826:     return '';
  827: }
  828: 
  829: sub end_choices {
  830:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  831: 
  832:     if ($target ne 'helper') {
  833:         return '';
  834:     }
  835:     Apache::lonhelper::choices->new();
  836:     return '';
  837: }
  838: 
  839: sub start_choice {
  840:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  841: 
  842:     if ($target ne 'helper') {
  843:         return '';
  844:     }
  845: 
  846:     my $computer = $token->[2]{'computer'};
  847:     my $human = &Apache::lonxml::get_all_text('/choice',
  848:                                               $parser);
  849:     my $nextstate = $token->[2]{'nextstate'};
  850:     push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate];
  851:     return '';
  852: }
  853: 
  854: sub end_choice {
  855:     return '';
  856: }
  857: 
  858: sub render {
  859:     # START HERE: Replace this with correct choices code.
  860:     my $self = shift;
  861:     my $var = $self->{'variable'};
  862:     my $buttons = '';
  863:     my $result = '';
  864: 
  865:     if ($self->{'multichoice'}) {
  866:         $result .= <<SCRIPT;
  867: <script>
  868:     function checkall(value) {
  869: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
  870:             document.forms.wizform.elements[i].checked=value;
  871:         }
  872:     }
  873: </script>
  874: SCRIPT
  875:         $buttons = <<BUTTONS;
  876: <br />
  877: <input type="button" onclick="checkall(true)" value="Select All" />
  878: <input type="button" onclick="checkall(false)" value="Unselect All" />
  879: <br />&nbsp;
  880: BUTTONS
  881:     }
  882: 
  883:     if (defined $self->{ERROR_MSG}) {
  884:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
  885:     }
  886: 
  887:     $result .= $buttons;
  888:     
  889:     $result .= "<table>\n\n";
  890: 
  891:     my $type = "radio";
  892:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
  893:     my $checked = 0;
  894:     foreach my $choice (@{$self->{CHOICES}}) {
  895:         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
  896:         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
  897:             . "' value='" . 
  898:             HTML::Entities::encode($choice->[1]) 
  899:             . "'";
  900:         if (!$self->{'multichoice'} && !$checked) {
  901:             $result .= " checked ";
  902:             $checked = 1;
  903:         }
  904:         $result .= "/></td><td> " . $choice->[0] . "</td></tr>\n";
  905:     }
  906:     $result .= "</table>\n\n\n";
  907:     $result .= $buttons;
  908: 
  909:     return $result;
  910: }
  911: 
  912: # If a NEXTSTATE was given or a nextstate for this choice was
  913: # given, switch to it
  914: sub postprocess {
  915:     my $self = shift;
  916:     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
  917: 
  918:     if (!$chosenValue) {
  919:         $self->{ERROR_MSG} = "You must choose one or more choices to" .
  920:             " continue.";
  921:         return 0;
  922:     }
  923: 
  924:     if ($self->{'multichoice'}) {
  925:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
  926:                                         $self->{'variable'});
  927:     }
  928: 
  929:     if (defined($self->{NEXTSTATE})) {
  930:         $helper->changeState($self->{NEXTSTATE});
  931:     }
  932:     
  933:     foreach my $choice (@{$self->{CHOICES}}) {
  934:         if ($choice->[1] eq $chosenValue) {
  935:             if (defined($choice->[2])) {
  936:                 $helper->changeState($choice->[2]);
  937:             }
  938:         }
  939:     }
  940:     return 1;
  941: }
  942: 1;
  943: 
  944: package Apache::lonhelper::date;
  945: 
  946: =pod
  947: 
  948: =head2 Element: date
  949: 
  950: Date elements allow the selection of a date with a drop down list.
  951: 
  952: Date elements can take two attributes:
  953: 
  954: =over 4
  955: 
  956: =item * B<variable>: The name of the variable to store the chosen
  957:         date in. Required.
  958: 
  959: =item * B<hoursminutes>: If a true value, the date will show hours
  960:         and minutes, as well as month/day/year. If false or missing,
  961:         the date will only show the month, day, and year.
  962: 
  963: =back
  964: 
  965: Date elements contain only an option <nextstate> tag to determine
  966: the next state.
  967: 
  968: Example:
  969: 
  970:  <date variable="DUE_DATE" hoursminutes="1">
  971:    <nextstate>choose_why</nextstate>
  972:    </date>
  973: 
  974: =cut
  975: 
  976: no strict;
  977: @ISA = ("Apache::lonhelper::element");
  978: use strict;
  979: 
  980: use Time::localtime;
  981: 
  982: BEGIN {
  983:     &Apache::lonhelper::register('Apache::lonhelper::date',
  984:                               ('date'));
  985: }
  986: 
  987: # Don't need to override the "new" from element
  988: sub new {
  989:     my $ref = Apache::lonhelper::element->new();
  990:     bless($ref);
  991: }
  992: 
  993: my @months = ("January", "February", "March", "April", "May", "June", "July",
  994: 	      "August", "September", "October", "November", "December");
  995: 
  996: # CONSTRUCTION: Construct the message element from the XML
  997: sub start_date {
  998:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  999: 
 1000:     if ($target ne 'helper') {
 1001:         return '';
 1002:     }
 1003: 
 1004:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1005:     $helper->declareVar($paramHash->{'variable'});
 1006:     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
 1007: }
 1008: 
 1009: sub end_date {
 1010:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1011: 
 1012:     if ($target ne 'helper') {
 1013:         return '';
 1014:     }
 1015:     Apache::lonhelper::date->new();
 1016:     return '';
 1017: }
 1018: 
 1019: sub render {
 1020:     my $self = shift;
 1021:     my $result = "";
 1022:     my $var = $self->{'variable'};
 1023: 
 1024:     my $date;
 1025:     
 1026:     # Default date: The current hour.
 1027:     $date = localtime();
 1028:     $date->min(0);
 1029: 
 1030:     if (defined $self->{ERROR_MSG}) {
 1031:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1032:     }
 1033: 
 1034:     # Month
 1035:     my $i;
 1036:     $result .= "<select name='${var}month'>\n";
 1037:     for ($i = 0; $i < 12; $i++) {
 1038:         if ($i == $date->mon) {
 1039:             $result .= "<option value='$i' selected>";
 1040:         } else {
 1041:             $result .= "<option value='$i'>";
 1042:         }
 1043:         $result .= $months[$i] . "</option>\n";
 1044:     }
 1045:     $result .= "</select>\n";
 1046: 
 1047:     # Day
 1048:     $result .= "<select name='${var}day'>\n";
 1049:     for ($i = 1; $i < 32; $i++) {
 1050:         if ($i == $date->mday) {
 1051:             $result .= '<option selected>';
 1052:         } else {
 1053:             $result .= '<option>';
 1054:         }
 1055:         $result .= "$i</option>\n";
 1056:     }
 1057:     $result .= "</select>,\n";
 1058: 
 1059:     # Year
 1060:     $result .= "<select name='${var}year'>\n";
 1061:     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
 1062:         if ($date->year + 1900 == $i) {
 1063:             $result .= "<option selected>";
 1064:         } else {
 1065:             $result .= "<option>";
 1066:         }
 1067:         $result .= "$i</option>\n";
 1068:     }
 1069:     $result .= "</select>,\n";
 1070: 
 1071:     # Display Hours and Minutes if they are called for
 1072:     if ($self->{'hoursminutes'}) {
 1073:         # Build hour
 1074:         $result .= "<select name='${var}hour'>\n";
 1075:         $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
 1076:             " value='0'>midnight</option>\n";
 1077:         for ($i = 1; $i < 12; $i++) {
 1078:             if ($date->hour == $i) {
 1079:                 $result .= "<option selected value='$i'>$i a.m.</option>\n";
 1080:             } else {
 1081:                 $result .= "<option value='$i'>$i a.m</option>\n";
 1082:             }
 1083:         }
 1084:         $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
 1085:             " value='12'>noon</option>\n";
 1086:         for ($i = 13; $i < 24; $i++) {
 1087:             my $printedHour = $i - 12;
 1088:             if ($date->hour == $i) {
 1089:                 $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
 1090:             } else {
 1091:                 $result .= "<option value='$i'>$printedHour p.m.</option>\n";
 1092:             }
 1093:         }
 1094: 
 1095:         $result .= "</select> :\n";
 1096: 
 1097:         $result .= "<select name='${var}minute'>\n";
 1098:         for ($i = 0; $i < 60; $i++) {
 1099:             my $printedMinute = $i;
 1100:             if ($i < 10) {
 1101:                 $printedMinute = "0" . $printedMinute;
 1102:             }
 1103:             if ($date->min == $i) {
 1104:                 $result .= "<option selected>";
 1105:             } else {
 1106:                 $result .= "<option>";
 1107:             }
 1108:             $result .= "$printedMinute</option>\n";
 1109:         }
 1110:         $result .= "</select>\n";
 1111:     }
 1112: 
 1113:     return $result;
 1114: 
 1115: }
 1116: # If a NEXTSTATE was given, switch to it
 1117: sub postprocess {
 1118:     my $self = shift;
 1119:     my $var = $self->{'variable'};
 1120:     my $month = $ENV{'form.' . $var . 'month'}; 
 1121:     my $day = $ENV{'form.' . $var . 'day'}; 
 1122:     my $year = $ENV{'form.' . $var . 'year'}; 
 1123:     my $min = 0; 
 1124:     my $hour = 0;
 1125:     if ($self->{'hoursminutes'}) {
 1126:         $min = $ENV{'form.' . $var . 'minute'};
 1127:         $hour = $ENV{'form.' . $var . 'hour'};
 1128:     }
 1129: 
 1130:     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
 1131:     # Check to make sure that the date was not automatically co-erced into a 
 1132:     # valid date, as we want to flag that as an error
 1133:     # This happens for "Feb. 31", for instance, which is coerced to March 2 or
 1134:     # 3, depending on if it's a leapyear
 1135:     my $checkDate = localtime($chosenDate);
 1136: 
 1137:     if ($checkDate->mon != $month || $checkDate->mday != $day ||
 1138:         $checkDate->year + 1900 != $year) {
 1139:         $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
 1140:             . "date because it doesn't exist. Please enter a valid date.";
 1141:         return 0;
 1142:     }
 1143: 
 1144:     $helper->{VARS}->{$var} = $chosenDate;
 1145: 
 1146:     if (defined($self->{NEXTSTATE})) {
 1147:         $helper->changeState($self->{NEXTSTATE});
 1148:     }
 1149: 
 1150:     return 1;
 1151: }
 1152: 1;
 1153: 
 1154: package Apache::lonhelper::resource;
 1155: 
 1156: =pod
 1157: 
 1158: =head2 Element: resource
 1159: 
 1160: <resource> elements allow the user to select one or multiple resources
 1161: from the current course. You can filter out which resources they can view,
 1162: and filter out which resources they can select. The course will always
 1163: be displayed fully expanded, because of the difficulty of maintaining
 1164: selections across folder openings and closings. If this is fixed, then
 1165: the user can manipulate the folders.
 1166: 
 1167: <resource> takes the standard variable attribute to control what helper
 1168: variable stores the results. It also takes a "multichoice" attribute,
 1169: which controls whether the user can select more then one resource.
 1170: 
 1171: B<SUB-TAGS>
 1172: 
 1173: =over 4
 1174: 
 1175: =item * <filterfunc>: If you want to filter what resources are displayed
 1176:   to the user, use a filter func. The <filterfunc> tag should contain
 1177:   Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
 1178:   a function that returns true if the resource should be displayed, 
 1179:   and false if it should be skipped. $res is a resource object. 
 1180:   (See Apache::lonnavmaps documentation for information about the 
 1181:   resource object.)
 1182: 
 1183: =item * <choicefunc>: Same as <filterfunc>, except that controls whether
 1184:   the given resource can be chosen. (It is almost always a good idea to
 1185:   show the user the folders, for instance, but you do not always want to 
 1186:   let the user select them.)
 1187: 
 1188: =item * <nextstate>: Standard nextstate behavior.
 1189: 
 1190: =item * <valuefunc>: This function controls what is returned by the resource
 1191:   when the user selects it. Like filterfunc and choicefunc, it should be
 1192:   a function fragment that when wrapped by "sub { my $res = shift; " and
 1193:   "}" returns a string representing what you want to have as the value. By
 1194:   default, the value will be the resource ID of the object ($res->{ID}).
 1195: 
 1196: =back
 1197: 
 1198: =cut
 1199: 
 1200: no strict;
 1201: @ISA = ("Apache::lonhelper::element");
 1202: use strict;
 1203: 
 1204: BEGIN {
 1205:     &Apache::lonhelper::register('Apache::lonhelper::resource',
 1206:                               ('resource', 'filterfunc', 
 1207:                                'choicefunc', 'valuefunc'));
 1208: }
 1209: 
 1210: sub new {
 1211:     my $ref = Apache::lonhelper::element->new();
 1212:     bless($ref);
 1213: }
 1214: 
 1215: # CONSTRUCTION: Construct the message element from the XML
 1216: sub start_resource {
 1217:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1218: 
 1219:     if ($target ne 'helper') {
 1220:         return '';
 1221:     }
 1222: 
 1223:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1224:     $helper->declareVar($paramHash->{'variable'});
 1225:     return '';
 1226: }
 1227: 
 1228: sub end_resource {
 1229:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1230: 
 1231:     if ($target ne 'helper') {
 1232:         return '';
 1233:     }
 1234:     if (!defined($paramHash->{FILTER_FUNC})) {
 1235:         $paramHash->{FILTER_FUNC} = sub {return 1;};
 1236:     }
 1237:     if (!defined($paramHash->{CHOICE_FUNC})) {
 1238:         $paramHash->{CHOICE_FUNC} = sub {return 1;};
 1239:     }
 1240:     if (!defined($paramHash->{VALUE_FUNC})) {
 1241:         $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
 1242:     }
 1243:     Apache::lonhelper::resource->new();
 1244:     return '';
 1245: }
 1246: 
 1247: sub start_filterfunc {
 1248:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1249: 
 1250:     if ($target ne 'helper') {
 1251:         return '';
 1252:     }
 1253: 
 1254:     my $contents = Apache::lonxml::get_all_text('/filterfunc',
 1255:                                                 $parser);
 1256:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1257:     $paramHash->{FILTER_FUNC} = eval $contents;
 1258: }
 1259: 
 1260: sub end_filterfunc { return ''; }
 1261: 
 1262: sub start_choicefunc {
 1263:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1264: 
 1265:     if ($target ne 'helper') {
 1266:         return '';
 1267:     }
 1268: 
 1269:     my $contents = Apache::lonxml::get_all_text('/choicefunc',
 1270:                                                 $parser);
 1271:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1272:     $paramHash->{CHOICE_FUNC} = eval $contents;
 1273: }
 1274: 
 1275: sub end_choicefunc { return ''; }
 1276: 
 1277: sub start_valuefunc {
 1278:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1279: 
 1280:     if ($target ne 'helper') {
 1281:         return '';
 1282:     }
 1283: 
 1284:     my $contents = Apache::lonxml::get_all_text('/valuefunc',
 1285:                                                 $parser);
 1286:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1287:     $paramHash->{VALUE_FUNC} = eval $contents;
 1288: }
 1289: 
 1290: sub end_valuefunc { return ''; }
 1291: 
 1292: # A note, in case I don't get to this before I leave.
 1293: # If someone complains about the "Back" button returning them
 1294: # to the previous folder state, instead of returning them to
 1295: # the previous helper state, the *correct* answer is for the helper
 1296: # to keep track of how many times the user has manipulated the folders,
 1297: # and feed that to the history.go() call in the helper rendering routines.
 1298: # If done correctly, the helper itself can keep track of how many times
 1299: # it renders the same states, so it doesn't go in just this state, and
 1300: # you can lean on the browser back button to make sure it all chains
 1301: # correctly.
 1302: # Right now, though, I'm just forcing all folders open.
 1303: 
 1304: sub render {
 1305:     my $self = shift;
 1306:     my $result = "";
 1307:     my $var = $self->{'variable'};
 1308:     my $curVal = $helper->{VARS}->{$var};
 1309: 
 1310:     if (defined $self->{ERROR_MSG}) {
 1311:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1312:     }
 1313: 
 1314:     my $filterFunc = $self->{FILTER_FUNC};
 1315:     my $choiceFunc = $self->{CHOICE_FUNC};
 1316:     my $valueFunc = $self->{VALUE_FUNC};
 1317: 
 1318:     # Create the composite function that renders the column on the nav map
 1319:     # have to admit any language that lets me do this can't be all bad
 1320:     #  - Jeremy (Pythonista) ;-)
 1321:     my $checked = 0;
 1322:     my $renderColFunc = sub {
 1323:         my ($resource, $part, $params) = @_;
 1324:         
 1325:         if (!&$choiceFunc($resource)) {
 1326:             return '<td>&nbsp;</td>';
 1327:         } else {
 1328:             my $col = "<td><input type='radio' name='${var}.forminput' ";
 1329:             if (!$checked) {
 1330:                 $col .= "checked ";
 1331:                 $checked = 1;
 1332:             }
 1333:             $col .= "value='" . 
 1334:                 HTML::Entities::encode(&$valueFunc($resource)) 
 1335:                 . "' /></td>";
 1336:             return $col;
 1337:         }
 1338:     };
 1339: 
 1340:     $ENV{'form.condition'} = 1;
 1341:     $result .= 
 1342:         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
 1343:                                                   Apache::lonnavmaps::resource()],
 1344:                                        'showParts' => 0,
 1345:                                        'url' => $helper->{URL},
 1346:                                        'filterFunc' => $filterFunc,
 1347:                                        'resource_no_folder_link' => 1 }
 1348:                                        );
 1349:                                                 
 1350:     return $result;
 1351: }
 1352:     
 1353: sub postprocess {
 1354:     my $self = shift;
 1355:     if (defined($self->{NEXTSTATE})) {
 1356:         $helper->changeState($self->{NEXTSTATE});
 1357:     }
 1358: 
 1359:     return 1;
 1360: }
 1361: 
 1362: 1;
 1363: 
 1364: package Apache::lonhelper::student;
 1365: 
 1366: =pod
 1367: 
 1368: =head2 Element: student
 1369: 
 1370: Student elements display a choice of students enrolled in the current
 1371: course. Currently it is primitive; this is expected to evolve later.
 1372: 
 1373: Student elements take two attributes: "variable", which means what
 1374: it usually does, and "multichoice", which if true allows the user
 1375: to select multiple students.
 1376: 
 1377: =cut
 1378: 
 1379: no strict;
 1380: @ISA = ("Apache::lonhelper::element");
 1381: use strict;
 1382: 
 1383: 
 1384: 
 1385: BEGIN {
 1386:     &Apache::lonhelper::register('Apache::lonhelper::student',
 1387:                               ('student'));
 1388: }
 1389: 
 1390: sub new {
 1391:     my $ref = Apache::lonhelper::element->new();
 1392:     bless($ref);
 1393: }
 1394: 
 1395: sub start_student {
 1396:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1397: 
 1398:     if ($target ne 'helper') {
 1399:         return '';
 1400:     }
 1401: 
 1402:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1403:     $helper->declareVar($paramHash->{'variable'});
 1404:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1405: }    
 1406: 
 1407: sub end_student {
 1408:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1409: 
 1410:     if ($target ne 'helper') {
 1411:         return '';
 1412:     }
 1413:     Apache::lonhelper::student->new();
 1414: }
 1415: 
 1416: sub render {
 1417:     my $self = shift;
 1418:     my $result = '';
 1419:     my $buttons = '';
 1420: 
 1421:     if ($self->{'multichoice'}) {
 1422:         $result = <<SCRIPT;
 1423: <script>
 1424:     function checkall(value) {
 1425: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1426:             document.forms.wizform.elements[i].checked=value;
 1427:         }
 1428:     }
 1429: </script>
 1430: SCRIPT
 1431:         $buttons = <<BUTTONS;
 1432: <br />
 1433: <input type="button" onclick="checkall(true)" value="Select All" />
 1434: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1435: <br />
 1436: BUTTONS
 1437:     }
 1438: 
 1439:     if (defined $self->{ERROR_MSG}) {
 1440:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1441:     }
 1442: 
 1443:     # Load up the students
 1444:     my $choices = &Apache::loncoursedata::get_classlist();
 1445: 
 1446:     my @keys = keys %{$choices};
 1447: 
 1448:     # Constants
 1449:     my $section = Apache::loncoursedata::CL_SECTION();
 1450:     my $fullname = Apache::loncoursedata::CL_FULLNAME();
 1451: 
 1452:     # Sort by: Section, name
 1453:     @keys = sort {
 1454:         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
 1455:             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
 1456:         }
 1457:         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
 1458:     } @keys;
 1459: 
 1460:     my $type = 'radio';
 1461:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1462:     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
 1463:     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
 1464:         "<td align='center'><b>Section</b></td></tr>";
 1465: 
 1466:     my $checked = 0;
 1467:     foreach (@keys) {
 1468:         $result .= "<tr><td><input type='$type' name='" .
 1469:             $self->{'variable'} . '.forminput' . "'";
 1470:             
 1471:         if (!$self->{'multichoice'} && !$checked) {
 1472:             $result .= " checked ";
 1473:             $checked = 1;
 1474:         }
 1475:         $result .=
 1476:             " value='" . HTML::Entities::encode($_)
 1477:             . "' /></td><td>"
 1478:             . HTML::Entities::encode($choices->{$_}->[$fullname])
 1479:             . "</td><td align='center'>" 
 1480:             . HTML::Entities::encode($choices->{$_}->[$section])
 1481:             . "</td></tr>\n";
 1482:     }
 1483: 
 1484:     $result .= "</table>\n\n";
 1485:     $result .= $buttons;    
 1486:     
 1487:     return $result;
 1488: }
 1489: 
 1490: sub postprocess {
 1491:     my $self = shift;
 1492: 
 1493:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1494:     if (!$result) {
 1495:         $self->{ERROR_MSG} = 'You must choose at least one student '.
 1496:             'to continue.';
 1497:         return 0;
 1498:     }
 1499: 
 1500:     if ($self->{'multichoice'}) {
 1501:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1502:                                         $self->{'variable'});
 1503:     }
 1504:     if (defined($self->{NEXTSTATE})) {
 1505:         $helper->changeState($self->{NEXTSTATE});
 1506:     }
 1507: 
 1508:     return 1;
 1509: }
 1510: 
 1511: 1;
 1512: 
 1513: package Apache::lonhelper::files;
 1514: 
 1515: =pod
 1516: 
 1517: =head2 Element: files
 1518: 
 1519: files allows the users to choose files from a given directory on the
 1520: server. It is always multichoice and stores the result as a triple-pipe
 1521: delimited entry in the helper variables. 
 1522: 
 1523: Since it is extremely unlikely that you can actually code a constant
 1524: representing the directory you wish to allow the user to search, <files>
 1525: takes a subroutine that returns the name of the directory you wish to
 1526: have the user browse.
 1527: 
 1528: files accepts the attribute "variable" to control where the files chosen
 1529: are put. It accepts the attribute "multichoice" as the other attribute,
 1530: defaulting to false, which if true will allow the user to select more
 1531: then one choice. 
 1532: 
 1533: <files> accepts three subtags. One is the "nextstate" sub-tag that works
 1534: as it does with the other tags. Another is a <filechoice> sub tag that
 1535: is Perl code that, when surrounded by "sub {" and "}" will return a
 1536: string representing what directory on the server to allow the user to 
 1537: choose files from. Finally, the <filefilter> subtag should contain Perl
 1538: code that when surrounded by "sub { my $filename = shift; " and "}",
 1539: returns a true value if the user can pick that file, or false otherwise.
 1540: The filename passed to the function will be just the name of the file, 
 1541: with no path info.
 1542: 
 1543: =cut
 1544: 
 1545: no strict;
 1546: @ISA = ("Apache::lonhelper::element");
 1547: use strict;
 1548: 
 1549: BEGIN {
 1550:     &Apache::lonhelper::register('Apache::lonhelper::files',
 1551:                                  ('files', 'filechoice', 'filefilter'));
 1552: }
 1553: 
 1554: sub new {
 1555:     my $ref = Apache::lonhelper::element->new();
 1556:     bless($ref);
 1557: }
 1558: 
 1559: sub start_files {
 1560:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1561: 
 1562:     if ($target ne 'helper') {
 1563:         return '';
 1564:     }
 1565:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1566:     $helper->declareVar($paramHash->{'variable'});
 1567:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1568: }    
 1569: 
 1570: sub end_files {
 1571:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1572: 
 1573:     if ($target ne 'helper') {
 1574:         return '';
 1575:     }
 1576:     if (!defined($paramHash->{FILTER_FUNC})) {
 1577:         $paramHash->{FILTER_FUNC} = sub { return 1; };
 1578:     }
 1579:     Apache::lonhelper::files->new();
 1580: }    
 1581: 
 1582: sub start_filechoice {
 1583:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1584: 
 1585:     if ($target ne 'helper') {
 1586:         return '';
 1587:     }
 1588:     $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
 1589:                                                               $parser);
 1590: }
 1591: 
 1592: sub end_filechoice { return ''; }
 1593: 
 1594: sub start_filefilter {
 1595:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1596: 
 1597:     if ($target ne 'helper') {
 1598:         return '';
 1599:     }
 1600: 
 1601:     my $contents = Apache::lonxml::get_all_text('/filefilter',
 1602:                                                 $parser);
 1603:     $contents = 'sub { my $filename = shift; ' . $contents . '}';
 1604:     $paramHash->{FILTER_FUNC} = eval $contents;
 1605: }
 1606: 
 1607: sub end_filefilter { return ''; }
 1608: 
 1609: sub render {
 1610:     my $self = shift;
 1611:     my $result = '';
 1612:     my $var = $self->{'variable'};
 1613:     
 1614:     my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
 1615:     my $subdir = &$subdirFunc();
 1616: 
 1617:     my $filterFunc = $self->{FILTER_FUNC};
 1618:     my $buttons = '';
 1619: 
 1620:     if ($self->{'multichoice'}) {
 1621:         $result = <<SCRIPT;
 1622: <script>
 1623:     function checkall(value) {
 1624: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1625:             ele = document.forms.wizform.elements[i];
 1626:             if (ele.type == "checkbox") {
 1627:                 document.forms.wizform.elements[i].checked=value;
 1628:             }
 1629:         }
 1630:     }
 1631: </script>
 1632: SCRIPT
 1633:         my $buttons = <<BUTTONS;
 1634: <br /> &nbsp;
 1635: <input type="button" onclick="checkall(true)" value="Select All" />
 1636: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1637: <br /> &nbsp;
 1638: BUTTONS
 1639:     }
 1640: 
 1641:     # Get the list of files in this directory.
 1642:     my @fileList;
 1643: 
 1644:     # If the subdirectory is in local CSTR space
 1645:     if ($subdir =~ m|/home/([^/]+)/public_html|) {
 1646:         my $user = $1;
 1647:         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
 1648:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 1649:     } else {
 1650:         # local library server resource space
 1651:         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
 1652:     }
 1653: 
 1654:     $result .= $buttons;
 1655: 
 1656:     if (defined $self->{ERROR_MSG}) {
 1657:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1658:     }
 1659: 
 1660:     $result .= '<table border="0" cellpadding="1" cellspacing="1">';
 1661: 
 1662:     # Keeps track if there are no choices, prints appropriate error
 1663:     # if there are none. 
 1664:     my $choices = 0;
 1665:     my $type = 'radio';
 1666:     if ($self->{'multichoice'}) {
 1667:         $type = 'checkbox';
 1668:     }
 1669:     # Print each legitimate file choice.
 1670:     for my $file (@fileList) {
 1671:         $file = (split(/&/, $file))[0];
 1672:         if ($file eq '.' || $file eq '..') {
 1673:             next;
 1674:         }
 1675:         my $fileName = $subdir .'/'. $file;
 1676:         if (&$filterFunc($file)) {
 1677:             $result .= '<tr><td align="right">' .
 1678:                 "<input type='$type' name='" . $var
 1679:             . ".forminput' value='" . HTML::Entities::encode($fileName) .
 1680:                 "'";
 1681:             if (!$self->{'multichoice'} && $choices == 0) {
 1682:                 $result .= ' checked';
 1683:             }
 1684:             $result .= "/></td><td>" . $file . "</td></tr>\n";
 1685:             $choices++;
 1686:         }
 1687:     }
 1688: 
 1689:     $result .= "</table>\n";
 1690: 
 1691:     if (!$choices) {
 1692:         $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
 1693:     }
 1694: 
 1695:     $result .= $buttons;
 1696: 
 1697:     return $result;
 1698: }
 1699: 
 1700: sub postprocess {
 1701:     my $self = shift;
 1702:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1703:     if (!$result) {
 1704:         $self->{ERROR_MSG} = 'You must choose at least one file '.
 1705:             'to continue.';
 1706:         return 0;
 1707:     }
 1708: 
 1709:     if ($self->{'multichoice'}) {
 1710:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1711:                                         $self->{'variable'});
 1712:     }
 1713:     if (defined($self->{NEXTSTATE})) {
 1714:         $helper->changeState($self->{NEXTSTATE});
 1715:     }
 1716: 
 1717:     return 1;
 1718: }
 1719: 
 1720: 1;
 1721: 
 1722: package Apache::lonhelper::general;
 1723: 
 1724: =pod
 1725: 
 1726: =head2 General-purpose tag: <exec>
 1727: 
 1728: The contents of the exec tag are executed as Perl code, not inside a 
 1729: safe space, so the full range of $ENV and such is available. The code
 1730: will be executed as a subroutine wrapped with the following code:
 1731: 
 1732: "sub { my $helper = shift; my $state = shift;" and
 1733: 
 1734: "}"
 1735: 
 1736: The return value is ignored.
 1737: 
 1738: $helper is the helper object. Feel free to add methods to the helper
 1739: object to support whatever manipulation you may need to do (for instance,
 1740: overriding the form location if the state is the final state; see 
 1741: lonparm.helper for an example).
 1742: 
 1743: $state is the $paramHash that has currently been generated and may
 1744: be manipulated by the code in exec. Note that the $state is not yet
 1745: an actual state B<object>, it is just a hash, so do not expect to
 1746: be able to call methods on it.
 1747: 
 1748: =cut
 1749: 
 1750: BEGIN {
 1751:     &Apache::lonhelper::register('Apache::lonhelper::general',
 1752:                                  'exec', 'condition', 'clause');
 1753: }
 1754: 
 1755: sub start_exec {
 1756:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1757: 
 1758:     if ($target ne 'helper') {
 1759:         return '';
 1760:     }
 1761:     
 1762:     my $code = &Apache::lonxml::get_all_text('/exec', $parser);
 1763:     
 1764:     $code = eval ('sub { my $helper = shift; my $state = shift; ' .
 1765:         $code . "}");
 1766:     &$code($helper, $paramHash);
 1767: }
 1768: 
 1769: sub end_exec { return ''; }
 1770: 
 1771: =pod
 1772: 
 1773: =head2 General-purpose tag: <condition>
 1774: 
 1775: The <condition> tag allows you to mask out parts of the helper code
 1776: depending on some programatically determined condition. The condition
 1777: tag contains a tag <clause> which contains perl code that when wrapped
 1778: with "sub { my $helper = shift; my $state = shift; " and "}", returns
 1779: a true value if the XML in the condition should be evaluated as a normal
 1780: part of the helper, or false if it should be completely discarded.
 1781: 
 1782: The <clause> tag must be the first sub-tag of the <condition> tag or
 1783: it will not work as expected.
 1784: 
 1785: =cut
 1786: 
 1787: # The condition tag just functions as a marker, it doesn't have
 1788: # to "do" anything. Technically it doesn't even have to be registered
 1789: # with the lonxml code, but I leave this here to be explicit about it.
 1790: sub start_condition { return ''; }
 1791: sub end_condition { return ''; }
 1792: 
 1793: sub start_clause {
 1794:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1795: 
 1796:     if ($target ne 'helper') {
 1797:         return '';
 1798:     }
 1799:     
 1800:     my $clause = Apache::lonxml::get_all_text('/clause', $parser);
 1801:     $clause = eval('sub { my $helper = shift; my $state = shift; '
 1802:         . $clause . '}');
 1803:     if (!&$clause($helper, $paramHash)) {
 1804:         # Discard all text until the /condition.
 1805:         &Apache::lonxml::get_all_text('/condition', $parser);
 1806:     }
 1807: }
 1808: 
 1809: sub end_clause { return ''; }
 1810: 
 1811: 1;
 1812: 
 1813: __END__
 1814: 

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