Diff for /loncom/interface/lonhelper.pm between versions 1.15 and 1.32

version 1.15, 2003/04/30 19:08:15 version 1.32, 2003/05/16 20:44:43
Line 53  Each state contains one or more state el Line 53  Each state contains one or more state el
 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 149  of the information used is persistent be Line 152  of the information used is persistent be
 and -maintain code.  and -maintain code.
   
 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.
   
 =cut  =cut
   
Line 189  my $substate; Line 196  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 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->process()" line. 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(); }
Line 202  sub handler { Line 237  sub handler {
     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 ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($ENV{'browser.mathml'}) {
Line 224  sub handler { Line 260  sub 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 251  sub start_helper { Line 294  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 310  sub new { Line 353  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 322  sub new { Line 366  sub new {
  $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
Line 428  sub declareVar { Line 470  sub declareVar {
   
     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 462  sub process { Line 518  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 529  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 495  sub display { Line 552  sub display {
   
     my $result = "";      my $result = "";
   
       if (!defined($state)) {
           $result = "<font color='#ff0000'>Error: state '$state' not defined!</font>";
           return $result;
       }
   
     # Phase 4: Display.      # Phase 4: Display.
     my $stateTitle = $state->title();      my $stateTitle = $state->title();
     my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');      my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
Line 506  sub display { Line 568  sub display {
     </head>      </head>
     $bodytag      $bodytag
 HEADER  HEADER
     if (!$state->overrideForm()) { $result.="<form name='helpform' method='GET'>"; }      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 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() . "<p>&nbsp;</p>";      $result .= $state->render();
   
       $result .= "</td><td valign='top' align='right'>";
   
       # Warning: Copy and pasted from below, because it's too much trouble to 
       # 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;';
         }          }
Line 527  HEADER Line 594  HEADER
             $result .= "<a href=\"$returnPage\">End Helper</a>";              $result .= "<a href=\"$returnPage\">End Helper</a>";
         }          }
         else {          else {
             $result .= '<input name="back" type="button" ';              $result .= '<nobr><input name="back" type="button" ';
             $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';              $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';
             $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';              $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" /></nobr>';
           }
       }
   
       $result .= "</td></tr><tr><td valign='bottom' align='right'>";
   
       # Warning: Copy and pasted from above, because it's too much trouble to 
       # turn this into a subroutine
       if (!$state->overrideForm()) {
           if ($self->{STATE} ne $self->{START_STATE}) {
               #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
           }
           if ($self->{DONE}) {
               my $returnPage = $self->{RETURN_PAGE};
               $result .= "<a href=\"$returnPage\">End Helper</a>";
           }
           else {
               $result .= '<nobr><input name="back" type="button" ';
               $result .= 'value="&lt;- Previous" onclick="history.go(-1)" /> ';
               $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" /></nobr>';
         }          }
         $result .= "</center>\n";  
     }      }
   
     #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 .= "</td></tr></table>";
   
     $result .= <<FOOTER;      $result .= <<FOOTER;
               </td>                </td>
             </tr>              </tr>
Line 549  FOOTER Line 636  FOOTER
   
     # 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();  
   
     return $result;      return $result;
 }  }
Line 650  sub render { Line 736  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 662  package Apache::lonhelper::element; Line 749  package Apache::lonhelper::element;
   
 =head2 Element Base Class  =head2 Element Base Class
   
 The Apache::lonhelper::element base class provides support methods for  The Apache::lonhelper::element base class provides support for elements
 the elements to use, such as a multiple value processer.  and defines some generally useful tags for use in elements.
   
 B<Methods>:  B<finalcode tag>
   
 =over 4  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;"
 =item * process_multiple_choices(formName, varName): Process the form   and "}". It is expected to return a string describing what it did, which 
 element named "formName" and place the selected items into the helper   may be an empty string. See course initialization helper for an example. This is
 variable named varName. This is for things like checkboxes or   generally intended for helpers like the course initialization helper, which consist
 multiple-selection listboxes where the user can select more then   of several panels, each of which is performing some sort of bite-sized functionality.
 one entry. The selected entries are delimited by triple pipes in   
 the helper variables, like this:    B<defaultvalue tag>
   
  CHOICE_1|||CHOICE_2|||CHOICE_3  Each element that accepts user input can contain a "defaultvalue" tag that,
   when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
 =back  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 wizard, 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.
   
   B<getValue method>
   
   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'));
 }  }
   
 # 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 824  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 ''; }
   
 sub preprocess {  sub preprocess {
     return 1;      return 1;
 }  }
Line 735  sub overrideForm { Line 870  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 865  the result is stored in. Line 983  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.
   
   <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.
   
 B<SUB-TAGS>  B<SUB-TAGS>
   
 <choices> can have the following subtags:  <choices> can have the following subtags:
Line 914  You can mix and match methods of creatin Line 1036  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>
   
   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;
Line 939  sub start_choices { Line 1073  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 987  sub render { Line 1122  sub render {
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result .= <<SCRIPT;          $result .= <<SCRIPT;
 <script>  <script>
     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) {
         $buttons = <<BUTTONS;          $buttons = <<BUTTONS;
 <br />  <br />
 <input type="button" onclick="checkall(true)" value="Select All" />  <input type="button" onclick="checkall(true, '$var')" value="Select All" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="Unselect All" />
 <br />&nbsp;  <br />&nbsp;
 BUTTONS  BUTTONS
     }      }
   
     if (defined $self->{ERRO_MSG}) {      if (defined $self->{ERROR_MSG}) {
         $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';          $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
     }      }
   
Line 1010  BUTTONS Line 1153  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 ' . 
               {'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}}) {
         $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 = 1;  
         }          }
         my $choiceLabel = $choice->[0];          my $choiceLabel = $choice->[0];
         if ($choice->[4]) {  # if we need to evaluate this choice          if ($choice->[4]) {  # if we need to evaluate this choice
Line 1044  sub postprocess { Line 1221  sub postprocess {
     my $self = shift;      my $self = shift;
     my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};      my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
   
     if (!$chosenValue) {      if (!defined($chosenValue) && !$self->{'allowempty'}) {
         $self->{ERROR_MSG} = "You must choose one or more choices to" .          $self->{ERROR_MSG} = "You must choose one or more choices to" .
             " continue.";              " continue.";
         return 0;          return 0;
     }      }
   
     if ($self->{'multichoice'}) {      if (ref($chosenValue)) {
         $self->process_multiple_choices($self->{'variable'}.'.forminput',          $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
                                         $self->{'variable'});  
     }      }
   
     if (defined($self->{NEXTSTATE})) {      if (defined($self->{NEXTSTATE})) {
Line 1295  the user can manipulate the folders. Line 1471  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" 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.
   
 B<SUB-TAGS>  B<SUB-TAGS>
   
Line 1356  sub start_resource { Line 1538  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'};
     return '';      return '';
 }  }
   
Line 1460  sub render { Line 1644  sub render {
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result = <<SCRIPT;          $result = <<SCRIPT;
 <script>  <script>
     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;
             }              }
         }          }
Line 1472  sub render { Line 1656  sub render {
 SCRIPT  SCRIPT
         $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="Select All Resources" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="Unselect All Resources" />
 <br /> &nbsp;  <br /> &nbsp;
 BUTTONS  BUTTONS
     }      }
Line 1516  BUTTONS Line 1700  BUTTONS
         }          }
     };      };
   
     $ENV{'form.condition'} = 1;      $ENV{'form.condition'} = !$self->{'toponly'};
     $result .=       $result .= 
         &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc,           &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
                                                   Apache::lonnavmaps::resource()],                                                    Apache::lonnavmaps::resource()],
                                        'showParts' => 0,                                         'showParts' => 0,
                                        'filterFunc' => $filterFunc,                                         'filterFunc' => $filterFunc,
                                        'resource_no_folder_link' => 1,                                         'resource_no_folder_link' => 1,
                                          'suppressEmptySequences' => $self->{'suppressEmptySequences'},
                                        'iterator_map' => $mapUrl }                                         'iterator_map' => $mapUrl }
                                        );                                         );
   
