[system] / branches / rel-2-3-dev / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetDetail.pm Repository:
ViewVC logotype

View of /branches/rel-2-3-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2952 - (download) (as text) (annotate)
Thu Oct 21 23:56:21 2004 UTC (8 years, 7 months ago) by gage
Original Path: trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm
File size: 44876 byte(s)
Adjusted the headers for the dates

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 #
    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::ProblemSetDetail;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::ProblemSetDetail - Edit general set and specific user/set information as well as problem information
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use CGI qw();
   29 use WeBWorK::HTML::ComboBox qw/comboBox/;
   30 use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max);
   31 use WeBWorK::DB::Record::Set;
   32 use WeBWorK::Utils::Tasks qw(renderProblems);
   33 use WeBWorK::Debug;
   34 
   35 # Important Note: the following two sets of constants may seem similar
   36 #   but they are functionally and semantically different
   37 
   38 # these constants determine which fields belong to what type of record
   39 use constant SET_FIELDS => [qw(set_header hardcopy_header open_date due_date answer_date published)];
   40 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
   41 use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
   42 
   43 # these constants determine what order those fields should be displayed in
   44 use constant HEADER_ORDER => [qw(set_header hardcopy_header)];
   45 use constant PROBLEM_FIELD_ORDER => [qw(problem_seed status value max_attempts attempted last_answer num_correct num_incorrect)];
   46 use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published)];
   47 
   48 # this constant is massive hash of information corresponding to each db field.
   49 # override indicates for how many students at a time a field can be overridden
   50 # this hash should make it possible to NEVER have explicitly: if (somefield) { blah() }
   51 #
   52 # All but name are optional
   53 # some_field => {
   54 #   name      => "Some Field",
   55 #   type      => "edit",    # edit, choose, hidden, view - defines how the data is displayed
   56 #   size      => "50",    # size of the edit box (if any)
   57 #   override  => "none",    # none, one, any, all - defines for whom this data can/must be overidden
   58 #   module    => "problem_list",  # WeBWorK module
   59 #   default   => 0      # if a field cannot default to undefined/empty what should it default to
   60 #   labels    => {      # special values can be hashed to display labels
   61 #       1 => "Yes",
   62 #       0 => "No",
   63 #   },
   64 use constant  FIELD_PROPERTIES => {
   65   # Set information
   66   set_header => {
   67     name      => "Set Header",
   68     type      => "edit",
   69     size      => "50",
   70     override  => "all",
   71     module    => "problem_list",
   72     default   => "",
   73   },
   74   hardcopy_header => {
   75     name      => "Hardcopy Header",
   76     type      => "edit",
   77     size      => "50",
   78     override  => "all",
   79     module    => "hardcopy_preselect_set",
   80     default   => "",
   81   },
   82   open_date => {
   83     name      => "Opens",
   84     type      => "edit",
   85     size      => "26",
   86     override  => "any",
   87     labels    => {
   88         0 => "None Specified",
   89         "" => "None Specified",
   90     },
   91   },
   92   due_date => {
   93     name      => "Answers Due",
   94     type      => "edit",
   95     size      => "26",
   96     override  => "any",
   97     labels    => {
   98         0 => "None Specified",
   99         "" => "None Specified",
  100     },
  101   },
  102   answer_date => {
  103     name      => "Answers Available",
  104     type      => "edit",
  105     size      => "26",
  106     override  => "any",
  107     labels    => {
  108         0 => "None Specified",
  109         "" => "None Specified",
  110     },
  111   },
  112   published => {
  113     name      => "Visible to Students",
  114     type      => "choose",
  115     override  => "all",
  116     choices   => [qw( 0 1 )],
  117     labels    => {
  118         1 => "Yes",
  119         0 => "No",
  120     },
  121   },
  122   # Problem information
  123   source_file => {
  124     name      => "Source File",
  125     type      => "edit",
  126     size      => 50,
  127     override  => "any",
  128     default   => "",
  129   },
  130   value => {
  131     name      => "Weight",
  132     type      => "edit",
  133     size      => 6,
  134     override  => "any",
  135   },
  136   max_attempts => {
  137     name      => "Max attempts",
  138     type      => "edit",
  139     size      => 6,
  140     override  => "any",
  141     labels    => {
  142         "-1" => "unlimited",
  143     },
  144   },
  145   problem_seed => {
  146     name      => "Seed",
  147     type      => "edit",
  148     size      => 6,
  149     override  => "one",
  150 
  151   },
  152   status => {
  153     name      => "Status",
  154     type      => "edit",
  155     size      => 6,
  156     override  => "one",
  157     default   => 0,
  158   },
  159   attempted => {
  160     name      => "Attempted",
  161     type      => "hidden",
  162     override  => "none",
  163     choices   => [qw( 0 1 )],
  164     labels    => {
  165         1 => "Yes",
  166         0 => "No",
  167     },
  168     default   => 0,
  169   },
  170   last_answer => {
  171     name      => "Last Answer",
  172     type      => "hidden",
  173     override  => "none",
  174   },
  175   num_correct => {
  176     name      => "Correct",
  177     type      => "hidden",
  178     override  => "none",
  179     default   => 0,
  180   },
  181   num_incorrect => {
  182     name      => "Incorrect",
  183     type      => "hidden",
  184     override  => "none",
  185     default   => 0,
  186   },
  187 };
  188 
  189 # Create a table of fields for the given parameters, one row for each db field
  190 # if only the setID is included, it creates a table of set information
  191 # if the problemID is included, it creates a table of problem information
  192 sub FieldTable {
  193   my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord) = @_;
  194 
  195   my $r = $self->r;
  196   my @editForUser = $r->param('editForUser');
  197   my $forUsers    = scalar(@editForUser);
  198   my $forOneUser  = $forUsers == 1;
  199 
  200   my @fieldOrder;
  201   if (defined $problemID) {
  202     @fieldOrder = @{ PROBLEM_FIELD_ORDER() };
  203   } else {
  204     @fieldOrder = @{ SET_FIELD_ORDER() };
  205   }
  206 
  207   my $output = CGI::start_table({border => 0, cellpadding => 1});
  208   if ($forUsers) {
  209     $output .= CGI::Tr(
  210         CGI::th({colspan=>"2"}, " "),
  211       CGI::th({colspan=>"1"}, "Individual set dates"),
  212       CGI::th({}, "Class set dates"),
  213     );
  214   }
  215 
  216   foreach my $field (@fieldOrder) {
  217     my %properties = %{ FIELD_PROPERTIES()->{$field} };
  218     unless ($properties{type} eq "hidden") {
  219       $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $field)]));
  220     }
  221   }
  222 
  223   if (defined $problemID) {
  224     #my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID);
  225     my $problemRecord = $userRecord; # we get this from the caller, hopefully
  226     $output .= CGI::Tr({}, CGI::td({}, ["","Attempts", ($problemRecord->num_correct || 0) + ($problemRecord->num_incorrect || 0)])) if $forOneUser;
  227   }
  228   $output .= CGI::end_table();
  229 
  230   return $output;
  231 }
  232 
  233 # Returns a list of information and HTML widgets
  234 # for viewing and editing the specified db fields
  235 # if only the setID is included, it creates a list of set information
  236 # if the problemID is included, it creates a list of problem information
  237 sub FieldHTML {
  238   my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord, $field) = @_;
  239 
  240   my $r = $self->r;
  241   my $db = $r->db;
  242   my @editForUser = $r->param('editForUser');
  243   my $forUsers    = scalar(@editForUser);
  244   my $forOneUser  = $forUsers == 1;
  245 
  246   #my ($globalRecord, $userRecord, $mergedRecord);
  247   #if (defined $problemID) {
  248   # $globalRecord = $db->getGlobalProblem($setID, $problemID);
  249   # $userRecord = $db->getUserProblem($userID, $setID, $problemID);
  250   # #$mergedRecord = $db->getMergedProblem($userID, $setID, $problemID); # never used --sam
  251   #} else {
  252   # $globalRecord = $db->getGlobalSet($setID);
  253   # $userRecord = $db->getUserSet($userID, $setID);
  254   # #$mergedRecord = $db->getMergedSet($userID, $setID); # never user --sam
  255   #}
  256 
  257   return "No data exists for set $setID and problem $problemID" unless $globalRecord;
  258   return "No user specific data exists for user $userID" if $forOneUser and $globalRecord and not $userRecord;
  259 
  260   my %properties = %{ FIELD_PROPERTIES()->{$field} };
  261   my %labels = %{ $properties{labels} };
  262   return "" if $properties{type} eq "hidden";
  263   return "" if $properties{override} eq "one" && not $forOneUser;
  264   return "" if $properties{override} eq "none" && not $forOneUser;
  265   return "" if $properties{override} eq "all" && $forUsers;
  266 
  267   my $edit = ($properties{type} eq "edit") && ($properties{override} ne "none");
  268   my $choose = ($properties{type} eq "choose") && ($properties{override} ne "none");
  269 
  270   my $globalValue = $globalRecord->{$field};
  271   $globalValue = $globalValue ? ($labels{$globalValue || ""} || $globalValue) : "";
  272   my $userValue = $userRecord->{$field};
  273   $userValue = $userValue ? ($labels{$userValue || ""} || $userValue) : "";
  274 
  275   if ($field =~ /_date/) {
  276     $globalValue = $self->formatDateTime($globalValue) if $globalValue;
  277     $userValue = $self->formatDateTime($userValue) if $userValue;
  278   }
  279 
  280   # check to make sure that a given value can be overridden
  281   my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() });
  282   my $check = $canOverride{$field};
  283 
  284   # $recordType is a shorthand in the return statement for problem or set
  285   # $recordID is a shorthand in the return statement for $problemID or $setID
  286   my $recordType = "";
  287   my $recordID = "";
  288   if (defined $problemID) {
  289     $recordType = "problem";
  290     $recordID = $problemID;
  291   } else {
  292     $recordType = "set";
  293     $recordID = $setID;
  294   }
  295 
  296   # $inputType contains either an input box or a popup_menu for changing a given db field
  297   my $inputType = "";
  298   if ($edit) {
  299     $inputType = CGI::input({
  300         name => "$recordType.$recordID.$field",
  301         value => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userValue : $globalValue),
  302         size => $properties{size} || 5,
  303     });
  304   } elsif ($choose) {
  305     # Note that in popup menus, you're almost guaranteed to have the choices hashed to labels in %properties
  306     # but $userValue and and $globalValue are the values in the hash not the keys
  307     # so we have to use the actual db record field values to select our default here.
  308     $inputType = CGI::popup_menu({
  309         name => "$recordType.$recordID.$field",
  310         values => $properties{choices},
  311         labels => \%labels,
  312         default => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userRecord->$field : $globalRecord->$field),
  313     });
  314   }
  315 
  316   return (($forUsers && $edit && $check) ? CGI::checkbox({
  317         type => "checkbox",
  318         name => "$recordType.$recordID.$field.override",
  319         label => "",
  320         value => $field,
  321         checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne "" ? 1 : 0),
  322     }) : "",
  323     $properties{name},
  324     $inputType,
  325     $forUsers ? " $globalValue" : "",
  326   );
  327 }
  328 
  329 # creates a popup menu of all possible problem numbers (for possible rearranging)
  330 sub problem_number_popup {
  331   my $num = shift;
  332   my $total = shift;
  333   return (CGI::popup_menu(-name => "problem_num_$num",
  334         -values => [1..$total],
  335         -default => $num));
  336 }
  337 
  338 # handles rearrangement necessary after changes to problem ordering
  339 sub handle_problem_numbers {
  340   my $newProblemNumbersref = shift;
  341   my %newProblemNumbers = %$newProblemNumbersref;
  342   my $maxNum = shift;
  343   my $db = shift;
  344   my $setID = shift;
  345   my $force = shift || 0;
  346   my @sortme=();
  347   my ($j, $val);
  348 
  349   foreach $j (keys %newProblemNumbers) {
  350     # what happens our first time on this page
  351     return "" if (not defined $newProblemNumbers{"$j"});
  352     if ($newProblemNumbers{"$j"} != $j) {
  353       $force = 1;
  354       $val = 1000 * $newProblemNumbers{$j} - $j;
  355     } else {
  356       $val = 1000 * $newProblemNumbers{$j};
  357     }
  358     push @sortme, [$j, $val];
  359     $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j);
  360     die "global $j for set $setID not found." unless $newProblemNumbers{$j};
  361   }
  362 
  363   return "" unless $force;
  364 
  365   @sortme = sort {$a->[1] <=> $b->[1]} @sortme;
  366   # now, for global and each user with this set, loop through problem list
  367   #   get all of the problem records
  368   # assign new problem numbers
  369   # loop - if number is new, put the problem record
  370   # print "Sorted to get ". join(', ', map {$_->[0] } @sortme) ."<p>\n";
  371 
  372 
  373   # Now, three stages.  First global values
  374 
  375   for ($j = 0; $j < scalar @sortme; $j++) {
  376     if($sortme[$j]->[0] == $j + 1) {
  377       # do nothing
  378     } elsif (not defined $newProblemNumbers{$j + 1}) {
  379       $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1);
  380       $db->addGlobalProblem($newProblemNumbers{$sortme[$j]->[0]});
  381     } else {
  382       $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1);
  383       $db->putGlobalProblem($newProblemNumbers{$sortme[$j]->[0]});
  384     }
  385   }
  386 
  387   my @setUsers = $db->listSetUsers($setID);
  388   my (@problist, $user);
  389   my $globalUserID = $db->{set}->{params}->{globalUserID} || '';
  390 
  391   foreach $user (@setUsers) {
  392     # if this is gdbm, the global user has been taken care of above.
  393     # we can't do it again.  This relies on the global user not having
  394     # a blank name.
  395     next if $globalUserID eq $user;
  396     for $j (keys %newProblemNumbers) {
  397       $problist[$j] = $db->getUserProblem($user, $setID, $j);
  398       die " problem $j for set $setID and effective user $user not found"
  399         unless $problist[$j];
  400     }
  401     # ok, now we have all problem data for $user
  402     for($j = 0; $j < scalar @sortme; $j++) {
  403       if ($sortme[$j]->[0] == $j + 1) {
  404         # do nothing
  405       } elsif (not defined $newProblemNumbers{$j + 1}) {
  406         $problist[$sortme[$j]->[0]]->problem_id($j + 1);
  407         $db->addUserProblem($problist[$sortme[$j]->[0]]);
  408       } else {
  409         $problist[$sortme[$j]->[0]]->problem_id($j + 1);
  410         $db->putUserProblem($problist[$sortme[$j]->[0]]);
  411       }
  412     }
  413   }
  414 
  415 
  416   foreach ($j = scalar @sortme; $j < $maxNum; $j++) {
  417     if (defined $newProblemNumbers{$j + 1}) {
  418       $db->deleteGlobalProblem($setID, $j+1);
  419     }
  420   }
  421 
  422   return join(', ', map {$_->[0]} @sortme);
  423 }
  424 
  425 # swap index given with next bigger index
  426 # leftover from when we had up/down buttons
  427 # maybe we will bring them back
  428 
  429 #sub moveme {
  430 # my $index = shift;
  431 # my $db = shift;
  432 # my $setID = shift;
  433 # my (@problemIDList) = @_;
  434 # my ($prob1, $prob2, $prob);
  435 #
  436 # foreach my $problemID (@problemIDList) {
  437 #   my $problemRecord = $db->getGlobalProblem($setID, $problemID); # checked
  438 #   die "global $problemID for set $setID not found." unless $problemRecord;
  439 #   if ($problemRecord->problem_id == $index) {
  440 #     $prob1 = $problemRecord;
  441 #   } elsif ($problemRecord->problem_id == $index + 1) {
  442 #     $prob2 = $problemRecord;
  443 #   }
  444 # }
  445 # if (not defined $prob1 or not defined $prob2) {
  446 #   die "cannot find problem $index or " . ($index + 1);
  447 # }
  448 #
  449 # $prob1->problem_id($index + 1);
  450 # $prob2->problem_id($index);
  451 # $db->putGlobalProblem($prob1);
  452 # $db->putGlobalProblem($prob2);
  453 #
  454 # my @setUsers = $db->listSetUsers($setID);
  455 #
  456 # my $user;
  457 # foreach $user (@setUsers) {
  458 #   $prob1 = $db->getUserProblem($user, $setID, $index); #checked
  459 #   die " problem $index for set $setID and effective user $user not found"
  460 #     unless $prob1;
  461 #   $prob2 = $db->getUserProblem($user, $setID, $index+1); #checked
  462 #   die " problem $index for set $setID and effective user $user not found"
  463 #     unless $prob2;
  464 #       $prob1->problem_id($index+1);
  465 #   $prob2->problem_id($index);
  466 #   $db->putUserProblem($prob1);
  467 #   $db->putUserProblem($prob2);
  468 # }
  469 #}
  470 
  471 # primarily saves any changes into the correct set or problem records (global vs user)
  472 # also deals with deleting or rearranging problems
  473 sub initialize {
  474   my ($self)    = @_;
  475   my $r         = $self->r;
  476   my $db        = $r->db;
  477   my $ce        = $r->ce;
  478   my $authz     = $r->authz;
  479   my $user      = $r->param('user');
  480   my $setID   = $r->urlpath->arg("setID");
  481   my $setRecord = $db->getGlobalSet($setID); # checked
  482   die "global set $setID  not found." unless $setRecord;
  483 
  484   $self->{set}  = $setRecord;
  485   my @editForUser = $r->param('editForUser');
  486   # some useful booleans
  487   my $forUsers   = scalar(@editForUser);
  488   my $forOneUser = $forUsers == 1;
  489 
  490   # Check permissions
  491   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  492   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  493 
  494 
  495   my %properties = %{ FIELD_PROPERTIES() };
  496 
  497   # takes a hash of hashes and inverts it
  498   my %undoLabels;
  499   foreach my $key (keys %properties) {
  500     %{ $undoLabels{$key} } = map { $properties{$key}->{labels}->{$_} => $_ } keys %{ $properties{$key}->{labels} };
  501   }
  502 
  503   # Unfortunately not everyone uses Javascript enabled browsers so
  504   # we must fudge the information coming from the ComboBoxes
  505   # Since the textfield and menu both have the same name, we get an array of two elements
  506   # We then reset the param to the first if its not-empty or the second (empty or not).
  507   foreach ( @{ HEADER_ORDER() } ) {
  508     my @values = $r->param("set.$setID.$_");
  509     my $value = $values[0] || $values[1] || "";
  510     $r->param("set.$setID.$_", $value);
  511   }
  512 
  513   #####################################################################
  514   # Check date information
  515   #####################################################################
  516 
  517   my ($open_date, $due_date, $answer_date);
  518   my $error = 0;
  519   if (defined $r->param('submit_changes')) {
  520 
  521     my $od_param = $r->param("set.$setID.open_date");
  522     my $dd_param = $r->param("set.$setID.due_date");
  523     my $ad_param = $r->param("set.$setID.answer_date");
  524     #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam
  525 
  526     $open_date = $od_param ? $self->parseDateTime($od_param) : $setRecord->open_date;
  527     $due_date = $dd_param ? $self->parseDateTime($dd_param) : $setRecord->due_date;
  528     $answer_date = $ad_param ? $self->parseDateTime($ad_param) : $setRecord->answer_date;
  529 
  530     if ($answer_date < $due_date || $answer_date < $open_date) {
  531       $self->addbadmessage("Answers cannot be made available until on or after the due date!");
  532       $error = $r->param('submit_changes');
  533     }
  534 
  535     if ($due_date < $open_date) {
  536       $self->addbadmessage("Answers cannot be due until on or after the open date!");
  537       $error = $r->param('submit_changes');
  538     }
  539 
  540     if ($error) {
  541       $self->addbadmessage("No changes were saved!");
  542     }
  543   }
  544 
  545 
  546   if (defined $r->param('submit_changes') && !$error) {
  547 
  548     #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam
  549 
  550     #####################################################################
  551     # Save general set information (including headers)
  552     #####################################################################
  553 
  554     if ($forUsers) {
  555       my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser);
  556       foreach my $record (@userRecords) {
  557         foreach my $field ( @{ SET_FIELDS() } ) {
  558           next unless canChange($forUsers, $field);
  559 
  560           my $override = $r->param("set.$setID.$field.override");
  561           if (defined $override && $override eq $field) {
  562 
  563             my $param = $r->param("set.$setID.$field");
  564             $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  565             $param = $undoLabels{$field}->{$param} || $param;
  566             if ($field =~ /_date/) {
  567               $param = $self->parseDateTime($param);
  568             }
  569             $record->$field($param);
  570           } else {
  571             $record->$field(undef);
  572           }
  573         }
  574         $db->putUserSet($record);
  575       }
  576     } else {
  577       foreach my $field ( @{ SET_FIELDS() } ) {
  578         next unless canChange($forUsers, $field);
  579 
  580         my $param = $r->param("set.$setID.$field");
  581         $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  582         $param = $undoLabels{$field}->{$param} || $param;
  583         if ($field =~ /_date/) {
  584           $param = $self->parseDateTime($param);
  585         }
  586         $setRecord->$field($param);
  587       }
  588       $db->putGlobalSet($setRecord);
  589     }
  590 
  591 
  592     #####################################################################
  593     # Save problem information
  594     #####################################################################
  595 
  596     my @problemIDs = sort { $a <=> $b } $db->listGlobalProblems($setID);;
  597     my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs);
  598     foreach my $problemRecord (@problemRecords) {
  599       my $problemID = $problemRecord->problem_id;
  600       die "Global problem $problemID for set $setID not found." unless $problemRecord;
  601 
  602       if ($forUsers) {
  603         # Since we're editing for specific users, we don't allow the GlobalProblem record to be altered on that same page
  604         # So we only need to make changes to the UserProblem record and only then if we are overriding a value
  605         # in the GlobalProblem record or for fields unique to the UserProblem record.
  606 
  607         my @userIDs = @editForUser;
  608         my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
  609         my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
  610         foreach my $record (@userProblemRecords) {
  611 
  612           my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
  613           foreach my $field ( @{ PROBLEM_FIELDS() } ) {
  614             next unless canChange($forUsers, $field);
  615 
  616             my $override = $r->param("problem.$problemID.$field.override");
  617             if (defined $override && $override eq $field) {
  618 
  619               my $param = $r->param("problem.$problemID.$field");
  620               $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  621               $param = $undoLabels{$field}->{$param} || $param;
  622               $changed ||= changed($record->$field, $param);
  623               $record->$field($param);
  624             } else {
  625               $changed ||= changed($record->$field, undef);
  626               $record->$field(undef);
  627             }
  628 
  629           }
  630 
  631           foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) {
  632             next unless canChange($forUsers, $field);
  633 
  634             my $param = $r->param("problem.$problemID.$field");
  635             $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  636             $param = $undoLabels{$field}->{$param} || $param;
  637             $changed ||= changed($record->$field, $param);
  638             $record->$field($param);
  639           }
  640           $db->putUserProblem($record) if $changed;
  641         }
  642       } else {
  643         # Since we're editing for ALL set users, we will make changes to the GlobalProblem record.
  644         # We may also have instances where a field is unique to the UserProblem record but we want
  645         # all users to (at least initially) have the same value
  646 
  647         # this only edits a globalProblem record
  648         my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
  649         foreach my $field ( @{ PROBLEM_FIELDS() } ) {
  650           next unless canChange($forUsers, $field);
  651 
  652           my $param = $r->param("problem.$problemID.$field");
  653           $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  654           $param = $undoLabels{$field}->{$param} || $param;
  655           $changed ||= changed($problemRecord->$field, $param);
  656           $problemRecord->$field($param);
  657         }
  658         $db->putGlobalProblem($problemRecord) if $changed;
  659 
  660 
  661         # sometimes (like for status) we might want to change an attribute in
  662         # the userProblem record for every assigned user
  663         # However, since this data is stored in the UserProblem records,
  664         # it won't be displayed once its been changed and if you hit "Save Changes" again
  665         # it gets erased
  666 
  667         # So we'll enforce that there be something worth putting in all the UserProblem records
  668         # This also will make hitting "Save Changes" on the global page MUCH faster
  669         my %useful;
  670         foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) {
  671           my $param = $r->param("problem.$problemID.$field");
  672           $useful{$field} = 1 if defined $param and $param ne "";
  673         }
  674 
  675         if (keys %useful) {
  676           my @userIDs = $db->listProblemUsers($setID, $problemID);
  677           my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
  678           my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
  679           foreach my $record (@userProblemRecords) {
  680             my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
  681             foreach my $field ( keys %useful ) {
  682               next unless canChange($forUsers, $field);
  683 
  684               my $param = $r->param("problem.$problemID.$field");
  685               $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
  686               $param = $undoLabels{$field}->{$param} || $param;
  687               $changed ||= changed($record->$field, $param);
  688               $record->$field($param);
  689             }
  690             $db->putUserProblem($record) if $changed;
  691           }
  692         }
  693       }
  694     }
  695 
  696     # Delete all problems marked for deletion
  697     foreach my $problemID ($r->param('deleteProblem')) {
  698       $db->deleteGlobalProblem($setID, $problemID);
  699     }
  700 
  701     # Sets the specified header to "" so that the default file will get used.
  702     foreach my $header ($r->param('defaultHeader')) {
  703       $setRecord->$header("");
  704     }
  705 
  706     # Mark the specified problems as correct for all users
  707     foreach my $problemID ($r->param('markCorrect')) {
  708       my @userProblemIDs = map { [$_, $setID, $problemID] } ($forUsers ? @editForUser : $db->listProblemUsers($setID, $problemID));
  709       my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
  710       foreach my $record (@userProblemRecords) {
  711 $self->addbadmessage($record->user_id);
  712         if (defined $record && ($record->status eq "" || $record->status < 1)) {
  713           $record->status(1);
  714           $record->attempted(1);
  715           $db->putUserProblem($record);
  716         }
  717       }
  718     }
  719   }
  720 
  721 # Leftover code from when there were up/down buttons
  722 
  723 # } else {
  724 #   # Look for up and down buttons
  725 #   my $index = 2;
  726 #   while ($index <= scalar @problemList) {
  727 #     if (defined $r->param("move.up.$index.x")) {
  728 #       moveme($index-1, $db, $setID, @problemList);
  729 #     }
  730 #     $index++;
  731 #   }
  732 #   $index = 1;
  733 #
  734 #   while ($index < scalar @problemList) {
  735 #     if (defined $r->param("move.down.$index.x")) {
  736 #       moveme($index, $db, $setID, @problemList);
  737 #     }
  738 #     $index++;
  739 #   }
  740 # }
  741 
  742 
  743   # This erases any sticky fields if the user saves changes, resets the form, or reorders problems
  744   # It may not be obvious why this is necessary when saving changes or reordering problems
  745   #   but when the problems are reorder the param problem.1.source_file needs to be the source
  746   # file of the problem that is NOW #1 and not the problem that WAS #1.
  747   unless (defined $r->param('refresh')) {
  748 
  749     # reset all the parameters dealing with set/problem/header information
  750     # if the current naming scheme is changed/broken, this could reek havoc
  751     # on all kinds of things
  752     foreach my $param ($r->param) {
  753       $r->param($param, "") if $param =~ /^(set|problem|header)\./;
  754     }
  755   }
  756 
  757 }
  758 
  759 # helper method for debugging
  760 sub definedness ($) {
  761   my ($variable) = @_;
  762 
  763   return "undefined" unless defined $variable;
  764   return "empty" unless $variable ne "";
  765   return $variable;
  766 }
  767 
  768 # helper method for checking if two things are different
  769 # the return values will usually be thrown away, but they could be useful for debugging
  770 sub changed ($$) {
  771   my ($first, $second) = @_;
  772 
  773   return "def/undef" if defined $first and not defined $second;
  774   return "undef/def" if not defined $first and defined $second;
  775   return "" if not defined $first and not defined $second;
  776   return "ne" if $first ne $second;
  777   return "";  # if they're equal, there's no change
  778 }
  779 
  780 # helper method that determines for how many users at a time a field can be changed
  781 #   none means it can't be changed for anyone
  782 #   any means it can be changed for anyone
  783 #   one means it can ONLY be changed for one at a time. (eg problem_seed)
  784 #   all means it can ONLY be changed for all at a time. (eg set_header)
  785 sub canChange ($$) {
  786   my ($forUsers, $field) = @_;
  787 
  788   my %properties = %{ FIELD_PROPERTIES() };
  789   my $forOneUser = $forUsers == 1;
  790 
  791   my $howManyCan = $properties{$field}->{override};
  792 
  793   return 0 if $howManyCan eq "none";
  794   return 1 if $howManyCan eq "any";
  795   return 1 if $howManyCan eq "one" && $forOneUser;
  796   return 1 if $howManyCan eq "all" && !$forUsers;
  797   return 0; # FIXME: maybe it should default to 1?
  798 }
  799 
  800 # helper method that determines if a file is valid and returns a pretty error message
  801 sub checkFile ($) {
  802   my ($self, $file) = @_;
  803 
  804   my $r = $self->r;
  805   my $ce = $r->ce;
  806 
  807   return "No source file specified" unless $file;
  808   $file = $ce->{courseDirs}->{templates} . '/' . $file unless $file =~ m|^/|;
  809 
  810   my $text = "This source file ";
  811   my $fileError;
  812   return "" if -e $file && -f $file && -r $file;
  813   return $text . "is not readable!" if -e $file && -f $file;
  814   return $text . "is a directory!" if -d $file;
  815   return $text . "does not exist!" unless -e $file;
  816   return $text . "is not a plain file!";
  817 }
  818 
  819 # Creates two separate tables, first of the headers, and the of the problems in a given set
  820 # If one or more users are specified in the "editForUser" param, only the data for those users
  821 # becomes editable, not all the data
  822 sub body {
  823 
  824   my ($self)      = @_;
  825   my $r           = $self->r;
  826   my $db          = $r->db;
  827   my $ce          = $r->ce;
  828   my $authz       = $r->authz;
  829   my $userID      = $r->param('user');
  830   my $urlpath     = $r->urlpath;
  831   my $courseID    = $urlpath->arg("courseID");
  832   my $setID       = $urlpath->arg("setID");
  833   my $setRecord   = $db->getGlobalSet($setID) or die "No record for global set $setID.";
  834 
  835   my $userRecord = $db->getUser($userID) or die "No record for user $userID.";
  836   # Check permissions
  837   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  838     unless $authz->hasPermissions($userRecord->user_id, "access_instructor_tools");
  839 
  840   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.")
  841     unless $authz->hasPermissions($userRecord->user_id, "modify_problem_sets");
  842 
  843   my @editForUser = $r->param('editForUser');
  844 
  845   # Check that every user that we're editing for has a valid UserSet
  846   my @assignedUsers;
  847   my @unassignedUsers;
  848   if (scalar @editForUser) {
  849     foreach my $ID (@editForUser) {
  850       if ($db->getUserSet($ID, $setID)) {
  851         unshift @assignedUsers, $ID;
  852       } else {
  853         unshift @unassignedUsers, $ID;
  854       }
  855     }
  856     @editForUser = @assignedUsers;
  857     $r->param("editForUser", \@editForUser);
  858 
  859     if (scalar @editForUser && scalar @unassignedUsers) {
  860       print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers)));
  861     } elsif (scalar @editForUser == 0) {
  862       print CGI::div({class=>"ResultsWithError"}, "None of the selected users are assigned to this set: " . CGI::b(join(", ", @unassignedUsers)));
  863       print CGI::div({class=>"ResultsWithError"}, "Global set data will be shown instead of user specific data");
  864     }
  865   }
  866 
  867   # some useful booleans
  868   my $forUsers    = scalar(@editForUser);
  869   my $forOneUser  = $forUsers == 1;
  870 
  871   # If you're editing for users, initially their records will be different but
  872   # if you make any changes to them they will be the same.
  873   # if you're editing for one user, the problems shown should be his/hers
  874   my $userToShow        = $forUsers ? $editForUser[0] : $userID;
  875 
  876   my $userCount        = $db->listUsers();
  877   my $setCount         = $db->listGlobalSets() if $forOneUser;
  878   my $setUserCount     = $db->countSetUsers($setID);
  879   my $userSetCount     = $db->countUserSets($editForUser[0]) if $forOneUser;
  880 
  881 
  882   my $editUsersAssignedToSetURL = $self->systemLink(
  883         $urlpath->newFromModule(
  884                 "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
  885                   courseID => $courseID, setID => $setID));
  886   my $editSetsAssignedToUserURL = $self->systemLink(
  887         $urlpath->newFromModule(
  888                 "WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser",
  889                   courseID => $courseID, userID => $editForUser[0])) if $forOneUser;
  890 
  891 
  892   my $setDetailPage  = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID);
  893   my $setDetailURL   = $self->systemLink($setDetailPage,authen=>0);
  894 
  895 
  896   my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount));
  897   my $setCountMessage = CGI::a({href=>$editSetsAssignedToUserURL}, $self->setCountMessage($userSetCount, $setCount)) if $forOneUser;
  898 
  899   $userCountMessage = "The set $setID is assigned to " . $userCountMessage . ".";
  900   $setCountMessage  = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser;
  901 
  902   if ($forUsers) {
  903     print CGI::p("$userCountMessage  Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
  904     if ($forOneUser) {
  905       print CGI::p($setCountMessage);
  906     }
  907   } else {
  908     print CGI::p($userCountMessage);
  909   }
  910 
  911   # handle renumbering of problems if necessary
  912   print CGI::a({name=>"problems"});
  913 
  914   my %newProblemNumbers = ();
  915   my $maxProblemNumber = -1;
  916   for my $jj (sort { $a <=> $b } $db->listGlobalProblems($setID)) {
  917     $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj);
  918     $maxProblemNumber = $jj if $jj > $maxProblemNumber;
  919   }
  920 
  921   my $forceRenumber = $r->param('force_renumber') || 0;
  922   handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setID, $forceRenumber) unless defined $r->param('undo_changes');
  923 
  924   my %properties = %{ FIELD_PROPERTIES() };
  925 
  926   my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()};
  927   my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}};
  928   push @active_modes, 'None';
  929   my $default_header_mode = $r->param('header.displaymode') || 'None';
  930   my $default_problem_mode = $r->param('problem.displaymode') || 'None';
  931 
  932   #####################################################################
  933   # Browse available header/problem files
  934   #####################################################################
  935 
  936   my $templates = $r->ce->{courseDirs}->{templates};
  937   my %probLibs = %{ $r->ce->{courseFiles}->{problibs} };
  938   my $skip = join("|", keys %probLibs);
  939 
  940   my @headerFileList = listFilesRecursive(
  941     $templates,
  942     qr/header.*\.pg$/i,     # match these files
  943     qr/^(?:$skip|CVS)$/,  # prune these directories
  944     0,        # match against file name only
  945     1,        # prune against path relative to $templates
  946   );
  947 
  948   # this just takes too much time to search
  949 # my @problemFileList = listFilesRecursive(
  950 #   $templates,
  951 #   qr/\.pg$/i,     # problem files don't say problem
  952 #   qr/^(?:$skip|CVS)$/,  # prune these directories
  953 #   0,        # match against file name only
  954 #   1,        # prune against path relative to $templates
  955 # );
  956 
  957   # Display a useful warning message
  958   if ($forUsers) {
  959     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ONLY the student" .
  960           ($forOneUser ? "" : "s") . " listed above."));
  961   } else {
  962     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ALL students."));
  963   }
  964 
  965   print CGI::start_form({method=>"POST", action=>$setDetailURL});
  966   print $self->hiddenEditForUserFields(@editForUser);
  967   print $self->hidden_authen_fields;
  968   print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
  969   print CGI::input({type=>"submit", name=>"undo_changes", value => "Reset Form"});
  970 
  971   # spacing
  972   print CGI::p();
  973 
  974   #####################################################################
  975   # Display general set information
  976   #####################################################################
  977 
  978   print CGI::start_table({border=>1, cellpadding=>4});
  979   print CGI::Tr({}, CGI::th({}, [
  980     "General Information",
  981   ]));
  982 
  983   # this is kind of a hack -- we need to get a user record here, so we can
  984   # pass it to FieldTable, so FieldTable can pass it to FieldHTML, so
  985   # FieldHTML doesn't have to fetch it itself.
  986   my $userSetRecord = $db->getUserSet($userToShow, $setID);
  987 
  988   print CGI::Tr({}, CGI::td({}, [
  989     $self->FieldTable($userToShow, $setID, undef, $setRecord, $userSetRecord),
  990   ]));
  991   print CGI::end_table();
  992 
  993   # spacing
  994   print CGI::p();
  995 
  996 
  997   #####################################################################
  998   # Display header information
  999   #####################################################################
 1000   my @headers = @{ HEADER_ORDER() };
 1001   my %headerModules = (set_header => 'problem_list', hardcopy_header => 'hardcopy_preselect_set');
 1002   my %headerDefaults = (set_header => $ce->{webworkFiles}->{screenSnippets}->{setHeader}, hardcopy_header => $ce->{webworkFiles}->{hardcopySnippets}->{setHeader});
 1003   my @headerFiles = map { $setRecord->{$_} } @headers;
 1004   if (scalar @headers and not $forUsers) {
 1005 
 1006     print CGI::start_table({border=>1, cellpadding=>4});
 1007     print CGI::Tr({}, CGI::th({}, [
 1008       "Headers",
 1009 #     "Data",
 1010       "Display&nbsp;Mode:&nbsp;" .
 1011       CGI::popup_menu(-name => "header.displaymode", -values => \@active_modes, -default => $default_header_mode) . '&nbsp;'.
 1012       CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
 1013     ]));
 1014 
 1015     my %header_html;
 1016 
 1017     my %error;
 1018     foreach my $header (@headers) {
 1019       my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header};
 1020 
 1021       $error{$header} = $self->checkFile($headerFile);
 1022       unless ($error{$header}) {
 1023         my @temp = renderProblems(  r=> $r,
 1024                 user => $db->getUser($userToShow),
 1025                 displayMode=> $default_header_mode,
 1026                 problem_number=> 0,
 1027                 this_set => $db->getMergedSet($userToShow, $setID),
 1028                 problem_list => [$headerFile],
 1029         );
 1030         $header_html{$header} = $temp[0];
 1031       }
 1032     }
 1033 
 1034     foreach my $header (@headers) {
 1035 
 1036       my $editHeaderPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => 0 });
 1037       my $editHeaderLink = $self->systemLink($editHeaderPage, params => { file_type => $header, make_local_copy => 1 });
 1038 
 1039       my $viewHeaderPage = $urlpath->new(type => $headerModules{$header}, args => { courseID => $courseID, setID => $setID });
 1040       my $viewHeaderLink = $self->systemLink($viewHeaderPage);
 1041 
 1042       print CGI::Tr({}, CGI::td({}, [
 1043         CGI::start_table({border => 0, cellpadding => 0}) .
 1044           CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) .
 1045           CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) .
 1046           CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) .
 1047 #         CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) .
 1048         CGI::end_table(),
 1049 #       "",
 1050 #       CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) .
 1051 #       join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) .
 1052 #               CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}),
 1053 
 1054         comboBox({
 1055           name => "set.$setID.$header",
 1056           request => $r,
 1057           default => $r->param("set.$setID.$header") || $setRecord->{$header},
 1058           multiple => 0,
 1059           values => ["", @headerFileList],
 1060           labels => { "" => "Use Default Header File" },
 1061         }) .
 1062         ($error{$header} ?
 1063           CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error{$header})
 1064           : CGI::div({class=> "RenderSolo"}, $header_html{$header}->{body_text})
 1065         ),
 1066       ]));
 1067     }
 1068 
 1069     print CGI::end_table();
 1070   } else {
 1071     print CGI::p(CGI::b("Screen and Hardcopy set header information can not be overridden for individual students."));
 1072   }
 1073 
 1074   # spacing
 1075   print CGI::p();
 1076 
 1077 
 1078   #####################################################################
 1079   # Display problem information
 1080   #####################################################################
 1081 
 1082   my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID);
 1083 
 1084   # get global problem records for all problems in one go
 1085   my %GlobalProblems;
 1086   my @globalKeypartsRef = map { [$setID, $_] } @problemIDList;
 1087   @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef);
 1088 
 1089   # if needed, get user problem records for all problems in one go
 1090   my (%UserProblems, %MergedProblems);
 1091   if ($forOneUser) {
 1092     my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList;
 1093     @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef);
 1094     @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef);
 1095   }
 1096 
 1097   if (scalar @problemIDList) {
 1098 
 1099     print CGI::start_table({border=>1, cellpadding=>4});
 1100     print CGI::Tr({}, CGI::th({}, [
 1101       "Problems",
 1102       "Data",
 1103       "Display&nbsp;Mode:&nbsp;" .
 1104       CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . '&nbsp;'.
 1105       CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
 1106     ]));
 1107 
 1108     my %shownYet;
 1109     my $repeatFile;
 1110     foreach my $problemID (@problemIDList) {
 1111 
 1112       my $problemRecord;
 1113       if ($forOneUser) {
 1114         #$problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID);
 1115         $problemRecord = $MergedProblems{$problemID}; # already fetched above --sam
 1116       } else {
 1117         #$problemRecord = $db->getGlobalProblem($setID, $problemID);
 1118         $problemRecord = $GlobalProblems{$problemID}; # already fetched above --sam
 1119       }
 1120 
 1121       #$self->addgoodmessage("");
 1122       #$self->addbadmessage($problemRecord->toString());
 1123 
 1124 
 1125       my $editProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
 1126       my $editProblemLink = $self->systemLink($editProblemPage, params => { make_local_copy => 0 });
 1127 
 1128       # FIXME: should we have an "act as" type link here when editing for multiple users?
 1129       my $viewProblemPage = $urlpath->new(type => 'problem_detail', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
 1130       my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)});
 1131 
 1132       my @fields = @{ PROBLEM_FIELDS() };
 1133       push @fields, @{ USER_PROBLEM_FIELDS() } if $forOneUser;
 1134 
 1135       my $problemFile = $r->param("problem.$problemID.source_file") || $problemRecord->source_file;
 1136 
 1137       # warn of repeat problems
 1138       if (defined $shownYet{$problemFile}) {
 1139         $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . ".";
 1140       } else {
 1141         $shownYet{$problemFile} = $problemID;
 1142       }
 1143 
 1144       my $error = $self->checkFile($problemFile);
 1145       my @problem_html;
 1146       unless ($error) {
 1147         @problem_html = renderProblems( r=> $r,
 1148                 user => $db->getUser($userToShow),
 1149                 displayMode=> $default_problem_mode,
 1150                 problem_number=> $problemID,
 1151                 this_set => $db->getMergedSet($userToShow, $setID),
 1152                 problem_seed => $forOneUser ? $problemRecord->problem_seed : 0,
 1153                 problem_list => [$problemRecord->source_file],
 1154         );
 1155       }
 1156 
 1157       print CGI::Tr({}, CGI::td({}, [
 1158         CGI::start_table({border => 0, cellpadding => 1}) .
 1159           CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) .
 1160           CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) .
 1161           CGI::Tr({}, CGI::td({}, CGI::a({href => $viewProblemLink}, "Try it" . ($forOneUser ? " (as $editForUser[0])" : "")))) .
 1162           ($forUsers ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "deleteProblem", value => $problemID, label => "Delete it?"})))) .
 1163 #         CGI::Tr({}, CGI::td({}, "Delete&nbsp;it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) .
 1164           ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) .
 1165         CGI::end_table(),
 1166         $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}),
 1167 # A comprehensive list of problems is just TOO big to be handled well
 1168 #       comboBox({
 1169 #         name => "set.$setID.$problemID",
 1170 #         request => $r,
 1171 #         default => $problemRecord->{problem_id},
 1172 #         multiple => 0,
 1173 #         values => \@problemFileList,
 1174 #       }) .
 1175 
 1176         join ("\n", $self->FieldHTML(
 1177           $userToShow,
 1178           $setID,
 1179           $problemID,
 1180           $GlobalProblems{$problemID}, # pass previously fetched global record to FieldHTML --sam
 1181           $UserProblems{$problemID}, # pass previously fetched user record to FieldHTML --sam
 1182           "source_file"
 1183         )) .
 1184                 CGI::br() .
 1185           ($error ?
 1186             CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error)
 1187             : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text})
 1188           ) .
 1189           ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''),
 1190       ]));
 1191     }
 1192 
 1193     print CGI::end_table();
 1194     print CGI::checkbox({
 1195           label=> "Force problems to be numbered consecutively from one",
 1196           name=>"force_renumber", value=>"1"}),
 1197 
 1198       CGI::br();
 1199     print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
 1200     print CGI::input({type=>"submit", name=>"handle_numbers", value=>"Reorder problems only"}) . "(Any unsaved changes will be lost.)";
 1201     print CGI::p(<<HERE);
 1202 Any time problem numbers are intentionally changed, the problems will
 1203 always be renumbered consecutively, starting from one.  When deleting
 1204 problems, gaps will be left in the numbering unless the box above is
 1205 checked.
 1206 HERE
 1207                 print CGI::p("It is before the open date.  You probably want to renumber the problems if you are deleting some from the middle.") if ($setRecord->open_date>time());
 1208     print CGI::p("When changing problem numbers, we will move
 1209  the problem to be ", CGI::em("before"), " the chosen number.");
 1210 
 1211   } else {
 1212     print CGI::p(CGI::b("This set doesn't contain any problems yet."));
 1213   }
 1214 
 1215   print CGI::end_form();
 1216 
 1217   return "";
 1218 }
 1219 
 1220 1;
 1221 
 1222 =head1 AUTHOR
 1223 
 1224 Written by Robert Van Dam, toenail (at) cif.rochester.edu
 1225 
 1226 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9