Diff for /loncom/interface/lonhelper.pm between versions 1.30 and 1.55

version 1.30, 2003/05/15 16:14:52 version 1.55, 2003/10/09 15:20:15
Line 32 Line 32
   
 =pod  =pod
   
 =head1 lonhelper - HTML Helper framework for LON-CAPA  =head1 NAME
   
 Helpers, often known as "wizards", are well-established UI widgets that users  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.
   
   =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 60  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 84  Of course this does nothing. In order fo Line 101  Of course this does nothing. In order fo
 necessary to put actual elements into the wizard. Documentation for each  necessary to put actual elements into the wizard. 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 wizard (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 
Line 143  Normally the machinery in the XML format Line 160  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. In that case it is   lonxml; again, see lonprintout.pm for an example. In that case it is 
Line 155  before parsing XML fragments and B<Apach Line 172  before parsing XML fragments and B<Apach
 when you are done. See lonprintout.pm for examples of this usage in the  when you are done. See lonprintout.pm for examples of this usage in the
 printHelper subroutine.  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
   
 package Apache::lonhelper;  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;
   
 # 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 207  my $paramHash; Line 232  my $paramHash;
 # mod_perl connection. In this code, it was manifesting itself in the existence  # mod_perl connection. In this code, it was manifesting itself in the existence
 # of two seperate file-scoped $helper variables, one set to the value of the  # of two seperate 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 in the helper constructor, and one referenced by the handler on the
 # "$helper->process()" line. The second was therefore never set, and was still  # "$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.  # undefined when I tried to call process on it.
 # By pushing the "real handler" down into the "real scope", everybody except the   # 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  # actual handler function directly below this comment gets the same $helper and
Line 238  sub real_handler { Line 265  sub real_handler {
     # Send header, don't cache this page      # Send header, don't cache this page
     if ($r->header_only) {      if ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($ENV{'browser.mathml'}) {
             $r->content_type('text/xml');              $r->content_type('text/xml; charset=UTF-8');
         } else {          } else {
             $r->content_type('text/html');              $r->content_type('text/html; charset=UTF-8');
         }          }
         $r->send_http_header;          $r->send_http_header;
         return OK;          return OK;
Line 257  sub real_handler { Line 284  sub real_handler {
     # 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 284  sub start_helper { Line 318  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 333  sub end_state { Line 367  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;
   
 sub new {  sub new {
     my $proto = shift;      my $proto = shift;
Line 343  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.
Line 467  sub declareVar { Line 503  sub declareVar {
     }      }
 }  }
   
   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 486  sub process { Line 532  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 537  sub display { Line 583  sub display {
     }      }
   
     # Phase 4: Display.      # Phase 4: Display.
     my $stateTitle = $state->title();      my $stateTitle = &mt($state->title());
     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');      my $helperTitle = &mt($self->{TITLE});
       my $bodytag = &Apache::loncommon::bodytag($helperTitle,'','');
       my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
       my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
       # FIXME: This should be parameterized, not concatenated - Jeremy
       my $loncapaHelper = &mt("LON-CAPA Helper:");
   
     $result .= <<HEADER;      $result .= <<HEADER;
 <html>  <html>
     <head>      <head>
         <title>LON-CAPA Helper: $self->{TITLE}</title>          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
           <title>$loncapaHelper: $helperTitle</title>
     </head>      </head>
     $bodytag      $bodytag
 HEADER  HEADER
     if (!$state->overrideForm()) { $result.="<form name='helpform' method='POST'>"; }      if (!$state->overrideForm()) { $result.="<form name='helpform' method='POST'>"; }
     $result .= <<HEADER;      $result .= <<HEADER;
         <table border="0"><tr><td>          <table border="0" width='100%'><tr><td>
         <h2><i>$stateTitle</i></h2>          <h2><i>$stateTitle</i></h2>
 HEADER  HEADER
   
     $result .= "<table><tr><td rowspan='2' valign='top'>";      $result .= "<table cellpadding='10' width='100%'><tr><td rowspan='2' valign='top'>";
   
     if (!$state->overrideForm()) {      if (!$state->overrideForm()) {
         $result .= $self->_saveVars();          $result .= $self->_saveVars();
     }      }
     $result .= $state->render();      $result .= $state->render();
   
     $result .= "</td><td valign='top'>";      $result .= "</td><td valign='top' align='right'>";
   
     # Warning: Copy and pasted from below, because it's too much trouble to       # Warning: Copy and pasted from below, because it's too much trouble to 
     # turn this into a subroutine      # turn this into a subroutine
     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;';
         }          }
         if ($self->{DONE}) {          if ($self->{DONE}) {
             my $returnPage = $self->{RETURN_PAGE};              my $returnPage = $self->{RETURN_PAGE};
             $result .= "<a href=\"$returnPage\">End Helper</a>";              $result .= "<a href=\"$returnPage\">" . &mt("End Helper") . "</a>";
         }          }
         else {          else {
             $result .= '<nobr><input name="back" type="button" ';              $result .= '<nobr><input name="back" type="button" ';
             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';              $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" /></nobr>&nbsp;';              $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></nobr>';
         }          }
         $result .= "</center>\n";  
     }      }
   
     $result .= "</td></tr><tr><td valign='bottom'>";      $result .= "</td></tr><tr><td valign='bottom' align='right'>";
   
     # Warning: Copy and pasted from above, because it's too much trouble to       # Warning: Copy and pasted from above, because it's too much trouble to 
     # turn this into a subroutine      # turn this into a subroutine
     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;';
         }          }
         if ($self->{DONE}) {          if ($self->{DONE}) {
             my $returnPage = $self->{RETURN_PAGE};              my $returnPage = $self->{RETURN_PAGE};
             $result .= "<a href=\"$returnPage\">End Helper</a>";              $result .= "<a href=\"$returnPage\">" . &mt('End Helper') . "</a>";
         }          }
         else {          else {
             $result .= '<nobr><input name="back" type="button" ';              $result .= '<nobr><input name="back" type="button" ';
             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';              $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" /></nobr>';              $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></nobr>';
         }          }
         $result .= "</center>\n";  
     }      }
   
     #foreach my $key (keys %{$self->{VARS}}) {      #foreach my $key (keys %{$self->{VARS}}) {
