File:  [LON-CAPA] / loncom / interface / lonhelper.pm
Revision 1.197.2.2: download - view: text, annotated - select for diffs
Mon Sep 3 20:25:53 2018 UTC (5 years, 8 months ago) by raeburn
Branches: version_2_11_X
CVS tags: version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3
- For 2.11
  Backport 1.202

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

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