[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetDetail.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3061 - (download) (as text) (annotate)
Tue Dec 21 17:47:57 2004 UTC (8 years, 5 months ago) by toenail
File size: 45730 byte(s)
Fixed minor display problem where empty (not-overriden) date values
were being displayed as 12/31/1969

Closes #748

    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)\./;
  762     }
  763   }
  764 
  765 }
  766 
  767 # helper method for debugging
  768 sub definedness ($) {
  769   my ($variable) = @_;
  770 
  771   return "undefined" unless defined $variable;
  772   return "empty" unless $variable ne "";
  773   return $variable;
  774 }
  775 
  776 # helper method for checking if two things are different
  777 # the return values will usually be thrown away, but they could be useful for debugging
  778 sub changed ($$) {
  779   my ($first, $second) = @_;
  780 
  781   return "def/undef" if defined $first and not defined $second;
  782   return "undef/def" if not defined $first and defined $second;
  783   return "" if not defined $first and not defined $second;
  784   return "ne" if $first ne $second;
  785   return "";  # if they're equal, there's no change
  786 }
  787 
  788 # helper method that determines for how many users at a time a field can be changed
  789 #   none means it can't be changed for anyone
  790 #   any means it can be changed for anyone
  791 #   one means it can ONLY be changed for one at a time. (eg problem_seed)
  792 #   all means it can ONLY be changed for all at a time. (eg set_header)
  793 sub canChange ($$) {
  794   my ($forUsers, $field) = @_;
  795 
  796   my %properties = %{ FIELD_PROPERTIES() };
  797   my $forOneUser = $forUsers == 1;
  798 
  799   my $howManyCan = $properties{$field}->{override};
  800 
  801   return 0 if $howManyCan eq "none";
  802   return 1 if $howManyCan eq "any";
  803   return 1 if $howManyCan eq "one" && $forOneUser;
  804   return 1 if $howManyCan eq "all" && !$forUsers;
  805   return 0; # FIXME: maybe it should default to 1?
  806 }
  807 
  808 # helper method that determines if a file is valid and returns a pretty error message
  809 sub checkFile ($) {
  810   my ($self, $file) = @_;
  811 
  812   my $r = $self->r;
  813   my $ce = $r->ce;
  814 
  815   return "No source file specified" unless $file;
  816   $file = $ce->{courseDirs}->{templates} . '/' . $file unless $file =~ m|^/|;
  817 
  818   my $text = "This source file ";
  819   my $fileError;
  820   return "" if -e $file && -f $file && -r $file;
  821   return $text . "is not readable!" if -e $file && -f $file;
  822   return $text . "is a directory!" if -d $file;
  823   return $text . "does not exist!" unless -e $file;
  824   return $text . "is not a plain file!";
  825 }
  826 
  827 # Creates two separate tables, first of the headers, and the of the problems in a given set
  828 # If one or more users are specified in the "editForUser" param, only the data for those users
  829 # becomes editable, not all the data
  830 sub body {
  831 
  832   my ($self)      = @_;
  833   my $r           = $self->r;
  834   my $db          = $r->db;
  835   my $ce          = $r->ce;
  836   my $authz       = $r->authz;
  837   my $userID      = $r->param('user');
  838   my $urlpath     = $r->urlpath;
  839   my $courseID    = $urlpath->arg("courseID");
  840   my $setID       = $urlpath->arg("setID");
  841   my $setRecord   = $db->getGlobalSet($setID) or die "No record for global set $setID.";
  842 
  843   my $userRecord = $db->getUser($userID) or die "No record for user $userID.";
  844   # Check permissions
  845   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  846     unless $authz->hasPermissions($userRecord->user_id, "access_instructor_tools");
  847 
  848   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.")
  849     unless $authz->hasPermissions($userRecord->user_id, "modify_problem_sets");
  850 
  851   my @editForUser = $r->param('editForUser');
  852 
  853   # Check that every user that we're editing for has a valid UserSet
  854   my @assignedUsers;
  855   my @unassignedUsers;
  856   if (scalar @editForUser) {
  857     foreach my $ID (@editForUser) {
  858       if ($db->getUserSet($ID, $setID)) {
  859         unshift @assignedUsers, $ID;
  860       } else {
  861         unshift @unassignedUsers, $ID;
  862       }
  863     }
  864     @editForUser = @assignedUsers;
  865     $r->param("editForUser", \@editForUser);
  866 
  867     if (scalar @editForUser && scalar @unassignedUsers) {
  868       print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers)));
  869     } elsif (scalar @editForUser == 0) {
  870       print CGI::div({class=>"ResultsWithError"}, "None of the selected users are assigned to this set: " . CGI::b(join(", ", @unassignedUsers)));
  871       print CGI::div({class=>"ResultsWithError"}, "Global set data will be shown instead of user specific data");
  872     }
  873   }
  874 
  875   # some useful booleans
  876   my $forUsers    = scalar(@editForUser);
  877   my $forOneUser  = $forUsers == 1;
  878 
  879   # If you're editing for users, initially their records will be different but
  880   # if you make any changes to them they will be the same.
  881   # if you're editing for one user, the problems shown should be his/hers
  882   my $userToShow        = $forUsers ? $editForUser[0] : $userID;
  883 
  884   my $userCount        = $db->listUsers();
  885   my $setCount         = $db->listGlobalSets() if $forOneUser;
  886   my $setUserCount     = $db->countSetUsers($setID);
  887   my $userSetCount     = $db->countUserSets($editForUser[0]) if $forOneUser;
  888 
  889 
  890   my $editUsersAssignedToSetURL = $self->systemLink(
  891         $urlpath->newFromModule(
  892                 "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
  893                   courseID => $courseID, setID => $setID));
  894   my $editSetsAssignedToUserURL = $self->systemLink(
  895         $urlpath->newFromModule(
  896                 "WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser",
  897                   courseID => $courseID, userID => $editForUser[0])) if $forOneUser;
  898 
  899 
  900   my $setDetailPage  = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID);
  901   my $setDetailURL   = $self->systemLink($setDetailPage,authen=>0);
  902 
  903 
  904   my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount));
  905   my $setCountMessage = CGI::a({href=>$editSetsAssignedToUserURL}, $self->setCountMessage($userSetCount, $setCount)) if $forOneUser;
  906 
  907   $userCountMessage = "The set $setID is assigned to " . $userCountMessage . ".";
  908   $setCountMessage  = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser;
  909 
  910   if ($forUsers) {
  911     print CGI::p("$userCountMessage  Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
  912     if ($forOneUser) {
  913       print CGI::p($setCountMessage);
  914     }
  915   } else {
  916     print CGI::p($userCountMessage);
  917   }
  918 
  919   # handle renumbering of problems if necessary
  920   print CGI::a({name=>"problems"});
  921 
  922   my %newProblemNumbers = ();
  923   my $maxProblemNumber = -1;
  924   for my $jj (sort { $a <=> $b } $db->listGlobalProblems($setID)) {
  925     $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj);
  926     $maxProblemNumber = $jj if $jj > $maxProblemNumber;
  927   }
  928 
  929   my $forceRenumber = $r->param('force_renumber') || 0;
  930   handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setID, $forceRenumber) unless defined $r->param('undo_changes');
  931 
  932   my %properties = %{ FIELD_PROPERTIES() };
  933 
  934   my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()};
  935   my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}};
  936   push @active_modes, 'None';
  937   my $default_header_mode = $r->param('header.displaymode') || 'None';
  938   my $default_problem_mode = $r->param('problem.displaymode') || 'None';
  939 
  940   #####################################################################
  941   # Browse available header/problem files
  942   #####################################################################
  943 
  944   my $templates = $r->ce->{courseDirs}->{templates};
  945   my %probLibs = %{ $r->ce->{courseFiles}->{problibs} };
  946   my $skip = join("|", keys %probLibs);
  947 
  948   my @headerFileList = listFilesRecursive(
  949     $templates,
  950     qr/header.*\.pg$/i,     # match these files
  951     qr/^(?:$skip|CVS)$/,  # prune these directories
  952     0,        # match against file name only
  953     1,        # prune against path relative to $templates
  954   );
  955 
  956   # this just takes too much time to search
  957 # my @problemFileList = listFilesRecursive(
  958 #   $templates,
  959 #   qr/\.pg$/i,     # problem files don't say problem
  960 #   qr/^(?:$skip|CVS)$/,  # prune these directories
  961 #   0,        # match against file name only
  962 #   1,        # prune against path relative to $templates
  963 # );
  964 
  965   # Display a useful warning message
  966   if ($forUsers) {
  967     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ONLY the student" .
  968           ($forOneUser ? "" : "s") . " listed above."));
  969   } else {
  970     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ALL students."));
  971   }
  972 
  973   print CGI::start_form({method=>"POST", action=>$setDetailURL});
  974   print $self->hiddenEditForUserFields(@editForUser);
  975   print $self->hidden_authen_fields;
  976   print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
  977   print CGI::input({type=>"submit", name=>"undo_changes", value => "Reset Form"});
  978 
  979   # spacing
  980   print CGI::p();
  981 
  982   #####################################################################
  983   # Display general set information
  984   #####################################################################
  985 
  986   print CGI::start_table({border=>1, cellpadding=>4});
  987   print CGI::Tr({}, CGI::th({}, [
  988     "General Information",
  989   ]));
  990 
  991   # this is kind of a hack -- we need to get a user record here, so we can
  992   # pass it to FieldTable, so FieldTable can pass it to FieldHTML, so
  993   # FieldHTML doesn't have to fetch it itself.
  994   my $userSetRecord = $db->getUserSet($userToShow, $setID);
  995 
  996   print CGI::Tr({}, CGI::td({}, [
  997     $self->FieldTable($userToShow, $setID, undef, $setRecord, $userSetRecord),
  998   ]));
  999   print CGI::end_table();
 1000 
 1001   # spacing
 1002   print CGI::p();
 1003 
 1004 
 1005   #####################################################################
 1006   # Display header information
 1007   #####################################################################
 1008   my @headers = @{ HEADER_ORDER() };
 1009   my %headerModules = (set_header => 'problem_list', hardcopy_header => 'hardcopy_preselect_set');
 1010   my %headerDefaults = (set_header => $ce->{webworkFiles}->{screenSnippets}->{setHeader}, hardcopy_header => $ce->{webworkFiles}->{hardcopySnippets}->{setHeader});
 1011   my @headerFiles = map { $setRecord->{$_} } @headers;
 1012   if (scalar @headers and not $forUsers) {
 1013 
 1014     print CGI::start_table({border=>1, cellpadding=>4});
 1015     print CGI::Tr({}, CGI::th({}, [
 1016       "Headers",
 1017 #     "Data",
 1018       "Display&nbsp;Mode:&nbsp;" .
 1019       CGI::popup_menu(-name => "header.displaymode", -values => \@active_modes, -default => $default_header_mode) . '&nbsp;'.
 1020       CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
 1021     ]));
 1022 
 1023     my %header_html;
 1024 
 1025     my %error;
 1026     foreach my $header (@headers) {
 1027       my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header};
 1028 
 1029       $error{$header} = $self->checkFile($headerFile);
 1030       unless ($error{$header}) {
 1031         my @temp = renderProblems(  r=> $r,
 1032                 user => $db->getUser($userToShow),
 1033                 displayMode=> $default_header_mode,
 1034                 problem_number=> 0,
 1035                 this_set => $db->getMergedSet($userToShow, $setID),
 1036                 problem_list => [$headerFile],
 1037         );
 1038         $header_html{$header} = $temp[0];
 1039       }
 1040     }
 1041 
 1042     foreach my $header (@headers) {
 1043 
 1044       my $editHeaderPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => 0 });
 1045       my $editHeaderLink = $self->systemLink($editHeaderPage, params => { file_type => $header, make_local_copy => 1 });
 1046 
 1047       my $viewHeaderPage = $urlpath->new(type => $headerModules{$header}, args => { courseID => $courseID, setID => $setID });
 1048       my $viewHeaderLink = $self->systemLink($viewHeaderPage);
 1049 
 1050       print CGI::Tr({}, CGI::td({}, [
 1051         CGI::start_table({border => 0, cellpadding => 0}) .
 1052           CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) .
 1053           CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) .
 1054           CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) .
 1055 #         CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) .
 1056         CGI::end_table(),
 1057 #       "",
 1058 #       CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) .
 1059 #       join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) .
 1060 #               CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}),
 1061 
 1062         comboBox({
 1063           name => "set.$setID.$header",
 1064           request => $r,
 1065           default => $r->param("set.$setID.$header") || $setRecord->{$header},
 1066           multiple => 0,
 1067           values => ["", @headerFileList],
 1068           labels => { "" => "Use Default Header File" },
 1069         }) .
 1070         ($error{$header} ?
 1071           CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error{$header})
 1072           : CGI::div({class=> "RenderSolo"}, $header_html{$header}->{body_text})
 1073         ),
 1074       ]));
 1075     }
 1076 
 1077     print CGI::end_table();
 1078   } else {
 1079     print CGI::p(CGI::b("Screen and Hardcopy set header information can not be overridden for individual students."));
 1080   }
 1081 
 1082   # spacing
 1083   print CGI::p();
 1084 
 1085 
 1086   #####################################################################
 1087   # Display problem information
 1088   #####################################################################
 1089 
 1090   my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID);
 1091 
 1092   # get global problem records for all problems in one go
 1093   my %GlobalProblems;
 1094   my @globalKeypartsRef = map { [$setID, $_] } @problemIDList;
 1095   @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef);
 1096 
 1097   # if needed, get user problem records for all problems in one go
 1098   my (%UserProblems, %MergedProblems);
 1099   if ($forOneUser) {
 1100     my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList;
 1101     @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef);
 1102     @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef);
 1103   }
 1104 
 1105   if (scalar @problemIDList) {
 1106 
 1107     print CGI::start_table({border=>1, cellpadding=>4});
 1108     print CGI::Tr({}, CGI::th({}, [
 1109       "Problems",
 1110       "Data",
 1111       "Display&nbsp;Mode:&nbsp;" .
 1112       CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . '&nbsp;'.
 1113       CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
 1114     ]));
 1115 
 1116     my %shownYet;
 1117     my $repeatFile;
 1118     foreach my $problemID (@problemIDList) {
 1119 
 1120       my $problemRecord;
 1121       if ($forOneUser) {
 1122         #$problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID);
 1123         $problemRecord = $MergedProblems{$problemID}; # already fetched above --sam
 1124       } else {
 1125         #$problemRecord = $db->getGlobalProblem($setID, $problemID);
 1126         $problemRecord = $GlobalProblems{$problemID}; # already fetched above --sam
 1127       }
 1128 
 1129       #$self->addgoodmessage("");
 1130       #$self->addbadmessage($problemRecord->toString());
 1131 
 1132 
 1133       my $editProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
 1134       my $editProblemLink = $self->systemLink($editProblemPage, params => { make_local_copy => 0 });
 1135 
 1136 
 1137       # FIXME: should we have an "act as" type link here when editing for multiple users?
 1138       my $viewProblemPage = $urlpath->new(type => 'problem_detail', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
 1139       my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)});
 1140 
 1141       my @fields = @{ PROBLEM_FIELDS() };
 1142       push @fields, @{ USER_PROBLEM_FIELDS() } if $forOneUser;
 1143 
 1144       my $problemFile = $r->param("problem.$problemID.source_file") || $problemRecord->source_file;
 1145 
 1146       # warn of repeat problems
 1147       if (defined $shownYet{$problemFile}) {
 1148         $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . ".";
 1149       } else {
 1150         $shownYet{$problemFile} = $problemID;
 1151         $repeatFile = "";
 1152       }
 1153 
 1154       my $error = $self->checkFile($problemFile);
 1155       my @problem_html;
 1156       unless ($error) {
 1157         @problem_html = renderProblems( r=> $r,
 1158                 user => $db->getUser($userToShow),
 1159                 displayMode=> $default_problem_mode,
 1160                 problem_number=> $problemID,
 1161                 this_set => $db->getMergedSet($userToShow, $setID),
 1162                 problem_seed => $forOneUser ? $problemRecord->problem_seed : 0,
 1163                 problem_list => [$problemRecord->source_file],
 1164         );
 1165       }
 1166 
 1167       print CGI::Tr({}, CGI::td({}, [
 1168         CGI::start_table({border => 0, cellpadding => 1}) .
 1169           CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) .
 1170           CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) .
 1171           CGI::Tr({}, CGI::td({}, CGI::a({href => $viewProblemLink}, "Try it" . ($forOneUser ? " (as $editForUser[0])" : "")))) .
 1172           ($forUsers ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "deleteProblem", value => $problemID, label => "Delete it?"})))) .
 1173 #         CGI::Tr({}, CGI::td({}, "Delete&nbsp;it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) .
 1174           ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) .
 1175         CGI::end_table(),
 1176         $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}),
 1177 # A comprehensive list of problems is just TOO big to be handled well
 1178 #       comboBox({
 1179 #         name => "set.$setID.$problemID",
 1180 #         request => $r,
 1181 #         default => $problemRecord->{problem_id},
 1182 #         multiple => 0,
 1183 #         values => \@problemFileList,
 1184 #       }) .
 1185 
 1186         join ("\n", $self->FieldHTML(
 1187           $userToShow,
 1188           $setID,
 1189           $problemID,
 1190           $GlobalProblems{$problemID}, # pass previously fetched global record to FieldHTML --sam
 1191           $UserProblems{$problemID}, # pass previously fetched user record to FieldHTML --sam
 1192           "source_file"
 1193         )) .
 1194                 CGI::br() .
 1195           ($error ?
 1196             CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error)
 1197             : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text})
 1198           ) .
 1199           ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''),
 1200       ]));
 1201     }
 1202 
 1203       my $editNewProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID =>'new_problem'    });
 1204         my $editNewProblemLink = $self->systemLink($editNewProblemPage, params => { make_local_copy => 1, file_type => 'blank_problem'  });
 1205 
 1206 # print final lines
 1207     print CGI::end_table();
 1208     print CGI::checkbox({
 1209           label=> "Force problems to be numbered consecutively from one",
 1210           name=>"force_renumber", value=>"1"}),
 1211 
 1212       CGI::br();
 1213     print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
 1214     print CGI::input({type=>"submit", name=>"handle_numbers", value=>"Reorder problems only"}) . "(Any unsaved changes will be lost.)";
 1215     print CGI::p( CGI::a({href=>$editNewProblemLink},'Create'). 'a new blank problem');
 1216     print CGI::p(<<HERE);
 1217 Any time problem numbers are intentionally changed, the problems will
 1218 always be renumbered consecutively, starting from one.  When deleting
 1219 problems, gaps will be left in the numbering unless the box above is
 1220 checked.
 1221 HERE
 1222                 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());
 1223     print CGI::p("When changing problem numbers, we will move
 1224  the problem to be ", CGI::em("before"), " the chosen number.");
 1225 
 1226   } else {
 1227     print CGI::p(CGI::b("This set doesn't contain any problems yet."));
 1228   }
 1229 
 1230   print CGI::end_form();
 1231 
 1232   return "";
 1233 }
 1234 
 1235 1;
 1236 
 1237 =head1 AUTHOR
 1238 
 1239 Written by Robert Van Dam, toenail (at) cif.rochester.edu
 1240 
 1241 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9