Diff for /loncom/interface/lonhelper.pm between versions 1.16 and 1.194

version 1.16, 2003/04/30 19:23:48 version 1.194, 2015/08/14 15:34:01
Line 25 Line 25
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # (Page Handler  
 #  
 # (.helper handler  
 #  
   
 =pod  =pod
   
 =head1 lonhelper - HTML Helper framework for LON-CAPA  =head1 NAME
   
   lonhelper - implements helper framework
   
   =head1 SYNOPSIS
   
   lonhelper implements the helper framework for LON-CAPA, and provides
       many generally useful components for that framework.
   
   Helpers are little programs which present the user with a sequence of
       simple choices, instead of one monolithic multi-dimensional
       choice. They are also referred to as "wizards", "druids", and
       other potentially trademarked or semantically-loaded words.
   
 Helpers, often known as "wizards", are well-established UI widgets that users  =head1 OVERVIEWX<lonhelper>
   
   Helpers are well-established UI widgets that users
 feel comfortable with. It can take a complicated multidimensional problem the  feel comfortable with. It can take a complicated multidimensional problem the
 user has and turn it into a series of bite-sized one-dimensional questions.  user has and turn it into a series of bite-sized one-dimensional questions.
   
Line 46  directory and having the .helper file ex Line 56  directory and having the .helper file ex
   
 All classes are in the Apache::lonhelper namespace.  All classes are in the Apache::lonhelper namespace.
   
 =head2 lonhelper XML file format  =head1 lonhelper XML file formatX<lonhelper, XML file format>
   
 A helper consists of a top-level <helper> tag which contains a series of states.  A helper consists of a top-level <helper> tag which contains a series of states.
 Each state contains one or more state elements, which are what the user sees, like  Each state contains one or more state elements, which are what the user sees, like
 messages, resource selections, or date queries.  messages, resource selections, or date queries.
   
 The helper tag is required to have one attribute, "title", which is the name  The helper tag is required to have one attribute, "title", which is the name
 of the helper itself, such as "Parameter helper".   of the helper itself, such as "Parameter helper". The helper tag may optionally
   have a "requiredpriv" attribute, specifying the priviledge a user must have
   to use the helper, or get denied access. See loncom/auth/rolesplain.tab for
   useful privs. Default is full access, which is often wrong!
   
 =head2 State tags  =head2 State tags
   
Line 68  State tags are also required to have an Line 81  State tags are also required to have an
 human name of the state, and will be displayed as the header on top of   human name of the state, and will be displayed as the header on top of 
 the screen for the user.  the screen for the user.
   
   State tags may also optionally have an attribute "help" which should be
   the filename of a help file, this will add a blue ? to the title.
   
 =head2 Example Helper Skeleton  =head2 Example Helper Skeleton
   
 An example of the tags so far:  An example of the tags so far:
   
  <helper title="Example Helper">   <helper title="Example Helper">
    <state name="START" title="Demonstrating the Example Helper">     <state name="START" title="Demonstrating the Example Helper">
      <!-- notice this is the START state the wizard requires -->       <!-- notice this is the START state the helper requires -->
      </state>       </state>
    <state name="GET_NAME" title="Enter Student Name">     <state name="GET_NAME" title="Enter Student Name">
      </state>       </state>
    </helper>     </helper>
   
 Of course this does nothing. In order for the wizard to do something, it is  Of course this does nothing. In order for the helper to do something, it is
 necessary to put actual elements into the wizard. Documentation for each  necessary to put actual elements into the helper. Documentation for each
 of these elements follows.  of these elements follows.
   
 =head2 Creating a Helper With Code, Not XML  =head1 Creating a Helper With Code, Not XML
   
 In some situations, such as the printing wizard (see lonprintout.pm),   In some situations, such as the printing helper (see lonprintout.pm), 
 writing the helper in XML would be too complicated, because of scope   writing the helper in XML would be too complicated, because of scope 
 issues or the fact that the code actually outweighs the XML. It is  issues or the fact that the code actually outweighs the XML. It is
 possible to create a helper via code, though it is a little odd.  possible to create a helper via code, though it is a little odd.
Line 143  Normally the machinery in the XML format Line 159  Normally the machinery in the XML format
 adding states can easily be done by wrapping the state in a <condition>  adding states can easily be done by wrapping the state in a <condition>
 tag. This should only be used when the code dominates the XML content,  tag. This should only be used when the code dominates the XML content,
 the code is so complicated that it is difficult to get access to  the code is so complicated that it is difficult to get access to
 all of the information you need because of scoping issues, or so much  all of the information you need because of scoping issues, or would-be <exec> or 
 of the information used is persistent because would-be <exec> or   <eval> blocks using the {DATA} mechanism results in hard-to-read
 <eval> blocks that using the {DATA} mechanism results in hard-to-read  and -maintain code. (See course.initialization.helper for a borderline
 and -maintain code.  case.)
   
 It is possible to do some of the work with an XML fragment parsed by  It is possible to do some of the work with an XML fragment parsed by
 lonxml; again, see lonprintout.pm for an example.  lonxml; again, see lonprintout.pm for an example. In that case it is 
   imperative that you call B<Apache::lonhelper::registerHelperTags()>
   before parsing XML fragments and B<Apache::lonhelper::unregisterHelperTags()>
   when you are done. See lonprintout.pm for examples of this usage in the
   printHelper subroutine.
   
   =head2 Localization
   
   The helper framework tries to handle as much localization as
   possible. The text is always run through
   Apache::lonlocal::normalize_string, so be sure to run the keys through
   that function for maximum usefulness and robustness.
   
 =cut  =cut
   
Line 157  package Apache::lonhelper; Line 184  package Apache::lonhelper;
 use Apache::Constants qw(:common);  use Apache::Constants qw(:common);
 use Apache::File;  use Apache::File;
 use Apache::lonxml;  use Apache::lonxml;
   use Apache::lonlocal;
   use Apache::lonnet;
   use Apache::longroup;
   use Apache::lonselstudent;
   
   
   use LONCAPA;
   
 # Register all the tags with the helper, so the helper can   # Register all the tags with the helper, so the helper can 
 # push and pop them  # push and pop them
Line 189  my $substate; Line 223  my $substate;
 # end of the element tag is located.  # end of the element tag is located.
 my $paramHash;   my $paramHash; 
   
   # Note from Jeremy 5-8-2003: It is *vital* that the real handler be called
   # as a subroutine from the handler, or very mysterious things might happen.
   # I don't know exactly why, but it seems that the scope where the Apache
   # server enters the perl handler is treated differently from the rest of
   # the handler. This also seems to manifest itself in the debugger as entering
   # the perl handler in seemingly random places (sometimes it starts in the
   # compiling phase, sometimes in the handler execution phase where it runs
   # the code and stepping into the "1;" the module ends with goes into the handler,
   # sometimes starting directly with the handler); I think the cause is related.
   # In the debugger, this means that breakpoints are ignored until you step into
   # a function and get out of what must be a "faked up scope" in the Apache->
   # mod_perl connection. In this code, it was manifesting itself in the existence
   # of two separate file-scoped $helper variables, one set to the value of the
   # helper in the helper constructor, and one referenced by the handler on the
   # "$helper->process()" line. Using the debugger, one could actually
   # see the two different $helper variables, as hashes at completely
   # different addresses. The second was therefore never set, and was still
   # undefined when I tried to call process on it.
   # By pushing the "real handler" down into the "real scope", everybody except the 
   # actual handler function directly below this comment gets the same $helper and
   # everybody is happy.
   # The upshot of all of this is that for safety when a handler is  using 
   # file-scoped variables in LON-CAPA, the handler should be pushed down one 
   # call level, as I do here, to ensure that the top-level handler function does
   # not get a different file scope from the rest of the code.
   sub handler {
       my $r = shift;
       return real_handler($r);
   }
   
 # For debugging purposes, one can send a second parameter into this  # For debugging purposes, one can send a second parameter into this
 # function, the 'uri' of the helper you wish to have rendered, and  # function, the 'uri' of the helper you wish to have rendered, and
 # call this from other handlers.  # call this from other handlers.
 sub handler {  sub real_handler {
     my $r = shift;      my $r = shift;
     my $uri = shift;      my $uri = shift;
     if (!defined($uri)) { $uri = $r->uri(); }      if (!defined($uri)) { $uri = $r->uri(); }
     $ENV{'request.uri'} = $uri;      $env{'request.uri'} = $uri;
     my $filename = '/home/httpd/html' . $uri;      my $filename = $r->dir_config('lonDocRoot').$uri;
     my $fh = Apache::File->new($filename);      my $fh = Apache::File->new($filename);
     my $file;      my $file;
     read $fh, $file, 100000000;      read $fh, $file, 100000000;
   
   
     # Send header, don't cache this page      # Send header, don't cache this page
     if ($r->header_only) {      if ($env{'browser.mathml'}) {
         if ($ENV{'browser.mathml'}) {   &Apache::loncommon::content_type($r,'text/xml');
             $r->content_type('text/xml');  
         } else {  
             $r->content_type('text/html');  
         }  
         $r->send_http_header;  
         return OK;  
     }  
     if ($ENV{'browser.mathml'}) {  
         $r->content_type('text/xml');  
     } else {      } else {
         $r->content_type('text/html');   &Apache::loncommon::content_type($r,'text/html');
     }      }
     $r->send_http_header;      $r->send_http_header;
       return OK if $r->header_only;
     $r->rflush();      $r->rflush();
   
     # Discard result, we just want the objects that get created by the      # Discard result, we just want the objects that get created by the
     # xml parsing      # xml parsing
     &Apache::lonxml::xmlparse($r, 'helper', $file);      &Apache::lonxml::xmlparse($r, 'helper', $file);
   
       my $allowed = $helper->allowedCheck();
       if (!$allowed) {
           $env{'user.error.msg'} = $env{'request.uri'}.':'.$helper->{REQUIRED_PRIV}.
               ":0:0:Permission denied to access this helper.";
           return HTTP_NOT_ACCEPTABLE;
       }
   
     $helper->process();      $helper->process();
   
     $r->print($helper->display());      $r->print($helper->display());
    return OK;      return OK;
 }  }
   
 sub registerHelperTags {  sub registerHelperTags {
Line 251  sub start_helper { Line 315  sub start_helper {
   
     registerHelperTags();      registerHelperTags();
   
     Apache::lonhelper::helper->new($token->[2]{'title'});      Apache::lonhelper::helper->new($token->[2]{'title'}, $token->[2]{'requiredpriv'});
     return '';      return '';
 }  }
   
Line 275  sub start_state { Line 339  sub start_state {
     }      }
   
     Apache::lonhelper::state->new($token->[2]{'name'},      Apache::lonhelper::state->new($token->[2]{'name'},
                                   $token->[2]{'title'});                                    $token->[2]{'title'},
     $token->[2]{'help'});
     return '';      return '';
 }  }
   