Line 1534  BUTTONS Line 1719  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;
Line 1613  sub render { Line 1793  sub render {
     my $self = shift;      my $self = shift;
     my $result = '';      my $result = '';
     my $buttons = '';      my $buttons = '';
       my $var = $self->{'variable'};
   
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result = <<SCRIPT;          $result = <<SCRIPT;
 <script>  <script>
     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
         $buttons = <<BUTTONS;          $buttons = <<BUTTONS;
 <br />  <br />
 <input type="button" onclick="checkall(true)" value="Select All" />  <input type="button" onclick="checkall(true, '$var')" value="Select All Students" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="Unselect All Students" />
 <br />  <br />
 BUTTONS  BUTTONS
     }      }
Line 1668  BUTTONS Line 1852  BUTTONS
             $checked = 1;              $checked = 1;
         }          }
         $result .=          $result .=
             " value='" . HTML::Entities::encode($_)              " value='" . HTML::Entities::encode($_ . ':' . $choices->{$_}->[$section])
             . "' /></td><td>"              . "' /></td><td>"
             . HTML::Entities::encode($choices->{$_}->[$fullname])              . HTML::Entities::encode($choices->{$_}->[$fullname])
             . "</td><td align='center'>"               . "</td><td align='center'>" 
Line 1692  sub postprocess { Line 1876  sub postprocess {
         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 1741  no strict; Line 1921  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'));
Line 1814  sub render { Line 1996  sub render {
   
     my $filterFunc = $self->{FILTER_FUNC};      my $filterFunc = $self->{FILTER_FUNC};
     my $buttons = '';      my $buttons = '';
       my $type = 'radio';
       if ($self->{'multichoice'}) {
           $type = 'checkbox';
       }
   
     if ($self->{'multichoice'}) {      if ($self->{'multichoice'}) {
         $result = <<SCRIPT;          $result = <<SCRIPT;
 <script>  <script>
     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;
               }
           }
       }
   
       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;                  document.forms.helpform.elements[i].checked=value;
             }              }
         }          }