Line 731  package Apache::lonhelper::element; Line 779  package Apache::lonhelper::element;
   
 =pod  =pod
   
 =head2 Element Base Class  =head1 Element Base Class
   
 The Apache::lonhelper::element base class provides support for elements  The Apache::lonhelper::element base class provides support for elements
 and defines some generally useful tags for use in elements.  and defines some generally useful tags for use in elements.
   
 B<finalcode tag>  =head2 finalcode tagX<finalcode>
   
 Each element can contain a "finalcode" tag that, when the special FINAL  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;"  helper state is used, will be executed, surrounded by "sub { my $helper = shift;"
Line 745  may be an empty string. See course initi Line 793  may be an empty string. See course initi
 generally intended for helpers like the course initialization helper, which consist  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.  of several panels, each of which is performing some sort of bite-sized functionality.
   
 B<defaultvalue tag>  =head2 defaultvalue tagX<defaultvalue>
   
 Each element that accepts user input can contain a "defaultvalue" tag that,  Each element that accepts user input can contain a "defaultvalue" tag that,
 when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",  when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
Line 762  some setting accidentally. Line 810  some setting accidentally.
   
 Again, see the course initialization helper for examples.  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', 'finalcode',                                   ('nextstate', 'finalcode',
                                   'defaultvalue'));                                    '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 832  sub start_defaultvalue { Line 898  sub start_defaultvalue {
   
 sub end_defaultvalue { 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 848  sub overrideForm { Line 931  sub overrideForm {
     return 0;      return 0;
 }  }
   
   sub getValue {
       my $self = shift;
       return $helper->{VARS}->{$self->{'variable'}};
   }
   
 1;  1;
   
 package Apache::lonhelper::message;  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 879  within each other.) Line 968  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 925  sub end_message { Line 1020  sub end_message {
 sub render {  sub render {
     my $self = shift;      my $self = shift;
   
     return $self->{MESSAGE_TEXT};      return &mtn($self->{MESSAGE_TEXT});
 }  }
 # If a NEXTSTATE was given, switch to it  # If a NEXTSTATE was given, switch to it
 sub postprocess {  sub postprocess {
Line 942  package Apache::lonhelper::choices; Line 1037  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 960  value, will allow the user to select mul Line 1055  value, will allow the user to select mul
 value, will allow the user to select none of the choices without raising  value, will allow the user to select none of the choices without raising
 an error message.  an error message.
   
 B<SUB-TAGS>  =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 977  B<SUB-TAGS> Line 1072  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.
   
 =back  =back
   
Line 1009  You can mix and match methods of creatin Line 1105  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.)
   
 B<defaultvalue support>  =head3 defaultvalue support
   
 Choices supports default values both in multichoice and single choice mode.  Choices supports default values both in multichoice and single choice mode.
 In single choice mode, have the defaultvalue tag's function return the   In single choice mode, have the defaultvalue tag's function return the 
Line 1026  tag is stored in the {VARS} hash. Line 1122  tag is stored in the {VARS} hash.
 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::choices',      &Apache::lonhelper::register('Apache::lonhelper::choices',
Line 1086  sub end_choice { Line 1183  sub end_choice {
 }  }
   
 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 1132  BUTTONS Line 1228  BUTTONS
     if (defined($self->{DEFAULT_VALUE})) {      if (defined($self->{DEFAULT_VALUE})) {
         $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});          $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
         die 'Error in default value code for variable ' .           die 'Error in default value code for variable ' . 
             {'variable'} . ', Perl said:' . $@ if $@;              $self->{'variable'} . ', Perl said: ' . $@ if $@;
     } else {      } else {
         $checkedChoicesFunc = sub { return ''; };          $checkedChoicesFunc = sub { return ''; };
     }      }
Line 1180  BUTTONS Line 1276  BUTTONS
             $choiceLabel = eval($choiceLabel);              $choiceLabel = eval($choiceLabel);
             $choiceLabel = &$choiceLabel($helper, $self);              $choiceLabel = &$choiceLabel($helper, $self);
         }          }
         $result .= "/></td><td> " . $choiceLabel . "</td></tr>\n";          $result .= "/></td><td> " . &mtn($choiceLabel) . "</td></tr>\n";
     }      }
     $result .= "</table>\n\n\n";      $result .= "</table>\n\n\n";
     $result .= $buttons;      $result .= $buttons;