Line 300  sub end_state { Line 365  sub end_state {
 package Apache::lonhelper::helper;  package Apache::lonhelper::helper;
   
 use Digest::MD5 qw(md5_hex);  use Digest::MD5 qw(md5_hex);
 use HTML::Entities;  use HTML::Entities();
 use Apache::loncommon;  use Apache::loncommon;
 use Apache::File;  use Apache::File;
   use Apache::lonlocal;
   use Apache::lonnet;
   use LONCAPA;
   
 sub new {  sub new {
     my $proto = shift;      my $proto = shift;
Line 310  sub new { Line 378  sub new {
     my $self = {};      my $self = {};
   
     $self->{TITLE} = shift;      $self->{TITLE} = shift;
       $self->{REQUIRED_PRIV} = shift;
           
     # If there is a state from the previous form, use that. If there is no      # If there is a state from the previous form, use that. If there is no
     # state, use the start state parameter.      # state, use the start state parameter.
     if (defined $ENV{"form.CURRENT_STATE"})      if (defined $env{"form.CURRENT_STATE"})
     {      {
  $self->{STATE} = $ENV{"form.CURRENT_STATE"};   $self->{STATE} = $env{"form.CURRENT_STATE"};
     }      }
     else      else
     {      {
  $self->{STATE} = "START";   $self->{STATE} = "START";
     }      }
   
     Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});      $self->{TOKEN} = $env{'form.TOKEN'};
   
     $self->{TOKEN} = $ENV{'form.TOKEN'};  
     # If a token was passed, we load that in. Otherwise, we need to create a       # If a token was passed, we load that in. Otherwise, we need to create a 
     # new storage file      # new storage file
     # Tried to use standard Tie'd hashes, but you can't seem to take a       # Tried to use standard Tie'd hashes, but you can't seem to take a 
Line 357  sub new { Line 424  sub new {
             return undef;              return undef;
         }          }
         # Must create the storage          # Must create the storage
         $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} .          $self->{TOKEN} = md5_hex($env{'user.name'} . $env{'user.domain'} .
                                  time() . rand());                                   time() . rand());
         $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});          $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
     }      }
   
     # OK, we now have our persistent storage.      # OK, we now have our persistent storage.
   
     if (defined $ENV{"form.RETURN_PAGE"})      if (defined $env{"form.RETURN_PAGE"})
     {      {
  $self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};   $self->{RETURN_PAGE} = $env{"form.RETURN_PAGE"};
     }      }
     else      else
     {      {
Line 395  sub _saveVars { Line 462  sub _saveVars {
     my $self = shift;      my $self = shift;
     my $result = "";      my $result = "";
     $result .= '<input type="hidden" name="CURRENT_STATE" value="' .      $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
         HTML::Entities::encode($self->{STATE}) . "\" />\n";          HTML::Entities::encode($self->{STATE},'<>&"') . "\" />\n";
     $result .= '<input type="hidden" name="TOKEN" value="' .      $result .= '<input type="hidden" name="TOKEN" value="' .
         $self->{TOKEN} . "\" />\n";          $self->{TOKEN} . "\" />\n";
     $result .= '<input type="hidden" name="RETURN_PAGE" value="' .      $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
         HTML::Entities::encode($self->{RETURN_PAGE}) . "\" />\n";          HTML::Entities::encode($self->{RETURN_PAGE},'<>&"') . "\" />\n";
   
     return $result;      return $result;
 }  }
Line 409  sub _saveVars { Line 476  sub _saveVars {
 sub _varsInFile {  sub _varsInFile {
     my $self = shift;      my $self = shift;
     my @vars = ();      my @vars = ();
     for my $key (keys %{$self->{VARS}}) {      for my $key (keys(%{$self->{VARS}})) {
         push @vars, &Apache::lonnet::escape($key) . '=' .          push(@vars, &escape($key) . '=' . &escape($self->{VARS}->{$key}));
             &Apache::lonnet::escape($self->{VARS}->{$key});  
     }      }
     return join ('&', @vars);      return join ('&', @vars);
 }  }
Line 426  sub declareVar { Line 492  sub declareVar {
         $self->{VARS}->{$var} = '';          $self->{VARS}->{$var} = '';
     }      }
   
     my $envname = 'form.' . $var . '.forminput';      my $envname = 'form.' . $var . '_forminput';
     if (defined($ENV{$envname})) {      if (defined($env{$envname})) {
         $self->{VARS}->{$var} = $ENV{$envname};          if (ref($env{$envname})) {
               $self->{VARS}->{$var} = join('|||', @{$env{$envname}});
           } else {
               $self->{VARS}->{$var} = $env{$envname};
           }
     }      }
 }  }
   
   sub allowedCheck {
       my $self = shift;
   
       if (!defined($self->{REQUIRED_PRIV})) { 
           return 1;
       }
   
       return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $env{'request.course.id'});
   }
   
 sub changeState {  sub changeState {
     my $self = shift;      my $self = shift;
     $self->{STATE} = shift;      $self->{STATE} = shift;
Line 451  sub process { Line 531  sub process {
     # Phase 1: Post processing for state of previous screen (which is actually      # Phase 1: Post processing for state of previous screen (which is actually
     # the "current state" in terms of the helper variables), if it wasn't the       # the "current state" in terms of the helper variables), if it wasn't the 
     # beginning state.      # beginning state.
     if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {      if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next")) {
  my $prevState = $self->{STATES}{$self->{STATE}};   my $prevState = $self->{STATES}{$self->{STATE}};
         $prevState->postprocess();          $prevState->postprocess();
     }      }
Line 462  sub process { Line 542  sub process {
   
     # Phase 2: Preprocess current state      # Phase 2: Preprocess current state
     my $startState = $self->{STATE};      my $startState = $self->{STATE};
     my $state = $self->{STATES}{$startState};      my $state = $self->{STATES}->{$startState};
           
     # For debugging, print something here to determine if you're going      # For debugging, print something here to determine if you're going
     # to an undefined state.      # to an undefined state.
Line 473  sub process { Line 553  sub process {
   
     # Phase 3: While the current state is different from the previous state,      # Phase 3: While the current state is different from the previous state,
     # keep processing.      # keep processing.
     while ( $startState ne $self->{STATE} )      while ( $startState ne $self->{STATE} && 
               defined($self->{STATES}->{$self->{STATE}}) )
     {      {
  $startState = $self->{STATE};   $startState = $self->{STATE};
  $state = $self->{STATES}{$startState};   $state = $self->{STATES}->{$startState};
  $state->preprocess();   $state->preprocess();
     }      }
   
Line 490  sub process { Line 571  sub process {
 # 4: Render the current state to the screen as an HTML page.  # 4: Render the current state to the screen as an HTML page.
 sub display {  sub display {
     my $self = shift;      my $self = shift;
       my $footer = shift;
     my $state = $self->{STATES}{$self->{STATE}};      my $state = $self->{STATES}{$self->{STATE}};
   
     my $result = "";      my $result = "";
   
     # Phase 4: Display.      if (!defined($state)) {
     my $stateTitle = $state->title();          $result = "<font color='#ff0000'>Error: state '$state' not defined!</font>";
     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');          return $result;
       }
     $result .= <<HEADER;  
 <html>  
     <head>  
         <title>LON-CAPA Helper: $self->{TITLE}</title>  
     </head>  
     $bodytag  
 HEADER  
     if (!$state->overrideForm()) { $result.="<form name='helpform' method='GET'>"; }  
     $result .= <<HEADER;  
         <table border="0"><tr><td>  
         <h2><i>$stateTitle</i></h2>  
 HEADER  
   
     if (!$state->overrideForm()) {      # Phase 4: Display.
         $result .= $self->_saveVars();      my $stateTitle=&mt($state->title());
       my $stateHelp=     $state->help();
       my $browser_searcher_js = 
    '<script type="text/javascript">'."\n".
    &Apache::loncommon::browser_and_searcher_javascript().
    "\n".'</script>';
   
       # Breadcrumbs
       my $brcrum = [{'href' => '',
                      'text' => 'Helper'}];
       # FIXME: Dynamically add context sensitive breadcrumbs
       #        depending on the caller,
       #        e.g. printing, parametrization, etc.
       # FIXME: Add breadcrumbs to reflect current helper state
   
       $result .= &Apache::loncommon::start_page($self->{TITLE},
                                                 $browser_searcher_js,
                                                 {'bread_crumbs' => $brcrum,});
   
       my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
       my $next = HTML::Entities::encode(&mt("Next"), '<>&"');
       # FIXME: This should be parameterized, not concatenated - Jeremy
   
   
       if (!$state->overrideForm()) { $result.='<form name="helpform" method="post" action="">'; }
       if ($stateHelp) {
           $stateHelp = &Apache::loncommon::help_open_topic($stateHelp);
     }      }
     $result .= $state->render() . "<p>&nbsp;</p>";  
   
       # Prepare buttons
       my $buttons;
     if (!$state->overrideForm()) {      if (!$state->overrideForm()) {
         $result .= '<center>';  
         if ($self->{STATE} ne $self->{START_STATE}) {          if ($self->{STATE} ne $self->{START_STATE}) {
             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';              #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
         }          }
           $buttons = '<p>'; # '<fieldset>';
         if ($self->{DONE}) {          if ($self->{DONE}) {
             my $returnPage = $self->{RETURN_PAGE};              my $returnPage = $self->{RETURN_PAGE};
             $result .= "<a href=\"$returnPage\">End Helper</a>";              $buttons .= '<a href="'.$returnPage.'">'.&mt('End Helper').'</a>';
         }          }
         else {          else {
             $result .= '<input name="back" type="button" ';              $buttons .= '<span class="LC_nobreak">'
             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';                         .'<input name="back" type="button" '
             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';                         .'value="'.$previous.'" onclick="history.go(-1)" /> '
                          .'<input name="SUBMIT" type="submit" value="'.$next.'" />'
                          .'</span>';
         }          }
         $result .= "</center>\n";      $buttons .= '</p>'; # '</fieldset>';
       }
   
   
   
       $result .= '<h2>'.$stateTitle.$stateHelp.'</h2>';
   
   #   $result .= '<div>';
   
       # Top buttons
       $result .= $buttons;
   
       # Main content of current helper screen
       if (!$state->overrideForm()) {
           $result .= $self->_saveVars();
     }      }
       $result .= $state->render();
   
       # Bottom buttons
       $result .= $buttons;
   
     #foreach my $key (keys %{$self->{VARS}}) {  
       #foreach my $key (keys(%{$self->{VARS}})) {
     #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";      #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
     #}      #}
   
   #   $result .= '</div>';
   
     $result .= <<FOOTER;      $result .= <<FOOTER;
               </td>  
             </tr>  
           </table>  
         </form>          </form>
     </body>  
 </html>  
 FOOTER  FOOTER
   
       $result .= $footer.&Apache::loncommon::end_page();
     # Handle writing out the vars to the file      # Handle writing out the vars to the file
     my $file = Apache::File->new('>'.$self->{FILENAME});      my $file = Apache::File->new('>'.$self->{FILENAME});
     print $file $self->_varsInFile();      print $file $self->_varsInFile();
Line 572  sub new { Line 687  sub new {
   
     $self->{NAME} = shift;      $self->{NAME} = shift;
     $self->{TITLE} = shift;      $self->{TITLE} = shift;
       $self->{HELP} = shift;
     $self->{ELEMENTS} = [];      $self->{ELEMENTS} = [];
   
     bless($self, $class);      bless($self, $class);
Line 593  sub title { Line 709  sub title {
     return $self->{TITLE};      return $self->{TITLE};
 }  }
   
   sub help {
       my $self = shift;
       return $self->{HELP};
   }
   
 sub preprocess {  sub preprocess {
     my $self = shift;      my $self = shift;
     for my $element (@{$self->{ELEMENTS}}) {      for my $element (@{$self->{ELEMENTS}}) {
Line 650  sub render { Line 771  sub render {
     for my $element (@{$self->{ELEMENTS}}) {      for my $element (@{$self->{ELEMENTS}}) {
         push @results, $element->render();          push @results, $element->render();
     }      }
   
     return join("\n", @results);      return join("\n", @results);
 }  }
   
Line 660  package Apache::lonhelper::element; Line 782  package Apache::lonhelper::element;
   
 =pod  =pod
   
 =head2 Element Base Class  =head1 Element Base Class
   
 The Apache::lonhelper::element base class provides support methods for  
 the elements to use, such as a multiple value processer.  
   
 B<Methods>:  
   
 =over 4  
   
 =item * process_multiple_choices(formName, varName): Process the form   The Apache::lonhelper::element base class provides support for elements
 element named "formName" and place the selected items into the helper   and defines some generally useful tags for use in elements.
 variable named varName. This is for things like checkboxes or   
 multiple-selection listboxes where the user can select more then   
 one entry. The selected entries are delimited by triple pipes in   
 the helper variables, like this:    
   
  CHOICE_1|||CHOICE_2|||CHOICE_3  =head2 finalcode tagX<finalcode>
   
 =back  Each element can contain a "finalcode" tag that, when the special FINAL
   helper state is used, will be executed, surrounded by "sub { my $helper = shift;"
   and "}". It is expected to return a string describing what it did, which 
   may be an empty string. See course initialization helper for an example. This is
   generally intended for helpers like the course initialization helper, which consist
   of several panels, each of which is performing some sort of bite-sized functionality.
   
   =head2 defaultvalue tagX<defaultvalue>
   
   Each element that accepts user input can contain a "defaultvalue" tag that,
   when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
   will form a subroutine that when called will provide a default value for
   the element. How this value is interpreted by the element is specific to
   the element itself, and possibly the settings the element has (such as 
   multichoice vs. single choice for <choices> tags). 
   
   This is also intended for things like the course initialization helper, where the
   user is setting various parameters. By correctly grabbing current settings 
   and including them into the helper, it allows the user to come back to the
   helper later and re-execute it, without needing to worry about overwriting
   some setting accidentally.
   
   Again, see the course initialization helper for examples.
   
   =head2 validator tagX<validator>
   
   Some elements that accepts user input can contain a "validator" tag that,
   when surrounded by "sub { my $helper = shift; my $state = shift; my $element = shift; my $val = shift " 
   and "}", where "$val" is the value the user entered, will form a subroutine 
   that when called will verify whether the given input is valid or not. If it 
   is valid, the routine will return a false value. If invalid, the routine 
   will return an error message to be displayed for the user.
   
   Consult the documentation for each element to see whether it supports this 
   tag.
   
   =head2 getValue methodX<getValue (helper elements)>
   
   If the element stores the name of the variable in a 'variable' member, which
   the provided ones all do, you can retreive the value of the variable by calling
   this method.
   
 =cut  =cut
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::element',      &Apache::lonhelper::register('Apache::lonhelper::element',
                                  ('nextstate'));                                   ('nextstate', 'finalcode',
                                     'defaultvalue', 'validator'));
 }  }
   
 # Because we use the param hash, this is often a sufficent  # Because we use the param hash, this is often a sufficent
Line 719  sub start_nextstate { Line 871  sub start_nextstate {
   
 sub end_nextstate { return ''; }  sub end_nextstate { return ''; }
   
   sub start_finalcode {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       
       $paramHash->{FINAL_CODE} = &Apache::lonxml::get_all_text('/finalcode',
                                                                $parser);
       return '';
   }
   
   sub end_finalcode { return ''; }
   
   sub start_defaultvalue {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       
       $paramHash->{DEFAULT_VALUE} = &Apache::lonxml::get_all_text('/defaultvalue',
                                                                $parser);
       $paramHash->{DEFAULT_VALUE} = 'sub { my $helper = shift; my $state = shift;' .
           $paramHash->{DEFAULT_VALUE} . '}';
       return '';
   }
   
   sub end_defaultvalue { return ''; }
   
   # Validators may need to take language specifications
   sub start_validator {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       
       $paramHash->{VALIDATOR} = &Apache::lonxml::get_all_text('/validator',
                                                                $parser);
       $paramHash->{VALIDATOR} = 'sub { my $helper = shift; my $state = shift; my $element = shift; my $val = shift;' .
           $paramHash->{VALIDATOR} . '}';
       return '';
   }
   
   sub end_validator { return ''; }
   
 sub preprocess {  sub preprocess {
     return 1;      return 1;
 }  }
Line 735  sub overrideForm { Line 934  sub overrideForm {
     return 0;      return 0;
 }  }
   
 sub process_multiple_choices {  sub getValue {
     my $self = shift;      my $self = shift;
     my $formname = shift;      return $helper->{VARS}->{$self->{'variable'}};
     my $var = shift;  
   
     # Must extract values from data directly, as there  
     # may be more then one.  
     my @values;  
     for my $formparam (split (/&/, $ENV{QUERY_STRING})) {  
         my ($name, $value) = split(/=/, $formparam);  
         if ($name ne $formname) {  
             next;  
         }  
         $value =~ tr/+/ /;  
         $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;  
         push @values, $value;  
     }  
     $helper->{VARS}->{$var} = join('|||', @values);  
       
     return;  
 }  }
   
 1;  1;
Line 763  package Apache::lonhelper::message; Line 945  package Apache::lonhelper::message;
   
 =pod  =pod
   
 =head2 Element: message  =head1 Elements
   
   =head2 Element: messageX<message, helper element>
   
 Message elements display the contents of their <message_text> tags, and  Message elements display their contents, and
 transition directly to the state in the <nextstate> tag. Example:  transition directly to the state in the <nextstate> attribute. Example:
   
  <message>   <message nextstate='GET_NAME'>
    <nextstate>GET_NAME</nextstate>     This is the <b>message</b> the user will see, 
    <message_text>This is the <b>message</b> the user will see,                    <i>HTML allowed</i>.
                  <i>HTML allowed</i>.</message_text>  
    </message>     </message>
   
 This will display the HTML message and transition to the <nextstate> if  This will display the HTML message and transition to the 'nextstate' if
 given. The HTML will be directly inserted into the helper, so if you don't  given. The HTML will be directly inserted into the helper, so if you don't
 want text to run together, you'll need to manually wrap the <message_text>  want text to run together, you'll need to manually wrap the message text
 in <p> tags, or whatever is appropriate for your HTML.  in <p> tags, or whatever is appropriate for your HTML.
   
 Message tags do not add in whitespace, so if you want it, you'll need to add  Message tags do not add in whitespace, so if you want it, you'll need to add
Line 788  within each other.) Line 971  within each other.)
 This is also a good template for creating your own new states, as it has  This is also a good template for creating your own new states, as it has
 very little code beyond the state template.  very little code beyond the state template.
   
   =head3 Localization
   
   The contents of the message tag will be run through the
   normalize_string function and that will be used as a call to &mt.
   
 =cut  =cut
   
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonlocal;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::message',      &Apache::lonhelper::register('Apache::lonhelper::message',
Line 812  sub start_message { Line 1001  sub start_message {
         return '';          return '';
     }      }
   
     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message',      $paramHash->{MESSAGE_TEXT} = &mtn(&Apache::lonxml::get_all_text('/message',
                                                                $parser);                                                                 $parser));
   
     if (defined($token->[2]{'nextstate'})) {      if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};          $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
     }      }
       if (defined($token->[2]{'type'})) {
           $paramHash->{TYPE} = $token->[2]{'type'};
       }
     return '';      return '';
 }  }
   
Line 833  sub end_message { Line 1025  sub end_message {
   
 sub render {  sub render {
     my $self = shift;      my $self = shift;
       
       if ($self->{TYPE} =~ /^\s*warning\s*$/i) {
    $self->{MESSAGE_TEXT} = 
        '<span class="LC_warning">'. $self->{MESSAGE_TEXT}.'</span>';
       }
       if ($self->{TYPE} =~ /^\s*error\s*$/i) {
    $self->{MESSAGE_TEXT} = 
        '<span class="LC_error">'. $self->{MESSAGE_TEXT}.'</span>';
       }
     return $self->{MESSAGE_TEXT};      return $self->{MESSAGE_TEXT};
 }  }
 # If a NEXTSTATE was given, switch to it  # If a NEXTSTATE was given, switch to it
Line 847  sub postprocess { Line 1047  sub postprocess {
 }  }
 1;  1;
   
   package Apache::lonhelper::helpicon;
   
   =pod
   
   =head1 Elements
   
   =head2 Element: helpiconX<helpicon, helper element>
   
   Helpicon elements add a help icon at the current location.
   Example:
   
      <helpicon file="Help">
        General Help
      </helpicon>
   
   In this example will generate a help icon to the Help.hlp url with a
   description of 'General Help'. The description is not required and if
   left out (Example: <helpicon file="Help" /> only the icon will be
   added.)
   
   =head3 Localization
   
   The description text will be run through the normalize_string function
   and that will be used as a call to &mt.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   use Apache::lonlocal;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::helpicon',
    ('helpicon'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_helpicon {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{HELP_TEXT} = &mtn(&Apache::lonxml::get_all_text('/helpicon',
    $parser));
   
       $paramHash->{HELP_TEXT} =~s/^\s+//;
       $paramHash->{HELP_TEXT} =~s/\s+$//;
   
       if (defined($token->[2]{'file'})) {
           $paramHash->{HELP_FILE} = $token->[2]{'file'};
       }
       return '';
   }
   
   sub end_helpicon {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::helpicon->new();
       return '';
   }
   
   sub render {
       my $self = shift;
   
       my $text;
       if ( $self->{HELP_TEXT} ne '') {
    $text=&mtn($self->{HELP_TEXT});
       }
   
       return &Apache::loncommon::help_open_topic($self->{HELP_FILE},
          $text);
   }
   sub postprocess {
       my $self = shift;
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   
       return 1;
   }
   
   1;
   
   package Apache::lonhelper::skip;
   
   =pod
   
   =head1 Elements
   
   =head2 Element: skipX<skip>
   
   The <skip> tag allows you define conditions under which the current state 
   should be skipped over and define what state to skip to.
   
     <state name="SKIP">
       <skip>
          <clause>
            #some code that decides whether to skip the state or not
          </clause>
          <nextstate>FINISH</nextstate>
       </skip>
       <message nextstate="FINISH">A possibly skipped state</message>
     </state>
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::skip',
    ('skip'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_skip {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       # let <cluase> know what text to skip to
       $paramHash->{SKIPTAG}='/skip';
       return '';
   }
   
   sub end_skip {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::skip->new();
       return '';
   }
   
   sub render {
       my $self = shift;
       return '';
   }
   # If a NEXTSTATE is set, switch to it
   sub preprocess {
       my ($self) = @_;
   
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   
       return 1;
   }
   
   1;
   
 package Apache::lonhelper::choices;  package Apache::lonhelper::choices;
   
 =pod  =pod
   
 =head2 Element: choices  =head2 Element: choicesX<choices, helper element>
   
 Choice states provide a single choice to the user as a text selection box.  Choice states provide a single choice to the user as a text selection box.
 A "choice" is two pieces of text, one which will be displayed to the user  A "choice" is two pieces of text, one which will be displayed to the user
Line 865  the result is stored in. Line 1234  the result is stored in.
 <choices> takes an attribute "multichoice" which, if set to a true  <choices> takes an attribute "multichoice" which, if set to a true
 value, will allow the user to select multiple choices.  value, will allow the user to select multiple choices.
   
 B<SUB-TAGS>  <choices> takes an attribute "allowempty" which, if set to a true 
   value, will allow the user to select none of the choices without raising
   an error message.
   
   =head3 SUB-TAGS
   
 <choices> can have the following subtags:  <choices> can have the following subtags:X<choice, helper tag>
   
 =over 4  =over 4
   
 =item * <nextstate>state_name</nextstate>: If given, this will cause the  =item * <nextstate>state_name</nextstate>: If given, this will cause the
       choice element to transition to the given state after executing. If        choice element to transition to the given state after executing.
       this is used, do not pass nextstates to the <choice> tag.        This will override the <nextstate> passed to <choices> (if any).
   
 =item * <choice />: If the choices are static,  =item * <choice />: If the choices are static,
       this element will allow you to specify them. Each choice        this element will allow you to specify them. Each choice
Line 882  B<SUB-TAGS> Line 1255  B<SUB-TAGS>
       For example,          For example,  
       <choice computer='234-12-7312'>Bobby McDormik</choice>.        <choice computer='234-12-7312'>Bobby McDormik</choice>.
   
       <choice> can take a parameter "eval", which if set to  <choice> can take a parameter "eval", which if set to
       a true value, will cause the contents of the tag to be  a true value, will cause the contents of the tag to be
       evaluated as it would be in an <eval> tag; see <eval> tag  evaluated as it would be in an <eval> tag; see <eval> tag
       below.  below.
   
 <choice> may optionally contain a 'nextstate' attribute, which  <choice> may optionally contain a 'nextstate' attribute, which
 will be the state transisitoned to if the choice is made, if  will be the state transistioned to if the choice is made, if
 the choice is not multichoice.  the choice is not multichoice. This will override the nextstate
   passed to the parent C<choices> tag.
   
   <choice> may optionally contain a 'relatedvalue' attribute, which
   if present will cause a text entry to appear to the right of the
   selection.  The value of the relatedvalue attribute is a variable
   into which the text entry will be stored e.g.:
   <choice computer='numberprovided" relatedvalue="num">Type the number in:</choice>
   
   <choice> may contain a relatededefault atribute which, if the
   relatedvalue attribute is present will be the initial value of the input
   box.
   
 =back  =back
   
Line 914  You can mix and match methods of creatin Line 1298  You can mix and match methods of creatin
 "push" onto the choice list, rather then wiping it out. (You can even   "push" onto the choice list, rather then wiping it out. (You can even 
 remove choices programmatically, but that would probably be bad form.)  remove choices programmatically, but that would probably be bad form.)
   
   =head3 defaultvalue support
   
   Choices supports default values both in multichoice and single choice mode.
   In single choice mode, have the defaultvalue tag's function return the 
   computer value of the box you want checked. If the function returns a value
   that does not correspond to any of the choices, the default behavior of selecting
   the first choice will be preserved.
   
   For multichoice, return a string with the computer values you want checked,
   delimited by triple pipes. Note this matches how the result of the <choices>
   tag is stored in the {VARS} hash.
   
 =cut  =cut
   
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::choices',      &Apache::lonhelper::register('Apache::lonhelper::choices',
Line 939  sub start_choices { Line 1337  sub start_choices {
     }      }
   
     # Need to initialize the choices list, so everything can assume it exists      # Need to initialize the choices list, so everything can assume it exists
     $paramHash->{'variable'} = $token->[2]{'variable'};      $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
     $helper->declareVar($paramHash->{'variable'});      $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};      $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
     $paramHash->{CHOICES} = [];      $paramHash->{CHOICES} = [];
     return '';      return '';
 }  }
Line 964  sub start_choice { Line 1363  sub start_choice {
     }      }
   
     my $computer = $token->[2]{'computer'};      my $computer = $token->[2]{'computer'};
     my $human = &Apache::lonxml::get_all_text('/choice',      my $human = &mt(&Apache::lonxml::get_all_text('/choice',
                                               $parser);                                                $parser));
     my $nextstate = $token->[2]{'nextstate'};      my $nextstate  = $token->[2]{'nextstate'};
     my $evalFlag = $token->[2]{'eval'};      my $evalFlag   = $token->[2]{'eval'};
     push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate,       my $relatedVar = $token->[2]{'relatedvalue'}; 
                                     $evalFlag];      my $relatedDefault = $token->[2]{'relateddefault'};
       push @{$paramHash->{CHOICES}}, [&mtn($human), $computer, $nextstate, 
                                       $evalFlag, $relatedVar, $relatedDefault];
     return '';      return '';
 }  }
   
Line 977  sub end_choice { Line 1378  sub end_choice {
     return '';      return '';
 }  }
   
   {
       # used to generate unique id attributes for <input> tags. 
       # internal use only.
       my $id = 0;
       sub new_id { return $id++; }
   }
   
 sub render {  sub render {
     # START HERE: Replace this with correct choices code.  
     my $self = shift;      my $self = shift;
     my $var = $self->{'variable'};      my $var = $self->{'variable'};
     my $buttons = '';      my $buttons = '';
Line 986  sub render { Line 1393  sub render {
   
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result .= <<SCRIPT;          $result .= <<SCRIPT;
 <script>  <script type="text/javascript">
     function checkall(value) {  // <!--
       function checkall(value, checkName) {
  for (i=0; i<document.forms.helpform.elements.length; i++) {   for (i=0; i<document.forms.helpform.elements.length; i++) {
             document.forms.helpform.elements[i].checked=value;              ele = document.forms.helpform.elements[i];
               if (ele.name == checkName + '_forminput') {
                   document.forms.helpform.elements[i].checked=value;
               }
         }          }
     }      }
   // -->
 </script>  </script>
 SCRIPT  SCRIPT
       }
   
       # Only print "select all" and "unselect all" if there are five or
       # more choices; fewer then that and it looks silly.
       if ($self->{'multichoice'} && scalar(@{$self->{CHOICES}}) > 4) {
           my %lt=&Apache::lonlocal::texthash(
    'sa'  => "Select All",
           'ua'  => "Unselect All");
         $buttons = <<BUTTONS;          $buttons = <<BUTTONS;
 <br />  <br />
 <input type="button" onclick="checkall(true)" value="Select All" />  <input type="button" onclick="checkall(true, '$var')" value="$lt{'sa'}" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="$lt{'ua'}" />
 <br />&nbsp;  <br />&nbsp;
 BUTTONS  BUTTONS
     }      }
Line 1010  BUTTONS Line 1430  BUTTONS
           
     $result .= "<table>\n\n";      $result .= "<table>\n\n";
   
       my %checkedChoices;
       my $checkedChoicesFunc;
   
       if (defined($self->{DEFAULT_VALUE})) {
           $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
           die 'Error in default value code for variable ' . 
               $self->{'variable'} . ', Perl said: ' . $@ if $@;
       } else {
           $checkedChoicesFunc = sub { return ''; };
       }
   
       # Process which choices should be checked.
       if ($self->{'multichoice'}) {
           for my $selectedChoice (split(/\|\|\|/, (&$checkedChoicesFunc($helper, $self)))) {
               $checkedChoices{$selectedChoice} = 1;
           }
       } else {
           # single choice
           my $selectedChoice = &$checkedChoicesFunc($helper, $self);
           
           my $foundChoice = 0;
           
           # check that the choice is in the list of choices.
           for my $choice (@{$self->{CHOICES}}) {
               if ($choice->[1] eq $selectedChoice) {
                   $checkedChoices{$choice->[1]} = 1;
                   $foundChoice = 1;
               }
           }
           
           # If we couldn't find the choice, pick the first one 
           if (!$foundChoice) {
               $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
           }
       }
   
     my $type = "radio";      my $type = "radio";
     if ($self->{'multichoice'}) { $type = 'checkbox'; }      if ($self->{'multichoice'}) { $type = 'checkbox'; }
     my $checked = 0;  
     foreach my $choice (@{$self->{CHOICES}}) {      foreach my $choice (@{$self->{CHOICES}}) {
           my $id = &new_id();
         $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";          $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
         $result .= "<td valign='top'><input type='$type' name='$var.forminput'"          $result .= "<td valign='top'><input type='$type' name='${var}_forminput'"
             . "' value='" .               . " value='" . 
             HTML::Entities::encode($choice->[1])               HTML::Entities::encode($choice->[1],"<>&\"'") 
             . "'";              . "'";
         if (!$self->{'multichoice'} && !$checked) {          if ($checkedChoices{$choice->[1]}) {
             $result .= " checked ";              $result .= " checked='checked'";
             $checked = 1;  
         }          }
           $result .= qq{ id="id$id"};
         my $choiceLabel = $choice->[0];          my $choiceLabel = $choice->[0];
         if ($choice->[4]) {  # if we need to evaluate this choice          if ($choice->[3]) {  # if we need to evaluate this choice
             $choiceLabel = "sub { my $helper = shift; my $state = shift;" .              $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
                 $choiceLabel . "}";                  $choiceLabel . "}";
             $choiceLabel = eval($choiceLabel);              $choiceLabel = eval($choiceLabel);
             $choiceLabel = &$choiceLabel($helper, $self);              $choiceLabel = &$choiceLabel($helper, $self);
         }          }
         $result .= "/></td><td> " . $choiceLabel . "</td></tr>\n";          $result .= " /></td><td> ".qq{<label for="id$id">}.
       $choiceLabel. "</label></td>";
    if ($choice->[4]) {
       $result .='<td><input type="text" size="5" name="'
    .$choice->[4].'_forminput" value="'
                   .$choice->[5].'" /></td>';
    }
    $result .= "</tr>\n";
     }      }
     $result .= "</table>\n\n\n";      $result .= "</table>\n\n\n";
     $result .= $buttons;      $result .= $buttons;
Line 1042  BUTTONS Line 1505  BUTTONS
 # given, switch to it  # given, switch to it
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};      my $chosenValue = $env{'form.' . $self->{'variable'} . '_forminput'};
   
     if (!$chosenValue) {  
         $self->{ERROR_MSG} = "You must choose one or more choices to" .      if (!defined($chosenValue) && !$self->{'allowempty'}) {
             " continue.";          $self->{ERROR_MSG} = 
       &mt("You must choose one or more choices to continue.");
         return 0;          return 0;
     }      }
   
     if ($self->{'multichoice'}) {  
         $self->process_multiple_choices($self->{'variable'}.'.forminput',  
                                         $self->{'variable'});      if (ref($chosenValue)) {
           $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
       }
   
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
       
       foreach my $choice (@{$self->{CHOICES}}) {
           if ($choice->[1] eq $chosenValue) {
               if (defined($choice->[2])) {
                   $helper->changeState($choice->[2]);
               }
           }
    if ($choice->[4]) {
       my $varname = $choice->[4];
       $helper->{'VARS'}->{$varname} = $env{'form.'."${varname}_forminput"};
    }
       }
       return 1;
   }
   1;
   
   package Apache::lonhelper::dropdown;
   
   =pod
   
   =head2 Element: dropdownX<dropdown, helper tag>
   
   A drop-down provides a drop-down box instead of a radio button
   box. Because most people do not know how to use a multi-select
   drop-down box, that option is not allowed. Otherwise, the arguments
   are the same as "choices", except "allowempty" is also meaningless.
   
   <dropdown> takes an attribute "variable" to control which helper variable
   the result is stored in.
   
   =head3 SUB-TAGS
   
   <choice>, which acts just as it does in the "choices" element.
   
   =cut
   
   # This really ought to be a sibling class to "choice" which is itself
   # a child of some abstract class.... *shrug*
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::dropdown',
                                 ('dropdown'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_dropdown {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       # Need to initialize the choices list, so everything can assume it exists
       $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{CHOICES} = [];
       return '';
   }
   
   sub end_dropdown {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::dropdown->new();
       return '';
   }
   
   sub render {
       my $self = shift;
       my $var = $self->{'variable'};
       my $result = '';
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
       }
   
       my %checkedChoices;
       my $checkedChoicesFunc;
   
       if (defined($self->{DEFAULT_VALUE})) {
           $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
           die 'Error in default value code for variable ' . 
               $self->{'variable'} . ', Perl said: ' . $@ if $@;
       } else {
           $checkedChoicesFunc = sub { return ''; };
       }
   
       # single choice
       my $selectedChoice = &$checkedChoicesFunc($helper, $self);
       
       my $foundChoice = 0;
       
       # check that the choice is in the list of choices.
       for my $choice (@{$self->{CHOICES}}) {
    if ($choice->[1] eq $selectedChoice) {
       $checkedChoices{$choice->[1]} = 1;
       $foundChoice = 1;
    }
       }
       
       # If we couldn't find the choice, pick the first one 
       if (!$foundChoice) {
    $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
       }
   
       $result .= "<select name='${var}_forminput'>\n";
       foreach my $choice (@{$self->{CHOICES}}) {
           $result .= "<option value='" . 
               HTML::Entities::encode($choice->[1],"<>&\"'") 
               . "'";
           if ($checkedChoices{$choice->[1]}) {
               $result .= " selected='selected' ";
           }
           my $choiceLabel = $choice->[0];
           if ($choice->[4]) {  # if we need to evaluate this choice
               $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
                   $choiceLabel . "}";
               $choiceLabel = eval($choiceLabel);
               $choiceLabel = &$choiceLabel($helper, $self);
           }
           $result .= ">" . &mtn($choiceLabel) . "</option>\n";
       }
       $result .= "</select>\n";
   
       return $result;
   }
   
   # If a NEXTSTATE was given or a nextstate for this choice was
   # given, switch to it
   sub postprocess {
       my $self = shift;
       my $chosenValue = $env{'form.' . $self->{'variable'} . '_forminput'};
   
       if (!defined($chosenValue) && !$self->{'allowempty'}) {
           $self->{ERROR_MSG} = "You must choose one or more choices to" .
               " continue.";
           return 0;
     }      }
   
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
Line 1074  package Apache::lonhelper::date; Line 1694  package Apache::lonhelper::date;
   
 =pod  =pod
   
 =head2 Element: date  =head2 Element: dateX<date, helper element>
   
 Date elements allow the selection of a date with a drop down list.  Date elements allow the selection of a date with a drop down list.
   
Line 1105  Example: Line 1725  Example:
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonlocal; # A localization nightmare
 use Time::localtime;  use Apache::lonnet;
   use DateTime;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::date',      &Apache::lonhelper::register('Apache::lonhelper::date',
Line 1133  sub start_date { Line 1754  sub start_date {
     $paramHash->{'variable'} = $token->[2]{'variable'};      $paramHash->{'variable'} = $token->[2]{'variable'};
     $helper->declareVar($paramHash->{'variable'});      $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};      $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
       $paramHash->{'anytime'} = $token->[2]{'anytime'};
 }  }
   
 sub end_date {  sub end_date {
Line 1151  sub render { Line 1773  sub render {
     my $var = $self->{'variable'};      my $var = $self->{'variable'};
   
     my $date;      my $date;
       
       my $time=time;
       my ($anytime,$onclick);
   
       # first check VARS for a valid new value from the user
       # then check DEFAULT_VALUE for a valid default time value
       # otherwise pick now as reasonably good time
   
       if (defined($helper->{VARS}{$var})
    &&  $helper->{VARS}{$var} > 0) {
           $date = &get_date_object($helper->{VARS}{$var}); 
       } elsif (defined($self->{DEFAULT_VALUE})) {
           my $valueFunc = eval($self->{DEFAULT_VALUE});
           die('Error in default value code for variable ' . 
               $self->{'variable'} . ', Perl said: ' . $@) if $@;
           $time = &$valueFunc($helper, $self);
    if (lc($time) eq 'anytime') {
       $anytime=1;
       $date = &get_date_object(time);
       $date->min(0);
    } elsif (defined($time) && $time ne 0) {
       $date = &get_date_object($time);
    } else {
       # leave date undefined so it'll default to now
    }
       }
   
       if (!defined($date)) {
    $date = &get_date_object(time);
    $date->min(0);
       }
   
       if ($anytime) {
    $onclick = "onclick=\"javascript:updateCheck(this.form,'${var}anytime',false)\"";
       }
     # Default date: The current hour.      # Default date: The current hour.
     $date = localtime();  
     $date->min(0);  
   
     if (defined $self->{ERROR_MSG}) {      if (defined $self->{ERROR_MSG}) {
         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';          $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
Line 1162  sub render { Line 1816  sub render {
   
     # Month      # Month
     my $i;      my $i;
     $result .= "<select name='${var}month'>\n";      $result .= "<select $onclick name='${var}month'>\n";
     for ($i = 0; $i < 12; $i++) {      for ($i = 0; $i < 12; $i++) {
         if ($i == $date->mon) {          if (($i + 1) == $date->mon) {
             $result .= "<option value='$i' selected>";              $result .= "<option value='$i' selected='selected'>";
         } else {          } else {
             $result .= "<option value='$i'>";              $result .= "<option value='$i'>";
         }          }
         $result .= $months[$i] . "</option>\n";          $result .= &mt($months[$i])."</option>\n";
     }      }
     $result .= "</select>\n";      $result .= "</select>\n";
   
     # Day      # Day
     $result .= "<select name='${var}day'>\n";      $result .= "<select $onclick name='${var}day'>\n";
     for ($i = 1; $i < 32; $i++) {      for ($i = 1; $i < 32; $i++) {
         if ($i == $date->mday) {          if ($i == $date->mday) {
             $result .= '<option selected>';              $result .= '<option selected="selected">';
         } else {          } else {
             $result .= '<option>';              $result .= '<option>';
         }          }
Line 1186  sub render { Line 1840  sub render {
     $result .= "</select>,\n";      $result .= "</select>,\n";
   
     # Year      # Year
     $result .= "<select name='${var}year'>\n";      $result .= "<select $onclick name='${var}year'>\n";
     for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates      for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
         if ($date->year + 1900 == $i) {          if ($date->year == $i) {
             $result .= "<option selected>";              $result .= "<option selected='selected'>";
         } else {          } else {
             $result .= "<option>";              $result .= "<option>";
         }          }
Line 1199  sub render { Line 1853  sub render {
   
     # Display Hours and Minutes if they are called for      # Display Hours and Minutes if they are called for
     if ($self->{'hoursminutes'}) {      if ($self->{'hoursminutes'}) {
    # This needs parameterization for times.
    my $am = &mt('a.m.');
    my $pm = &mt('p.m.');
         # Build hour          # Build hour
         $result .= "<select name='${var}hour'>\n";          $result .= "<select $onclick name='${var}hour'>\n";
         $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .          $result .= "<option " . ($date->hour == 0 ? 'selected="selected" ':'') .
             " value='0'>midnight</option>\n";              " value='0'>" . &mt('midnight') . "</option>\n";
         for ($i = 1; $i < 12; $i++) {          for ($i = 1; $i < 12; $i++) {
             if ($date->hour == $i) {              if ($date->hour == $i) {
                 $result .= "<option selected value='$i'>$i a.m.</option>\n";                  $result .= "<option selected='selected' value='$i'>$i $am</option>\n";
             } else {              } else {
                 $result .= "<option value='$i'>$i a.m</option>\n";                  $result .= "<option value='$i'>$i $am</option>\n";
             }              }
         }          }
         $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .          $result .= "<option " . ($date->hour == 12 ? 'selected="selected" ':'') .
             " value='12'>noon</option>\n";              " value='12'>" . &mt('noon') . "</option>\n";
         for ($i = 13; $i < 24; $i++) {          for ($i = 13; $i < 24; $i++) {
             my $printedHour = $i - 12;              my $printedHour = $i - 12;
             if ($date->hour == $i) {              if ($date->hour == $i) {
                 $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";                  $result .= "<option selected='selected' value='$i'>$printedHour $pm</option>\n";
             } else {              } else {
                 $result .= "<option value='$i'>$printedHour p.m.</option>\n";                  $result .= "<option value='$i'>$printedHour $pm</option>\n";
             }              }
         }          }
   
         $result .= "</select> :\n";          $result .= "</select> :\n";
   
         $result .= "<select name='${var}minute'>\n";          $result .= "<select $onclick name='${var}minute'>\n";
         for ($i = 0; $i < 60; $i++) {   my $selected=0;
           for my $i ((0,15,30,45,59,undef,0..59)) {
             my $printedMinute = $i;              my $printedMinute = $i;
             if ($i < 10) {              if (defined($i) && $i < 10) {
                 $printedMinute = "0" . $printedMinute;                  $printedMinute = "0" . $printedMinute;
             }              }
             if ($date->min == $i) {              if (!$selected && $date->min == $i) {
                 $result .= "<option selected>";                  $result .= "<option selected='selected'>";
    $selected=1;
             } else {              } else {
                 $result .= "<option>";                  $result .= "<option>";
             }              }
Line 1238  sub render { Line 1897  sub render {
         }          }
         $result .= "</select>\n";          $result .= "</select>\n";
     }      }
       $result  .= ' '.$date->time_zone_short_name().' ';
       if ($self->{'anytime'}) {
    $result.=(<<CHECK);
   <script type="text/javascript">
   // <!--
       function updateCheck(form,name,value) {
    var checkbox=form[name];
    checkbox.checked = value;
       }
   // -->
   </script>
   CHECK
    $result.="&nbsp;or&nbsp;<label><input type='checkbox' ";
    if ($anytime) {
       $result.=' checked="checked" '
    }
           my $anytimetext = &mt('Any time');
           if (($var eq 'startreserve') || ($var eq 'endreserve')) {
               $anytimetext = &mt('Any time before slot starts');
           } elsif (($var eq 'startunique') || ($var eq 'endunique')) {
               $anytimetext = &mt('No restriction on uniqueness');     
           }
    $result.="name='${var}anytime'/>".$anytimetext.'</label>'
       }
     return $result;      return $result;
   
 }  }
Line 1246  sub render { Line 1928  sub render {
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
     my $var = $self->{'variable'};      my $var = $self->{'variable'};
     my $month = $ENV{'form.' . $var . 'month'};       if ($env{'form.' . $var . 'anytime'}) {
     my $day = $ENV{'form.' . $var . 'day'};    $helper->{VARS}->{$var} = undef;
     my $year = $ENV{'form.' . $var . 'year'};       } else {
     my $min = 0;    my $month = $env{'form.' . $var . 'month'};
     my $hour = 0;          $month ++;
     if ($self->{'hoursminutes'}) {   my $day = $env{'form.' . $var . 'day'}; 
         $min = $ENV{'form.' . $var . 'minute'};   my $year = $env{'form.' . $var . 'year'}; 
         $hour = $ENV{'form.' . $var . 'hour'};   my $min = 0; 
     }   my $hour = 0;
    if ($self->{'hoursminutes'}) {
     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);      $min = $env{'form.' . $var . 'minute'};
     # Check to make sure that the date was not automatically co-erced into a       $hour = $env{'form.' . $var . 'hour'};
     # valid date, as we want to flag that as an error   }
     # This happens for "Feb. 31", for instance, which is coerced to March 2 or  
     # 3, depending on if it's a leapyear   my ($chosenDate,$checkDate);
     my $checkDate = localtime($chosenDate);          my $timezone = &Apache::lonlocal::gettimezone();
           my $dt;
     if ($checkDate->mon != $month || $checkDate->mday != $day ||   eval {
         $checkDate->year + 1900 != $year) {                 $dt = DateTime->new( year   => $year,
         $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "                                      month  => $month,
             . "date because it doesn't exist. Please enter a valid date.";                                      day    => $day,
         return 0;                                      hour   => $hour,
                                       minute => $min,
                                       second => 0,
                                       time_zone => $timezone,
                                );
           };
   
    my $error = $@;
           if (!$error) {
               $chosenDate  = $dt->epoch;
               $checkDate = &get_date_object($chosenDate);
           }
   
    # Check to make sure that the date was not automatically co-erced into a 
    # valid date, as we want to flag that as an error
    # This happens for "Feb. 31", for instance, which is coerced to March 2 or
    # 3, depending on if it's a leap year
   
    if ($error || $checkDate->mon != $month || $checkDate->mday != $day ||
       $checkDate->year != $year) {
       unless (Apache::lonlocal::current_language()== ~/^en/) {
    $self->{ERROR_MSG} = &mt("Invalid date entry");
    return 0;
       }
       # LOCALIZATION FIXME: Needs to be parameterized
       $self->{ERROR_MSG} = "Can't use ".$months[$env{'form.'.$var.'month'}].                                 " $day, $year as a ".
                    "date because it doesn't exist. Please enter a valid date.";
   
       return 0;
    }
    $helper->{VARS}->{$var} = $chosenDate;
       }
   
       if (defined($self->{VALIDATOR})) {
    my $validator = eval($self->{VALIDATOR});
    die 'Died during evaluation of validator code; Perl said: ' . $@ if $@;
    my $invalid = &$validator($helper, $state, $self, $self->getValue());
    if ($invalid) {
       $self->{ERROR_MSG} = $invalid;
       return 0;
    }
     }      }
   
     $helper->{VARS}->{$var} = $chosenDate;  
   
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
         $helper->changeState($self->{NEXTSTATE});          $helper->changeState($self->{NEXTSTATE});
     }      }
   
     return 1;      return 1;
 }  }
   
   sub get_date_object {
       my ($epoch) = @_;
       my $dt = DateTime->from_epoch(epoch => $epoch)
                        ->set_time_zone(&Apache::lonlocal::gettimezone());
       my $lang = Apache::lonlocal::current_language();
       if ($lang ne '') {
           eval {
               $dt->set_locale($lang);
           };
       }
       return $dt;
   }
   
 1;  1;
   
 package Apache::lonhelper::resource;  package Apache::lonhelper::resource;
   
 =pod  =pod
   
 =head2 Element: resource  =head2 Element: resourceX<resource, helper element>
   
 <resource> elements allow the user to select one or multiple resources  <resource> elements allow the user to select one or multiple resources
 from the current course. You can filter out which resources they can view,  from the current course. You can filter out which resources they can view,
Line 1294  selections across folder openings and cl Line 2028  selections across folder openings and cl
 the user can manipulate the folders.  the user can manipulate the folders.
   
 <resource> takes the standard variable attribute to control what helper  <resource> takes the standard variable attribute to control what helper
 variable stores the results. It also takes a "multichoice" attribute,  variable stores the results. It also takes a "multichoice"X<multichoice> attribute,
 which controls whether the user can select more then one resource.  which controls whether the user can select more then one resource. The 
   "toponly" attribute controls whether the resource display shows just the
   resources in that sequence, or recurses into all sub-sequences, defaulting
   to false. The "suppressEmptySequences" attribute reflects the 
   suppressEmptySequences argument to the render routine, which will cause
   folders that have all of their contained resources filtered out to also
   be filtered out. The 'addstatus' attribute, if true, will add the icon
   and long status display columns to the display. The 'addparts'
   attribute will add in a part selector beside problems that have more
   than 1 part. The 'includecourse' attribute if true, will include
   the toplevel default.sequence in the results.
   
 B<SUB-TAGS>  =head3 SUB-TAGS
   
 =over 4  =over 4
   
 =item * <filterfunc>: If you want to filter what resources are displayed  =item * <filterfunc>X<filterfunc>: If you want to filter what resources are displayed
   to the user, use a filter func. The <filterfunc> tag should contain    to the user, use a filter func. The <filterfunc> tag should contain
   Perl code that when wrapped with "sub { my $res = shift; " and "}" is     Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
   a function that returns true if the resource should be displayed,     a function that returns true if the resource should be displayed, 
Line 1309  B<SUB-TAGS> Line 2053  B<SUB-TAGS>
   (See Apache::lonnavmaps documentation for information about the     (See Apache::lonnavmaps documentation for information about the 
   resource object.)    resource object.)
   
 =item * <choicefunc>: Same as <filterfunc>, except that controls whether  =item * <choicefunc>X<choicefunc>: Same as <filterfunc>, except that controls whether
   the given resource can be chosen. (It is almost always a good idea to    the given resource can be chosen. (It is almost always a good idea to
   show the user the folders, for instance, but you do not always want to     show the user the folders, for instance, but you do not always want to 
   let the user select them.)    let the user select them.)
   
 =item * <nextstate>: Standard nextstate behavior.  =item * <nextstate>: Standard nextstate behavior.
   
 =item * <valuefunc>: This function controls what is returned by the resource  =item * <valuefunc>X<valuefunc>: This function controls what is returned by the resource
   when the user selects it. Like filterfunc and choicefunc, it should be    when the user selects it. Like filterfunc and choicefunc, it should be
   a function fragment that when wrapped by "sub { my $res = shift; " and    a function fragment that when wrapped by "sub { my $res = shift; " and
   "}" returns a string representing what you want to have as the value. By    "}" returns a string representing what you want to have as the value. By
   default, the value will be the resource ID of the object ($res->{ID}).    default, the value will be the resource ID of the object ($res->{ID}).
   
 =item * <mapurl>: If the URL of a map is given here, only that map  =item * <mapurl>X<mapurl>: If the URL of a map is given here, only that map
   will be displayed, instead of the whole course.    will be displayed, instead of the whole course. If the attribute
     "evaluate" is given and is true, the contents of the mapurl will be
     evaluated with "sub { my $helper = shift; my $state = shift;" and
     "}", with the return value used as the mapurl.
   
   =item * <option />: Allows you to add optional elements to the
     resource chooser currently these can be a checkbox, or a text entry
     or hidden (see the 'type' attribute below).
     the following attributes are supported by this tag:
   
   =over 4
   
   =item * type=control-type : determines the type of control displayed.
     This can be one of the following types: 'checkbox' provides a true/false
     checkbox.  'text' provides a text entry control. 'hidden' provides a
     hidden form element that returns the name of the resource for each
     element of the text box.
   
   =item * text=header-text : provides column header text for the option.
     
   =item * variable=helpervar : provides a helper variable to contain the
     value of the input control for each resource.  In general, the result
     will be a set of values separated by |||  for the checkbox the value between
     the |||'s will either be empty, if the box is not checked, or the resource
     name if checked.  For the text entry, the values will be the text in the
     text box.  This could be empty.  Hidden elements unconditionally provide
     the resource name for each row of the chooser and allow you to therefore
     correlate text entries to their resources.
     The helper variable can be initialized by the user code to pre-load values
     into the controls:
   
   =over 4
   
     
   =item * Preloading checkboxes : Set the helper variable to the value you
      would have gotten from the control if it had been manually set as desired.
   
   =item * Preloading text entries : Set the helper variable to triple pipe
      separated values where each value is of the form resource-name=value
   
   =item * Preloading hidden fields : These cannot be pre-loaded and will always
     be pipe separated resource names.
   
   =back
   
   
   =back
   
 =back  =back
   
Line 1332  B<SUB-TAGS> Line 2122  B<SUB-TAGS>
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonnet;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::resource',      &Apache::lonhelper::register('Apache::lonhelper::resource',
                               ('resource', 'filterfunc',                                 ('resource', 'filterfunc', 
                                'choicefunc', 'valuefunc',                                 'choicefunc', 'valuefunc',
                                'mapurl'));                                 'mapurl','option'));
 }  }
   
 sub new {  sub new {
Line 1356  sub start_resource { Line 2147  sub start_resource {
     $paramHash->{'variable'} = $token->[2]{'variable'};      $paramHash->{'variable'} = $token->[2]{'variable'};
     $helper->declareVar($paramHash->{'variable'});      $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};      $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'};
       $paramHash->{'toponly'} = $token->[2]{'toponly'};
       $paramHash->{'addstatus'} = $token->[2]{'addstatus'};
       $paramHash->{'addparts'} = $token->[2]{'addparts'};
       if ($paramHash->{'addparts'}) {
    $helper->declareVar($paramHash->{'variable'}.'_part');
       }
       $paramHash->{'closeallpages'} = $token->[2]{'closeallpages'};
       $paramHash->{'include_top_level_map'} = $token->[2]{'includecourse'};
     return '';      return '';
 }  }
   
Line 1432  sub start_mapurl { Line 2232  sub start_mapurl {
   
     my $contents = Apache::lonxml::get_all_text('/mapurl',      my $contents = Apache::lonxml::get_all_text('/mapurl',
                                                 $parser);                                                  $parser);
       $paramHash->{EVAL_MAP_URL} = $token->[2]{'evaluate'};
     $paramHash->{MAP_URL} = $contents;      $paramHash->{MAP_URL} = $contents;
 }  }
   
 sub end_mapurl { return ''; }  sub end_mapurl { return ''; }
   
   
   sub start_option {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
       if (!defined($paramHash->{OPTION_TEXTS})) {
    $paramHash->{OPTION_TEXTS} = [ ];
    $paramHash->{OPTION_VARS}  = [ ];
    $paramHash->{OPTION_TYPES} = [ ];
   
       }
       #  We can have an attribute: type which can have the
       #  values: "checkbox" or "text" which defaults to 
       #           checkbox allowing us to change the type of input
       #           for the option:
       #
       my $input_widget_type = 'checkbox';
       if(defined($token->[2]{'type'})) {
    my $widget_type  = $token->[2]{'type'};
    if ($widget_type eq 'text') {          # only accept legal alternatives
       $input_widget_type = $widget_type; # Illegals are checks.
    } elsif ($widget_type eq 'hidden') {
       $input_widget_type = $widget_type;
    }
       }
   
       # OPTION_TEXTS is a list of the text attribute
       #               values used to create column headings.
       # OPTION_VARS is a list of the variable names, used to create the checkbox
       #             inputs.
       # OPTION_TYPES is a list of the option types:
       #
       #  We're ok with empty elements. as place holders
       # Although the 'variable' element should really exist.
       #
   
   
       my $option_texts  = $paramHash->{OPTION_TEXTS};
       my $option_vars   = $paramHash->{OPTION_VARS};
       my $option_types   = $paramHash->{OPTION_TYPES};
       push(@$option_texts,  $token->[2]{'text'});
       push(@$option_vars,   $token->[2]{'variable'});
       push(@$option_types,   $input_widget_type);
   
   
       #  Need to create and declare the option variables as well to make them
       # persistent.
       #
       my $varname = $token->[2]{'variable'};
       $helper->declareVar($varname);
   
   
       return '';
   }
   
   sub end_option {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
       return '';
   }
   
 # A note, in case I don't get to this before I leave.  # A note, in case I don't get to this before I leave.
 # If someone complains about the "Back" button returning them  # If someone complains about the "Back" button returning them
 # to the previous folder state, instead of returning them to  # to the previous folder state, instead of returning them to
Line 1459  sub render { Line 2318  sub render {
   
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result = <<SCRIPT;          $result = <<SCRIPT;
 <script>  <script type="text/javascript">
     function checkall(value) {  // <!--
       function checkall(value, checkName) {
  for (i=0; i<document.forms.helpform.elements.length; i++) {   for (i=0; i<document.forms.helpform.elements.length; i++) {
             ele = document.forms.helpform.elements[i];              ele = document.forms.helpform.elements[i];
             if (ele.type == "checkbox") {              if (ele.name == checkName + '_forminput') {
                 document.forms.helpform.elements[i].checked=value;                  document.forms.helpform.elements[i].checked=value;
             }              }
         }          }
     }      }
   // -->
 </script>  </script>
 SCRIPT  SCRIPT
           my %lt=&Apache::lonlocal::texthash(
    'sar'  => "Select All Resources",
           'uar'  => "Unselect All Resources");
   
         $buttons = <<BUTTONS;          $buttons = <<BUTTONS;
 <br /> &nbsp;  <br /> &nbsp;
 <input type="button" onclick="checkall(true)" value="Select All" />  <input type="button" onclick="checkall(true, '$var')" value="$lt{'sar'}" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="$lt{'uar'}" />
 <br /> &nbsp;  <br /> &nbsp;
 BUTTONS  BUTTONS
     }      }
Line 1484  BUTTONS Line 2349  BUTTONS
   
     $result .= $buttons;      $result .= $buttons;
   
     my $filterFunc = $self->{FILTER_FUNC};      my $filterFunc     = $self->{FILTER_FUNC};
     my $choiceFunc = $self->{CHOICE_FUNC};      my $choiceFunc     = $self->{CHOICE_FUNC};
     my $valueFunc = $self->{VALUE_FUNC};      my $valueFunc      = $self->{VALUE_FUNC};
     my $mapUrl = $self->{MAP_URL};      my $multichoice    = $self->{'multichoice'};
     my $multichoice = $self->{'multichoice'};      my $option_vars    = $self->{OPTION_VARS};
       my $option_texts   = $self->{OPTION_TEXTS};
       my $option_types   = $self->{OPTION_TYPES};
       my $addparts       = $self->{'addparts'};
       my $headings_done  = 0;
   
       # Evaluate the map url as needed
       my $mapUrl;
       if ($self->{EVAL_MAP_URL}) {
    my $mapUrlFunc = eval('sub { my $helper = shift; my $state = shift; ' . 
       $self->{MAP_URL} . '}');
    $mapUrl = &$mapUrlFunc($helper, $self);
       } else {
    $mapUrl = $self->{MAP_URL};
       }
   
       my %defaultSymbs;
       if (defined($self->{DEFAULT_VALUE})) {
           my $valueFunc = eval($self->{DEFAULT_VALUE});
           die 'Error in default value code for variable ' . 
               $self->{'variable'} . ', Perl said: ' . $@ if $@;
           my @defaultSymbs = &$valueFunc($helper, $self);
    if (!$multichoice && @defaultSymbs) { # only allowed 1
       @defaultSymbs = ($defaultSymbs[0]);
    }
    %defaultSymbs = map { if ($_) {($_,1) } } @defaultSymbs;
    delete($defaultSymbs{''});
       }
   
     # Create the composite function that renders the column on the nav map      # Create the composite function that renders the column on the nav map
     # have to admit any language that lets me do this can't be all bad      # have to admit any language that lets me do this can't be all bad
Line 1496  BUTTONS Line 2388  BUTTONS
     my $checked = 0;      my $checked = 0;
     my $renderColFunc = sub {      my $renderColFunc = sub {
         my ($resource, $part, $params) = @_;          my ($resource, $part, $params) = @_;
    my $result = "";
   
    if(!$headings_done) {
       if ($option_texts) {
    foreach my $text (@$option_texts) {
       $result .= "<th>$text</th>";
    }
       }
       $result .= '<th>'.&Apache::lonlocal::mt('Select').'</th>';
       $result .= "</tr><tr>"; # Close off the extra row and start a new one.
       $headings_done = 1;
    }
   
         my $inputType;          my $inputType;
         if ($multichoice) { $inputType = 'checkbox'; }          if ($multichoice) { $inputType = 'checkbox'; }
         else {$inputType = 'radio'; }          else {$inputType = 'radio'; }
   
         if (!&$choiceFunc($resource)) {          if (!&$choiceFunc($resource)) {
             return '<td>&nbsp;</td>';      $result .= '<td>&nbsp;</td>';
               return $result;
         } else {          } else {
             my $col = "<td><input type='$inputType' name='${var}.forminput' ";      my $col = "";
             if (!$checked && !$multichoice) {      my $raw_name = &$valueFunc($resource);
                 $col .= "checked ";      my $resource_name =   
                 $checked = 1;                     HTML::Entities::encode($raw_name,"<>&\"'");
             }      if($option_vars) {
             $col .= "value='" .    my $option_num = 0;
                 HTML::Entities::encode(&$valueFunc($resource))    foreach my $option_var (@$option_vars) {
                 . "' /></td>";      my $option_type = $option_types->[$option_num];
             return $col;      $option_num++;
       my $var_value = "\|\|\|" . $helper->{VARS}->{$option_var} . 
    "\|\|\|";
       my $checked ="";
       if($var_value =~ /\Q|||$raw_name|||\E/) {
    $checked = "checked='checked'";
       }
       if ($option_type eq 'text') {
    #
    # For text's the variable value is a ||| separated set of
    # resource_name=value 
    #
    my @values = split(/\|\|\|/, $helper->{VARS}->{$option_var});
   
    # Normal practice would be to toss this in a hash but 
    # the only thing that saves is the compare in the loop
    # below and for all but one case we'll break out of the loop
    # before it completes.
   
    my $text_value = '';    # In case there's no match.
    foreach my $value (@values) {
       my ($res, $skip) = split(/=/, $value);
       if($res eq $resource_name) {
    $text_value = $skip;
    last;
       }
    }
    # TODO: add an attribute to <option> that allows the
    #       programmer to set the width of the tex entry box.
   
    $col .=
       "<td align='center'><input type='text' name ='$option_var".
       "_forminput' value='".$text_value."' size='5' /> </td>";
       } elsif ($option_type eq 'hidden') {
     $col .= "<td align='center'><input type='hidden' name ='$option_var".
       "_forminput' value='".
       $resource_name . "'/> </td>";
       } else {
    $col .= 
       "<td align='center'><input type=$option_type name ='$option_var".
       "_forminput' value='".
       $resource_name . "' $checked /> </td>";
       }
    }
       }
   
               $col .= "<td align='center'><input type='$inputType' name='${var}_forminput' ";
       if (%defaultSymbs) {
    my $symb=$resource->symb();
    if (exists($defaultSymbs{$symb})) {
       $col .= "checked='checked' ";
       $checked = 1;
    }
       } else {
    if (!$checked && !$multichoice) {
       $col .= "checked='checked' ";
       $checked = 1;
    }
    if ($multichoice) { # all resources start checked; see bug 1174
       $col .= "checked='checked' ";
       $checked = 1;
    }
       }
               $col .= "value='" . $resource_name  . "' /></td>";
   
               return $result.$col;
         }          }
     };      };
       my $renderPartsFunc = sub {
           my ($resource, $part, $params) = @_;
    my $col= "<td>";
    my $id=$resource->{ID};
    my $resource_name =   
       &HTML::Entities::encode(&$valueFunc($resource),"<>&\"'");
    if ($addparts && (scalar(@{$resource->parts}) > 1)) {
       $col .= "<select onclick=\"javascript:updateRadio(this.form,'${var}_forminput','$resource_name');updateHidden(this.form,'$id','${var}');\" name='part_${id}_forminput'>\n";
       $col .= "<option value=\"$part\">".&Apache::lonlocal::mt('All Parts')."</option>\n";
       foreach my $part (@{$resource->parts}) {
    $col .= "<option value=\"$part\">".&Apache::lonlocal::mt('Part: [_1]',$part)."</option>\n";
       }
       $col .= "</select>";
    }
    $col .= "</td>";
       };
       $result.=(<<RADIO);
   <script type="text/javascript">
   // <!--
       function updateRadio(form,name,value) {
    var radiobutton=form[name];
    for (var i=0; i<radiobutton.length; i++) {
       if (radiobutton[i].value == value) {
    radiobutton[i].checked = true;
    break;
       }
    }
       }
       function updateHidden(form,id,name) {
    var select=form['part_'+id+'_forminput'];
    var hidden=form[name+'_part_forminput'];
    var which=select.selectedIndex;
    hidden.value=select.options[which].value;
       }
   // -->
   </script>
   <input type="hidden" name="${var}_part_forminput" />
   
     $ENV{'form.condition'} = 1;  RADIO
       $env{'form.condition'} = !$self->{'toponly'};
       my $cols = [$renderColFunc];
       if ($self->{'addparts'}) { push(@$cols, $renderPartsFunc); }
       push(@$cols, Apache::lonnavmaps::resource());
       if ($self->{'addstatus'}) {
    push @$cols, (Apache::lonnavmaps::part_status_summary());
   
       }
     $result .=       $result .= 
         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc,           &Apache::lonnavmaps::render( { 'cols' => $cols,
                                                   Apache::lonnavmaps::resource()],  
                                        'showParts' => 0,                                         'showParts' => 0,
                                        'filterFunc' => $filterFunc,                                         'filterFunc' => $filterFunc,
                                        'resource_no_folder_link' => 1,                                         'resource_no_folder_link' => 1,
                                        'iterator_map' => $mapUrl }         'closeAllPages' => $self->{'closeallpages'},
                                          'suppressEmptySequences' => $self->{'suppressEmptySequences'},
          'include_top_level_map' => $self->{'include_top_level_map'},
                                          'iterator_map' => $mapUrl,
                                          'map_no_edit_link' => 1, } 
                                        );                                         );
   
     $result .= $buttons;      $result .= $buttons;
Line 1534  BUTTONS Line 2552  BUTTONS
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
   
     if ($self->{'multichoice'}) {  
         $self->process_multiple_choices($self->{'variable'}.'.forminput',  
                                         $self->{'variable'});  
     }  
   
     if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) {      if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) {
         $self->{ERROR_MSG} = 'You must choose at least one resource to continue.';          $self->{ERROR_MSG} = 'You must choose at least one resource to continue.';
         return 0;          return 0;
     }      }
       # For each of the attached options.  If it's env var is undefined, set it to
       # an empty string instead.. an undef'd env var means no choices selected.
       #
   
       my $option_vars = $self->{OPTION_VARS};
       if ($option_vars) {
    foreach my $var (@$option_vars) {
       my $env_name = "form.".$var."_forminput";
       if (!defined($env{$env_name})) {
    $env{$env_name} = '';
    $helper->{VARS}->{$var} = '';
       }
    }
       }
   
   
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
         $helper->changeState($self->{NEXTSTATE});          $helper->changeState($self->{NEXTSTATE});
Line 1557  package Apache::lonhelper::student; Line 2585  package Apache::lonhelper::student;
   
 =pod  =pod
   
 =head2 Element: student  =head2 Element: studentX<student, helper element>
   
 Student elements display a choice of students enrolled in the current  Student elements display a choice of students enrolled in the current
 course. Currently it is primitive; this is expected to evolve later.  course. Currently it is primitive; this is expected to evolve later.
   
 Student elements take two attributes: "variable", which means what  Student elements take the following attributes: 
 it usually does, and "multichoice", which if true allows the user  
 to select multiple students.  =over 4
   
   =item * B<variable>: 
   
   Does what it usually does: declare which helper variable to put the
   result in.
   
   =item * B<multichoice>: 
   
   If true allows the user to select multiple students. Defaults to false.
   
   =item * B<coursepersonnel>: 
   
   If true adds the course personnel to the top of the student
   selection. Defaults to false.
   
   =item * B<activeonly>:
   
   If true, only active students and course personnel will be
   shown. Defaults to false.
   
   =item * B<emptyallowed>:
   
   If true, the selection of no users is allowed. Defaults to false.
   
   =back
   
 =cut  =cut
   
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::student',      &Apache::lonhelper::register('Apache::lonhelper::student',
Line 1594  sub start_student { Line 2647  sub start_student {
     $paramHash->{'variable'} = $token->[2]{'variable'};      $paramHash->{'variable'} = $token->[2]{'variable'};
     $helper->declareVar($paramHash->{'variable'});      $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};      $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'coursepersonnel'} = $token->[2]{'coursepersonnel'};
       $paramHash->{'activeonly'} = $token->[2]{'activeonly'};
     if (defined($token->[2]{'nextstate'})) {      if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};          $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
     }      }
       $paramHash->{'emptyallowed'} = $token->[2]{'emptyallowed'};
           
 }      }    
   
Line 1613  sub render { Line 2669  sub render {
     my $self = shift;      my $self = shift;
     my $result = '';      my $result = '';
     my $buttons = '';      my $buttons = '';
       my $var = $self->{'variable'};
   
     if ($self->{'multichoice'}) {  
         $result = <<SCRIPT;  
 <script>  
     function checkall(value) {  
  for (i=0; i<document.forms.helpform.elements.length; i++) {  
             document.forms.helpform.elements[i].checked=value;  
         }  
     }  
 </script>  
 SCRIPT  
         $buttons = <<BUTTONS;  
 <br />  
 <input type="button" onclick="checkall(true)" value="Select All" />  
 <input type="button" onclick="checkall(false)" value="Unselect All" />  
 <br />  
 BUTTONS  
     }  
   
     if (defined $self->{ERROR_MSG}) {      if (defined $self->{ERROR_MSG}) {
         $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';          $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
     }      }
   
     # Load up the students      my %defaultUsers;
     my $choices = &Apache::loncoursedata::get_classlist();      if (defined($self->{DEFAULT_VALUE})) {
     my @keys = keys %{$choices};          my $valueFunc = eval($self->{DEFAULT_VALUE});
           die 'Error in default value code for variable ' . 
     # Constants              $self->{'variable'} . ', Perl said: ' . $@ if $@;
     my $section = Apache::loncoursedata::CL_SECTION();          my @defaultUsers = &$valueFunc($helper, $self);
     my $fullname = Apache::loncoursedata::CL_FULLNAME();   if (!$self->{'multichoice'} && @defaultUsers) { # only allowed 1
       @defaultUsers = ($defaultUsers[0]);
    }
    %defaultUsers = map { if ($_) {($_,1) } } @defaultUsers;
    delete($defaultUsers{''});
       }
   
   
       my ($course_personnel, 
    $current_members, 
    $expired_members, 
    $future_members) = 
       &Apache::lonselstudent::get_people_in_class($env{'request.course.sec'});
   
   
   
       # Load up the non-students, if necessary
   
       if ($self->{'coursepersonnel'}) {
    unshift @$current_members, (@$course_personnel);
       }
   
   
       #   Current personel
   
       $result .= '<h4>'.&mt('Select Currently Enrolled Students and Active Course Personnel').'</h4>';
       $result .= &Apache::lonselstudent::render_student_list( $current_members,
       "helpform",
       "current", 
       \%defaultUsers,
       $self->{'multichoice'},
       $self->{'variable'},
       1);
   
   
       # If activeonly is not set then we can also give the expired students:
       #
       if (!$self->{'activeonly'} && ((scalar(@$future_members)) > 0)) {
   
    # And future.
   
    $result .= '<h4>'.&mt('Select Future Enrolled Students and Future Course Personnel').'</h4>';
          
    $result .= &Apache::lonselstudent::render_student_list( $future_members,
    "helpform",
    "future",
    \%defaultUsers,
    $self->{'multichoice'},
    $self->{'variable'},
    0);
       }
       if (!$self->{'activeonly'} && ((scalar(@$expired_members)) > 0)) {
    # Past 
   
    $result .= '<h4>'.&mt('Select Previously Enrolled Students and Inactive Course Personnel').'</h4>';
    $result .= &Apache::lonselstudent::render_student_list($expired_members,
          "helpform",
          "past",
          \%defaultUsers,
          $self->{'multichoice'},
          $self->{'variable'},
          0);
       }
   
     # Sort by: Section, name  
     @keys = sort {  
         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {  
             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];  
         }  
         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];  
     } @keys;  
   
     my $type = 'radio';  
     if ($self->{'multichoice'}) { $type = 'checkbox'; }  
     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";  
     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".  
         "<td align='center'><b>Section</b></td></tr>";  
   
     my $checked = 0;  
     foreach (@keys) {  
         $result .= "<tr><td><input type='$type' name='" .  
             $self->{'variable'} . '.forminput' . "'";  
               
         if (!$self->{'multichoice'} && !$checked) {  
             $result .= " checked ";  
             $checked = 1;  
         }  
         $result .=  
             " value='" . HTML::Entities::encode($_)  
             . "' /></td><td>"  
             . HTML::Entities::encode($choices->{$_}->[$fullname])  
             . "</td><td align='center'>"   
             . HTML::Entities::encode($choices->{$_}->[$section])  
             . "</td></tr>\n";  
     }  
   
     $result .= "</table>\n\n";  
     $result .= $buttons;      
       
     return $result;      return $result;
 }  }
   
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
   
     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};      my $result = $env{'form.' . $self->{'variable'} . '_forminput'};
     if (!$result) {      if (!$result && !$self->{'emptyallowed'}) {
         $self->{ERROR_MSG} = 'You must choose at least one student '.   if ($self->{'coursepersonnel'}) {
             'to continue.';      $self->{ERROR_MSG} = 
    &mt('You must choose at least one user to continue.');
    } else {
       $self->{ERROR_MSG} = 
    &mt('You must choose at least one student to continue.');
    }
         return 0;          return 0;
     }      }
   
     if ($self->{'multichoice'}) {  
         $self->process_multiple_choices($self->{'variable'}.'.forminput',  
                                         $self->{'variable'});  
     }  
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
         $helper->changeState($self->{NEXTSTATE});          $helper->changeState($self->{NEXTSTATE});
     }      }