Line 1830  sub render { Line 2025  sub render {
 SCRIPT  SCRIPT
         $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="Select All Files" />
 <input type="button" onclick="checkall(false)" value="Unselect All" />  <input type="button" onclick="checkall(false, '$var')" value="Unselect All Files" />
   BUTTONS
   
           if ($helper->{VARS}->{'construction'}) {
               $buttons .= <<BUTTONS;
   <input type="button" onclick="checkallclass(true, 'Published')" value="Select All Published" />
   <input type="button" onclick="checkallclass(false, 'Published')" value="Unselect All Published" />
 <br /> &nbsp;  <br /> &nbsp;
 BUTTONS  BUTTONS
          }
     }      }
   
     # Get the list of files in this directory.      # Get the list of files in this directory.
Line 1855  BUTTONS Line 2057  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 2070  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;
       if ($helper->{VARS}->{'construction'}) {
    ($status, $color) = @{fileState($subdir, $file)};
       } else {
    $status = '';
    $color = '';
       }
   
               # Get the title
               my $title = Apache::lonpubdir::getTitleString($fileName);
   
               # 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" ';
               }
               $result .= '<tr><td align="right"' . " bgcolor='$color'>" .
                   "<input $onclick type='$type' name='" . $var
             . ".forminput' value='" . HTML::Entities::encode($fileName) .              . ".forminput' value='" . HTML::Entities::encode($fileName) .
                 "'";                  "'";
             if (!$self->{'multichoice'} && $choices == 0) {              if (!$self->{'multichoice'} && $choices == 0) {
                 $result .= ' checked';                  $result .= ' checked';
             }              }
             $result .= "/></td><td>" . $file . "</td></tr>\n";              $result .= "/></td><td bgcolor='$color'>" . $file . "</td>" .
                   "<td bgcolor='$color'>$title</td>" .
                   "<td bgcolor='$color'>$status</td>" . "</tr>\n";
             $choices++;              $choices++;
         }          }
     }      }
Line 1895  BUTTONS Line 2126  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 $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
       my $subdirpart = $constructionSpaceDir;
       $subdirpart =~ s/^\/home\/$ENV{'user.name'}\/public_html//;
       my $resdir = $docroot . '/res/' . $ENV{'user.domain'} . '/' . $ENV{'user.name'} .
           $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'};
