File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.8: download - view: text, annotated - select for diffs
Fri Apr 11 18:16:04 2003 UTC (21 years, 2 months ago) by bowersj2
Branches: MAIN
CVS tags: HEAD
Add the "condition" and "exec" tags for executing code and conditionally
dropping selected parts of the helper XML. This gives me most of the tools
I need to replicate current wizards.

    1: # The LearningOnline Network with CAPA
    2: # .helper XML handler to implement the LON-CAPA helper
    3: #
    4: # $Id: lonhelper.pm,v 1.8 2003/04/11 18:16:04 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:     bless($self, $class);
  287:     return $self;
  288: }
  289: 
  290: # Private function; returns a string to construct the hidden fields
  291: # necessary to have the helper track state.
  292: sub _saveVars {
  293:     my $self = shift;
  294:     my $result = "";
  295:     $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
  296:         HTML::Entities::encode($self->{STATE}) . "\" />\n";
  297:     $result .= '<input type="hidden" name="TOKEN" value="' .
  298:         $self->{TOKEN} . "\" />\n";
  299:     $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
  300:         HTML::Entities::encode($self->{RETURN_PAGE}) . "\" />\n";
  301: 
  302:     return $result;
  303: }
  304: 
  305: # Private function: Create the querystring-like representation of the stored
  306: # data to write to disk.
  307: sub _varsInFile {
  308:     my $self = shift;
  309:     my @vars = ();
  310:     for my $key (keys %{$self->{VARS}}) {
  311:         push @vars, &Apache::lonnet::escape($key) . '=' .
  312:             &Apache::lonnet::escape($self->{VARS}->{$key});
  313:     }
  314:     return join ('&', @vars);
  315: }
  316: 
  317: # Use this to declare variables.
  318: # FIXME: Document this
  319: sub declareVar {
  320:     my $self = shift;
  321:     my $var = shift;
  322: 
  323:     if (!defined($self->{VARS}->{$var})) {
  324:         $self->{VARS}->{$var} = '';
  325:     }
  326: 
  327:     my $envname = 'form.' . $var . '.forminput';
  328:     if (defined($ENV{$envname})) {
  329:         $self->{VARS}->{$var} = $ENV{$envname};
  330:     }
  331: }
  332: 
  333: sub changeState {
  334:     my $self = shift;
  335:     $self->{STATE} = shift;
  336: }
  337: 
  338: sub registerState {
  339:     my $self = shift;
  340:     my $state = shift;
  341: 
  342:     my $stateName = $state->name();
  343:     $self->{STATES}{$stateName} = $state;
  344: }
  345: 
  346: # Done in four phases
  347: # 1: Do the post processing for the previous state.
  348: # 2: Do the preprocessing for the current state.
  349: # 3: Check to see if state changed, if so, postprocess current and move to next.
  350: #    Repeat until state stays stable.
  351: # 4: Render the current state to the screen as an HTML page.
  352: sub display {
  353:     my $self = shift;
  354: 
  355:     my $result = "";
  356: 
  357:     # Phase 1: Post processing for state of previous screen (which is actually
  358:     # the "current state" in terms of the helper variables), if it wasn't the 
  359:     # beginning state.
  360:     if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {
  361: 	my $prevState = $self->{STATES}{$self->{STATE}};
  362:             $prevState->postprocess();
  363:     }
  364:     
  365:     # Note, to handle errors in a state's input that a user must correct,
  366:     # do not transition in the postprocess, and force the user to correct
  367:     # the error.
  368: 
  369:     # Phase 2: Preprocess current state
  370:     my $startState = $self->{STATE};
  371:     my $state = $self->{STATES}{$startState};
  372:     
  373:     # Error checking; it is intended that the developer will have
  374:     # checked all paths and the user can't see this!
  375:     if (!defined($state)) {
  376:         $result .="Error! The state ". $startState ." is not defined.";
  377:         return $result;
  378:     }
  379:     $state->preprocess();
  380: 
  381:     # Phase 3: While the current state is different from the previous state,
  382:     # keep processing.
  383:     while ( $startState ne $self->{STATE} )
  384:     {
  385: 	$startState = $self->{STATE};
  386: 	$state = $self->{STATES}{$startState};
  387: 	$state->preprocess();
  388:     }
  389: 
  390:     # Phase 4: Display.
  391:     my $stateTitle = $state->title();
  392:     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
  393: 
  394:     $result .= <<HEADER;
  395: <html>
  396:     <head>
  397:         <title>LON-CAPA Helper: $self->{TITLE}</title>
  398:     </head>
  399:     $bodytag
  400: HEADER
  401:     if (!$state->overrideForm()) { $result.="<form name='wizform' method='GET'>"; }
  402:     $result .= <<HEADER;
  403:         <table border="0"><tr><td>
  404:         <h2><i>$stateTitle</i></h2>
  405: HEADER
  406: 
  407:     if (!$state->overrideForm()) {
  408:         $result .= $self->_saveVars();
  409:     }
  410:     $result .= $state->render() . "<p>&nbsp;</p>";
  411: 
  412:     if (!$state->overrideForm()) {
  413:         $result .= '<center>';
  414:         if ($self->{STATE} ne $self->{START_STATE}) {
  415:             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
  416:         }
  417:         if ($self->{DONE}) {
  418:             my $returnPage = $self->{RETURN_PAGE};
  419:             $result .= "<a href=\"$returnPage\">End Helper</a>";
  420:         }
  421:         else {
  422:             $result .= '<input name="back" type="button" ';
  423:             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';
  424:             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';
  425:         }
  426:         $result .= "</center>\n";
  427:     }
  428: 
  429:     foreach my $key (keys %{$self->{VARS}}) {
  430:         $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
  431:     }
  432: 
  433:     $result .= <<FOOTER;
  434:               </td>
  435:             </tr>
  436:           </table>
  437:         </form>
  438:     </body>
  439: </html>
  440: FOOTER
  441: 
  442:     # Handle writing out the vars to the file
  443:     my $file = Apache::File->new('>'.$self->{FILENAME});
  444:     print $file $self->_varsInFile();
  445: 
  446:     return $result;
  447: }
  448: 
  449: 1;
  450: 
  451: package Apache::lonhelper::state;
  452: 
  453: # States bundle things together and are responsible for compositing the
  454: # various elements together. It is not generally necessary for users to
  455: # use the state object directly, so it is not perldoc'ed.
  456: 
  457: # Basically, all the states do is pass calls to the elements and aggregate
  458: # the results.
  459: 
  460: sub new {
  461:     my $proto = shift;
  462:     my $class = ref($proto) || $proto;
  463:     my $self = {};
  464: 
  465:     $self->{NAME} = shift;
  466:     $self->{TITLE} = shift;
  467:     $self->{ELEMENTS} = [];
  468: 
  469:     bless($self, $class);
  470: 
  471:     $helper->registerState($self);
  472: 
  473:     return $self;
  474: }
  475: 
  476: sub name {
  477:     my $self = shift;
  478:     return $self->{NAME};
  479: }
  480: 
  481: sub title {
  482:     my $self = shift;
  483:     return $self->{TITLE};
  484: }
  485: 
  486: sub preprocess {
  487:     my $self = shift;
  488:     for my $element (@{$self->{ELEMENTS}}) {
  489:         $element->preprocess();
  490:     }
  491: }
  492: 
  493: # FIXME: Document that all postprocesses must return a true value or
  494: # the state transition will be overridden
  495: sub postprocess {
  496:     my $self = shift;
  497: 
  498:     # Save the state so we can roll it back if we need to.
  499:     my $originalState = $helper->{STATE};
  500:     my $everythingSuccessful = 1;
  501: 
  502:     for my $element (@{$self->{ELEMENTS}}) {
  503:         my $result = $element->postprocess();
  504:         if (!$result) { $everythingSuccessful = 0; }
  505:     }
  506: 
  507:     # If not all the postprocesses were successful, override
  508:     # any state transitions that may have occurred. It is the
  509:     # responsibility of the states to make sure they have 
  510:     # error handling in that case.
  511:     if (!$everythingSuccessful) {
  512:         $helper->{STATE} = $originalState;
  513:     }
  514: }
  515: 
  516: sub overrideForm {
  517:     return 0;
  518: }
  519: 
  520: sub addElement {
  521:     my $self = shift;
  522:     my $element = shift;
  523:     
  524:     push @{$self->{ELEMENTS}}, $element;
  525: }
  526: 
  527: sub render {
  528:     my $self = shift;
  529:     my @results = ();
  530: 
  531:     for my $element (@{$self->{ELEMENTS}}) {
  532:         push @results, $element->render();
  533:     }
  534:     return join("\n", @results);
  535: }
  536: 
  537: 1;
  538: 
  539: package Apache::lonhelper::element;
  540: # Support code for elements
  541: 
  542: =pod
  543: 
  544: =head2 Element Base Class
  545: 
  546: The Apache::lonhelper::element base class provides support methods for
  547: the elements to use, such as a multiple value processer.
  548: 
  549: B<Methods>:
  550: 
  551: =over 4
  552: 
  553: =item * process_multiple_choices(formName, varName): Process the form 
  554: element named "formName" and place the selected items into the helper 
  555: variable named varName. This is for things like checkboxes or 
  556: multiple-selection listboxes where the user can select more then 
  557: one entry. The selected entries are delimited by triple pipes in 
  558: the helper variables, like this:  
  559: 
  560:  CHOICE_1|||CHOICE_2|||CHOICE_3
  561: 
  562: =back
  563: 
  564: =cut
  565: 
  566: BEGIN {
  567:     &Apache::lonhelper::register('Apache::lonhelper::element',
  568:                                  ('nextstate'));
  569: }
  570: 
  571: # Because we use the param hash, this is often a sufficent
  572: # constructor
  573: sub new {
  574:     my $proto = shift;
  575:     my $class = ref($proto) || $proto;
  576:     my $self = $paramHash;
  577:     bless($self, $class);
  578: 
  579:     $self->{PARAMS} = $paramHash;
  580:     $self->{STATE} = $state;
  581:     $state->addElement($self);
  582:     
  583:     # Ensure param hash is not reused
  584:     $paramHash = {};
  585: 
  586:     return $self;
  587: }   
  588: 
  589: sub start_nextstate {
  590:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  591: 
  592:     if ($target ne 'helper') {
  593:         return '';
  594:     }
  595:     
  596:     $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
  597:                                                              $parser);
  598:     return '';
  599: }
  600: 
  601: sub end_nextstate { return ''; }
  602: 
  603: sub preprocess {
  604:     return 1;
  605: }
  606: 
  607: sub postprocess {
  608:     return 1;
  609: }
  610: 
  611: sub render {
  612:     return '';
  613: }
  614: 
  615: sub process_multiple_choices {
  616:     my $self = shift;
  617:     my $formname = shift;
  618:     my $var = shift;
  619: 
  620:     my $formvalue = $ENV{'form.' . $formname};
  621:     if ($formvalue) {
  622:         # Must extract values from querystring directly, as there
  623:         # may be more then one.
  624:         my @values;
  625:         for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
  626:             my ($name, $value) = split(/=/, $formparam);
  627:             if ($name ne $formname) {
  628:                 next;
  629:             }
  630:             $value =~ tr/+/ /;
  631:             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  632:             push @values, $value;
  633:         }
  634:         $helper->{VARS}->{$var} = join('|||', @values);
  635:     }
  636:     
  637:     return;
  638: }
  639: 
  640: 1;
  641: 
  642: package Apache::lonhelper::message;
  643: 
  644: =pod
  645: 
  646: =head2 Element: message
  647: 
  648: Message elements display the contents of their <message_text> tags, and
  649: transition directly to the state in the <nextstate> tag. Example:
  650: 
  651:  <message>
  652:    <nextstate>GET_NAME</nextstate>
  653:    <message_text>This is the <b>message</b> the user will see, 
  654:                  <i>HTML allowed</i>.</message_text>
  655:    </message>
  656: 
  657: This will display the HTML message and transition to the <nextstate> if
  658: given. The HTML will be directly inserted into the helper, so if you don't
  659: want text to run together, you'll need to manually wrap the <message_text>
  660: in <p> tags, or whatever is appropriate for your HTML.
  661: 
  662: Message tags do not add in whitespace, so if you want it, you'll need to add
  663: it into states. This is done so you can inline some elements, such as 
  664: the <date> element, right between two messages, giving the appearence that 
  665: the <date> element appears inline. (Note the elements can not be embedded
  666: within each other.)
  667: 
  668: This is also a good template for creating your own new states, as it has
  669: very little code beyond the state template.
  670: 
  671: =cut
  672: 
  673: no strict;
  674: @ISA = ("Apache::lonhelper::element");
  675: use strict;
  676: 
  677: BEGIN {
  678:     &Apache::lonhelper::register('Apache::lonhelper::message',
  679:                               ('message', 'message_text'));
  680: }
  681: 
  682: sub new {
  683:     my $ref = Apache::lonhelper::element->new();
  684:     bless($ref);
  685: }
  686: 
  687: # CONSTRUCTION: Construct the message element from the XML
  688: sub start_message {
  689:     return '';
  690: }
  691: 
  692: sub end_message {
  693:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  694: 
  695:     if ($target ne 'helper') {
  696:         return '';
  697:     }
  698:     Apache::lonhelper::message->new();
  699:     return '';
  700: }
  701: 
  702: sub start_message_text {
  703:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  704: 
  705:     if ($target ne 'helper') {
  706:         return '';
  707:     }
  708: 
  709:     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',
  710:                                                                $parser);
  711: }
  712:     
  713: sub end_message_text { return 1; }
  714: 
  715: sub render {
  716:     my $self = shift;
  717: 
  718:     return $self->{MESSAGE_TEXT};
  719: }
  720: # If a NEXTSTATE was given, switch to it
  721: sub postprocess {
  722:     my $self = shift;
  723:     if (defined($self->{NEXTSTATE})) {
  724:         $helper->changeState($self->{NEXTSTATE});
  725:     }
  726: 
  727:     return 1;
  728: }
  729: 1;
  730: 
  731: package Apache::lonhelper::choices;
  732: 
  733: =pod
  734: 
  735: =head2 Element: choices
  736: 
  737: Choice states provide a single choice to the user as a text selection box.
  738: A "choice" is two pieces of text, one which will be displayed to the user
  739: (the "human" value), and one which will be passed back to the program
  740: (the "computer" value). For instance, a human may choose from a list of
  741: resources on disk by title, while your program wants the file name.
  742: 
  743: <choices> takes an attribute "variable" to control which helper variable
  744: the result is stored in.
  745: 
  746: <choices> takes an attribute "multichoice" which, if set to a true
  747: value, will allow the user to select multiple choices.
  748: 
  749: B<SUB-TAGS>
  750: 
  751: <choices> can have the following subtags:
  752: 
  753: =over 4
  754: 
  755: =item * <nextstate>state_name</nextstate>: If given, this will cause the
  756:       choice element to transition to the given state after executing. If
  757:       this is used, do not pass nextstates to the <choice> tag.
  758: 
  759: =item * <choice />: If the choices are static,
  760:       this element will allow you to specify them. Each choice
  761:       contains  attribute, "computer", as described above. The
  762:       content of the tag will be used as the human label.
  763:       For example,  
  764:       <choice computer='234-12-7312'>Bobby McDormik</choice>.
  765: 
  766: <choice> may optionally contain a 'nextstate' attribute, which
  767: will be the state transisitoned to if the choice is made, if
  768: the choice is not multichoice.
  769: 
  770: =back
  771: 
  772: To create the choices programmatically, either wrap the choices in 
  773: <condition> tags (prefered), or use an <exec> block inside the <choice>
  774: tag. Store the choices in $state->{CHOICES}, which is a list of list
  775: references, where each list has three strings. The first is the human
  776: name, the second is the computer name. and the third is the option
  777: next state. For example:
  778: 
  779:  <exec>
  780:     for (my $i = 65; $i < 65 + 26; $i++) {
  781:         push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
  782:     }
  783:  </exec>
  784: 
  785: This will allow the user to select from the letters A-Z (in ASCII), while
  786: passing the ASCII value back into the helper variables, and the state
  787: will in all cases transition to 'next'.
  788: 
  789: You can mix and match methods of creating choices, as long as you always 
  790: "push" onto the choice list, rather then wiping it out. (You can even 
  791: remove choices programmatically, but that would probably be bad form.)
  792: 
  793: =cut
  794: 
  795: no strict;
  796: @ISA = ("Apache::lonhelper::element");
  797: use strict;
  798: 
  799: BEGIN {
  800:     &Apache::lonhelper::register('Apache::lonhelper::choices',
  801:                               ('choice', 'choices'));
  802: }
  803: 
  804: sub new {
  805:     my $ref = Apache::lonhelper::element->new();
  806:     bless($ref);
  807: }
  808: 
  809: # CONSTRUCTION: Construct the message element from the XML
  810: sub start_choices {
  811:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  812: 
  813:     if ($target ne 'helper') {
  814:         return '';
  815:     }
  816: 
  817:     # Need to initialize the choices list, so everything can assume it exists
  818:     $paramHash->{'variable'} = $token->[2]{'variable'};
  819:     $helper->declareVar($paramHash->{'variable'});
  820:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
  821:     $paramHash->{CHOICES} = [];
  822:     return '';
  823: }
  824: 
  825: sub end_choices {
  826:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  827: 
  828:     if ($target ne 'helper') {
  829:         return '';
  830:     }
  831:     Apache::lonhelper::choices->new();
  832:     return '';
  833: }
  834: 
  835: sub start_choice {
  836:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  837: 
  838:     if ($target ne 'helper') {
  839:         return '';
  840:     }
  841: 
  842:     my $computer = $token->[2]{'computer'};
  843:     my $human = &Apache::lonxml::get_all_text('/choice',
  844:                                               $parser);
  845:     my $nextstate = $token->[2]{'nextstate'};
  846:     push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate];
  847:     return '';
  848: }
  849: 
  850: sub end_choice {
  851:     return '';
  852: }
  853: 
  854: sub render {
  855:     # START HERE: Replace this with correct choices code.
  856:     my $self = shift;
  857:     my $var = $self->{'variable'};
  858:     my $buttons = '';
  859:     my $result = '';
  860: 
  861:     if ($self->{'multichoice'}) {
  862:         $result .= <<SCRIPT;
  863: <script>
  864:     function checkall(value) {
  865: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
  866:             document.forms.wizform.elements[i].checked=value;
  867:         }
  868:     }
  869: </script>
  870: SCRIPT
  871:         $buttons = <<BUTTONS;
  872: <br />
  873: <input type="button" onclick="checkall(true)" value="Select All" />
  874: <input type="button" onclick="checkall(false)" value="Unselect All" />
  875: <br />&nbsp;
  876: BUTTONS
  877:     }
  878: 
  879:     if (defined $self->{ERROR_MSG}) {
  880:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
  881:     }
  882: 
  883:     $result .= $buttons;
  884:     
  885:     $result .= "<table>\n\n";
  886: 
  887:     my $type = "radio";
  888:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
  889:     my $checked = 0;
  890:     foreach my $choice (@{$self->{CHOICES}}) {
  891:         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
  892:         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
  893:             . "' value='" . 
  894:             HTML::Entities::encode($choice->[1]) 
  895:             . "'";
  896:         if (!$self->{'multichoice'} && !$checked) {
  897:             $result .= " checked ";
  898:             $checked = 1;
  899:         }
  900:         $result .= "/></td><td> " . $choice->[0] . "</td></tr>\n";
  901:     }
  902:     $result .= "</table>\n\n\n";
  903:     $result .= $buttons;
  904: 
  905:     return $result;
  906: }
  907: 
  908: # If a NEXTSTATE was given or a nextstate for this choice was
  909: # given, switch to it
  910: sub postprocess {
  911:     my $self = shift;
  912:     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
  913: 
  914:     if (!$chosenValue) {
  915:         $self->{ERROR_MSG} = "You must choose one or more choices to" .
  916:             " continue.";
  917:         return 0;
  918:     }
  919: 
  920:     if ($self->{'multichoice'}) {
  921:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
  922:                                         $self->{'variable'});
  923:     }
  924: 
  925:     if (defined($self->{NEXTSTATE})) {
  926:         $helper->changeState($self->{NEXTSTATE});
  927:     }
  928:     
  929:     foreach my $choice (@{$self->{CHOICES}}) {
  930:         if ($choice->[1] eq $chosenValue) {
  931:             if (defined($choice->[2])) {
  932:                 $helper->changeState($choice->[2]);
  933:             }
  934:         }
  935:     }
  936:     return 1;
  937: }
  938: 1;
  939: 
  940: package Apache::lonhelper::date;
  941: 
  942: =pod
  943: 
  944: =head2 Element: date
  945: 
  946: Date elements allow the selection of a date with a drop down list.
  947: 
  948: Date elements can take two attributes:
  949: 
  950: =over 4
  951: 
  952: =item * B<variable>: The name of the variable to store the chosen
  953:         date in. Required.
  954: 
  955: =item * B<hoursminutes>: If a true value, the date will show hours
  956:         and minutes, as well as month/day/year. If false or missing,
  957:         the date will only show the month, day, and year.
  958: 
  959: =back
  960: 
  961: Date elements contain only an option <nextstate> tag to determine
  962: the next state.
  963: 
  964: Example:
  965: 
  966:  <date variable="DUE_DATE" hoursminutes="1">
  967:    <nextstate>choose_why</nextstate>
  968:    </date>
  969: 
  970: =cut
  971: 
  972: no strict;
  973: @ISA = ("Apache::lonhelper::element");
  974: use strict;
  975: 
  976: use Time::localtime;
  977: 
  978: BEGIN {
  979:     &Apache::lonhelper::register('Apache::lonhelper::date',
  980:                               ('date'));
  981: }
  982: 
  983: # Don't need to override the "new" from element
  984: sub new {
  985:     my $ref = Apache::lonhelper::element->new();
  986:     bless($ref);
  987: }
  988: 
  989: my @months = ("January", "February", "March", "April", "May", "June", "July",
  990: 	      "August", "September", "October", "November", "December");
  991: 
  992: # CONSTRUCTION: Construct the message element from the XML
  993: sub start_date {
  994:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  995: 
  996:     if ($target ne 'helper') {
  997:         return '';
  998:     }
  999: 
 1000:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1001:     $helper->declareVar($paramHash->{'variable'});
 1002:     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
 1003: }
 1004: 
 1005: sub end_date {
 1006:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1007: 
 1008:     if ($target ne 'helper') {
 1009:         return '';
 1010:     }
 1011:     Apache::lonhelper::date->new();
 1012:     return '';
 1013: }
 1014: 
 1015: sub render {
 1016:     my $self = shift;
 1017:     my $result = "";
 1018:     my $var = $self->{'variable'};
 1019: 
 1020:     my $date;
 1021:     
 1022:     # Default date: The current hour.
 1023:     $date = localtime();
 1024:     $date->min(0);
 1025: 
 1026:     if (defined $self->{ERROR_MSG}) {
 1027:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1028:     }
 1029: 
 1030:     # Month
 1031:     my $i;
 1032:     $result .= "<select name='${var}month'>\n";
 1033:     for ($i = 0; $i < 12; $i++) {
 1034:         if ($i == $date->mon) {
 1035:             $result .= "<option value='$i' selected>";
 1036:         } else {
 1037:             $result .= "<option value='$i'>";
 1038:         }
 1039:         $result .= $months[$i] . "</option>\n";
 1040:     }
 1041:     $result .= "</select>\n";
 1042: 
 1043:     # Day
 1044:     $result .= "<select name='${var}day'>\n";
 1045:     for ($i = 1; $i < 32; $i++) {
 1046:         if ($i == $date->mday) {
 1047:             $result .= '<option selected>';
 1048:         } else {
 1049:             $result .= '<option>';
 1050:         }
 1051:         $result .= "$i</option>\n";
 1052:     }
 1053:     $result .= "</select>,\n";
 1054: 
 1055:     # Year
 1056:     $result .= "<select name='${var}year'>\n";
 1057:     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
 1058:         if ($date->year + 1900 == $i) {
 1059:             $result .= "<option selected>";
 1060:         } else {
 1061:             $result .= "<option>";
 1062:         }
 1063:         $result .= "$i</option>\n";
 1064:     }
 1065:     $result .= "</select>,\n";
 1066: 
 1067:     # Display Hours and Minutes if they are called for
 1068:     if ($self->{'hoursminutes'}) {
 1069:         # Build hour
 1070:         $result .= "<select name='${var}hour'>\n";
 1071:         $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
 1072:             " value='0'>midnight</option>\n";
 1073:         for ($i = 1; $i < 12; $i++) {
 1074:             if ($date->hour == $i) {
 1075:                 $result .= "<option selected value='$i'>$i a.m.</option>\n";
 1076:             } else {
 1077:                 $result .= "<option value='$i'>$i a.m</option>\n";
 1078:             }
 1079:         }
 1080:         $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
 1081:             " value='12'>noon</option>\n";
 1082:         for ($i = 13; $i < 24; $i++) {
 1083:             my $printedHour = $i - 12;
 1084:             if ($date->hour == $i) {
 1085:                 $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
 1086:             } else {
 1087:                 $result .= "<option value='$i'>$printedHour p.m.</option>\n";
 1088:             }
 1089:         }
 1090: 
 1091:         $result .= "</select> :\n";
 1092: 
 1093:         $result .= "<select name='${var}minute'>\n";
 1094:         for ($i = 0; $i < 60; $i++) {
 1095:             my $printedMinute = $i;
 1096:             if ($i < 10) {
 1097:                 $printedMinute = "0" . $printedMinute;
 1098:             }
 1099:             if ($date->min == $i) {
 1100:                 $result .= "<option selected>";
 1101:             } else {
 1102:                 $result .= "<option>";
 1103:             }
 1104:             $result .= "$printedMinute</option>\n";
 1105:         }
 1106:         $result .= "</select>\n";
 1107:     }
 1108: 
 1109:     return $result;
 1110: 
 1111: }
 1112: # If a NEXTSTATE was given, switch to it
 1113: sub postprocess {
 1114:     my $self = shift;
 1115:     my $var = $self->{'variable'};
 1116:     my $month = $ENV{'form.' . $var . 'month'}; 
 1117:     my $day = $ENV{'form.' . $var . 'day'}; 
 1118:     my $year = $ENV{'form.' . $var . 'year'}; 
 1119:     my $min = 0; 
 1120:     my $hour = 0;
 1121:     if ($self->{'hoursminutes'}) {
 1122:         $min = $ENV{'form.' . $var . 'minute'};
 1123:         $hour = $ENV{'form.' . $var . 'hour'};
 1124:     }
 1125: 
 1126:     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
 1127:     # Check to make sure that the date was not automatically co-erced into a 
 1128:     # valid date, as we want to flag that as an error
 1129:     # This happens for "Feb. 31", for instance, which is coerced to March 2 or
 1130:     # 3, depending on if it's a leapyear
 1131:     my $checkDate = localtime($chosenDate);
 1132: 
 1133:     if ($checkDate->mon != $month || $checkDate->mday != $day ||
 1134:         $checkDate->year + 1900 != $year) {
 1135:         $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
 1136:             . "date because it doesn't exist. Please enter a valid date.";
 1137:         return 0;
 1138:     }
 1139: 
 1140:     $helper->{VARS}->{$var} = $chosenDate;
 1141: 
 1142:     if (defined($self->{NEXTSTATE})) {
 1143:         $helper->changeState($self->{NEXTSTATE});
 1144:     }
 1145: 
 1146:     return 1;
 1147: }
 1148: 1;
 1149: 
 1150: package Apache::lonhelper::resource;
 1151: 
 1152: =pod
 1153: 
 1154: =head2 Element: resource
 1155: 
 1156: <resource> elements allow the user to select one or multiple resources
 1157: from the current course. You can filter out which resources they can view,
 1158: and filter out which resources they can select. The course will always
 1159: be displayed fully expanded, because of the difficulty of maintaining
 1160: selections across folder openings and closings. If this is fixed, then
 1161: the user can manipulate the folders.
 1162: 
 1163: <resource> takes the standard variable attribute to control what helper
 1164: variable stores the results. It also takes a "multichoice" attribute,
 1165: which controls whether the user can select more then one resource.
 1166: 
 1167: B<SUB-TAGS>
 1168: 
 1169: =over 4
 1170: 
 1171: =item * <filterfunc>: If you want to filter what resources are displayed
 1172:   to the user, use a filter func. The <filterfunc> tag should contain
 1173:   Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
 1174:   a function that returns true if the resource should be displayed, 
 1175:   and false if it should be skipped. $res is a resource object. 
 1176:   (See Apache::lonnavmaps documentation for information about the 
 1177:   resource object.)
 1178: 
 1179: =item * <choicefunc>: Same as <filterfunc>, except that controls whether
 1180:   the given resource can be chosen. (It is almost always a good idea to
 1181:   show the user the folders, for instance, but you do not always want to 
 1182:   let the user select them.)
 1183: 
 1184: =item * <nextstate>: Standard nextstate behavior.
 1185: 
 1186: =item * <valuefunc>: This function controls what is returned by the resource
 1187:   when the user selects it. Like filterfunc and choicefunc, it should be
 1188:   a function fragment that when wrapped by "sub { my $res = shift; " and
 1189:   "}" returns a string representing what you want to have as the value. By
 1190:   default, the value will be the resource ID of the object ($res->{ID}).
 1191: 
 1192: =back
 1193: 
 1194: =cut
 1195: 
 1196: no strict;
 1197: @ISA = ("Apache::lonhelper::element");
 1198: use strict;
 1199: 
 1200: BEGIN {
 1201:     &Apache::lonhelper::register('Apache::lonhelper::resource',
 1202:                               ('resource', 'filterfunc', 
 1203:                                'choicefunc', 'valuefunc'));
 1204: }
 1205: 
 1206: sub new {
 1207:     my $ref = Apache::lonhelper::element->new();
 1208:     bless($ref);
 1209: }
 1210: 
 1211: # CONSTRUCTION: Construct the message element from the XML
 1212: sub start_resource {
 1213:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1214: 
 1215:     if ($target ne 'helper') {
 1216:         return '';
 1217:     }
 1218: 
 1219:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1220:     $helper->declareVar($paramHash->{'variable'});
 1221:     return '';
 1222: }
 1223: 
 1224: sub end_resource {
 1225:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1226: 
 1227:     if ($target ne 'helper') {
 1228:         return '';
 1229:     }
 1230:     if (!defined($paramHash->{FILTER_FUNC})) {
 1231:         $paramHash->{FILTER_FUNC} = sub {return 1;};
 1232:     }
 1233:     if (!defined($paramHash->{CHOICE_FUNC})) {
 1234:         $paramHash->{CHOICE_FUNC} = sub {return 1;};
 1235:     }
 1236:     if (!defined($paramHash->{VALUE_FUNC})) {
 1237:         $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
 1238:     }
 1239:     Apache::lonhelper::resource->new();
 1240:     return '';
 1241: }
 1242: 
 1243: sub start_filterfunc {
 1244:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1245: 
 1246:     if ($target ne 'helper') {
 1247:         return '';
 1248:     }
 1249: 
 1250:     my $contents = Apache::lonxml::get_all_text('/filterfunc',
 1251:                                                 $parser);
 1252:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1253:     $paramHash->{FILTER_FUNC} = eval $contents;
 1254: }
 1255: 
 1256: sub end_filterfunc { return ''; }
 1257: 
 1258: sub start_choicefunc {
 1259:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1260: 
 1261:     if ($target ne 'helper') {
 1262:         return '';
 1263:     }
 1264: 
 1265:     my $contents = Apache::lonxml::get_all_text('/choicefunc',
 1266:                                                 $parser);
 1267:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1268:     $paramHash->{CHOICE_FUNC} = eval $contents;
 1269: }
 1270: 
 1271: sub end_choicefunc { return ''; }
 1272: 
 1273: sub start_valuefunc {
 1274:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1275: 
 1276:     if ($target ne 'helper') {
 1277:         return '';
 1278:     }
 1279: 
 1280:     my $contents = Apache::lonxml::get_all_text('/valuefunc',
 1281:                                                 $parser);
 1282:     $contents = 'sub { my $res = shift; ' . $contents . '}';
 1283:     $paramHash->{VALUE_FUNC} = eval $contents;
 1284: }
 1285: 
 1286: sub end_valuefunc { return ''; }
 1287: 
 1288: # A note, in case I don't get to this before I leave.
 1289: # If someone complains about the "Back" button returning them
 1290: # to the previous folder state, instead of returning them to
 1291: # the previous helper state, the *correct* answer is for the helper
 1292: # to keep track of how many times the user has manipulated the folders,
 1293: # and feed that to the history.go() call in the helper rendering routines.
 1294: # If done correctly, the helper itself can keep track of how many times
 1295: # it renders the same states, so it doesn't go in just this state, and
 1296: # you can lean on the browser back button to make sure it all chains
 1297: # correctly.
 1298: # Right now, though, I'm just forcing all folders open.
 1299: 
 1300: sub render {
 1301:     my $self = shift;
 1302:     my $result = "";
 1303:     my $var = $self->{'variable'};
 1304:     my $curVal = $helper->{VARS}->{$var};
 1305: 
 1306:     if (defined $self->{ERROR_MSG}) {
 1307:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1308:     }
 1309: 
 1310:     my $filterFunc = $self->{FILTER_FUNC};
 1311:     my $choiceFunc = $self->{CHOICE_FUNC};
 1312:     my $valueFunc = $self->{VALUE_FUNC};
 1313: 
 1314:     # Create the composite function that renders the column on the nav map
 1315:     # have to admit any language that lets me do this can't be all bad
 1316:     #  - Jeremy (Pythonista) ;-)
 1317:     my $checked = 0;
 1318:     my $renderColFunc = sub {
 1319:         my ($resource, $part, $params) = @_;
 1320:         
 1321:         if (!&$choiceFunc($resource)) {
 1322:             return '<td>&nbsp;</td>';
 1323:         } else {
 1324:             my $col = "<td><input type='radio' name='${var}.forminput' ";
 1325:             if (!$checked) {
 1326:                 $col .= "checked ";
 1327:                 $checked = 1;
 1328:             }
 1329:             $col .= "value='" . 
 1330:                 HTML::Entities::encode(&$valueFunc($resource)) 
 1331:                 . "' /></td>";
 1332:             return $col;
 1333:         }
 1334:     };
 1335: 
 1336:     $ENV{'form.condition'} = 1;
 1337:     $result .= 
 1338:         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
 1339:                                                   Apache::lonnavmaps::resource()],
 1340:                                        'showParts' => 0,
 1341:                                        'url' => $helper->{URL},
 1342:                                        'filterFunc' => $filterFunc,
 1343:                                        'resource_no_folder_link' => 1 }
 1344:                                        );
 1345:                                                 
 1346:     return $result;
 1347: }
 1348:     
 1349: sub postprocess {
 1350:     my $self = shift;
 1351:     if (defined($self->{NEXTSTATE})) {
 1352:         $helper->changeState($self->{NEXTSTATE});
 1353:     }
 1354: 
 1355:     return 1;
 1356: }
 1357: 
 1358: 1;
 1359: 
 1360: package Apache::lonhelper::student;
 1361: 
 1362: =pod
 1363: 
 1364: =head2 Element: student
 1365: 
 1366: Student elements display a choice of students enrolled in the current
 1367: course. Currently it is primitive; this is expected to evolve later.
 1368: 
 1369: Student elements take two attributes: "variable", which means what
 1370: it usually does, and "multichoice", which if true allows the user
 1371: to select multiple students.
 1372: 
 1373: =cut
 1374: 
 1375: no strict;
 1376: @ISA = ("Apache::lonhelper::element");
 1377: use strict;
 1378: 
 1379: 
 1380: 
 1381: BEGIN {
 1382:     &Apache::lonhelper::register('Apache::lonhelper::student',
 1383:                               ('student'));
 1384: }
 1385: 
 1386: sub new {
 1387:     my $ref = Apache::lonhelper::element->new();
 1388:     bless($ref);
 1389: }
 1390: 
 1391: sub start_student {
 1392:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1393: 
 1394:     if ($target ne 'helper') {
 1395:         return '';
 1396:     }
 1397: 
 1398:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1399:     $helper->declareVar($paramHash->{'variable'});
 1400:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1401: }    
 1402: 
 1403: sub end_student {
 1404:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1405: 
 1406:     if ($target ne 'helper') {
 1407:         return '';
 1408:     }
 1409:     Apache::lonhelper::student->new();
 1410: }
 1411: 
 1412: sub render {
 1413:     my $self = shift;
 1414:     my $result = '';
 1415:     my $buttons = '';
 1416: 
 1417:     if ($self->{'multichoice'}) {
 1418:         $result = <<SCRIPT;
 1419: <script>
 1420:     function checkall(value) {
 1421: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1422:             document.forms.wizform.elements[i].checked=value;
 1423:         }
 1424:     }
 1425: </script>
 1426: SCRIPT
 1427:         $buttons = <<BUTTONS;
 1428: <br />
 1429: <input type="button" onclick="checkall(true)" value="Select All" />
 1430: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1431: <br />
 1432: BUTTONS
 1433:     }
 1434: 
 1435:     if (defined $self->{ERROR_MSG}) {
 1436:         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1437:     }
 1438: 
 1439:     # Load up the students
 1440:     my $choices = &Apache::loncoursedata::get_classlist();
 1441: 
 1442:     my @keys = keys %{$choices};
 1443: 
 1444:     # Constants
 1445:     my $section = Apache::loncoursedata::CL_SECTION();
 1446:     my $fullname = Apache::loncoursedata::CL_FULLNAME();
 1447: 
 1448:     # Sort by: Section, name
 1449:     @keys = sort {
 1450:         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
 1451:             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
 1452:         }
 1453:         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
 1454:     } @keys;
 1455: 
 1456:     my $type = 'radio';
 1457:     if ($self->{'multichoice'}) { $type = 'checkbox'; }
 1458:     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
 1459:     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
 1460:         "<td align='center'><b>Section</b></td></tr>";
 1461: 
 1462:     my $checked = 0;
 1463:     foreach (@keys) {
 1464:         $result .= "<tr><td><input type='$type' name='" .
 1465:             $self->{'variable'} . '.forminput' . "'";
 1466:             
 1467:         if (!$self->{'multichoice'} && !$checked) {
 1468:             $result .= " checked ";
 1469:             $checked = 1;
 1470:         }
 1471:         $result .=
 1472:             " value='" . HTML::Entities::encode($_)
 1473:             . "' /></td><td>"
 1474:             . HTML::Entities::encode($choices->{$_}->[$fullname])
 1475:             . "</td><td align='center'>" 
 1476:             . HTML::Entities::encode($choices->{$_}->[$section])
 1477:             . "</td></tr>\n";
 1478:     }
 1479: 
 1480:     $result .= "</table>\n\n";
 1481:     $result .= $buttons;    
 1482:     
 1483:     return $result;
 1484: }
 1485: 
 1486: sub postprocess {
 1487:     my $self = shift;
 1488: 
 1489:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1490:     if (!$result) {
 1491:         $self->{ERROR_MSG} = 'You must choose at least one student '.
 1492:             'to continue.';
 1493:         return 0;
 1494:     }
 1495: 
 1496:     if ($self->{'multichoice'}) {
 1497:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1498:                                         $self->{'variable'});
 1499:     }
 1500:     if (defined($self->{NEXTSTATE})) {
 1501:         $helper->changeState($self->{NEXTSTATE});
 1502:     }
 1503: 
 1504:     return 1;
 1505: }
 1506: 
 1507: 1;
 1508: 
 1509: package Apache::lonhelper::files;
 1510: 
 1511: =pod
 1512: 
 1513: =head2 Element: files
 1514: 
 1515: files allows the users to choose files from a given directory on the
 1516: server. It is always multichoice and stores the result as a triple-pipe
 1517: delimited entry in the helper variables. 
 1518: 
 1519: Since it is extremely unlikely that you can actually code a constant
 1520: representing the directory you wish to allow the user to search, <files>
 1521: takes a subroutine that returns the name of the directory you wish to
 1522: have the user browse.
 1523: 
 1524: files accepts the attribute "variable" to control where the files chosen
 1525: are put. It accepts the attribute "multichoice" as the other attribute,
 1526: defaulting to false, which if true will allow the user to select more
 1527: then one choice. 
 1528: 
 1529: <files> accepts three subtags. One is the "nextstate" sub-tag that works
 1530: as it does with the other tags. Another is a <filechoice> sub tag that
 1531: is Perl code that, when surrounded by "sub {" and "}" will return a
 1532: string representing what directory on the server to allow the user to 
 1533: choose files from. Finally, the <filefilter> subtag should contain Perl
 1534: code that when surrounded by "sub { my $filename = shift; " and "}",
 1535: returns a true value if the user can pick that file, or false otherwise.
 1536: The filename passed to the function will be just the name of the file, 
 1537: with no path info.
 1538: 
 1539: =cut
 1540: 
 1541: no strict;
 1542: @ISA = ("Apache::lonhelper::element");
 1543: use strict;
 1544: 
 1545: BEGIN {
 1546:     &Apache::lonhelper::register('Apache::lonhelper::files',
 1547:                                  ('files', 'filechoice', 'filefilter'));
 1548: }
 1549: 
 1550: sub new {
 1551:     my $ref = Apache::lonhelper::element->new();
 1552:     bless($ref);
 1553: }
 1554: 
 1555: sub start_files {
 1556:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1557: 
 1558:     if ($target ne 'helper') {
 1559:         return '';
 1560:     }
 1561:     $paramHash->{'variable'} = $token->[2]{'variable'};
 1562:     $helper->declareVar($paramHash->{'variable'});
 1563:     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
 1564: }    
 1565: 
 1566: sub end_files {
 1567:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1568: 
 1569:     if ($target ne 'helper') {
 1570:         return '';
 1571:     }
 1572:     if (!defined($paramHash->{FILTER_FUNC})) {
 1573:         $paramHash->{FILTER_FUNC} = sub { return 1; };
 1574:     }
 1575:     Apache::lonhelper::files->new();
 1576: }    
 1577: 
 1578: sub start_filechoice {
 1579:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1580: 
 1581:     if ($target ne 'helper') {
 1582:         return '';
 1583:     }
 1584:     $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
 1585:                                                               $parser);
 1586: }
 1587: 
 1588: sub end_filechoice { return ''; }
 1589: 
 1590: sub start_filefilter {
 1591:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1592: 
 1593:     if ($target ne 'helper') {
 1594:         return '';
 1595:     }
 1596: 
 1597:     my $contents = Apache::lonxml::get_all_text('/filefilter',
 1598:                                                 $parser);
 1599:     $contents = 'sub { my $filename = shift; ' . $contents . '}';
 1600:     $paramHash->{FILTER_FUNC} = eval $contents;
 1601: }
 1602: 
 1603: sub end_filefilter { return ''; }
 1604: 
 1605: sub render {
 1606:     my $self = shift;
 1607:     my $result = '';
 1608:     my $var = $self->{'variable'};
 1609:     
 1610:     my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
 1611:     my $subdir = &$subdirFunc();
 1612: 
 1613:     my $filterFunc = $self->{FILTER_FUNC};
 1614:     my $buttons = '';
 1615: 
 1616:     if ($self->{'multichoice'}) {
 1617:         $result = <<SCRIPT;
 1618: <script>
 1619:     function checkall(value) {
 1620: 	for (i=0; i<document.forms.wizform.elements.length; i++) {
 1621:             ele = document.forms.wizform.elements[i];
 1622:             if (ele.type == "checkbox") {
 1623:                 document.forms.wizform.elements[i].checked=value;
 1624:             }
 1625:         }
 1626:     }
 1627: </script>
 1628: SCRIPT
 1629:         my $buttons = <<BUTTONS;
 1630: <br /> &nbsp;
 1631: <input type="button" onclick="checkall(true)" value="Select All" />
 1632: <input type="button" onclick="checkall(false)" value="Unselect All" />
 1633: <br /> &nbsp;
 1634: BUTTONS
 1635:     }
 1636: 
 1637:     # Get the list of files in this directory.
 1638:     my @fileList;
 1639: 
 1640:     # If the subdirectory is in local CSTR space
 1641:     if ($subdir =~ m|/home/([^/]+)/public_html|) {
 1642:         my $user = $1;
 1643:         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
 1644:         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
 1645:     } else {
 1646:         # local library server resource space
 1647:         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
 1648:     }
 1649: 
 1650:     $result .= $buttons;
 1651: 
 1652:     if (defined $self->{ERROR_MSG}) {
 1653:         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
 1654:     }
 1655: 
 1656:     $result .= '<table border="0" cellpadding="1" cellspacing="1">';
 1657: 
 1658:     # Keeps track if there are no choices, prints appropriate error
 1659:     # if there are none. 
 1660:     my $choices = 0;
 1661:     my $type = 'radio';
 1662:     if ($self->{'multichoice'}) {
 1663:         $type = 'checkbox';
 1664:     }
 1665:     # Print each legitimate file choice.
 1666:     for my $file (@fileList) {
 1667:         $file = (split(/&/, $file))[0];
 1668:         if ($file eq '.' || $file eq '..') {
 1669:             next;
 1670:         }
 1671:         my $fileName = $subdir .'/'. $file;
 1672:         if (&$filterFunc($file)) {
 1673:             $result .= '<tr><td align="right">' .
 1674:                 "<input type='$type' name='" . $var
 1675:             . ".forminput' value='" . HTML::Entities::encode($fileName) .
 1676:                 "'";
 1677:             if (!$self->{'multichoice'} && $choices == 0) {
 1678:                 $result .= ' checked';
 1679:             }
 1680:             $result .= "/></td><td>" . $file . "</td></tr>\n";
 1681:             $choices++;
 1682:         }
 1683:     }
 1684: 
 1685:     $result .= "</table>\n";
 1686: 
 1687:     if (!$choices) {
 1688:         $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
 1689:     }
 1690: 
 1691:     $result .= $buttons;
 1692: 
 1693:     return $result;
 1694: }
 1695: 
 1696: sub postprocess {
 1697:     my $self = shift;
 1698:     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
 1699:     if (!$result) {
 1700:         $self->{ERROR_MSG} = 'You must choose at least one file '.
 1701:             'to continue.';
 1702:         return 0;
 1703:     }
 1704: 
 1705:     if ($self->{'multichoice'}) {
 1706:         $self->process_multiple_choices($self->{'variable'}.'.forminput',
 1707:                                         $self->{'variable'});
 1708:     }
 1709:     if (defined($self->{NEXTSTATE})) {
 1710:         $helper->changeState($self->{NEXTSTATE});
 1711:     }
 1712: 
 1713:     return 1;
 1714: }
 1715: 
 1716: 1;
 1717: 
 1718: package Apache::lonhelper::general;
 1719: 
 1720: =pod
 1721: 
 1722: =head2 General-purpose tag: <exec>
 1723: 
 1724: The contents of the exec tag are executed as Perl code, not inside a 
 1725: safe space, so the full range of $ENV and such is available. The code
 1726: will be executed as a subroutine wrapped with the following code:
 1727: 
 1728: "sub { my $helper = shift; my $state = shift;" and
 1729: 
 1730: "}"
 1731: 
 1732: The return value is ignored.
 1733: 
 1734: $helper is the helper object. Feel free to add methods to the helper
 1735: object to support whatever manipulation you may need to do (for instance,
 1736: overriding the form location if the state is the final state; see 
 1737: lonparm.helper for an example).
 1738: 
 1739: $state is the $paramHash that has currently been generated and may
 1740: be manipulated by the code in exec. Note that the $state is not yet
 1741: an actual state B<object>, it is just a hash, so do not expect to
 1742: be able to call methods on it.
 1743: 
 1744: =cut
 1745: 
 1746: BEGIN {
 1747:     &Apache::lonhelper::register('Apache::lonhelper::general',
 1748:                                  'exec', 'condition', 'clause');
 1749: }
 1750: 
 1751: sub start_exec {
 1752:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1753: 
 1754:     if ($target ne 'helper') {
 1755:         return '';
 1756:     }
 1757:     
 1758:     my $code = &Apache::lonxml::get_all_text('/exec', $parser);
 1759:     
 1760:     $code = eval ('sub { my $helper = shift; my $state = shift; ' .
 1761:         $code . "}");
 1762:     &$code($helper, $paramHash);
 1763: }
 1764: 
 1765: sub end_exec { return ''; }
 1766: 
 1767: =pod
 1768: 
 1769: =head2 General-purpose tag: <condition>
 1770: 
 1771: The <condition> tag allows you to mask out parts of the helper code
 1772: depending on some programatically determined condition. The condition
 1773: tag contains a tag <clause> which contains perl code that when wrapped
 1774: with "sub { my $helper = shift; my $state = shift; " and "}", returns
 1775: a true value if the XML in the condition should be evaluated as a normal
 1776: part of the helper, or false if it should be completely discarded.
 1777: 
 1778: The <clause> tag must be the first sub-tag of the <condition> tag or
 1779: it will not work as expected.
 1780: 
 1781: =cut
 1782: 
 1783: # The condition tag just functions as a marker, it doesn't have
 1784: # to "do" anything. Technically it doesn't even have to be registered
 1785: # with the lonxml code, but I leave this here to be explicit about it.
 1786: sub start_condition { return ''; }
 1787: sub end_condition { return ''; }
 1788: 
 1789: sub start_clause {
 1790:     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 1791: 
 1792:     if ($target ne 'helper') {
 1793:         return '';
 1794:     }
 1795:     
 1796:     my $clause = Apache::lonxml::get_all_text('/clause', $parser);
 1797:     $clause = eval('sub { my $helper = shift; my $state = shift; '
 1798:         . $clause . '}');
 1799:     if (!&$clause($helper, $paramHash)) {
 1800:         # Discard all text until the /condition.
 1801:         &Apache::lonxml::get_all_text('/condition', $parser);
 1802:     }
 1803: }
 1804: 
 1805: sub end_clause { return ''; }
 1806: 
 1807: 1;
 1808: 
 1809: __END__
 1810: 

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