Line 1709  package Apache::lonhelper::files; Line 2779  package Apache::lonhelper::files;
   
 =pod  =pod
   
 =head2 Element: files  =head2 Element: filesX<files, helper element>
   
 files allows the users to choose files from a given directory on the  files allows the users to choose files from a given directory on the
 server. It is always multichoice and stores the result as a triple-pipe  server. It is always multichoice and stores the result as a triple-pipe
Line 1725  are put. It accepts the attribute "multi Line 2795  are put. It accepts the attribute "multi
 defaulting to false, which if true will allow the user to select more  defaulting to false, which if true will allow the user to select more
 then one choice.   then one choice. 
   
 <files> accepts three subtags. One is the "nextstate" sub-tag that works  <files> accepts three subtags: 
 as it does with the other tags. Another is a <filechoice> sub tag that  
 is Perl code that, when surrounded by "sub {" and "}" will return a  =over 4
 string representing what directory on the server to allow the user to   
 choose files from. Finally, the <filefilter> subtag should contain Perl  =item * B<nextstate>: works as it does with the other tags. 
 code that when surrounded by "sub { my $filename = shift; " and "}",  
 returns a true value if the user can pick that file, or false otherwise.  =item * B<filechoice>: When the contents of this tag are surrounded by
 The filename passed to the function will be just the name of the file,       "sub {" and "}", will return a string representing what directory
 with no path info.      on the server to allow the user to choose files from. 
   
   =item * B<filefilter>: Should contain Perl code that when surrounded
       by "sub { my $filename = shift; " and "}", returns a true value if
       the user can pick that file, or false otherwise. The filename
       passed to the function will be just the name of the file, with no
       path info. By default, a filter function will be used that will
       mask out old versions of files. This function is available as
       Apache::lonhelper::files::not_old_version if you want to use it to
       composite your own filters.
   
   =back
   
   B<General security note>: You should ensure the user can not somehow 
   pass something into your code that would allow them to look places 
   they should not be able to see, like the C</etc/> directory. However,
   the security impact would be minimal, since it would only expose
   the existence of files, there should be no way to parlay that into
   viewing the files. 
   
 =cut  =cut
   
 no strict;  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   use Apache::lonpubdir; # for getTitleString
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::files',      &Apache::lonhelper::register('Apache::lonhelper::files',
                                  ('files', 'filechoice', 'filefilter'));                                   ('files', 'filechoice', 'filefilter'));
 }  }
   
   sub not_old_version {
       my $file = shift;
       
       # Given a file name, return false if it is an "old version" of a
       # file, or true if it is not.
   
       if ($file =~ /^.*\.[0-9]+\.[A-Za-z]+(\.meta)?$/) {
    return 0;
       }
       return 1;
   }
   
 sub new {  sub new {
     my $ref = Apache::lonhelper::element->new();      my $ref = Apache::lonhelper::element->new();
     bless($ref);      bless($ref);
Line 1801  sub start_filefilter { Line 2904  sub start_filefilter {
   
 sub end_filefilter { return ''; }  sub end_filefilter { return ''; }
   
   { 
       # used to generate unique id attributes for <input> tags. 
       # internal use only.
       my $id=0;
       sub new_id { return $id++;}
   }
   
 sub render {  sub render {
     my $self = shift;      my $self = shift;
     my $result = '';      my $result = '';
Line 1813  sub render { Line 2923  sub render {
     my $subdir = &$subdirFunc();      my $subdir = &$subdirFunc();
   
     my $filterFunc = $self->{FILTER_FUNC};      my $filterFunc = $self->{FILTER_FUNC};
       if (!defined($filterFunc)) {
    $filterFunc = &not_old_version;
       }
     my $buttons = '';      my $buttons = '';
       my $type = 'radio';
       if ($self->{'multichoice'}) {
           $type = 'checkbox';
       }
   
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result = <<SCRIPT;          $result = <<SCRIPT;
 <script>  <script type="text/javascript">
     function checkall(value) {  // <!--
       function checkall(value, checkName) {
  for (i=0; i<document.forms.helpform.elements.length; i++) {   for (i=0; i<document.forms.helpform.elements.length; i++) {
             ele = document.forms.helpform.elements[i];              ele = document.forms.helpform.elements[i];
             if (ele.type == "checkbox") {              if (ele.name == checkName + '_forminput') {
                 document.forms.helpform.elements[i].checked=value;                  document.forms.helpform.elements[i].checked=value;
             }              }
         }          }
     }      }
   
       function checkallclass(value, className) {
           for (i=0; i<document.forms.helpform.elements.length; i++) {
               ele = document.forms.helpform.elements[i];
               if (ele.type == "$type" && ele.onclick) {
                   document.forms.helpform.elements[i].checked=value;
               }
           }
       }
   // -->
 </script>  </script>
 SCRIPT  SCRIPT
         $buttons = <<BUTTONS;         my %lt=&Apache::lonlocal::texthash(
    'saf'  => "Select All Files",
           'uaf'  => "Unselect All Files");
          $buttons = <<BUTTONS;
 <br /> &nbsp;  <br /> &nbsp;
 <input type="button" onclick="checkall(true)" value="Select All" />  <input type="button" onclick="checkall(true, '$var')" value="$lt{'saf'}" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="$lt{'uaf'}" />
   BUTTONS
   
          %lt=&Apache::lonlocal::texthash(
    'sap'  => "Select All Published",
           'uap'  => "Unselect All Published");
           if ($helper->{VARS}->{'construction'}) {
          $buttons .= <<BUTTONS;
   <input type="button" onclick="checkallclass(true, 'Published')" value="$lt{'sap'}" />
   <input type="button" onclick="checkallclass(false, 'Published')" value="$lt{'uap'}" />
 <br /> &nbsp;  <br /> &nbsp;
 BUTTONS  BUTTONS
          }
     }      }
   
     # Get the list of files in this directory.      # Get the list of files in this directory.
     my @fileList;      my (@fileList,$listref,$listerror);
   
     # If the subdirectory is in local CSTR space      # If the subdirectory is in local CSTR space
     if ($subdir =~ m|/home/([^/]+)/public_html|) {      my $metadir;
         my $user = $1;      my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};      if ($subdir =~ m{^(?:\Q$londocroot\E)*/priv/[^/]+/[^/]+/(.*)$}) {
         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');          my $innerpath=$1;
           unless ($subdir=~m{^\Q$londocroot\E}) {
              $subdir=$londocroot.$subdir;
           }
    my ($user,$domain)= 
       &Apache::lonnet::constructaccess($subdir);
    $metadir='/res/'.$domain.'/'.$user.'/'.$innerpath;
           ($listref,$listerror) =
               &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/');
     } else {      } else {
         # local library server resource space          # local library server resource space
         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');          ($listref,$listerror) = 
               &Apache::lonnet::dirlist($subdir,$env{'user.domain'},$env{'user.name'},undef,undef,'/');
       }
   
       # Sort the fileList into order
       if (ref($listref) eq 'ARRAY') {
           @fileList = sort {lc($a) cmp lc($b)} @{$listref};
     }      }
   
     $result .= $buttons;      $result .= $buttons;
Line 1855  BUTTONS Line 3010  BUTTONS
         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';          $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
     }      }
   
     $result .= '<table border="0" cellpadding="1" cellspacing="1">';      $result .= '<table border="0" cellpadding="2" cellspacing="0">';
   
     # Keeps track if there are no choices, prints appropriate error      # Keeps track if there are no choices, prints appropriate error
     # if there are none.       # if there are none. 
     my $choices = 0;      my $choices = 0;
     my $type = 'radio';  
     if ($self->{'multichoice'}) {  
         $type = 'checkbox';  
     }  
     # Print each legitimate file choice.      # Print each legitimate file choice.
     for my $file (@fileList) {      for my $file (@fileList) {
         $file = (split(/&/, $file))[0];          $file = (split(/&/, $file))[0];
Line 1872  BUTTONS Line 3023  BUTTONS
         }          }
         my $fileName = $subdir .'/'. $file;          my $fileName = $subdir .'/'. $file;
         if (&$filterFunc($file)) {          if (&$filterFunc($file)) {
             $result .= '<tr><td align="right">' .      my $status;
                 "<input type='$type' name='" . $var      my $color;
             . ".forminput' value='" . HTML::Entities::encode($fileName) .      if ($helper->{VARS}->{'construction'}) {
    ($status, $color) = @{fileState($subdir, $file)};
       } else {
    $status = '';
    $color = '';
       }
   
               # Get the title
               my $title = Apache::lonpubdir::getTitleString(($metadir?$metadir:$subdir) .'/'. $file);
   
               # Netscape 4 is stupid and there's nowhere to put the
               # information on the input tag that the file is Published,
               # Unpublished, etc. In *real* browsers we can just say
               # "class='Published'" and check the className attribute of
               # the input tag, but Netscape 4 is too stupid to understand
               # that attribute, and un-comprehended attributes are not
               # reflected into the object model. So instead, what I do 
               # is either have or don't have an "onclick" handler that 
               # does nothing, give Published files the onclick handler, and
               # have the checker scripts check for that. Stupid and clumsy,
               # and only gives us binary "yes/no" information (at least I
               # couldn't figure out how to reach into the event handler's
               # actual code to retreive a value), but it works well enough
               # here.
           
               my $onclick = '';
               if ($status eq 'Published' && $helper->{VARS}->{'construction'}) {
                   $onclick = 'onclick="a=1" ';
               }
               my $id = &new_id();
               $result .= '<tr><td align="right"' . " bgcolor='$color'>" .
                   "<input $onclick type='$type' name='" . $var
               . "_forminput' ".qq{id="$id"}." value='" . HTML::Entities::encode($fileName,"<>&\"'").
                 "'";                  "'";
             if (!$self->{'multichoice'} && $choices == 0) {              if (!$self->{'multichoice'} && $choices == 0) {
                 $result .= ' checked';                  $result .= ' checked="checked"';
             }              }
             $result .= "/></td><td>" . $file . "</td></tr>\n";              $result .= "/></td><td bgcolor='$color'>".
                   qq{<label for="$id">}. $file . "</label></td>" .
                   "<td bgcolor='$color'>$title</td>" .
                   "<td bgcolor='$color'>$status</td>" . "</tr>\n";
             $choices++;              $choices++;
         }          }
     }      }
Line 1887  BUTTONS Line 3073  BUTTONS
     $result .= "</table>\n";      $result .= "</table>\n";
   
     if (!$choices) {      if (!$choices) {
         $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';          $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 />';
     }      }
   
     $result .= $buttons;      $result .= $buttons;
Line 1895  BUTTONS Line 3081  BUTTONS
     return $result;      return $result;
 }  }
   
   # Determine the state of the file: Published, unpublished, modified.
   # Return the color it should be in and a label as a two-element array
   # reference.
   # Logic lifted from lonpubdir.pm, even though I don't know that it's still
   # the most right thing to do.
   
   sub fileState {
       my $constructionSpaceDir = shift;
       my $file = shift;
       
       my ($uname,$udom)=($env{'user.name'},$env{'user.domain'});
       if ($env{'request.role'}=~/^ca\./) {
    (undef,$udom,$uname)=split(/\//,$env{'request.role'});
       }
       my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
       my $subdirpart = $constructionSpaceDir;
       $subdirpart =~ s{^\Q$docroot/priv/$udom/$uname\E}{};
       my $resdir = $docroot . '/res/' . $udom . '/' . $uname .
           $subdirpart;
   
       my @constructionSpaceFileStat = stat($constructionSpaceDir . '/' . $file);
       my @resourceSpaceFileStat = stat($resdir . '/' . $file);
       if (!@resourceSpaceFileStat) {
           return ['Unpublished', '#FFCCCC'];
       }
   
       my $constructionSpaceFileModified = $constructionSpaceFileStat[9];
       my $resourceSpaceFileModified = $resourceSpaceFileStat[9];
       
       if ($constructionSpaceFileModified > $resourceSpaceFileModified) {
           return ['Modified', '#FFFFCC'];
       }
       return ['Published', '#CCFFCC'];
   }
   
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
     my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};      my $result = $env{'form.' . $self->{'variable'} . '_forminput'};
     if (!$result) {      if (!$result) {
         $self->{ERROR_MSG} = 'You must choose at least one file '.          $self->{ERROR_MSG} = 'You must choose at least one file '.
             'to continue.';              'to continue.';
         return 0;          return 0;
     }      }
   
     if ($self->{'multichoice'}) {  
         $self->process_multiple_choices($self->{'variable'}.'.forminput',  
                                         $self->{'variable'});  
     }  
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
         $helper->changeState($self->{NEXTSTATE});          $helper->changeState($self->{NEXTSTATE});
     }      }