Line 1219  sub postprocess { Line 1315  sub postprocess {
 }  }
 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;
   
   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";
           }
           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) . "\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})) {
           $helper->changeState($self->{NEXTSTATE});
       }
       
       foreach my $choice (@{$self->{CHOICES}}) {
           if ($choice->[1] eq $chosenValue) {
               if (defined($choice->[2])) {
                   $helper->changeState($choice->[2]);
               }
           }
       }
       return 1;
   }
   1;
   
 package Apache::lonhelper::date;  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 1254  Example: Line 1500  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 Time::localtime;
   
Line 1318  sub render { Line 1565  sub render {
         } 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";
   
Line 1405  sub postprocess { Line 1652  sub postprocess {
         $hour = $ENV{'form.' . $var . 'hour'};          $hour = $ENV{'form.' . $var . 'hour'};
     }      }
   
     my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);      my $chosenDate;
       eval {$chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);};
       my $error = $@;
   
     # Check to make sure that the date was not automatically co-erced into a       # 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      # 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      # This happens for "Feb. 31", for instance, which is coerced to March 2 or
     # 3, depending on if it's a leapyear      # 3, depending on if it's a leap year
     my $checkDate = localtime($chosenDate);      my $checkDate = localtime($chosenDate);
   
     if ($checkDate->mon != $month || $checkDate->mday != $day ||      if ($error || $checkDate->mon != $month || $checkDate->mday != $day ||
         $checkDate->year + 1900 != $year) {          $checkDate->year + 1900 != $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[$month] . " $day, $year as a "          $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
             . "date because it doesn't exist. Please enter a valid date.";              . "date because it doesn't exist. Please enter a valid date.";
   
         return 0;          return 0;
     }      }
   
Line 1433  package Apache::lonhelper::resource; Line 1689  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 1443  selections across folder openings and cl Line 1699  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. The   which controls whether the user can select more then one resource. The 
 "toponly" attribute controls whether the resource display shows just the  "toponly" attribute controls whether the resource display shows just the
 resources in that sequence, or recurses into all sub-sequences, defaulting  resources in that sequence, or recurses into all sub-sequences, defaulting
 to false. The "suppressEmptySequences" attribute reflects the   to false. The "suppressEmptySequences" attribute reflects the 
 suppressEmptySequences argument to the render routine, which will cause  suppressEmptySequences argument to the render routine, which will cause
 folders that have all of their contained resources filtered out to also  folders that have all of their contained resources filtered out to also
 be filtered out.  be filtered out. The 'addstatus' attribute, if true, will add the icon
   and long status display columns to the display.
   
 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 1464  B<SUB-TAGS> Line 1721  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.
   
 =back  =back
   
