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

View of /branches/rel-2-1-patches/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3167 - (download) (as text) (annotate)
Tue Feb 15 05:11:48 2005 UTC (8 years, 4 months ago) by gage
File size: 45791 byte(s)
Added changes to allow create new blank problem to work.  These include significant
cleanup of PGproblemEditor.pm

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9