Line 1921  package Apache::lonhelper::section; Line 3138  package Apache::lonhelper::section;
   
 =pod  =pod
   
 =head2 Element: section  =head2 Element: sectionX<section, helper element>
   
 <section> allows the user to choose one or more sections from the current  <section> allows the user to choose one or more sections from the current
 course.  course.
   
 It takes the standard attributes "variable", "multichoice", and  It takes the standard attributes "variable", "multichoice",
 "nextstate", meaning what they do for most other elements.  "allowempty" and "nextstate", meaning what they do for most other
   elements.
   
   also takes a boolean 'onlysections' whcih will restrict this to only
   have sections and not include groups
   
 =cut  =cut
   
Line 1957  sub start_section { Line 3178  sub start_section {
     $paramHash->{'variable'} = $token->[2]{'variable'};      $paramHash->{'variable'} = $token->[2]{'variable'};
     $helper->declareVar($paramHash->{'variable'});      $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};      $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
     if (defined($token->[2]{'nextstate'})) {      if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};          $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
     }      }
Line 1966  sub start_section { Line 3188  sub start_section {
   
     my $section = Apache::loncoursedata::CL_SECTION();      my $section = Apache::loncoursedata::CL_SECTION();
     my $classlist = Apache::loncoursedata::get_classlist();      my $classlist = Apache::loncoursedata::get_classlist();
     foreach (keys %$classlist) {      foreach my $user (keys(%$classlist)) {
         my $sectionName = $classlist->{$_}->[$section];          my $section_name = $classlist->{$user}[$section];
         if (!$sectionName) {          if (!$section_name) {
             $choices{"No section assigned"} = "";              $choices{"No section assigned"} = "";
         } else {          } else {
             $choices{$sectionName} = $sectionName;              $choices{$section_name} = $section_name;
         }          }
     }       } 
         
     for my $sectionName (sort(keys(%choices))) {      if (exists($choices{"No section assigned"})) {
            push(@{$paramHash->{CHOICES}}, 
         push @{$paramHash->{CHOICES}}, [$sectionName, $sectionName];       ['No section assigned','No section assigned']);
    delete($choices{"No section assigned"});
       }
       for my $section_name (sort {lc($a) cmp lc($b) } (keys(%choices))) {
    push @{$paramHash->{CHOICES}}, [$section_name, $section_name];
       }
       return if ($token->[2]{'onlysections'});
   
       # add in groups to the end of the list
       my %curr_groups = &Apache::longroup::coursegroups();
       foreach my $group_name (sort(keys(%curr_groups))) {
    push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]);
     }      }
 }      }    
   