Line 1513  sub start_resource { Line 1773  sub start_resource {
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};      $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
     $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'};      $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'};
     $paramHash->{'toponly'} = $token->[2]{'toponly'};      $paramHash->{'toponly'} = $token->[2]{'toponly'};
       $paramHash->{'addstatus'} = $token->[2]{'addstatus'};
     return '';      return '';
 }  }
   
Line 1589  sub start_mapurl { Line 1850  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;
 }  }
   
Line 1644  BUTTONS Line 1906  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'};
   
       # 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};
       }
   
     # 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
     #  - Jeremy (Pythonista) ;-)      #  - Jeremy (Pythonista) ;-)
Line 1666  BUTTONS Line 1937  BUTTONS
                 $col .= "checked ";                  $col .= "checked ";
                 $checked = 1;                  $checked = 1;
             }              }
       if ($multichoice) { # all resources start checked; see bug 1174
    $col .= "checked ";
    $checked = 1;
       }
             $col .= "value='" .               $col .= "value='" . 
                 HTML::Entities::encode(&$valueFunc($resource))                   HTML::Entities::encode(&$valueFunc($resource)) 
                 . "' /></td>";                  . "' /></td>";
Line 1674  BUTTONS Line 1949  BUTTONS
     };      };
   
     $ENV{'form.condition'} = !$self->{'toponly'};      $ENV{'form.condition'} = !$self->{'toponly'};
       my $cols = [$renderColFunc, 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,
Line 1710  package Apache::lonhelper::student; Line 1989  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.
   
   =back
   
 =cut  =cut
   
Line 1747  sub start_student { Line 2047  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->{'sctiveonly'} = $token->[2]{'activeonly'};
     if (defined($token->[2]{'nextstate'})) {      if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};          $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
     }      }
Line 1779  sub render { Line 2081  sub render {
             }              }
         }          }
     }      }
       function checksec() {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
       comp = document.forms.helpform.elements.chksec.value;
               if (document.forms.helpform.elements[i].value.indexOf(':'+comp+':') != -1) {
                   document.forms.helpform.elements[i].checked=true;
               }
           }
       }
       function checkactive() {
    for (i=0; i<document.forms.helpform.elements.length; i++) {
               if (document.forms.helpform.elements[i].value.indexOf(':Active') != -1) {
                   document.forms.helpform.elements[i].checked=true;
               }
           }
       }
 </script>  </script>
 SCRIPT  SCRIPT
         $buttons = <<BUTTONS;          $buttons = <<BUTTONS;
 <br />  <br />
   <input type="button" onclick="checkactive()" value="Select Only Current Students" />
 <input type="button" onclick="checkall(true, '$var')" value="Select All Students" />  <input type="button" onclick="checkall(true, '$var')" value="Select All Students" />
 <input type="button" onclick="checkall(false, '$var')" value="Unselect All Students" />  <input type="button" onclick="checkall(false, '$var')" value="Unselect All Students" />
   <input type="button" onclick="checksec()" value="Check for Section/Group">
   <input type="text" size="5" name="chksec">&nbsp;
 <br />  <br />
 BUTTONS  BUTTONS
     }      }
