[system] / branches / rel-2-1-a1 / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetEditor.pm Repository:
ViewVC logotype

View of /branches/rel-2-1-a1/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3275 - (download) (as text) (annotate)
Thu Jun 9 14:59:42 2005 UTC (7 years, 11 months ago) by glarose
File size: 22681 byte(s)
Gateway bugfixes/feature additions
 - added test time to student progress display
 - corrected bugs from overtime proctored tests
 - corrected behavior for closed tests
 - added restrictions to prevent gateways from being taken as regular
   assignments
 - updated problem set lists to better deal with gateways

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm,v 1.57.2.2 2004/07/26 15:11:18 gage Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor::ProblemSetEditor;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::ProblemSetEditor - Edit a set definition list
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use CGI qw();
   29 use File::Copy;
   30 use WeBWorK::DB::Record::Problem;
   31 use WeBWorK::Utils qw(readFile formatDateTime parseDateTime list2hash readDirectory max);
   32 
   33 our $rowheight = 20;  #controls the length of the popup menus.
   34 our $libraryName;  #library directory name
   35 
   36 # added gateway fields here: everything after published
   37 use constant SET_FIELDS => [qw(open_date due_date answer_date set_header hardcopy_header published assignment_type attempts_per_version version_time_limit versions_per_interval time_interval problem_randorder)];
   38 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
   39 use constant PROBLEM_USER_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
   40 
   41 # this defines allowed values for the assignment_type field in the set definition
   42 # ideally we should probably have this imported from some global file (global.conf?)
   43 use constant ASSIGNMENT_TYPES => [ qw(default gateway proctored_gateway) ];
   44 
   45 sub getSetName {
   46   my ($self, $pathSetName) = @_;
   47   if (ref $pathSetName eq "HASH") {
   48     $pathSetName = undef;
   49   }
   50   return $pathSetName;
   51 }
   52 
   53 # One wrinkle here: if $override is undefined, do the global thing,
   54 # otherwise, it's truth value determines the checkbox and the current fieldValue is not directly editable
   55 sub setRowHTML {
   56   my ($description, $fieldName, $fieldValue, $size, $override, $overrideValue) = @_;
   57 
   58   my $attributeHash = {type=>"text", name=>$fieldName, value=>$fieldValue};
   59   $attributeHash->{size} = $size if defined $size;
   60 
   61   my $input = (defined $override) ? $fieldValue : CGI::input($attributeHash);
   62 
   63   my $html = CGI::td({}, [$description, $input]);
   64 
   65   if (defined $override) {
   66     $attributeHash->{name}="${fieldName}_override";
   67     $attributeHash->{value}=($override ? $overrideValue : "" );
   68 
   69     $html .= CGI::td({}, [
   70       CGI::checkbox({
   71         type=>"checkbox",
   72         name=>"override",
   73         label=>"override with:",
   74         value=>$fieldName,
   75         checked=>($override ? 1 : 0)
   76       }),
   77       CGI::input($attributeHash)
   78     ]);
   79   }
   80 
   81   return $html;
   82 
   83 }
   84 
   85 
   86 # FIXME: this or something similar could get pulled up to Instructor.pm
   87 sub recurseDirectory {
   88 
   89   my ($self, $dir, $pattern) = @_;
   90 
   91   my @dirs = grep {$_ ne "." and $_ ne ".." and $_ ne "Library" and $_ ne "CVS" and -d "$dir/$_"} readDirectory($dir);
   92 
   93   my @files = map { "$dir/$_" } $self->read_dir($dir, $pattern);
   94 
   95   foreach (@dirs) {
   96     push (@files, $self->recurseDirectory("$dir/$_", $pattern));
   97   }
   98 
   99   return @files;
  100 }
  101 
  102 
  103 
  104 # Initialize does all of the form processing.  It's extensive, and could probably be cleaned up and
  105 # consolidated with a little abstraction.
  106 sub initialize {
  107   my ($self)      = @_;
  108   my $r           = $self->r;
  109   my $db          = $r->db;
  110   my $ce          = $r->ce;
  111   my $authz       = $r->authz;
  112   my $user        = $r->param('user');
  113   #my $setName    = $self->getSetName(@components);
  114   my $setName     = $r->urlpath->arg("setID");
  115   my $setRecord   = $db->getGlobalSet($setName); #checked
  116   die "global set $setName not found." unless $setRecord;
  117 
  118   $self->{set}    = $setRecord;
  119   my @editForUser = $r->param('editForUser');
  120   # some useful booleans
  121   my $forUsers    = scalar(@editForUser);
  122   my $forOneUser  = $forUsers == 1;
  123 
  124   # build a quick lookup table
  125   my %overrides = list2hash $r->param('override');
  126 
  127   # Check permissions
  128   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  129   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  130 
  131   ###################################################
  132   # The set form was submitted with the save button pressed
  133   # Save changes to the set
  134   ###################################################
  135 
  136   if (defined($r->param('submit_set_changes'))) {
  137 
  138     if (!$forUsers) {
  139       foreach (@{SET_FIELDS()}) {
  140             # this is an unnecessary logical division: we deal with gateway
  141       #   fields separately from the rest, for no particular reason other
  142             #   than it makes life somewhat easier for those who don't care
  143             #   about gateways
  144           if ( /(assignment_type)|(attempts_per_version)|(version_time_limit)|(versions_per_interval)|(time_interval)|(problem_randorder)/ ) {
  145         if (defined($r->param($_))) {
  146             if ( /assignment_type/ &&
  147            $r->param($_) =~ /default/i ) {
  148           $setRecord->$_(undef);
  149             } else {
  150 
  151           if ( m/time/ ) {
  152                     # times are input as minutes, not seconds, so multiply by 60
  153               $setRecord->$_( 60*($r->param($_)) );
  154           } else {
  155               $setRecord->$_( $r->param($_) );
  156           }
  157             }
  158 
  159         } elsif ( m/assignment_type/ ) {
  160             $setRecord->$_(undef);
  161         }
  162 
  163             # we now return you to your regularly scheduled programming
  164           } else {
  165         if (defined($r->param($_))) {
  166           if (m/_date$/) {
  167             $setRecord->$_(parseDateTime($r->param($_)));
  168           } else {
  169             $setRecord->$_($r->param($_)) unless ($_ eq 'set_header' and $r->param($_) eq "Use System Default");
  170 
  171             if($_ eq 'set_header') {
  172               # be nice and copy the default file here if it doesn't exist yet
  173               # empty set headers lead to trouble
  174               my $set_header = ($r->param($_) eq "Use System Default") ? $setRecord->set_header : $r->param($_);
  175 
  176               my $newheaderpath = $r->{ce}->{courseDirs}->{templates} . '/'. $set_header;
  177               unless(($set_header !~ /\S/) or -e $newheaderpath) {
  178                 my $default_header = $ce->{webworkFiles}->{screenSnippets}->{setHeader};
  179                 File::Copy::copy($default_header, $newheaderpath);
  180               }
  181             }
  182           }
  183         } else {
  184           if (m/published$/) {
  185             $setRecord->$_(0);
  186           }
  187              }
  188       }
  189       }
  190 
  191 
  192 
  193 
  194       ###################################################
  195       # Check that the open, due and answer dates are in increasing order.
  196       # Bail if this is not correct.
  197       ###################################################
  198       if ($setRecord->open_date > $setRecord->due_date)  {
  199         $self->addbadmessage('Error: Due date must come after open date');
  200         return;
  201       }
  202       if ($setRecord->due_date > $setRecord->answer_date) {
  203         $self->addbadmessage('Error: Answer date must come after due date');
  204         return;
  205       }
  206       ###################################################
  207       # End date check section.
  208       ###################################################
  209       $self->addgoodmessage("Changes to set $setName were successfully saved.");
  210       $db->putGlobalSet($setRecord);
  211     } else {
  212 
  213       my $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
  214       die "set $setName not found for $editForUser[0]." unless $userSetRecord;
  215       foreach my $field (@{SET_FIELDS()}) {
  216         if (defined $r->param("${field}_override")) {
  217           if (exists $overrides{$field}) {
  218             if ($field =~ m/_date$/) {
  219               $userSetRecord->$field(parseDateTime($r->param("${field}_override")));
  220             } else {
  221               $userSetRecord->$field($r->param("${field}_override"));
  222             }
  223           } else {
  224             $userSetRecord->$field(undef);
  225           }
  226         }
  227       }
  228       ###################################################
  229       # Check that the open, due and answer dates are in increasing order.
  230       # Bail if this is not correct.
  231       ###################################################
  232       my $active_open_date   = $userSetRecord->open_date   ? $userSetRecord->open_date   : $setRecord->open_date;
  233       my $active_due_date    = $userSetRecord->due_date    ? $userSetRecord->due_date    : $setRecord->due_date;
  234       my $active_answer_date = $userSetRecord->answer_date ? $userSetRecord->answer_date : $setRecord->answer_date;
  235       if ( $active_open_date > $active_due_date ) {
  236         $self->addbadmessage('Error: Due date override must come after open date');
  237         return;
  238       }
  239       if ( $active_due_date > $active_answer_date ) {
  240         $self->addbadmessage('Error: Answer date override must come after due date');
  241         return;
  242       }
  243       ###################################################
  244       # End date check section.
  245       ###################################################
  246       $self->addgoodmessage("Changes to set $setName for user ", CGI::b($editForUser[0]), "were successfully saved.");
  247       $db->putUserSet($userSetRecord);
  248     }
  249 
  250   }
  251 
  252   ###################################################
  253   # The set form was submitted with the export button pressed
  254   # Export the set structure to a set definition file
  255   ###################################################
  256 
  257   if (  defined($r->param('export_set'))  ) {
  258     my $fileName = $r->param('export_file_name');
  259     die "Please specify a file name for saving the set definition" unless $fileName;
  260     $fileName    .= '.def' unless $fileName =~ /\.def$/;
  261     my $filePath  = $ce->{courseDirs}->{templates}.'/'.$fileName;
  262     # back up existing file
  263     if(-e $filePath) {
  264         rename($filePath,"$filePath.bak") or
  265                die "Can't rename $filePath to $filePath.bak ",
  266                    "Check permissions for webserver on directories. $!";
  267     }
  268       my $openDate     = formatDateTime($setRecord->open_date);
  269       my $dueDate      = formatDateTime($setRecord->due_date);
  270       my $answerDate   = formatDateTime($setRecord->answer_date);
  271       my $setHeader    = $setRecord->set_header;
  272 
  273       my @problemList = $db->listGlobalProblems($setName);
  274       my $problemList  = '';
  275       foreach my $prob (sort {$a <=> $b} @problemList) {
  276         my $problemRecord = $db->getGlobalProblem($setName, $prob); # checked
  277         die "global problem $prob for set $setName not found" unless defined($problemRecord);
  278         my $source_file   = $problemRecord->source_file();
  279       my $value         = $problemRecord->value();
  280       my $max_attempts  = $problemRecord->max_attempts();
  281         $problemList     .= "$source_file, $value, $max_attempts \n";
  282       }
  283       my $fileContents = <<EOF;
  284 
  285 openDate          = $openDate
  286 dueDate           = $dueDate
  287 answerDate        = $answerDate
  288 paperHeaderFile   = $setHeader
  289 screenHeaderFile  = $setHeader
  290 problemList       =
  291 
  292 $problemList
  293 
  294 
  295 
  296 EOF
  297 
  298 
  299       $self->saveProblem($fileContents, $filePath);
  300       $self->addgoodmessage(CGI::p("Set definition saved to $filePath"));
  301 
  302   }
  303 }
  304 
  305 
  306 sub body {
  307   my ($self, @components) = @_;
  308   my $r                   = $self->r;
  309   my $urlpath             = $r->urlpath;
  310   my $db                  = $r->db;
  311   my $ce                  = $r->ce;
  312   my $authz               = $r->authz;
  313   my $user                = $r->param('user');
  314   my $courseName          = $urlpath->arg("courseID");
  315   my $setName             = $urlpath->arg("setID");
  316   my $setRecord           = $db->getGlobalSet($setName);  # checked
  317   die "global set $setName not found." unless $setRecord;
  318   my @editForUser         = $r->param('editForUser');
  319   # some useful booleans
  320   my $forUsers            = scalar(@editForUser);
  321   my $forOneUser          = $forUsers == 1;
  322 
  323   # Check permissions
  324   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  325     unless $authz->hasPermissions($r->param("user"), "access_instructor_tools");
  326 
  327   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problem sets.")
  328     unless $authz->hasPermissions($r->param("user"), "modify_problem_sets");
  329 
  330 
  331   ## Set Form ##
  332   my $userSetRecord;
  333   my %overrideArgs;
  334   if ($forOneUser) {
  335     $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
  336     die "set $setName not found for user $editForUser[0]." unless $userSetRecord;
  337     foreach my $field (@{SET_FIELDS()}) {
  338       $overrideArgs{$field} = [defined $userSetRecord->$field, ($field =~ /_date$/ ? formatDateTime($userSetRecord->$field) : $userSetRecord->$field)];
  339     }
  340   } else {
  341     foreach my $field (@{SET_FIELDS()}) {
  342       $overrideArgs{$field} = [undef, undef];
  343     }
  344   }
  345   print CGI::h2({}, "Set Data"), "\n";
  346   if (@editForUser) {
  347     print CGI::p("Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
  348   }
  349 
  350   my @headers = $self->recurseDirectory($self->{ce}->{courseDirs}->{templates}, '(?i)header.*?\\.pg$');
  351   map { s|^$self->{ce}->{courseDirs}->{templates}/?|| } @headers;
  352   @headers = sort @headers;
  353   unshift (@headers, "Use System Default");
  354 
  355   print CGI::start_form({method=>"post", action=>$r->uri}), "\n";
  356   print CGI::table({},
  357     CGI::Tr({}, [
  358       setRowHTML( "Open Date:",
  359             "open_date",
  360             formatDateTime($setRecord->open_date),
  361             undef,
  362             @{$overrideArgs{open_date}})."\n",
  363       setRowHTML( "Due Date:",
  364             "due_date",
  365             formatDateTime($setRecord->due_date),
  366             undef,
  367             @{$overrideArgs{due_date}})."\n",
  368       setRowHTML( "Answer Date:",
  369             "answer_date",
  370             formatDateTime($setRecord->answer_date),
  371             undef,
  372             @{$overrideArgs{answer_date}})."\n",
  373 #     setRowHTML( "Set Header:", "set_header",
  374 #           $setRecord->set_header,
  375 #           32,
  376 #           @{$overrideArgs{set_header}})."\n",
  377 # FIXME  we're not using this right at the moment as far as I know.  There may someday be a use for it, so don't take this out yet.
  378 #       setRowHTML( "Problem Header:",
  379 #             "hardcopy_header",
  380 #             $setRecord->hardcopy_header,
  381 #             undef,
  382 #             @{$overrideArgs{hardcopy_header}})."\n",
  383       CGI::td({}, [ "Set Header:" ,
  384           ($forOneUser)
  385             ? $setRecord->set_header || "None selected."
  386             : CGI::popup_menu(
  387               -name=>'set_header',
  388               -values=>\@headers,
  389               -default=>0) .
  390             "(currently: " . ($setRecord->set_header || "None selected.") . ")" . "\n",
  391         ]) . "\n",
  392 #
  393 # assignment type added for gateway compatibility
  394                        CGI::td({}, [ "Assignment Type:",
  395                                      ($forOneUser) ?
  396                                          $setRecord->assignment_type || "Default." :
  397                                          CGI::popup_menu( -name=>'assignment_type',
  398                                                           -values=>ASSIGNMENT_TYPES,
  399                                                           -default=>($setRecord->assignment_type || "default.") ) .
  400                                          " (currently: " .
  401                                          ( $setRecord->assignment_type || "default." ) .
  402                                          ")\n" ]) . "\n"
  403 
  404     ])
  405   );
  406 
  407 # add input fields for gateway tests, if we're dealing with that type of assignment
  408         if ( defined($setRecord->assignment_type) &&
  409              $setRecord->assignment_type =~ /gateway/ ) {
  410             print "Gateway parameters:", CGI::br(), "\n";
  411             my $versionTimeLimit = ( defined( $setRecord->version_time_limit ) &&
  412                                      $setRecord->version_time_limit ) ?
  413                                      int(($setRecord->version_time_limit() + 0.5)/60) :
  414                                      0;
  415             my $timeInterval = ( defined( $setRecord->time_interval ) &&
  416                                      $setRecord->time_interval ne '' ) ?
  417                                      int(($setRecord->time_interval() + 0.5)/60) :
  418                                      720;  # default is 12 hours
  419             print CGI::table( {},
  420                     CGI::Tr( {}, [
  421                       CGI::td( {}, "&nbsp;&nbsp;",
  422                                    setRowHTML( "Attempts per test version",
  423                                                "attempts_per_version",
  424                                                $setRecord->attempts_per_version ?
  425                                                  $setRecord->attempts_per_version : 1,
  426                                                3,
  427                                                @{$overrideArgs{attempts_per_version}}) .
  428                                       "\n" ),
  429                       CGI::td( {}, "&nbsp;&nbsp;",
  430                                    setRowHTML( "Time limit for test (min)",
  431                                                "version_time_limit",
  432                                                $versionTimeLimit, 3,
  433                                                @{$overrideArgs{version_time_limit}}) .
  434                                       "\n" ),
  435                       CGI::td( {}, "&nbsp;&nbsp;",
  436                                    setRowHTML( "Versions per time interval (0=infty)",
  437                                                "versions_per_interval",
  438                                                $setRecord->versions_per_interval ne '' ?
  439                                                  $setRecord->versions_per_interval : 1,
  440                                                3,
  441                                                @{$overrideArgs{versions_per_interval}}).
  442                                       "\n" ),
  443                       CGI::td( {}, "&nbsp;&nbsp;",
  444                                    setRowHTML( "Time interval (min)",
  445                                                "time_interval", $timeInterval, 4,
  446                                                @{$overrideArgs{time_interval}}) .
  447                                       "\n" ),
  448                       CGI::td( {}, "&nbsp;&nbsp;",
  449                                    setRowHTML( "Order problems randomly in set (0|1)",
  450                                                "problem_randorder",
  451                                                $setRecord->problem_randorder ne '' ?
  452                                                  $setRecord->problem_randorder : 1,
  453                                                3,
  454                                                @{$overrideArgs{problem_randorder}}) .
  455                                       "\n" )
  456                     ] )
  457                  ), "\n";
  458         }
  459 
  460   if (@editForUser) {
  461     my $publishedClass = ($setRecord->published) ? "Published" : "Unpublished";
  462     my $publishedText = ($setRecord->published) ? "visible to students" : "hidden from students";
  463     print CGI::p("This set is currently", CGI::font({class=>$publishedClass}, $publishedText),
  464     CGI::br(), "(You cannot hide or make a set visible for specific users.)");
  465   } else {
  466     print CGI::checkbox({type=>"checkbox", name=>"published", label=>"Visible to students", value=>"1", checked=>(($setRecord->published) ? 1 : 0)}), CGI::br();
  467 
  468   }
  469 
  470   print $self->hiddenEditForUserFields(@editForUser),
  471         $self->hidden_authen_fields,
  472         CGI::input({type=>"submit", name=>"submit_set_changes", value=>"Save Set", style=>"{width: 13ex}"}),
  473         '&nbsp;';
  474 
  475     #### link to edit setHeader
  476     my $PGProblemEditor    = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
  477                                                      courseID  => $courseName,
  478                                                      setID     => $setName,
  479                                                      problemID => '0'
  480     );
  481     my $setHeaderEditLink = $self->systemLink($PGProblemEditor);
  482   if (defined($setRecord) and $setRecord->set_header) {
  483     print CGI::a({-href=>$setHeaderEditLink},'Edit set header: '.$setRecord->set_header);
  484   }
  485 
  486   print CGI::br(),
  487         CGI::submit({ name=>"export_set", label=>"Export Set",  style=>"{width: 13ex}"} ),
  488         ' as ',
  489         CGI::input({type=>'text',name=>'export_file_name',value=>"set$setName.def",size=>32});
  490 
  491   print CGI::br();
  492 
  493 
  494 
  495   print CGI::end_form();
  496 
  497   my $problemCount = $db->listGlobalProblems($setName);
  498   print CGI::h2({}, "Problems"), "\n";
  499   print CGI::p({}, "This set contains $problemCount problem" . ($problemCount == 1 ? "" : "s").".");
  500   #FIXME
  501   # the code below doesn't work ---
  502   # get message
  503   #no type matches module WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser with args at
  504   # /home/gage/webwork/webwork-modperl/lib/WeBWorK/URLPath.pm line 497.
  505     # error in URLPath.pm??????
  506   my $problemSetListPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemList",
  507                                                     courseID => $courseName,
  508                                                     setID    => $setName
  509   );
  510 
  511   my $editProblemsURL        = $self->systemLink($problemSetListPage,
  512                                                  params => ['editForUser']   # include all editForUser parameters
  513   );
  514   my $usersAssignedToSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
  515                                                     courseID => $courseName,
  516                                                     setID    => $setName
  517   );
  518 
  519   my $editUsersAssignedToSetURL        = $self->systemLink($usersAssignedToSetPage,
  520 
  521   );
  522   print CGI::a({href=>$editProblemsURL},
  523    (@editForUser) ? "Edit the list of problems in this set for ". CGI::b(join ", ", @editForUser) :
  524                     "Edit the list of problems in this set");
  525 
  526   unless (@editForUser) {      # this is not needed when we are editing details for a user
  527     my $userCount = $db->listUsers;
  528     my $usersOfSet = $db->countSetUsers($setName);
  529     print CGI::h2({}, "Users"), "\n";
  530     print CGI::p({}, "This set is assigned to ".$self->userCountMessage($usersOfSet, $userCount).".");
  531     print CGI::a({href=>$editUsersAssignedToSetURL}, "Determine who this set is assigned to");
  532   }
  533 
  534   return "";
  535 }
  536 ###########################################################################
  537 # utility
  538 ###########################################################################
  539 sub saveProblem {
  540     my $self      = shift;
  541   my ($body, $probFileName)= @_;
  542   local(*PROBLEM);
  543   open (PROBLEM, ">$probFileName") ||
  544     $self->addbadmessage(CGI::p("Could not open $probFileName for writing. Check that the  permissions for this problem are 660 (-rw-rw----)"));
  545   print PROBLEM $body;
  546   close PROBLEM;
  547   chmod 0660, "$probFileName" ||
  548     $self->addbadmessage(CGI::p("CAN'T CHANGE PERMISSIONS ON FILE $probFileName"));
  549 }
  550 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9