Line 1991  sub end_section { Line 3224  sub end_section {
 }      }    
 1;  1;
   
   package Apache::lonhelper::group;
   
   =pod
    
   =head2 Element: groupX<group, helper element>
    
   <group> allows the user to choose one or more groups from the current course.
   
   It takes the standard attributes "variable", "multichoice",
    "allowempty" and "nextstate", meaning what they do for most other
    elements.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::choices");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::group',
                                    ('group'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::choices->new();
       bless($ref);
   }
    
   sub start_group {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{CHOICES} = [];
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
       if (defined($token->[2]{'nextstate'})) {
           $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
       }
   
       # Populate the CHOICES element
       my %choices;
   
       my %curr_groups = &Apache::longroup::coursegroups();
       foreach my $group_name (sort {lc($a) cmp lc($b)} (keys(%curr_groups))) {
    push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]);
       }
   }
   
   sub end_group {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::group->new();
   }
   1;
   
   package Apache::lonhelper::string;
   
   =pod
   
   =head2 Element: stringX<string, helper element>
   
   string elements provide a string entry field for the user. string elements
   take the usual 'variable' and 'nextstate' parameters. string elements
   also pass through 'maxlength' and 'size' attributes to the input tag.
   Since you could have multiple strings in a helper state, each with its own
   validator, all but the last string should have
   noproceed='1' so that _all_ validators are evaluated before the next
   state can be reached.
   
   string honors the defaultvalue tag, if given.
   
   string honors the validation function, if given.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   use Apache::lonlocal;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::string',
                                 ('string'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       $ref->{'PROCEED'} = 1;      # By default postprocess goes to next state.
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_string {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'nextstate'} = $token->[2]{'nextstate'};
       $paramHash->{'maxlength'} = $token->[2]{'maxlength'};
       $paramHash->{'size'} = $token->[2]{'size'};
       return '';
   }
   
   sub end_string {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
   
       if ($target ne 'helper') {
           return '';
       }
       my $state = Apache::lonhelper::string->new();
   
   
       if(&Apache::lonxml::get_param('noproceed', $parstack, $safeeval, undef, 1)) {
    $state->noproceed();
       }
   
      
   
       return '';
   }
   
   sub noproceed() {
       my $self = shift;
       $self->{PROCEED}  = 0;
   }
   
   sub render {
       my $self = shift;
       my $result = '';
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<p><font color="#FF0000">' . $self->{ERROR_MSG} . '</font></p>';
       }
   
       $result .= '<input type="text" name="' . $self->{'variable'} . '_forminput"';
   
       if (defined($self->{'size'})) {
           $result .= ' size="' . $self->{'size'} . '"';
       }
       if (defined($self->{'maxlength'})) {
           $result .= ' maxlength="' . $self->{'maxlength'} . '"';
       }
   
       if (defined($self->{DEFAULT_VALUE})) {
           my $valueFunc = eval($self->{DEFAULT_VALUE});
           die 'Error in default value code for variable ' . 
               $self->{'variable'} . ', Perl said: ' . $@ if $@;
           $result .= ' value="' . &$valueFunc($helper, $self) . '"';
       }
   
       $result .= ' />';
   
       return $result;
   }
   
   # If a NEXTSTATE was given, switch to it
   sub postprocess {
       my $self = shift;
   
       if (defined($self->{VALIDATOR})) {
    my $validator = eval($self->{VALIDATOR});
    die 'Died during evaluation of validator code; Perl said: ' . $@ if $@;
    my $invalid = &$validator($helper, $state, $self, $self->getValue());
    if ($invalid) {
       $self->{ERROR_MSG} = $invalid;
       return 0;
    }
       }
   
       if (defined($self->{'nextstate'}) && $self->{PROCEED}) {
           $helper->changeState($self->{'nextstate'});
       }
   
       return 1;
   }
   
   1;
   
 package Apache::lonhelper::general;  package Apache::lonhelper::general;
   
 =pod  =pod
   
 =head2 General-purpose tag: <exec>  =head2 General-purpose tag: <exec>X<exec, helper tag>
   
 The contents of the exec tag are executed as Perl code, not inside a   The contents of the exec tag are executed as Perl code, B<not> inside a 
 safe space, so the full range of $ENV and such is available. The code  safe space, so the full range of $env and such is available. The code
 will be executed as a subroutine wrapped with the following code:  will be executed as a subroutine wrapped with the following code:
   
 "sub { my $helper = shift; my $state = shift;" and  "sub { my $helper = shift; my $state = shift;" and