Line 1904  sub postprocess { Line 2166  sub postprocess {
         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 2124  sub end_eval { Line 2382  sub end_eval {
   
 1;  1;
   
   package Apache::lonhelper::final;
   
   =pod
   
   =head2 Element: final
   
   <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>
   snippets and collecting the results. Finally, it takes the user out of the
   helper, going to a provided page.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonhelper::register('Apache::lonhelper::final',
                                    ('final', 'exitpage'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_final { 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;
                   }
               }
           }
       }
   
       if (scalar(@results) == 0) {
           return '';
       }
   
       my $result = "<ul>\n";
       for my $re (@results) {
           $result .= '    <li>' . $re . "</li>\n";
       }
       return $result . '</ul>';
   }
   
   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 parmwizard. It is not generally useful,
Line 2171  sub render { Line 2524  sub render {
                         'due_date' => "0_duedate",                          'due_date' => "0_duedate",
                         'answer_date' => "0_answerdate");                          'answer_date' => "0_answerdate");
           
     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;
     # Print the type of manipulation:      my $symb;
     $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}      my $paramlevel;
                . "</b></li>\n";  
     if ($vars->{ACTION_TYPE} eq 'due_date' ||   
         $vars->{ACTION_TYPE} eq 'answer_date') {  
         # for due dates, we default to "date end" type entries  
         $result .= "<input type='hidden' name='recent_date_end' " .  
             "value='" . $vars->{PARM_DATE} . "' />\n";  
         $result .= "<input type='hidden' name='pres_value' " .   
             "value='" . $vars->{PARM_DATE} . "' />\n";  
         $result .= "<input type='hidden' name='pres_type' " .  
             "value='date_end' />\n";  
     } elsif ($vars->{ACTION_TYPE} eq 'open_date') {  
         $result .= "<input type='hidden' name='recent_date_start' ".  
             "value='" . $vars->{PARM_DATE} . "' />\n";  
         $result .= "<input type='hidden' name='pres_value' " .  
             "value='" . $vars->{PARM_DATE} . "' />\n";  
         $result .= "<input type='hidden' name='pres_type' " .  
             "value='date_start' />\n";  
     }   
       
     # Print the granularity, depending on the action      # Print the granularity, depending on the action
     if ($vars->{GRANULARITY} eq 'whole_course') {      if ($vars->{GRANULARITY} eq 'whole_course') {
         $result .= '<li>for <b>all resources in the course</b></li>';          $resourceString .= '<li>for <b>all resources in the course</b></li>';
         $level = 9; # general course, see lonparmset.pm perldoc          $level = 9; # general course, see lonparmset.pm perldoc
         $affectedResourceId = "0.0";          $affectedResourceId = "0.0";
           $symb = 'a';
           $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",                             $ENV{"request.course.fn"}.".db",
                            $ENV{"request.course.fn"}."_parms.db", 0, 0);                             $ENV{"request.course.fn"}."_parms.db", 0, 0);
         my $res = $navmap->getById($vars->{RESOURCE_ID});          my $res = $navmap->getById($vars->{RESOURCE_ID});
         my $title = $res->compTitle();          my $title = $res->compTitle();
           $symb = $res->symb();
         $navmap->untieHashes();          $navmap->untieHashes();
         $result .= "<li>for the map named <b>$title</b></li>";          $resourceString .= "<li>for the map named <b>$title</b></li>";
         $level = 8;          $level = 8;
         $affectedResourceId = $vars->{RESOURCE_ID};          $affectedResourceId = $vars->{RESOURCE_ID};
           $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"}.".db",
                            $ENV{"request.course.fn"}."_parms.db", 0, 0);                             $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();
         my $title = $res->compTitle();          my $title = $res->compTitle();
         $navmap->untieHashes();          $navmap->untieHashes();
         $result .= "<li>for the resource named <b>$title</b></li>";          $resourceString .= "<li>for the resource named <b>$title</b></li>";
         $level = 7;          $level = 7;
         $affectedResourceId = $vars->{RESOURCE_ID};          $affectedResourceId = $vars->{RESOURCE_ID};
           $paramlevel = 'full';
     }      }
   
       my $result = "<form name='helpform' method='get' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
       $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:
       $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}
                  . "</b></li>\n";
       if ($vars->{ACTION_TYPE} eq 'due_date' || 
           $vars->{ACTION_TYPE} eq 'answer_date') {
           # for due dates, we default to "date end" type entries
           $result .= "<input type='hidden' name='recent_date_end' " .
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_value' " . 
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_type' " .
               "value='date_end' />\n";
       } elsif ($vars->{ACTION_TYPE} eq 'open_date') {
           $result .= "<input type='hidden' name='recent_date_start' ".
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_value' " .
               "value='" . $vars->{PARM_DATE} . "' />\n";
           $result .= "<input type='hidden' name='pres_type' " .
               "value='date_start' />\n";
       } 
   
       $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>for <b>all students in course</b></li>';
Line 2237  sub render { Line 2602  sub render {
     } 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 $username = $vars->{USER_NAME};
           # Chop off everything after the last colon (section)
           $username = substr($username, 0, rindex($username, ':'));
           my $name = $classlist->{$username}->[6];
         $result .= "<li>for <b>$name</b></li>";          $result .= "<li>for <b>$name</b></li>";
         $level -= 6;          $level -= 6;
         my ($uname, $udom) = split /:/, $vars->{USER_NAME};          my ($uname, $udom) = split /:/, $vars->{USER_NAME};
Line 2255  sub render { Line 2623  sub render {
     # 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='all' name='pschp' />";
       $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 .= "<br /><br /><center><input type='submit' value='Finish Helper' /></center></form>\n";
   

Removed from v.1.15  
changed lines
  Added in v.1.32


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