Line 1793  BUTTONS Line 2113  BUTTONS
         $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 $choices = [];
     my $choices = &Apache::loncoursedata::get_classlist();  
     my @keys = keys %{$choices};      # Load up the non-students, if necessary
       if ($self->{'coursepersonnel'}) {
    my %coursepersonnel = Apache::lonnet::get_course_adv_roles();
    for (sort keys %coursepersonnel) {
       for my $role (split /,/, $coursepersonnel{$_}) {
    # extract the names so we can sort them
    my @people;
   
    for (split /,/, $role) {
       push @people, [split /:/, $role];
    }
   
    @people = sort { $a->[0] cmp $b->[0] } @people;
   
    for my $person (@people) {
       push @$choices, [join(':', @$person), $person->[0], '', $_];
    }
       }
    }
       }
   
     # Constants      # Constants
     my $section = Apache::loncoursedata::CL_SECTION();      my $section = Apache::loncoursedata::CL_SECTION();
     my $fullname = Apache::loncoursedata::CL_FULLNAME();      my $fullname = Apache::loncoursedata::CL_FULLNAME();
       my $status = Apache::loncoursedata::CL_STATUS();
   
       # Load up the students
       my $classlist = &Apache::loncoursedata::get_classlist();
       my @keys = keys %{$classlist};
     # Sort by: Section, name      # Sort by: Section, name
     @keys = sort {      @keys = sort {
         if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {          if ($classlist->{$a}->[$section] ne $classlist->{$b}->[$section]) {
             return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];              return $classlist->{$a}->[$section] cmp $classlist->{$b}->[$section];
         }          }
         return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];          return $classlist->{$a}->[$fullname] cmp $classlist->{$b}->[$fullname];
     } @keys;      } @keys;
   
       # username, fullname, section, type
       for (@keys) {
    # Filter out inactive students if we've set "activeonly"
    if (!$self->{'activeonly'} || $classlist->{$_}->[$status] eq
       'Active') {
       push @$choices, [$_, $classlist->{$_}->[$fullname], 
        $classlist->{$_}->[$section],
        $classlist->{$_}->[$status], 'Student'];
    }
       }
   
       my $name = $self->{'coursepersonnel'} ? 'Name' : 'Student Name';
     my $type = 'radio';      my $type = 'radio';
     if ($self->{'multichoice'}) { $type = 'checkbox'; }      if ($self->{'multichoice'}) { $type = 'checkbox'; }
     $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";      $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
     $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".      $result .= "<tr><td></td><td align='center'><b>$name</b></td>".
         "<td align='center'><b>Section</b></td></tr>";          "<td align='center'><b>Section</b></td>" . 
    "<td align='center'><b>Status</b></td>" . 
    "<td align='center'><b>Role</b></td>" .
    "<td align='center'><b>Username:Domain</b></td></tr>";
   
     my $checked = 0;      my $checked = 0;
     foreach (@keys) {      for my $choice (@$choices) {
         $result .= "<tr><td><input type='$type' name='" .          $result .= "<tr><td><input type='$type' name='" .
             $self->{'variable'} . '.forminput' . "'";              $self->{'variable'} . '.forminput' . "'";
                           
Line 1825  BUTTONS Line 2183  BUTTONS
             $checked = 1;              $checked = 1;
         }          }
         $result .=          $result .=
             " value='" . HTML::Entities::encode($_ . ':' . $choices->{$_}->[$section])              " value='" . HTML::Entities::encode($choice->[0] . ':' . $choice->[2] . ':' . $choice->[1] . ':' . $choice->[3])
             . "' /></td><td>"              . "' /></td><td>"
             . HTML::Entities::encode($choices->{$_}->[$fullname])              . HTML::Entities::encode($choice->[1])
             . "</td><td align='center'>"               . "</td><td align='center'>" 
             . HTML::Entities::encode($choices->{$_}->[$section])              . HTML::Entities::encode($choice->[2])
             . "</td></tr>\n";              . "</td>\n<td>" 
       . HTML::Entities::encode($choice->[3])
               . "</td>\n<td>" 
       . HTML::Entities::encode($choice->[4])
               . "</td>\n<td>" 
       . HTML::Entities::encode($choice->[0])
       . "</td></tr>\n";
     }      }
   
     $result .= "</table>\n\n";      $result .= "</table>\n\n";
Line 1862  package Apache::lonhelper::files; Line 2226  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 1878  are put. It accepts the attribute "multi Line 2242  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
   