Line 2010  The return value is ignored. Line 3435  The return value is ignored.
 $helper is the helper object. Feel free to add methods to the helper  $helper is the helper object. Feel free to add methods to the helper
 object to support whatever manipulation you may need to do (for instance,  object to support whatever manipulation you may need to do (for instance,
 overriding the form location if the state is the final state; see   overriding the form location if the state is the final state; see 
 lonparm.helper for an example).  parameter.helper for an example).
   
 $state is the $paramHash that has currently been generated and may  $state is the $paramHash that has currently been generated and may
 be manipulated by the code in exec. Note that the $state is not yet  be manipulated by the code in exec. Note that the $state is not yet
Line 2019  be able to call methods on it. Line 3444  be able to call methods on it.
   
 =cut  =cut
   
   use Apache::lonlocal;
   use Apache::lonnet;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::general',      &Apache::lonhelper::register('Apache::lonhelper::general',
                                  'exec', 'condition', 'clause',                                   'exec', 'condition', 'clause',
Line 2077  sub start_clause { Line 3505  sub start_clause {
     die 'Error in clause of condition, Perl said: ' . $@ if $@;      die 'Error in clause of condition, Perl said: ' . $@ if $@;
     if (!&$clause($helper, $paramHash)) {      if (!&$clause($helper, $paramHash)) {
         # Discard all text until the /condition.          # Discard all text until the /condition.
         &Apache::lonxml::get_all_text('/condition', $parser);   my $end_tag = $paramHash->{SKIPTAG} || '/condition';
           &Apache::lonxml::get_all_text($end_tag, $parser);
     }      }
 }  }
   