Line 1894  no strict; Line 2276  no strict;
 @ISA = ("Apache::lonhelper::element");  @ISA = ("Apache::lonhelper::element");
 use strict;  use strict;
   
   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 1966  sub render { Line 2362  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';      my $type = 'radio';
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
Line 2013  BUTTONS Line 2412  BUTTONS
     my @fileList;      my @fileList;
   
     # If the subdirectory is in local CSTR space      # If the subdirectory is in local CSTR space
     if ($subdir =~ m|/home/([^/]+)/public_html|) {      my $metadir;
       if ($subdir =~ m|/home/([^/]+)/public_html/(.*)|) {
         my $user = $1;          my $user = $1;
         my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};          my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
    $metadir='/res/'.$domain.'/'.$user.'/'.$2;
           @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
       } elsif ($subdir =~ m|^~([^/]+)/(.*)$|) {
    $subdir='/home/'.$1.'/public_html/'.$2;
           my $user = $1;
           my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
    $metadir='/res/'.$domain.'/'.$user.'/'.$2;
         @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');          @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
     } else {      } else {
         # local library server resource space          # local library server resource space
         @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');          @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
     }      }
   
       # Sort the fileList into order
       @fileList = sort @fileList;
   
     $result .= $buttons;      $result .= $buttons;
   
     if (defined $self->{ERROR_MSG}) {      if (defined $self->{ERROR_MSG}) {
Line 2050  BUTTONS Line 2460  BUTTONS
  $color = '';   $color = '';
     }      }
   
               # Get the title
               my $title = Apache::lonpubdir::getTitleString(($metadir?$metadir:$subdir) .'/'. $file);
   
             # Netscape 4 is stupid and there's nowhere to put the              # Netscape 4 is stupid and there's nowhere to put the
             # information on the input tag that the file is Published,              # information on the input tag that the file is Published,
             # Unpublished, etc. In *real* browsers we can just say              # Unpublished, etc. In *real* browsers we can just say
Line 2076  BUTTONS Line 2489  BUTTONS
             if (!$self->{'multichoice'} && $choices == 0) {              if (!$self->{'multichoice'} && $choices == 0) {
                 $result .= ' checked';                  $result .= ' checked';
             }              }
             $result .= "/></td><td bgcolor='$color'>" . $file .              $result .= "/></td><td bgcolor='$color'>" . $file . "</td>" .
                  "</td><td bgcolor='$color'>$status</td></tr>\n";                  "<td bgcolor='$color'>$title</td>" .
                   "<td bgcolor='$color'>$status</td>" . "</tr>\n";
             $choices++;              $choices++;
         }          }
     }      }
Line 2085  BUTTONS Line 2499  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 2146  package Apache::lonhelper::section; Line 2560  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.
Line 2216  sub end_section { Line 2630  sub end_section {
 }      }    
 1;  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.
   
   string honors the defaultvalue tag, if given.
   
   string honors the validation function, if given.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::string',
                                 ('string'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       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 '';
       }
       Apache::lonhelper::string->new();
       return '';
   }
   
   sub render {
       my $self = shift;
       my $result = '';
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       $result .= '<input type="string" 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 evaulation code; Perl said: ' . $@ if $@;
    my $invalid = &$validator($helper, $state, $self, $self->getValue());
    if ($invalid) {
       $self->{ERROR_MSG} = $invalid;
       return 0;
    }
       }
   
       if (defined($self->{'nextstate'})) {
           $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:
   
Line 2235  The return value is ignored. Line 2758  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 2310  sub end_clause { return ''; } Line 2833  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 2353  package Apache::lonhelper::final; Line 2876  package Apache::lonhelper::final;
   
 =pod  =pod
   
 =head2 Element: final  =head2 Element: finalX<final, helper tag>
   
 <final> is a special element that works with helpers that use the <finalcode>  <final> is a special element that works with helpers that use the <finalcode>
 tag. It goes through all the states and elements, executing 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  snippets and collecting the results. Finally, it takes the user out of the
 helper, going to a provided page.  helper, going to a provided page.
   
   If the parameter "restartCourse" is true, this will override the buttons and
   will make a "Finish Helper" button 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.
   
 =cut  =cut
   
 no strict;  no strict;
Line 2376  sub new { Line 2904  sub new {
     bless($ref);      bless($ref);
 }  }
   
 sub start_final { return ''; }  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 {  sub end_final {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;      my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
Line 2417  sub render { Line 2955  sub render {
         for my $element (@{$state->{ELEMENTS}}) {          for my $element (@{$state->{ELEMENTS}}) {
             if (defined($element->{FINAL_CODE})) {              if (defined($element->{FINAL_CODE})) {
                 # Compile the code.                  # Compile the code.
                 my $code = 'sub { my $helper = shift; ' . $element->{FINAL_CODE} .                  my $code = 'sub { my $helper = shift; my $element = shift; ' 
                     '}';                      . $element->{FINAL_CODE} . '}';
                 $code = eval($code);                  $code = eval($code);
                 die 'Error while executing final code for element with var ' .                  die 'Error while executing final code for element with var ' .
                     $element->{'variable'} . ', Perl said: ' . $@ if $@;                      $element->{'variable'} . ', Perl said: ' . $@ if $@;
   
                 my $result = &$code($helper);                  my $result = &$code($helper, $element);
                 if ($result) {                  if ($result) {
                     push @results, $result;                      push @results, $result;
                 }                  }
Line 2431  sub render { Line 2969  sub render {
         }          }
     }      }
   
     if (scalar(@results) == 0) {      my $result;
         return '';  
     }  
   
     my $result = "<ul>\n";      if (scalar(@results) != 0) {
     for my $re (@results) {   $result .= "<ul>\n";
         $result .= '    <li>' . $re . "</li>\n";   for my $re (@results) {
       $result .= '    <li>' . $re . "</li>\n";
    }
   
    if (!@results) {
       $result .= '    <li>No changes were made to current settings.</li>';
    }
   
    $result .= '</ul>';
       }
   
       if ($self->{'restartCourse'}) {
    my $targetURL = '/adm/menu';
    if ($ENV{'course.'.$ENV{'request.course.id'}.'.clonedfrom'}) {
       $targetURL = '/adm/parmset?overview=1';
    }
           $result .= "<center>\n" .
               "<form action='/adm/roles' method='post' target='loncapaclient'>\n" .
               "<input type='button' onclick='history.go(-1)' value='&lt;- 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 Course Initialization' />\n" .
               "</form></center>";
     }      }
     return $result . '</ul>';  
       return $result;
   }
   
   sub overrideForm {
       my $self = shift;
       return $self->{'restartCourse'};
 }  }
   
 1;  1;
Line 2484  sub render { Line 3049  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 $affectedResourceId = "";      my $affectedResourceId = "";
     my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};      my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}};
Line 2506  sub render { Line 3076  sub render {
         $symb = 'a';          $symb = 'a';
         $paramlevel = 'general';          $paramlevel = 'general';
     } elsif ($vars->{GRANULARITY} eq 'map') {      } elsif ($vars->{GRANULARITY} eq 'map') {
         my $navmap = Apache::lonnavmaps::navmap->new(          my $navmap = Apache::lonnavmaps::navmap->new();
                            $ENV{"request.course.fn"}.".db",          my $res = $navmap->getByMapPc($vars->{RESOURCE_ID});
                            $ENV{"request.course.fn"}."_parms.db", 0, 0);  
         my $res = $navmap->getById($vars->{RESOURCE_ID});  
         my $title = $res->compTitle();          my $title = $res->compTitle();
         $symb = $res->symb();          $symb = $res->symb();
         $navmap->untieHashes();          $navmap->untieHashes();
Line 2518  sub render { Line 3086  sub render {
         $affectedResourceId = $vars->{RESOURCE_ID};          $affectedResourceId = $vars->{RESOURCE_ID};
         $paramlevel = 'map';          $paramlevel = 'map';
     } else {      } else {
         my $navmap = Apache::lonnavmaps::navmap->new(          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 $res = $navmap->getById($vars->{RESOURCE_ID});
         $symb = $res->symb();          $symb = $res->symb();
         my $title = $res->compTitle();          my $title = $res->compTitle();
Line 2535  sub render { Line 3101  sub render {
     $result .= '<p>Confirm that this information is correct, then click &quot;Finish Wizard&quot; to complete setting the parameter.<ul>';      $result .= '<p>Confirm that this information is correct, then click &quot;Finish Wizard&quot; to complete setting the parameter.<ul>';
           
     # Print the type of manipulation:      # Print the type of manipulation:
     $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}      $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}} . '</b>';
                . "</b></li>\n";      if ($vars->{ACTION_TYPE} eq 'tries') {
    $result .= ' to <b>' . $vars->{TRIES} . '</b>';
       }
       if ($vars->{ACTION_TYPE} eq 'weight') {
    $result .= ' to <b>' . $vars->{WEIGHT} . '</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 2553  sub render { Line 3125  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' " .
       "value='" . $vars->{TRIES} . "' />\n";
       } elsif ($vars->{ACTION_TYPE} eq 'weight') {
    $result .= "<input type='hidden' name='pres_value' " .
       "value='" . $vars->{WEIGHT} . "' />\n";
       }
   
     $result .= $resourceString;      $result .= $resourceString;
           
Line 2583  sub render { Line 3161  sub render {
     }      }
   
     # 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})    $result .= "<li>to <b>" . ctime($vars->{PARM_DATE}) . "</b> (" .
         . ")</li>\n";      Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}) 
       . ")</li>\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";

Removed from v.1.30  
changed lines
  Added in v.1.55


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