Line 2085  sub end_clause { return ''; } Line 3514  sub end_clause { return ''; }
   
 =pod  =pod
   
 =head2 General-purpose tag: <eval>  =head2 General-purpose tag: <eval>X<eval, helper tag>
   
 The <eval> tag will be evaluated as a subroutine call passed in the  The <eval> tag will be evaluated as a subroutine call passed in the
 current helper object and state hash as described in <condition> above,  current helper object and state hash as described in <condition> above,
Line 2124  sub end_eval { Line 3553  sub end_eval {
   
 1;  1;
   
   package Apache::lonhelper::final;
   
   =pod
   
   =head2 Element: finalX<final, helper tag>
   
   <final> is a special element that works with helpers that use the <finalcode>
   tagX<finalcode, helper tag>. It goes through all the states and elements, executing the <finalcode>
   snippets and collecting the results. Finally, it takes the user out of the
   helper, going to a provided page.
   
   If the parameter "restartCourse" is true, this will override the buttons and
   will make a Save button (Finish Helper) that will re-initialize the course for them,
   which is useful for the Course Initialization helper so the users never see
   the old values taking effect.
   
   If the parameter "restartCourse" is not true a 'Finish' Button will be
   presented that takes the user back to whatever was defined as <exitpage>
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::final',
                                    ('final', 'exitpage'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_final { 
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'restartCourse'} = $token->[2]{'restartCourse'};
   
       return ''; 
   }
   
   sub end_final {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       Apache::lonhelper::final->new();
      
       return '';
   }
   
   sub start_exitpage {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{EXIT_PAGE} = &Apache::lonxml::get_all_text('/exitpage',
                                                               $parser);
   
       return '';
   }
   
   sub end_exitpage { return ''; }
   
   sub render {
       my $self = shift;
   
       my @results;
   
       # Collect all the results
       for my $stateName (keys(%{$helper->{STATES}})) {
           my $state = $helper->{STATES}->{$stateName};
           
           for my $element (@{$state->{ELEMENTS}}) {
               if (defined($element->{FINAL_CODE})) {
                   # Compile the code.
                   my $code = 'sub { my $helper = shift; my $element = shift; ' 
                       . $element->{FINAL_CODE} . '}';
                   $code = eval($code);
                   die 'Error while executing final code for element with var ' .
                       $element->{'variable'} . ', Perl said: ' . $@ if $@;
   
                   my $result = &$code($helper, $element);
                   if ($result) {
                       push @results, $result;
                   }
               }
           }
       }
   
       my $result;
   
       if (scalar(@results) != 0) {
    $result .= "<ul>\n";
    for my $re (@results) {
       $result .= '    <li>' . $re . "</li>\n";
    }
   
    if (!@results) {
       $result .= '    <li>' . 
    &mt('No changes were made to current settings.') . '</li>';
    }
   
    $result .= '</ul>';
       }
   
       my $actionURL = $self->{EXIT_PAGE};
       my $targetURL = '';
    my $finish=&mt('Save');
       if ($self->{'restartCourse'}) {
    $actionURL = '/adm/roles';
    $targetURL = '/adm/menu';
    if ($env{'course.'.$env{'request.course.id'}.'.url'}=~/^uploaded/) {
       $targetURL = '/adm/coursedocs';
    } else {
       $targetURL = '/adm/navmaps';
    }
    if ($env{'course.'.$env{'request.course.id'}.'.clonedfrom'}) {
       $targetURL = '/adm/parmset?overview=1';
    }
       }
       my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
       my $next = HTML::Entities::encode(&mt("Next"), '<>&"');
       $result .= "<p>\n" .
    "<form action='".$actionURL."' method='post' >\n" .
    "<input type='button' onclick='history.go(-1)' value='$previous' />" .
    "<input type='hidden' name='orgurl' value='$targetURL' />" .
    "<input type='hidden' name='selectrole' value='1' />\n" .
    "<input type='hidden' name='" . $env{'request.role'} . 
    "' value='1' />\n<input type='submit' value='" . $finish . "' />\n" .
    "</form></p>\n";
   
       return $result;
   }
   
   sub overrideForm {
       return 1;
   }
   
   1;
   
 package Apache::lonhelper::parmwizfinal;  package Apache::lonhelper::parmwizfinal;
   
 # This is the final state for the parmwizard. It is not generally useful,  # This is the final state for the parm helper. It is not generally useful,
 # so it is not perldoc'ed. It does its own processing.  # so it is not perldoc'ed. It does its own processing.
 # It is represented with <parmwizfinal />, and  # It is represented with <parmwizfinal />, and
 # should later be moved to lonparmset.pm .  # should later be moved to lonparmset.pm .
Line 2134  package Apache::lonhelper::parmwizfinal; Line 3715  package Apache::lonhelper::parmwizfinal;
 no strict;  no strict;
 @ISA = ('Apache::lonhelper::element');  @ISA = ('Apache::lonhelper::element');
 use strict;  use strict;
   use Apache::lonlocal;
   use Apache::lonnet;
   
 BEGIN {  BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal',      &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal',
Line 2164  sub render { Line 3747  sub render {
     my $vars = $helper->{VARS};      my $vars = $helper->{VARS};
   
     # FIXME: Unify my designators with the standard ones      # FIXME: Unify my designators with the standard ones
     my %dateTypeHash = ('open_date' => "Opening Date",      my %dateTypeHash = ('open_date' => "opening date",
                         'due_date' => "Due Date",                          'due_date' => "due date",
                         'answer_date' => "Answer Date");                          'answer_date' => "answer date",
    'tries' => 'number of tries',
    'weight' => 'problem weight'
    );
     my %parmTypeHash = ('open_date' => "0_opendate",      my %parmTypeHash = ('open_date' => "0_opendate",
                         'due_date' => "0_duedate",                          'due_date' => "0_duedate",
                         'answer_date' => "0_answerdate");                          'answer_date' => "0_answerdate",
    'tries' => '0_maxtries',
    'weight' => '0_weight' );
       my %realParmName = ('open_date' => "opendate",
                           'due_date' => "duedate",
                           'answer_date' => "answerdate",
    'tries' => 'maxtries',
    'weight' => 'weight' );
           
     my $result = "<form name='helpform' method='get' action='/adm/parmset'>\n";  
     $result .= '<p>Confirm that this information is correct, then click &quot;Finish Wizard&quot; to complete setting the parameter.<ul>';  
     my $affectedResourceId = "";      my $affectedResourceId = "";
     my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};      my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};
     my $level = "";      my $level = "";
       my $resourceString;
       my $symb;
       my $paramlevel;
       
       # Print the granularity, depending on the action
       if ($vars->{GRANULARITY} eq 'whole_course') {
           $resourceString .= '<li>'.&mt('for [_1]all resources in the course[_2]','<b>','</b>').'</li>';
    if ($vars->{TARGETS} eq 'course') {
       $level = 14; # general course, see lonparmset.pm perldoc
    } elsif ($vars->{TARGETS} eq 'section') {
       $level = 9;
    } elsif ($vars->{TARGETS} eq 'group') {
       $level = 6;
    } else {
       $level = 3;
    }
           $affectedResourceId = "0.0";
           $symb = 'a';
           $paramlevel = 'general';
       } elsif ($vars->{GRANULARITY} eq 'map') {
           my $navmap = Apache::lonnavmaps::navmap->new();
           if (defined($navmap)) {
                my $res = $navmap->getByMapPc($vars->{RESOURCE_ID});
                my $title = $res->compTitle();
                $symb = $res->symb();
                $resourceString .= '<li>'.&mt('for the map named [_1]',"<b>$title</b>").'</li>';
           } else {
               $resourceString .= '<li>'.&mt('for the map ID [_1] (name unavailable)','<b>'.$vars->{RESOURCE_ID}.'</b>').'</li>';
               &Apache::lonnet::logthis('Retrieval of map title failed in lonhelper.pm - could not create navmap object for course.');
   
           }
    if ($vars->{TARGETS} eq 'course') {
       $level = 13; # general course, see lonparmset.pm perldoc
    } elsif ($vars->{TARGETS} eq 'section') {
       $level = 8;
    } elsif ($vars->{TARGETS} eq 'group') {
       $level = 5;
    } else {
       $level = 2;
    }
           $affectedResourceId = $vars->{RESOURCE_ID};
           $paramlevel = 'map';
       } else {
           my $part = $vars->{RESOURCE_ID_part};
    if ($part ne 'All Parts' && $part) { $parm_name=~s/^0/$part/; } else { $part=&mt('All Parts'); }
           my $navmap = Apache::lonnavmaps::navmap->new();
           if (defined($navmap)) {
               my $res = $navmap->getById($vars->{RESOURCE_ID});
               $symb = $res->symb();
               my $title = $res->compTitle();
               $resourceString .= '<li>'.&mt('for the resource named [_1], part [_2]',"<b>$title</b>","<b>$part</b>").'</li>';
           } else {
               $resourceString .= '<li>'.&mt('for the resource ID [_1] (name unavailable), part [_2]','<b>'.$vars->{RESOURCE_ID}.'</b>',"<b>$part</b>").'</li>';
               &Apache::lonnet::logthis('Retrieval of resource title failed in lonhelper.pm - could not create navmap object for course.');
           }
    if ($vars->{TARGETS} eq 'course') {
       $level = 10; # general course, see lonparmset.pm perldoc
    } elsif ($vars->{TARGETS} eq 'section') {
       $level = 7;
    } elsif ($vars->{TARGETS} eq 'group') {
       $level = 4;
    } else {
       $level = 1;
    }
           $affectedResourceId = $vars->{RESOURCE_ID};
           $paramlevel = 'full';
       }
   
       my $result = "<form name='helpform' method='post' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
       $result .= "<input type='hidden' name='action' value='settable' />\n";
       $result .= "<input type='hidden' name='dis' value='helper' />\n";
       $result .= "<input type='hidden' name='pscat' value='".
    $realParmName{$vars->{ACTION_TYPE}}."' />\n";
       if ($vars->{GRANULARITY} eq 'resource') {
    $result .= "<input type='hidden' name='symb' value='".
       HTML::Entities::encode($symb,"'<>&\"") . "' />\n";
       } elsif ($vars->{GRANULARITY} eq 'map') {
    $result .= "<input type='hidden' name='pschp' value='".
       $affectedResourceId."' />\n";
       }
       my $part = $vars->{RESOURCE_ID_part};
       if ($part eq 'All Parts' || !$part) { $part=0; }
       $result .= "<input type='hidden' name='psprt' value='".
    HTML::Entities::encode($part,"'<>&\"") . "' />\n";
   
       $result .= '<p class="LC_info">'
                 .&mt('Confirm that this information is correct, then click &quot;Save&quot; to complete setting the parameter.')
                 .'</p>'
                 .'<ul>';
           
     # Print the type of manipulation:      # Print the type of manipulation:
     $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}      my $extra;
                . "</b></li>\n";      if ($vars->{ACTION_TYPE} eq 'tries') {
    $extra =  $vars->{TRIES};
       }
       if ($vars->{ACTION_TYPE} eq 'weight') {
    $extra =  $vars->{WEIGHT};
       }
       $result .= "<li>";
       my $what = &mt($dateTypeHash{$vars->{ACTION_TYPE}});
       if ($extra) {
    $result .= &mt('Setting the [_1] to [_2]',"<b>$what</b>",$extra);
       } else {
    $result .= &mt('Setting the [_1]',"<b>$what</b>");
       }
       $result .= "</li>\n";
     if ($vars->{ACTION_TYPE} eq 'due_date' ||       if ($vars->{ACTION_TYPE} eq 'due_date' || 
         $vars->{ACTION_TYPE} eq 'answer_date') {          $vars->{ACTION_TYPE} eq 'answer_date') {
         # for due dates, we default to "date end" type entries          # for due dates, we default to "date end" type entries
Line 2196  sub render { Line 3889  sub render {
             "value='" . $vars->{PARM_DATE} . "' />\n";              "value='" . $vars->{PARM_DATE} . "' />\n";
         $result .= "<input type='hidden' name='pres_type' " .          $result .= "<input type='hidden' name='pres_type' " .
             "value='date_start' />\n";              "value='date_start' />\n";
     }       } elsif ($vars->{ACTION_TYPE} eq 'tries') {
        $result .= "<input type='hidden' name='pres_value' " .
     # Print the granularity, depending on the action      "value='" . $vars->{TRIES} . "' />\n";
     if ($vars->{GRANULARITY} eq 'whole_course') {          $result .= "<input type='hidden' name='pres_type' " .
         $result .= '<li>for <b>all resources in the course</b></li>';              "value='int_pos' />\n";
         $level = 9; # general course, see lonparmset.pm perldoc      } elsif ($vars->{ACTION_TYPE} eq 'weight') {
         $affectedResourceId = "0.0";   $result .= "<input type='hidden' name='pres_value' " .
     } elsif ($vars->{GRANULARITY} eq 'map') {      "value='" . $vars->{WEIGHT} . "' />\n";
         my $navmap = Apache::lonnavmaps::navmap->new(  
                            $ENV{"request.course.fn"}.".db",  
                            $ENV{"request.course.fn"}."_parms.db", 0, 0);  
         my $res = $navmap->getById($vars->{RESOURCE_ID});  
         my $title = $res->compTitle();  
         $navmap->untieHashes();  
         $result .= "<li>for the map named <b>$title</b></li>";  
         $level = 8;  
         $affectedResourceId = $vars->{RESOURCE_ID};  
     } else {  
         my $navmap = Apache::lonnavmaps::navmap->new(  
                            $ENV{"request.course.fn"}.".db",  
                            $ENV{"request.course.fn"}."_parms.db", 0, 0);  
         my $res = $navmap->getById($vars->{RESOURCE_ID});  
         my $title = $res->compTitle();  
         $navmap->untieHashes();  
         $result .= "<li>for the resource named <b>$title</b></li>";  
         $level = 7;  
         $affectedResourceId = $vars->{RESOURCE_ID};  
     }      }
   
       $result .= $resourceString;
       
     # Print targets      # Print targets
     if ($vars->{TARGETS} eq 'course') {      if ($vars->{TARGETS} eq 'course') {
         $result .= '<li>for <b>all students in course</b></li>';          $result .= '<li>'.&mt('for [_1]all students in course[_2]','<b>','</b>').'</li>';
     } elsif ($vars->{TARGETS} eq 'section') {      } elsif ($vars->{TARGETS} eq 'section') {
         my $section = $vars->{SECTION_NAME};          my $section = $vars->{SECTION_NAME};
         $result .= "<li>for section <b>$section</b></li>";          $result .= '<li>'.&mt('for section [_1]',"<b>$section</b>").'</li>';
         $level -= 3;   $result .= "<input type='hidden' name='csec' value='" .
         $result .= "<input type='hidden' name='csec' value='" .              HTML::Entities::encode($section,"'<>&\"") . "' />\n";
             HTML::Entities::encode($section) . "' />\n";      } elsif ($vars->{TARGETS} eq 'group') {
           my $group = $vars->{GROUP_NAME};
           $result .= '<li>'.&mt('for group [_1]',"<b>$group</b>").'</li>';
           $result .= "<input type='hidden' name='cgroup' value='" .
               HTML::Entities::encode($group,"'<>&\"") . "' />\n";
     } else {      } else {
         # FIXME: This is probably wasteful! Store the name!          # FIXME: This is probably wasteful! Store the name!
         my $classlist = Apache::loncoursedata::get_classlist();          my $classlist = Apache::loncoursedata::get_classlist();
         my $name = $classlist->{$vars->{USER_NAME}}->[6];   my ($uname,$udom)=split(':',$vars->{USER_NAME});
         $result .= "<li>for <b>$name</b></li>";          my $name = $classlist->{$uname.':'.$udom}->[6];
         $level -= 6;          $result .= '<li>'.&mt('for [_1]',"<b>$name</b>").'</li>';
         my ($uname, $udom) = split /:/, $vars->{USER_NAME};  
         $result .= "<input type='hidden' name='uname' value='".          $result .= "<input type='hidden' name='uname' value='".
             HTML::Entities::encode($uname) . "' />\n";              HTML::Entities::encode($uname,"'<>&\"") . "' />\n";
         $result .= "<input type='hidden' name='udom' value='".          $result .= "<input type='hidden' name='udom' value='".
             HTML::Entities::encode($udom) . "' />\n";              HTML::Entities::encode($udom,"'<>&\"") . "' />\n";
     }      }
   
     # Print value      # Print value
     $result .= "<li>to <b>" . ctime($vars->{PARM_DATE}) . "</b> (" .      if ($vars->{ACTION_TYPE} ne 'tries' && $vars->{ACTION_TYPE} ne 'weight') {
         Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE})           my $showdate = &Apache::lonlocal::locallocaltime($vars->{PARM_DATE});
         . ")</li>\n";   $result .= '<li>'.&mt('to [_1] ([_2])',"<b>".$showdate."</b>",Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}))."</li>\n";
       }
   
    $result .= '</ul>';
    
   # FIXME: Make previous button working
   #        Found to be dysfunctional when used to change the selected student
   #   my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
       my $buttons .= '<p><span class="LC_nobreak">'
   #                 .'<input name="back" type="button"'
   #                 .' value="'.$previous.'" onclick="history.go(-1)" />'
                     .' <input type="submit" value="'.&mt('Save').'" />' # Finish Helper
                     .'</span></p>'."\n";
   
     # print pres_marker      # print pres_marker
     $result .= "\n<input type='hidden' name='pres_marker'" .      $result .= "\n<input type='hidden' name='pres_marker'" .
         " value='$affectedResourceId&$parm_name&$level' />\n";          " value='$affectedResourceId&$parm_name&$level' />\n";
       
       # Make the table appear
       $result .= "\n<input type='hidden' value='true' name='prevvisit' />";
       $result .= "\n<input type='hidden' value='$symb' name='pssymb' />";
       $result .= "\n<input type='hidden' value='$paramlevel' name='parmlev' />";
   
     $result .= "<br /><br /><center><input type='submit' value='Finish Helper' /></center></form>\n";      $result .= $buttons;
   
     return $result;      return $result;
 }  }

Removed from v.1.16  
changed lines
  Added in v.1.194


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