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

View of /branches/rel-2-0-patches/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2783 - (download) (as text) (annotate)
Tue Sep 14 22:45:22 2004 UTC (8 years, 9 months ago) by toenail
File size: 53428 byte(s)
Exported defs were being exported with setHeader and paperHeader
set to be the same thing, even if they weren't really the same in the
set record
CVS :----------------------------------------------------------------------

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader:
    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::ProblemSetList;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::ProblemSetList - Entry point for Set-specific
   23 data editing/viewing
   24 
   25 =cut
   26 
   27 =for comment
   28 
   29 What do we want to be able to do here?
   30 
   31 filter sort edit publish import create delete
   32 
   33 Filter what sets are shown:
   34   - none, all, selected
   35   - matching set_id, visible to students, hidden from students
   36 
   37 Sort sets by:
   38   - set name
   39   - open date
   40   - due date
   41   - answer date
   42   - header files
   43   - visibility to students
   44 
   45 Switch from view mode to edit mode:
   46   - showing visible sets
   47   - showing selected sets
   48 Switch from edit mode to view and save changes
   49 Switch from edit mode to view and abandon changes
   50 
   51 Make sets visible to or hidden from students:
   52   - all, selected
   53 
   54 Import sets:
   55   - replace:
   56     - any users
   57     - visible users
   58     - selected users
   59     - no users
   60   - add:
   61     - any users
   62     - no users
   63 
   64 Score sets:
   65   - all
   66   - visible
   67   - selected
   68 
   69 Create a set with a given name
   70 
   71 Delete sets:
   72   - visible
   73   - selected
   74 
   75 =cut
   76 
   77 # FIXME: rather than having two types of boolean modes $editMode and $exportMode
   78 # make one $mode variable that contains a string like "edit", "view", or "export"
   79 
   80 use strict;
   81 use warnings;
   82 use CGI qw();
   83 use WeBWorK::Utils qw(formatDateTime parseDateTime readFile readDirectory cryptPassword);
   84 
   85 use constant HIDE_SETS_THRESHOLD => 50;
   86 use constant DEFAULT_PUBLISHED_STATE => 1;
   87 
   88 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)];
   89 use constant VIEW_FORMS => [qw(filter sort edit publish import export score create delete)];
   90 use constant EXPORT_FORMS => [qw(cancelExport saveExport)];
   91 
   92 use constant VIEW_FIELD_ORDER => [ qw( select set_id problems users published open_date due_date answer_date set_header hardcopy_header) ];
   93 use constant EDIT_FIELD_ORDER => [ qw( set_id published open_date due_date answer_date set_header hardcopy_header) ];
   94 use constant EXPORT_FIELD_ORDER => [ qw( select set_id filename) ];
   95 
   96 # permissions needed to perform a given action
   97 use constant FORM_PERMS => {
   98     saveEdit => "modify_problem_sets",
   99     edit => "modify_problem_sets",
  100     publish => "modify_problem_sets",
  101     import => "create_and_delete_problem_sets",
  102     export => "modify_set_def_files",
  103     saveExport => "modify_set_def_files",
  104     score => "score_sets",
  105     create => "create_and_delete_problem_sets",
  106     delete => "create_and_delete_problem_sets",
  107 };
  108 
  109 # permissions needed to view a given field
  110 use constant FIELD_PERMS => {
  111     problems => "modify_problem_sets",
  112     users => "assign_problem_sets",
  113 };
  114 
  115 use constant STATE_PARAMS => [qw(user effectiveUser key visible_sets no_visible_sets prev_visible_sets no_prev_visible_set editMode exportMode primarySortField secondarySortField)];
  116 
  117 use constant SORT_SUBS => {
  118   set_id    => \&bySetID,
  119   set_header  => \&bySetHeader,
  120   hardcopy_header => \&byHardcopyHeader,
  121   open_date => \&byOpenDate,
  122   due_date  => \&byDueDate,
  123   answer_date => \&byAnswerDate,
  124   published => \&byPublished,
  125 
  126 };
  127 
  128 use constant  FIELD_PROPERTIES => {
  129   set_id => {
  130     type => "text",
  131     size => 8,
  132     access => "readonly",
  133   },
  134   set_header => {
  135     type => "filelist",
  136     size => 10,
  137     access => "readwrite",
  138   },
  139   hardcopy_header => {
  140     type => "filelist",
  141     size => 10,
  142     access => "readwrite",
  143   },
  144   open_date => {
  145     type => "text",
  146     size => 20,
  147     access => "readwrite",
  148   },
  149   due_date => {
  150     type => "text",
  151     size => 20,
  152     access => "readwrite",
  153   },
  154   answer_date => {
  155     type => "text",
  156     size => 20,
  157     access => "readwrite",
  158   },
  159   published => {
  160     type => "checked",
  161     size => 4,
  162     access => "readwrite",
  163   },
  164 };
  165 
  166 sub pre_header_initialize {
  167   my ($self) = @_;
  168   my $r      = $self->r;
  169   my $db     = $r->db;
  170   my $ce     = $r->ce;
  171   my $authz  = $r->authz;
  172   my $urlpath = $r->urlpath;
  173   my $user   = $r->param('user');
  174   my $courseName = $urlpath->arg("courseID");
  175 
  176 
  177   # Check permissions
  178   return unless $authz->hasPermissions($user, "access_instructor_tools");
  179 
  180   if (defined $r->param("action") and $r->param("action") eq "score" and $authz->hasPermissions($user, "score_sets")) {
  181     my $scope = $r->param("action.score.scope");
  182     my @setsToScore = ();
  183 
  184     if ($scope eq "none") {
  185       return "No sets selected for scoring.";
  186     } elsif ($scope eq "all") {
  187       @setsToScore = @{ $r->param("allSetIDs") };
  188     } elsif ($scope eq "visible") {
  189       @setsToScore = @{ $r->param("visibleSetIDs") };
  190     } elsif ($scope eq "selected") {
  191       @setsToScore = $r->param("selected_sets");
  192     }
  193 
  194     my $uri = $self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::Scoring', courseID=>$courseName),
  195             params=>{
  196               scoreSelected=>"ScoreSelected",
  197               selectedSet=>\@setsToScore,
  198 #             recordSingleSetScores=>''
  199             }
  200     );
  201 
  202     $self->reply_with_redirect($uri);
  203   }
  204 
  205 }
  206 
  207 sub body {
  208   my ($self)       = @_;
  209   my $r            = $self->r;
  210   my $urlpath      = $r->urlpath;
  211   my $db           = $r->db;
  212   my $ce           = $r->ce;
  213   my $authz        = $r->authz;
  214   my $courseName   = $urlpath->arg("courseID");
  215   my $setID        = $urlpath->arg("setID");
  216   my $user         = $r->param('user');
  217 
  218   my $root = $ce->{webworkURLs}->{root};
  219 
  220   # templates for getting field names
  221   my $setTemplate = $self->{setTemplate} = $db->newGlobalSet;
  222 
  223   return CGI::div({class => "ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  224     unless $authz->hasPermissions($user, "access_instructor_tools");
  225 
  226   # This table can be consulted when display-ready forms of field names are needed.
  227   my %prettyFieldNames = map { $_ => $_ }
  228     $setTemplate->FIELDS();
  229 
  230   @prettyFieldNames{qw(
  231     select
  232     problems
  233     users
  234     filename
  235     set_id
  236     set_header
  237     hardcopy_header
  238     open_date
  239     due_date
  240     answer_date
  241     published
  242   )} = (
  243     "Select",
  244     "Problems",
  245     "Assigned Users",
  246     "Set Definition Filename",
  247     "Set Name",
  248     "Set Header",
  249     "Hardcopy Header",
  250     "Open Date",
  251     "Due Date",
  252     "Answer Date",
  253     "Visible",
  254   );
  255 
  256   ########## set initial values for state fields
  257 
  258   my @allSetIDs = $db->listGlobalSets;
  259   my @users = $db->listUsers;
  260   $self->{allSetIDs} = \@allSetIDs;
  261   $self->{totalUsers} = scalar @users;
  262 
  263   if (defined $r->param("visible_sets")) {
  264     $self->{visibleSetIDs} = [ $r->param("visible_sets") ];
  265   } elsif (defined $r->param("no_visible_sets")) {
  266     $self->{visibleSetIDs} = [];
  267   } else {
  268     if (@allSetIDs > HIDE_SETS_THRESHOLD) {
  269       $self->{visibleSetIDs} = [];
  270     } else {
  271       $self->{visibleSetIDs} = [ @allSetIDs ];
  272     }
  273   }
  274 
  275   $self->{prevVisibleSetIDs} = $self->{visibleSetIDs};
  276 
  277   if (defined $r->param("selected_sets")) {
  278     $self->{selectedSetIDs} = [ $r->param("selected_sets") ];
  279   } else {
  280     $self->{selectedSetIDs} = [];
  281   }
  282 
  283   $self->{editMode} = $r->param("editMode") || 0;
  284 
  285   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify problem sets."))
  286     if $self->{editMode} and not $authz->hasPermissions($user, "modify_problem_sets");
  287 
  288   $self->{exportMode} = $r->param("exportMode") || 0;
  289 
  290   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify set definition files."))
  291     if $self->{exportMode} and not $authz->hasPermissions($user, "modify_set_def_files");
  292 
  293   $self->{primarySortField} = $r->param("primarySortField") || "due_date";
  294   $self->{secondarySortField} = $r->param("secondarySortField") || "open_date";
  295 
  296   my @allSets = $db->getGlobalSets(@allSetIDs);
  297 
  298   my (%open_dates, %due_dates, %answer_dates);
  299   foreach my $Set (@allSets) {
  300     push @{$open_dates{defined $Set->open_date ? $Set->open_date : ""}}, $Set->set_id;
  301     push @{$due_dates{defined $Set->due_date ? $Set->due_date : ""}}, $Set->set_id;
  302     push @{$answer_dates{defined $Set->answer_date ? $Set->answer_date : ""}}, $Set->set_id;
  303   }
  304   $self->{open_dates} = \%open_dates;
  305   $self->{due_dates} = \%due_dates;
  306   $self->{answer_dates} = \%answer_dates;
  307 
  308   ########## call action handler
  309 
  310   my $actionID = $r->param("action");
  311   if ($actionID) {
  312     unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }, @{ EXPORT_FORMS() }) {
  313       die "Action $actionID not found";
  314     }
  315     # Check permissions
  316     if (not FORM_PERMS()->{$actionID} or $authz->hasPermissions($user, FORM_PERMS()->{$actionID})) {
  317       my $actionHandler = "${actionID}_handler";
  318       my %genericParams;
  319       foreach my $param (qw(selected_sets)) {
  320         $genericParams{$param} = [ $r->param($param) ];
  321       }
  322       my %actionParams = $self->getActionParams($actionID);
  323       my %tableParams = $self->getTableParams();
  324       print CGI::div({class=>"Message"}, CGI::p("Results of last action performed: ", $self->$actionHandler(\%genericParams, \%actionParams, \%tableParams))), CGI::hr();
  325     } else {
  326       return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to perform this action."));
  327     }
  328 
  329   }
  330 
  331   ########## retrieve possibly changed values for member fields
  332 
  333   @allSetIDs = @{ $self->{allSetIDs} }; # do we need this one? YES, deleting or importing a set will change this.
  334   my @visibleSetIDs = @{ $self->{visibleSetIDs} };
  335   my @prevVisibleSetIDs = @{ $self->{prevVisibleSetIDs} };
  336   my @selectedSetIDs = @{ $self->{selectedSetIDs} };
  337   my $editMode = $self->{editMode};
  338   my $exportMode = $self->{exportMode};
  339   my $primarySortField = $self->{primarySortField};
  340   my $secondarySortField = $self->{secondarySortField};
  341 
  342   #warn "visibleSetIDs=@visibleSetIDs\n";
  343   #warn "prevVisibleSetIDs=@prevVisibleSetIDs\n";
  344   #warn "selectedSetIDs=@selectedSetIDs\n";
  345   #warn "editMode=$editMode\n";
  346 
  347   ########## get required users
  348 
  349   my @Sets = grep { defined $_ } @visibleSetIDs ? $db->getGlobalSets(@visibleSetIDs) : ();
  350 
  351   # presort users
  352   my %sortSubs = %{ SORT_SUBS() };
  353   my $primarySortSub = $sortSubs{$primarySortField};
  354   my $secondarySortSub = $sortSubs{$secondarySortField};
  355 
  356   # don't forget to sort in opposite order of importance
  357   @Sets = sort $secondarySortSub @Sets;
  358   @Sets = sort $primarySortSub @Sets;
  359 
  360   ########## print beginning of form
  361 
  362   print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"problemsetlist"});
  363   print $self->hidden_authen_fields();
  364 
  365   ########## print state data
  366 
  367   print "\n<!-- state data here -->\n";
  368 
  369   if (@visibleSetIDs) {
  370     print CGI::hidden(-name=>"visible_sets", -value=>\@visibleSetIDs);
  371   } else {
  372     print CGI::hidden(-name=>"no_visible_sets", -value=>"1");
  373   }
  374 
  375   if (@prevVisibleSetIDs) {
  376     print CGI::hidden(-name=>"prev_visible_sets", -value=>\@prevVisibleSetIDs);
  377   } else {
  378     print CGI::hidden(-name=>"no_prev_visible_sets", -value=>"1");
  379   }
  380 
  381   print CGI::hidden(-name=>"editMode", -value=>$editMode);
  382   print CGI::hidden(-name=>"exportMode", -value=>$exportMode);
  383 
  384   print CGI::hidden(-name=>"primarySortField", -value=>$primarySortField);
  385   print CGI::hidden(-name=>"secondarySortField", -value=>$secondarySortField);
  386 
  387   print "\n<!-- state data here -->\n";
  388 
  389   ########## print action forms
  390 
  391   print CGI::start_table({});
  392   print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
  393 
  394   my @formsToShow;
  395   if ($editMode) {
  396     @formsToShow = @{ EDIT_FORMS() };
  397   } else {
  398     @formsToShow = @{ VIEW_FORMS() };
  399   }
  400 
  401   if ($exportMode) {
  402     @formsToShow = @{ EXPORT_FORMS() };
  403   }
  404 
  405   my $i = 0;
  406   foreach my $actionID (@formsToShow) {
  407     # Check permissions
  408     next if FORM_PERMS()->{$actionID} and not $authz->hasPermissions($user, FORM_PERMS()->{$actionID});
  409     my $actionForm = "${actionID}_form";
  410     my $onChange = "document.problemsetlist.action[$i].checked=true";
  411     my %actionParams = $self->getActionParams($actionID);
  412 
  413     print CGI::Tr({-valign=>"top"},
  414       CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})),
  415       CGI::td({}, $self->$actionForm($onChange, %actionParams))
  416     );
  417 
  418     $i++;
  419   }
  420 
  421   print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"},
  422     CGI::submit(-value=>"Take Action!"))
  423   );
  424   print CGI::end_table();
  425 
  426   ########## print table
  427 
  428   print CGI::p("Showing ", scalar @visibleSetIDs, " out of ", scalar @allSetIDs, " sets.");
  429 
  430   $self->printTableHTML(\@Sets, \%prettyFieldNames,
  431     editMode => $editMode,
  432     exportMode => $exportMode,
  433     selectedSetIDs => \@selectedSetIDs,
  434   );
  435 
  436 
  437   ########## print end of form
  438 
  439   print CGI::end_form();
  440 
  441   return "";
  442 }
  443 
  444 ################################################################################
  445 # extract particular params and put them in a hash (values are ARRAYREFs!)
  446 ################################################################################
  447 
  448 sub getActionParams {
  449   my ($self, $actionID) = @_;
  450   my $r = $self->{r};
  451 
  452   my %actionParams;
  453   foreach my $param ($r->param) {
  454     next unless $param =~ m/^action\.$actionID\./;
  455     $actionParams{$param} = [ $r->param($param) ];
  456   }
  457   return %actionParams;
  458 }
  459 
  460 sub getTableParams {
  461   my ($self) = @_;
  462   my $r = $self->{r};
  463 
  464   my %tableParams;
  465   foreach my $param ($r->param) {
  466     next unless $param =~ m/^(?:set)\./;
  467     $tableParams{$param} = [ $r->param($param) ];
  468   }
  469   return %tableParams;
  470 }
  471 
  472 ################################################################################
  473 # actions and action triggers
  474 ################################################################################
  475 
  476 # filter, edit, cancelEdit, and saveEdit should stay with the display module and
  477 # not be real "actions". that way, all actions are shown in view mode and no
  478 # actions are shown in edit mode.
  479 
  480 sub filter_form {
  481   my ($self, $onChange, %actionParams) = @_;
  482   #return CGI::table({}, CGI::Tr({-valign=>"top"},
  483   # CGI::td({},
  484   return join("",
  485       "Show ",
  486       CGI::popup_menu(
  487         -name => "action.filter.scope",
  488         -values => [qw(all none selected match_ids published unpublished)],
  489         -default => $actionParams{"action.filter.scope"}->[0] || "match_ids",
  490         -labels => {
  491           all => "all sets",
  492           none => "no sets",
  493           selected => "sets checked below",
  494           published => "sets visible to students",
  495           unpublished => "sets hidden from students",
  496           match_ids => "sets with matching set IDs:",
  497         },
  498         -onchange => $onChange,
  499       ),
  500       " ",
  501       CGI::textfield(
  502         -name => "action.filter.set_ids",
  503         -value => $actionParams{"action.filter.set_ids"}->[0] || "",,
  504         -width => "50",
  505         -onchange => $onChange,
  506       ),
  507       " (separate multiple IDs with commas)",
  508       CGI::br(),
  509 #     "Open dates: ",
  510 #     CGI::popup_menu(
  511 #       -name => "action.filter.open_date",
  512 #       -values => [ keys %{ $self->{open_dates} } ],
  513 #       -default => $actionParams{"action.filter.open_date"}->[0] || "",
  514 #       -labels => { $self->menuLabels($self->{open_dates}) },
  515 #       -onchange => $onChange,
  516 #     ),
  517 #     " Due dates: ",
  518 #     CGI::popup_menu(
  519 #       -name => "action.filter.due_date",
  520 #       -values => [ keys %{ $self->{due_dates} } ],
  521 #       -default => $actionParams{"action.filter.due_date"}->[0] || "",
  522 #       -labels => { $self->menuLabels($self->{due_dates}) },
  523 #       -onchange => $onChange,
  524 #     ),
  525 #     " Answer dates: ",
  526 #     CGI::popup_menu(
  527 #       -name => "action.filter.answer_date",
  528 #       -values => [ keys %{ $self->{answer_dates} } ],
  529 #       -default => $actionParams{"action.filter.answer_date"}->[0] || "",
  530 #       -labels => { $self->menuLabels($self->{answer_dates}) },
  531 #       -onchange => $onChange,
  532 #     ),
  533 
  534   );
  535 }
  536 
  537 # this action handler modifies the "visibleUserIDs" field based on the contents
  538 # of the "action.filter.scope" parameter and the "selected_users"
  539 sub filter_handler {
  540   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  541 
  542   my $r = $self->r ;
  543   my $db = $r->db;
  544 
  545   my $result;
  546 
  547   my $scope = $actionParams->{"action.filter.scope"}->[0];
  548   if ($scope eq "all") {
  549     $result = "showing all sets";
  550     $self->{visibleSetIDs} = $self->{allSetIDs};
  551   } elsif ($scope eq "none") {
  552     $result = "showing no sets";
  553     $self->{visibleSetIDs} = [];
  554   } elsif ($scope eq "selected") {
  555     $result = "showing selected sets";
  556     $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
  557   } elsif ($scope eq "match_ids") {
  558     my @setIDs = split /\s*,\s*/, $actionParams->{"action.filter.set_ids"}->[0];
  559     $self->{visibleSetIDs} = \@setIDs;
  560   } elsif ($scope eq "match_open_date") {
  561     my $open_date = $actionParams->{"action.filter.open_date"}->[0];
  562     $self->{visibleSetIDs} = $self->{open_dates}->{$open_date}; # an arrayref
  563   } elsif ($scope eq "match_due_date") {
  564     my $due_date = $actionParams->{"action.filter.due_date"}->[0];
  565     $self->{visibleSetIDs} = $self->{due_date}->{$due_date}; # an arrayref
  566   } elsif ($scope eq "match_answer_date") {
  567     my $answer_date = $actionParams->{"action.filter.answer_date"}->[0];
  568     $self->{visibleSetIDs} = $self->{answer_dates}->{$answer_date}; # an arrayref
  569   } elsif ($scope eq "published") {
  570     my @setRecords = $db->getGlobalSets(@{$self->{allSetIDs}});
  571     my @publishedSetIDs = map { $_->published ? $_->set_id : ""} @setRecords;
  572     $self->{visibleSetIDs} = \@publishedSetIDs;
  573   } elsif ($scope eq "unpublished") {
  574     my @setRecords = $db->getGlobalSets(@{$self->{allSetIDs}});
  575     my @unpublishedSetIDs = map { (not $_->published) ? $_->set_id : ""} @setRecords;
  576     $self->{visibleSetIDs} = \@unpublishedSetIDs;
  577   }
  578 
  579   return $result;
  580 }
  581 
  582 sub sort_form {
  583   my ($self, $onChange, %actionParams) = @_;
  584   return join ("",
  585     "Primary sort: ",
  586     CGI::popup_menu(
  587       -name => "action.sort.primary",
  588       -values => [qw(set_id set_header hardcopy_header open_date due_date answer_date published)],
  589       -default => $actionParams{"action.sort.primary"}->[0] || "due_date",
  590       -labels => {
  591         set_id    => "Set Name",
  592         set_header  => "Set Header",
  593         hardcopy_header => "Hardcopy Header",
  594         open_date => "Open Date",
  595         due_date  => "Due Date",
  596         answer_date => "Answer Date",
  597         published => "Visibility",
  598       },
  599       -onchange => $onChange,
  600     ),
  601     " Secondary sort: ",
  602     CGI::popup_menu(
  603       -name => "action.sort.secondary",
  604       -values => [qw(set_id set_header hardcopy_header open_date due_date answer_date published)],
  605       -default => $actionParams{"action.sort.secondary"}->[0] || "open_date",
  606       -labels => {
  607         set_id    => "Set Name",
  608         set_header  => "Set Header",
  609         hardcopy_header => "Hardcopy Header",
  610         open_date => "Open Date",
  611         due_date  => "Due Date",
  612         answer_date => "Answer Date",
  613         published => "Visibility",
  614       },
  615       -onchange => $onChange,
  616     ),
  617     ".",
  618   );
  619 }
  620 
  621 sub sort_handler {
  622   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  623 
  624   my $primary = $actionParams->{"action.sort.primary"}->[0];
  625   my $secondary = $actionParams->{"action.sort.secondary"}->[0];
  626 
  627   $self->{primarySortField} = $primary;
  628   $self->{secondarySortField} = $secondary;
  629 
  630   my %names = (
  631     set_id    => "Set Name",
  632     set_header  => "Set Header",
  633     hardcopy_header => "Hardcopy Header",
  634     open_date => "Open Date",
  635     due_date  => "Due Date",
  636     answer_date => "Answer Date",
  637     published => "Visibility",
  638   );
  639 
  640   return "sort by $names{$primary} and then by $names{$secondary}.";
  641 }
  642 
  643 
  644 sub edit_form {
  645   my ($self, $onChange, %actionParams) = @_;
  646 
  647   return join("",
  648     "Edit ",
  649     CGI::popup_menu(
  650       -name => "action.edit.scope",
  651       -values => [qw(all visible selected)],
  652       -default => $actionParams{"action.edit.scope"}->[0] || "selected",
  653       -labels => {
  654         all => "all sets",
  655         visible => "visible sets",
  656         selected => "selected sets",
  657       },
  658       -onchange => $onChange,
  659     ),
  660   );
  661 }
  662 
  663 sub edit_handler {
  664   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  665 
  666   my $result;
  667 
  668   my $scope = $actionParams->{"action.edit.scope"}->[0];
  669   if ($scope eq "all") {
  670     $result = "editing all sets";
  671     $self->{visibleSetIDs} = $self->{allSetIDs};
  672   } elsif ($scope eq "visible") {
  673     $result = "editing visible sets";
  674     # leave visibleUserIDs alone
  675   } elsif ($scope eq "selected") {
  676     $result = "editing selected sets";
  677     $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
  678   }
  679   $self->{editMode} = 1;
  680 
  681   return $result;
  682 }
  683 
  684 sub publish_form {
  685   my ($self, $onChange, %actionParams) = @_;
  686 
  687   return join ("",
  688     "Make ",
  689     CGI::popup_menu(
  690       -name => "action.publish.scope",
  691       -values => [ qw(none all selected) ],
  692       -default => $actionParams{"action.publish.scope"}->[0] || "selected",
  693       -labels => {
  694         none => "",
  695         all => "all sets",
  696 #       visible => "visible sets",
  697         selected => "selected sets",
  698       },
  699       -onchange => $onChange,
  700     ),
  701     CGI::popup_menu(
  702       -name => "action.publish.value",
  703       -values => [ 0, 1 ],
  704       -default => $actionParams{"action.publish.value"}->[0] || "1",
  705       -labels => {
  706         0 => "hidden",
  707         1 => "visible",
  708       },
  709       -onchange => $onChange,
  710     ),
  711     " for students.",
  712   );
  713 }
  714 
  715 sub publish_handler {
  716   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  717 
  718   my $r = $self->r;
  719   my $db = $r->db;
  720 
  721   my $result = "";
  722 
  723   my $scope = $actionParams->{"action.publish.scope"}->[0];
  724   my $value = $actionParams->{"action.publish.value"}->[0];
  725 
  726   my $verb = $value ? "made visible for" : "hidden from";
  727 
  728   my @setIDs;
  729 
  730   if ($scope eq "none") { # FIXME: double negative "Make no sets hidden" might make professor expect all sets to be made visible.
  731     @setIDs = ();
  732     $result = "No change made to any set.";
  733   } elsif ($scope eq "all") {
  734     @setIDs = @{ $self->{allSetIDs} };
  735     $result = "All sets $verb all students.";
  736   } elsif ($scope eq "visible") {
  737     @setIDs = @{ $self->{visibleSetIDs} };
  738     $result = "All visible sets $verb all students.";
  739   } elsif ($scope eq "selected") {
  740     @setIDs = @{ $genericParams->{selected_sets} };
  741     $result = "All selected sets $verb all students.";
  742   }
  743 
  744   my @sets = $db->getGlobalSets(@setIDs);
  745 
  746   map { $_->published("$value") if $_; $db->putGlobalSet($_); } @sets;
  747 
  748   return $result
  749 
  750 }
  751 
  752 sub score_form {
  753   my ($self, $onChange, %actionParams) = @_;
  754 
  755   return join ("",
  756     "Score ",
  757     CGI::popup_menu(
  758       -name => "action.score.scope",
  759       -values => [qw(none all selected)],
  760       -default => $actionParams{"action.score.scope"}->[0] || "none",
  761       -labels => {
  762         none => "no sets.",
  763         all => "all sets.",
  764         selected => "selected sets.",
  765       },
  766       -onchange => $onChange,
  767     ),
  768   );
  769 
  770 
  771 
  772 }
  773 
  774 sub score_handler {
  775   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  776 
  777   my $r      = $self->r;
  778   my $urlpath = $r->urlpath;
  779   my $courseName = $urlpath->arg("courseID");
  780 
  781   my $scope = $actionParams->{"action.score.scope"}->[0];
  782   my @setsToScore;
  783 
  784   if ($scope eq "none") {
  785     @setsToScore = ();
  786     return "No sets selected for scoring.";
  787   } elsif ($scope eq "all") {
  788     @setsToScore = @{ $self->{allSetIDs} };
  789   } elsif ($scope eq "visible") {
  790     @setsToScore = @{ $self->{visibleSetIDs} };
  791   } elsif ($scope eq "selected") {
  792     @setsToScore = @{ $genericParams->{selected_sets} };
  793   }
  794 
  795   my $uri = $self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::Scoring', courseID=>$courseName),
  796           params=>{
  797             scoreSelected=>"Score Selected",
  798             selectedSet=>\@setsToScore,
  799 #           recordSingleSetScores=>''
  800           }
  801   );
  802 
  803 
  804   return $uri;
  805 }
  806 
  807 
  808 sub delete_form {
  809   my ($self, $onChange, %actionParams) = @_;
  810 
  811   return join("",
  812     CGI::div({class=>"ResultsWithError"},
  813       "Delete ",
  814       CGI::popup_menu(
  815         -name => "action.delete.scope",
  816         -values => [qw(none selected)],
  817         -default => $actionParams{"action.delete.scope"}->[0] || "none",
  818         -labels => {
  819           none => "no sets.",
  820           #visble => "visible sets.",
  821           selected => "selected sets.",
  822         },
  823         -onchange => $onChange,
  824       ),
  825       CGI::em(" Deletion destroys all set-related data and is not undoable!"),
  826     )
  827   );
  828 }
  829 
  830 sub delete_handler {
  831   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  832 
  833   my $r      = $self->r;
  834   my $db     = $r->db;
  835 
  836   my $scope = $actionParams->{"action.delete.scope"}->[0];
  837 
  838 
  839   my @setIDsToDelete = ();
  840 
  841   if ($scope eq "selected") {
  842     @setIDsToDelete = @{ $self->{selectedSetIDs} };
  843   }
  844 
  845   my %allSetIDs = map { $_ => 1 } @{ $self->{allSetIDs} };
  846   my %visibleSetIDs = map { $_ => 1 } @{ $self->{visibleSetIDs} };
  847   my %selectedSetIDs = map { $_ => 1 } @{ $self->{selectedSetIDs} };
  848 
  849   foreach my $setID (@setIDsToDelete) {
  850     delete $allSetIDs{$setID};
  851     delete $visibleSetIDs{$setID};
  852     delete $selectedSetIDs{$setID};
  853     $db->deleteGlobalSet($setID);
  854   }
  855 
  856   $self->{allSetIDs} = [ keys %allSetIDs ];
  857   $self->{visibleSetIDs} = [ keys %visibleSetIDs ];
  858   $self->{selectedSetIDs} = [ keys %selectedSetIDs ];
  859 
  860   my $num = @setIDsToDelete;
  861   return "deleted $num set" . ($num == 1 ? "" : "s");
  862 }
  863 
  864 sub create_form {
  865   my ($self, $onChange, %actionParams) = @_;
  866 
  867   my $r      = $self->r;
  868 
  869   return "Create a new set named: ",
  870     CGI::textfield(
  871       -name => "action.create.name",
  872       -value => $actionParams{"action.create.name"}->[0] || "",
  873       -width => "50",
  874       -onchange => $onChange,
  875     );
  876 }
  877 
  878 sub create_handler {
  879   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  880 
  881   my $r      = $self->r;
  882   my $db     = $r->db;
  883 
  884   my $newSetRecord = $db->newGlobalSet;
  885   my $newSetName = $actionParams->{"action.create.name"}->[0];
  886   return CGI::div({class => "ResultsWithError"}, "Failed to create new set: no set name specified!") unless $newSetName =~ /\S/;
  887   $newSetRecord->set_id($newSetName);
  888   $newSetRecord->set_header("");
  889   $newSetRecord->hardcopy_header("");
  890   $newSetRecord->open_date("0");
  891   $newSetRecord->due_date("0");
  892   $newSetRecord->answer_date("0");
  893   $newSetRecord->published(DEFAULT_PUBLISHED_STATE);  # don't want students to see an empty set
  894   eval {$db->addGlobalSet($newSetRecord)};
  895 
  896   return CGI::div({class => "ResultsWithError"}, "Failed to create new set: $@") if $@;
  897 
  898   return "Successfully created new set $newSetName";
  899 
  900 }
  901 
  902 sub import_form {
  903   my ($self, $onChange, %actionParams) = @_;
  904 
  905   my $r = $self->r;
  906   my $authz = $r->authz;
  907   my $user = $r->param('user');
  908 
  909   # this will make the popup menu alternate between a single selection and a multiple selection menu
  910   # Note: search by name is required since document.problemsetlist.action.import.number is not seen as
  911   # a valid reference to the object named 'action.import.number'
  912   my $importScript = join (" ",
  913         "var number = document.getElementsByName('action.import.number')[0].value;",
  914         "document.getElementsByName('action.import.source')[0].size = number;",
  915         "document.getElementsByName('action.import.source')[0].multiple = (number > 1 ? true : false);",
  916         "document.getElementsByName('action.import.name')[0].value = (number > 1 ? '(taken from filenames)' : '');",
  917       );
  918 
  919   return join(" ",
  920     "Import ",
  921     CGI::popup_menu(
  922       -name => "action.import.number",
  923       -values => [ 1, 8 ],
  924       -default => $actionParams{"action.import.number"}->[0] || "1",
  925       -labels => {
  926         1 => "a single set",
  927         8 => "multiple sets",
  928       },
  929       -onchange => "$onChange;$importScript",
  930     ),
  931     " from ", # set definition file(s) ",
  932     CGI::popup_menu(
  933       -name => "action.import.source",
  934       -values => [ "", $self->getDefList() ],
  935       -labels => { "" => "the following file(s)" },
  936       -default => $actionParams{"action.import.source"}->[0] || "",
  937       -size => $actionParams{"action.import.number"}->[0] || "1",
  938       -onchange => $onChange,
  939     ),
  940     " with set name(s): ",
  941     CGI::textfield(
  942       -name => "action.import.name",
  943       -value => $actionParams{"action.import.name"}->[0] || "",
  944       -width => "50",
  945       -onchange => $onChange,
  946     ),
  947     ($authz->hasPermissions($user, "assign_problem_sets"))
  948       ?
  949       "assigning this set to " .
  950       CGI::popup_menu(
  951         -name => "action.import.assign",
  952         -value => [qw(all none)],
  953         -default => $actionParams{"action.import.assign"}->[0] || "none",
  954         -labels => {
  955           all => "all current users.",
  956           none => "no users.",
  957         },
  958         -onchange => $onChange,
  959       )
  960       :
  961       ""  #user does not have permissions to assign problem sets
  962   );
  963 }
  964 
  965 sub import_handler {
  966   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  967 
  968   my @fileNames = @{ $actionParams->{"action.import.source"} };
  969   my $newSetName = $actionParams->{"action.import.name"}->[0];
  970   $newSetName = "" if @fileNames > 1; # cannot assign set names to multiple imports
  971   my $assign = $actionParams->{"action.import.assign"}->[0];
  972 
  973   my ($added, $skipped) = $self->importSetsFromDef($newSetName, $assign, @fileNames);
  974 
  975   # make new sets visible... do we really want to do this? probably.
  976   push @{ $self->{visibleSetIDs} }, @$added;
  977   push @{ $self->{allSetIDs} }, @$added;
  978 
  979   my $numAdded = @$added;
  980   my $numSkipped = @$skipped;
  981 
  982   return  $numAdded . " set" . ($numAdded == 1 ? "" : "s") . " added, "
  983     . $numSkipped . " set" . ($numSkipped == 1 ? "" : "s") . " skipped.";
  984 }
  985 
  986 sub export_form {
  987   my ($self, $onChange, %actionParams) = @_;
  988 
  989   return join("",
  990     "Export ",
  991     CGI::popup_menu(
  992       -name => "action.export.scope",
  993       -values => [qw(all visible selected)],
  994       -default => $actionParams{"action.export.scope"}->[0] || "visible",
  995       -labels => {
  996         all => "all sets",
  997         visible => "visible sets",
  998         selected => "selected sets",
  999       },
 1000       -onchange => $onChange,
 1001     ),
 1002   );
 1003 }
 1004 
 1005 # this does not actually export any files, rather it sends us to a new page in order to export the files
 1006 sub export_handler {
 1007   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1008 
 1009   my $result;
 1010 
 1011   my $scope = $actionParams->{"action.export.scope"}->[0];
 1012   if ($scope eq "all") {
 1013     $result = "exporting all sets";
 1014     $self->{selectedSetIDs} = $self->{visibleSetIDs} = $self->{allSetIDs};
 1015 
 1016   } elsif ($scope eq "visible") {
 1017     $result = "exporting visible sets";
 1018     $self->{selectedSetIDs} = $self->{visibleSetIDs};
 1019   } elsif ($scope eq "selected") {
 1020     $result = "exporting selected sets";
 1021     $self->{selectedSetIDs} = $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
 1022   }
 1023   $self->{exportMode} = 1;
 1024 
 1025   return $result;
 1026 }
 1027 
 1028 sub cancelExport_form {
 1029   my ($self, $onChange, %actionParams) = @_;
 1030   return "Abandon export";
 1031 }
 1032 
 1033 sub cancelExport_handler {
 1034   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1035   my $r      = $self->r;
 1036 
 1037   #$self->{selectedSetIDs) = $self->{visibleSetIDs};
 1038     # only do the above if we arrived here via "edit selected users"
 1039   if (defined $r->param("prev_visible_sets")) {
 1040     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
 1041   } elsif (defined $r->param("no_prev_visible_sets")) {
 1042     $self->{visibleSetIDs} = [];
 1043   } else {
 1044     # leave it alone
 1045   }
 1046   $self->{exportMode} = 0;
 1047 
 1048   return "export abandoned";
 1049 }
 1050 
 1051 sub saveExport_form {
 1052   my ($self, $onChange, %actionParams) = @_;
 1053   return "Export selected sets (This may take a long time.  Even if your browser times out, all the files will be exported).";
 1054 }
 1055 
 1056 sub saveExport_handler {
 1057   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1058   my $r           = $self->r;
 1059   my $db          = $r->db;
 1060 
 1061   my @setIDsToExport = @{ $self->{selectedSetIDs} };
 1062 
 1063   my %filenames = map { $_ => (@{ $tableParams->{"set.$_"} }[0] || $_) } @setIDsToExport;
 1064 
 1065   my ($exported, $skipped, $reason) = $self->exportSetsToDef(%filenames);
 1066 
 1067   if (defined $r->param("prev_visible_sets")) {
 1068     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
 1069   } elsif (defined $r->param("no_prev_visble_sets")) {
 1070     $self->{visibleSetIDs} = [];
 1071   } else {
 1072     # leave it alone
 1073   }
 1074 
 1075   $self->{exportMode} = 0;
 1076 
 1077   my $numExported = @$exported;
 1078   my $numSkipped = @$skipped;
 1079 
 1080   my @reasons = map { "set $_ - " . $reason->{$_} } keys %$reason;
 1081 
 1082   return  $numExported . " set" . ($numExported == 1 ? "" : "s") . " exported, "
 1083     . $numSkipped . " set" . ($numSkipped == 1 ? "" : "s") . " skipped."
 1084     . (($numSkipped) ? CGI::ul(CGI::li(\@reasons)) : "");
 1085 
 1086 }
 1087 
 1088 sub cancelEdit_form {
 1089   my ($self, $onChange, %actionParams) = @_;
 1090   return "Abandon changes";
 1091 }
 1092 
 1093 sub cancelEdit_handler {
 1094   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1095   my $r      = $self->r;
 1096 
 1097   #$self->{selectedSetIDs) = $self->{visibleSetIDs};
 1098     # only do the above if we arrived here via "edit selected users"
 1099   if (defined $r->param("prev_visible_sets")) {
 1100     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
 1101   } elsif (defined $r->param("no_prev_visible_sets")) {
 1102     $self->{visibleSetIDs} = [];
 1103   } else {
 1104     # leave it alone
 1105   }
 1106   $self->{editMode} = 0;
 1107 
 1108   return "changes abandoned";
 1109 }
 1110 
 1111 sub saveEdit_form {
 1112   my ($self, $onChange, %actionParams) = @_;
 1113   return "Save changes";
 1114 }
 1115 
 1116 sub saveEdit_handler {
 1117   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1118   my $r           = $self->r;
 1119   my $db          = $r->db;
 1120 
 1121   my @visibleSetIDs = @{ $self->{visibleSetIDs} };
 1122   foreach my $setID (@visibleSetIDs) {
 1123     my $Set = $db->getGlobalSet($setID); # checked
 1124 # FIXME: we may not want to die on bad sets, they're not as bad as bad users
 1125     die "record for visible set $setID not found" unless $Set;
 1126 
 1127     foreach my $field ($Set->NONKEYFIELDS()) {
 1128       my $param = "set.${setID}.${field}";
 1129       if (defined $tableParams->{$param}->[0]) {
 1130         if ($field =~ /_date/) {
 1131           $Set->$field(parseDateTime($tableParams->{$param}->[0]));
 1132         } else {
 1133           $Set->$field($tableParams->{$param}->[0]);
 1134         }
 1135       }
 1136     }
 1137 
 1138     ###################################################
 1139     # Check that the open, due and answer dates are in increasing order.
 1140     # Bail if this is not correct.
 1141     ###################################################
 1142     if ($Set->open_date > $Set->due_date)  {
 1143       return CGI::div({class=>'ResultsWithError'}, "Error: Due date must come after open date in set $setID");
 1144     }
 1145     if ($Set->due_date > $Set->answer_date) {
 1146       return CGI::div({class=>'ResultsWithError'}, "Error: Answer date must come after due date in set $setID");
 1147     }
 1148     ###################################################
 1149     # End date check section.
 1150     ###################################################
 1151     $db->putGlobalSet($Set);
 1152   }
 1153 
 1154   if (defined $r->param("prev_visible_sets")) {
 1155     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
 1156   } elsif (defined $r->param("no_prev_visble_sets")) {
 1157     $self->{visibleSetIDs} = [];
 1158   } else {
 1159     # leave it alone
 1160   }
 1161 
 1162   $self->{editMode} = 0;
 1163 
 1164   return "changes saved";
 1165 }
 1166 
 1167 ################################################################################
 1168 # sorts
 1169 ################################################################################
 1170 
 1171 sub bySetID         { $a->set_id         cmp $b->set_id         }
 1172 sub bySetHeader     { $a->set_header     cmp $b->set_header     }
 1173 sub byHardcopyHeader { $a->hardcopy_header cmp $b->hardcopy_header }
 1174 sub byOpenDate      { $a->open_date      <=> $b->open_date      }
 1175 sub byDueDate       { $a->due_date       <=> $b->due_date       }
 1176 sub byAnswerDate    { $a->answer_date    <=> $b->answer_date    }
 1177 sub byPublished     { $a->published      cmp $b->published      }
 1178 
 1179 sub byOpenDue       { &byOpenDate || &byDueDate }
 1180 
 1181 ################################################################################
 1182 # utilities
 1183 ################################################################################
 1184 
 1185 # generate labels for open_date/due_date/answer_date popup menus
 1186 sub menuLabels {
 1187   my ($self, $hashRef) = @_;
 1188   my %hash = %$hashRef;
 1189 
 1190   my %result;
 1191   foreach my $key (keys %hash) {
 1192     my $count = @{ $hash{$key} };
 1193     my $displayKey = formatDateTime($key) || "<none>";
 1194     $result{$key} = "$displayKey ($count sets)";
 1195   }
 1196   return %result;
 1197 }
 1198 
 1199 sub importSetsFromDef {
 1200   my ($self, $newSetName, $assign, @setDefFiles) = @_;
 1201   my $r     = $self->r;
 1202   my $ce    = $r->ce;
 1203   my $db    = $r->db;
 1204   my $dir   = $ce->{courseDirs}->{templates};
 1205 
 1206   # FIXME: do we really want everything to fail on one bad file name?
 1207   foreach my $fileName (@setDefFiles) {
 1208     die "won't be able to read from file $dir/$fileName: does it exist? is it readable?"
 1209       unless -r "$dir/$fileName";
 1210   }
 1211 
 1212   my @allSetIDs = $db->listGlobalSets();
 1213   # FIXME: getGlobalSets takes a lot of time just for checking to see if a set already exists
 1214   #   this could be avoided by waiting until the call to addGlobalSet below
 1215   # and checking to see if the error message says that the set already exists
 1216   # but if the error message is ever changed the code here might be broken
 1217   # then again, one call to getGlobalSets and skipping unnecessary calls to addGlobalSet
 1218   # could be faster than no call to getGlobalSets and lots of unnecessary calls to addGlobalSet
 1219   my %allSets = map { $_->set_id => 1 if $_} $db->getGlobalSets(@allSetIDs); # checked
 1220 
 1221   my (@added, @skipped);
 1222 
 1223   foreach my $set_definition_file (@setDefFiles) {
 1224 
 1225     $WeBWorK::timer->continue("$set_definition_file: reading set definition file") if defined $WeBWorK::timer;
 1226     # read data in set definition file
 1227     my ($setName, $paperHeaderFile, $screenHeaderFile, $openDate, $dueDate, $answerDate, $ra_problemData) = $self->readSetDef($set_definition_file);
 1228     my @problemList = @{$ra_problemData};
 1229 
 1230     # Use the original name if form doesn't specify a new one.
 1231     # The set acquires the new name specified by the form.  A blank
 1232     # entry on the form indicates that the imported set name will be used.
 1233     $setName = $newSetName if $newSetName;
 1234 
 1235     if ($allSets{$setName}) {
 1236       # this set already exists!!
 1237       push @skipped, $setName;
 1238       next;
 1239     } else {
 1240       push @added, $setName;
 1241     }
 1242 
 1243     $WeBWorK::timer->continue("$set_definition_file: adding set") if defined $WeBWorK::timer;
 1244     # add the data to the set record
 1245     my $newSetRecord = $db->newGlobalSet;
 1246     $newSetRecord->set_id($setName);
 1247     $newSetRecord->set_header($screenHeaderFile);
 1248     $newSetRecord->hardcopy_header($paperHeaderFile);
 1249     $newSetRecord->open_date($openDate);
 1250     $newSetRecord->due_date($dueDate);
 1251     $newSetRecord->answer_date($answerDate);
 1252     $newSetRecord->published(DEFAULT_PUBLISHED_STATE);
 1253 
 1254     #create the set
 1255     eval {$db->addGlobalSet($newSetRecord)};
 1256     die "addGlobalSet $setName in ProblemSetList:  $@" if $@;
 1257 
 1258     $WeBWorK::timer->continue("$set_definition_file: adding problems to database") if defined $WeBWorK::timer;
 1259     # add problems
 1260     my $freeProblemID = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1;
 1261     foreach my $rh_problem (@problemList) {
 1262       $self->addProblemToSet(
 1263         setName => $setName,
 1264         sourceFile => $rh_problem->{source_file},
 1265         problemID => $freeProblemID++,
 1266         value => $rh_problem->{value},
 1267         maxAttempts => $rh_problem->{max_attempts});
 1268     }
 1269 
 1270 
 1271     if ($assign eq "all") {
 1272       $self->assignSetToAllUsers($setName);
 1273     }
 1274   }
 1275 
 1276 
 1277   return \@added, \@skipped;
 1278 }
 1279 
 1280 sub readSetDef {
 1281   my ($self, $fileName) = @_;
 1282   my $templateDir   = $self->{ce}->{courseDirs}->{templates};
 1283   my $filePath      = "$templateDir/$fileName";
 1284 
 1285   my $setName = '';
 1286 
 1287   if ($fileName =~ m|^set([\w-]+)\.def$|) {
 1288     $setName = $1;
 1289   } else {
 1290     warn qq{The setDefinition file name must begin with   <CODE>set</CODE>},
 1291       qq{and must end with   <CODE>.def</CODE>  . Every thing in between becomes the name of the set. },
 1292       qq{For example <CODE>set1.def</CODE>, <CODE>setExam.def</CODE>, and <CODE>setsample7.def</CODE> },
 1293       qq{define sets named <CODE>1</CODE>, <CODE>Exam</CODE>, and <CODE>sample7</CODE> respectively. },
 1294       qq{The filename, $fileName, you entered is not legal\n };
 1295 
 1296   }
 1297 
 1298   my ($line, $name, $value, $attemptLimit, $continueFlag);
 1299   my $paperHeaderFile = '';
 1300   my $screenHeaderFile = '';
 1301   my ($dueDate, $openDate, $answerDate);
 1302   my @problemData;
 1303 
 1304 
 1305   my %setInfo;
 1306   if ( open (SETFILENAME, "$filePath") )    {
 1307   #####################################################################
 1308   # Read and check set data
 1309   #####################################################################
 1310     while (<SETFILENAME>) {
 1311 
 1312       chomp($line = $_);
 1313       $line =~ s|(#.*)||;                              ## don't read past comments
 1314       unless ($line =~ /\S/) {next;}                   ## skip blank lines
 1315       $line =~ s|\s*$||;                               ## trim trailing spaces
 1316       $line =~ m|^\s*(\w+)\s*=\s*(.*)|;
 1317 
 1318       ######################
 1319       # sanity check entries
 1320       ######################
 1321       my $item = $1;
 1322       $item    = '' unless defined $item;
 1323       my $value = $2;
 1324       $value    = '' unless defined $value;
 1325 
 1326       if ($item eq 'setNumber') {
 1327         next;
 1328       } elsif ($item eq 'paperHeaderFile') {
 1329         $paperHeaderFile = $value;
 1330       } elsif ($item eq 'screenHeaderFile') {
 1331         $screenHeaderFile = $value;
 1332       } elsif ($item eq 'dueDate') {
 1333         $dueDate = $value;
 1334       } elsif ($item eq 'openDate') {
 1335         $openDate = $value;
 1336       } elsif ($item eq 'answerDate') {
 1337         $answerDate = $value;
 1338       } elsif ($item eq 'problemList') {
 1339         last;
 1340       } else {
 1341         warn "readSetDef error, can't read the line: ||$line||";
 1342       }
 1343     }
 1344 
 1345     #####################################################################
 1346     # Check and format dates
 1347     #####################################################################
 1348     my ($time1, $time2, $time3) = map { $_ =~ s/\s*at\s*/ /; WeBWorK::Utils::parseDateTime($_);  }    ($openDate, $dueDate, $answerDate);
 1349 
 1350     unless ($time1 <= $time2 and $time2 <= $time3) {
 1351       warn "The open date: $openDate, due date: $dueDate, and answer date: $answerDate must be defined and in chronological order.";
 1352     }
 1353 
 1354     # Check header file names
 1355     $paperHeaderFile =~ s/(.*?)\s*$/$1/;   #remove trailing white space
 1356     $screenHeaderFile =~ s/(.*?)\s*$/$1/;   #remove trailing white space
 1357 
 1358     #####################################################################
 1359     # Read and check list of problems for the set
 1360     #####################################################################
 1361     while(<SETFILENAME>) {
 1362       chomp($line=$_);
 1363       $line =~ s/(#.*)//;                             ## don't read past comments
 1364       unless ($line =~ /\S/) {next;}                  ## skip blank lines
 1365 
 1366       ($name, $value, $attemptLimit, $continueFlag) = split (/\s*,\s*/,$line);
 1367       #####################
 1368       #  clean up problem values
 1369       ###########################
 1370       $name =~ s/\s*//g;
 1371       $value = "" unless defined($value);
 1372       $value =~ s/[^\d\.]*//g;
 1373       unless ($value =~ /\d+/) {$value = 1;}
 1374       $attemptLimit = "" unless defined($attemptLimit);
 1375       $attemptLimit =~ s/[^\d-]*//g;
 1376       unless ($attemptLimit =~ /\d+/) {$attemptLimit = -1;}
 1377       $continueFlag = "0" unless( defined($continueFlag) && @problemData );
 1378       # can't put continuation flag onto the first problem
 1379       push(@problemData, {source_file    => $name,
 1380                           value          =>  $value,
 1381                           max_attempts   =>, $attemptLimit,
 1382                           continuation   => $continueFlag
 1383                           });
 1384     }
 1385     close(SETFILENAME);
 1386     ($setName,
 1387      $paperHeaderFile,
 1388      $screenHeaderFile,
 1389      $time1,
 1390      $time2,
 1391      $time3,
 1392      \@problemData,
 1393     );
 1394   } else {
 1395     warn "Can't open file $filePath\n";
 1396   }
 1397 }
 1398 
 1399 sub exportSetsToDef {
 1400       my ($self, %filenames) = @_;
 1401 
 1402   my $r        = $self->r;
 1403   my $ce       = $r->ce;
 1404   my $db       = $r->db;
 1405 
 1406   my (@exported, @skipped, %reason);
 1407 
 1408 SET:  foreach my $set (keys %filenames) {
 1409 
 1410     my $fileName = $filenames{$set};
 1411     $fileName .= ".def" unless $fileName =~ m/\.def$/;
 1412     $fileName  = "set" . $fileName unless $fileName =~ m/^set/;
 1413     # files can be exported to sub directories but not parent directories
 1414     if ($fileName =~ /\.\./) {
 1415       push @skipped, $set;
 1416       $reason{$set} = "Illegal filename contains '..'";
 1417       next SET;
 1418     }
 1419 
 1420     my $setRecord = $db->getGlobalSet($set);
 1421     unless (defined $setRecord) {
 1422       push @skipped, $set;
 1423       $reason{$set} = "No record found.";
 1424       next SET;
 1425     }
 1426     my $filePath = $ce->{courseDirs}->{templates} . '/' . $fileName;
 1427 
 1428     # back up existing file
 1429     if(-e $filePath) {
 1430       rename($filePath, "$filePath.bak") or
 1431         $reason{$set} = "Existing file $filePath could not be backed up and was lost.";
 1432     }
 1433 
 1434     my $openDate     = formatDateTime($setRecord->open_date);
 1435     my $dueDate      = formatDateTime($setRecord->due_date);
 1436     my $answerDate   = formatDateTime($setRecord->answer_date);
 1437     my $setHeader    = $setRecord->set_header;
 1438     my $paperHeader  = $setRecord->hardcopy_header;
 1439     my @problemList = $db->listGlobalProblems($set);
 1440 
 1441     my $problemList  = '';
 1442     foreach my $prob (sort {$a <=> $b} @problemList) {
 1443       my $problemRecord = $db->getGlobalProblem($set, $prob); # checked
 1444       unless (defined $problemRecord) {
 1445         push @skipped, $set;
 1446         $reason{$set} = "No record found for problem $prob.";
 1447         next SET;
 1448       }
 1449       my $source_file   = $problemRecord->source_file();
 1450       my $value         = $problemRecord->value();
 1451       my $max_attempts  = $problemRecord->max_attempts();
 1452       $problemList     .= "$source_file, $value, $max_attempts \n";
 1453     }
 1454     my $fileContents = <<EOF;
 1455 
 1456 openDate          = $openDate
 1457 dueDate           = $dueDate
 1458 answerDate        = $answerDate
 1459 paperHeaderFile   = $paperHeader
 1460 screenHeaderFile  = $setHeader
 1461 problemList       =
 1462 
 1463 $problemList
 1464 
 1465 
 1466 
 1467 EOF
 1468 
 1469     $filePath = WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates}, $filePath);
 1470     eval {
 1471       local *SETDEF;
 1472       open SETDEF, ">$filePath" or die "Failed to open $filePath";
 1473       print SETDEF $fileContents;
 1474       close SETDEF;
 1475     };
 1476 
 1477     if ($@) {
 1478       push @skipped, $set;
 1479       $reason{$set} = $@;
 1480     } else {
 1481       push @exported, $set;
 1482     }
 1483 
 1484   }
 1485 
 1486   return \@exported, \@skipped, \%reason;
 1487 
 1488 }
 1489 
 1490 
 1491 # search recursively through a directory looking for all filenames matching a given pattern
 1492 sub recurseDirectory {
 1493 
 1494   my ($self, $dir, $pattern) = @_;
 1495 
 1496   # search for all immediate readable subdirectories that are not:
 1497   #  .
 1498   # ..
 1499   # Library
 1500   # CVS
 1501   # FIXME: we're throwing away any errors from the eval, is that bad?
 1502   my @dirs = grep {$_ ne "." and $_ ne ".." and $_ ne "Library" and $_ ne "CVS" and -d "$dir/$_" and -r "$dir/$_" } eval{ readDirectory($dir) };
 1503 
 1504   my @files = map { "$dir/$_" } $self->read_dir($dir, $pattern);
 1505 
 1506   foreach (@dirs) {
 1507     push (@files, $self->recurseDirectory("$dir/$_", $pattern));
 1508   }
 1509 
 1510   return @files;
 1511 }
 1512 
 1513 ################################################################################
 1514 # "display" methods
 1515 ################################################################################
 1516 
 1517 sub fieldEditHTML {
 1518   my ($self, $fieldName, $value, $properties) = @_;
 1519   my $size = $properties->{size};
 1520   my $type = $properties->{type};
 1521   my $access = $properties->{access};
 1522   my $items = $properties->{items};
 1523   my $synonyms = $properties->{synonyms};
 1524   my $headerFiles = $self->{headerFiles};
 1525 
 1526   if ($access eq "readonly") {
 1527     return $value;
 1528   }
 1529 
 1530   if ($type eq "number" or $type eq "text") {
 1531     return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
 1532   }
 1533 
 1534   if ($type eq "filelist") {
 1535     return CGI::popup_menu({
 1536       name => $fieldName,
 1537       value => [ sort keys %$headerFiles ],
 1538       labels => $headerFiles,
 1539       default => $value || 0,
 1540     });
 1541   }
 1542 
 1543   if ($type eq "enumerable") {
 1544     my $matched = undef; # Whether a synonym match has occurred
 1545 
 1546     # Process synonyms for enumerable objects
 1547     foreach my $synonym (keys %$synonyms) {
 1548       if ($synonym ne "*" and $value =~ m/$synonym/) {
 1549         $value = $synonyms->{$synonym};
 1550         $matched = 1;
 1551       }
 1552     }
 1553 
 1554     if (!$matched and exists $synonyms->{"*"}) {
 1555       $value = $synonyms->{"*"};
 1556     }
 1557 
 1558     return CGI::popup_menu({
 1559       name => $fieldName,
 1560       values => [keys %$items],
 1561       default => $value,
 1562       labels => $items,
 1563     });
 1564   }
 1565 
 1566   if ($type eq "checked") {
 1567 
 1568     # FIXME: kludge (R)
 1569     # if the checkbox is checked it returns a 1, if it is unchecked it returns nothing
 1570     # in which case the hidden field overrides the parameter with a 0
 1571     return CGI::checkbox(
 1572       -name => $fieldName,
 1573       -checked => $value,
 1574       -label => "",
 1575       -value => 1
 1576     ) . CGI::hidden(
 1577       -name => $fieldName,
 1578       -value => 0
 1579     );
 1580   }
 1581 }
 1582 
 1583 sub recordEditHTML {
 1584   my ($self, $Set, %options) = @_;
 1585   my $r           = $self->r;
 1586   my $urlpath     = $r->urlpath;
 1587   my $ce          = $r->ce;
 1588   my $db    = $r->db;
 1589   my $authz = $r->authz;
 1590   my $user  = $r->param('user');
 1591   my $root        = $ce->{webworkURLs}->{root};
 1592   my $courseName  = $urlpath->arg("courseID");
 1593 
 1594   my $editMode = $options{editMode};
 1595   my $exportMode = $options{exportMode};
 1596   my $setSelected = $options{setSelected};
 1597 
 1598   my $publishedClass = $Set->published ? "Published" : "Unpublished";
 1599 
 1600   my $users = $db->countSetUsers($Set->set_id);
 1601   my $totalUsers = $self->{totalUsers};
 1602   my $problems = $db->listGlobalProblems($Set->set_id);
 1603 
 1604         my $usersAssignedToSetURL  = $self->systemLink($urlpath->new(type=>'instructor_users_assigned_to_set', args=>{courseID => $courseName, setID => $Set->set_id} ));
 1605   my $problemListURL  = $self->systemLink($urlpath->new(type=>'instructor_problem_list', args=>{courseID => $courseName, setID => $Set->set_id} ));
 1606   my $problemSetListURL = $self->systemLink($urlpath->new(type=>'instructor_set_list', args=>{courseID => $courseName, setID => $Set->set_id})) . "&editMode=1&visible_sets=" . $Set->set_id;
 1607   my $imageURL = $ce->{webworkURLs}->{htdocs}."/images/edit.gif";
 1608         my $imageLink = CGI::a({href => $problemSetListURL}, CGI::img({src=>$imageURL, border=>0}));
 1609 
 1610   my @tableCells;
 1611   my %fakeRecord;
 1612   my $set_id = $Set->set_id;
 1613   $fakeRecord{select} = CGI::checkbox(-name => "selected_sets", -value => $set_id, -checked => $setSelected, -label => "", );
 1614   $fakeRecord{set_id} = CGI::font({class=>$publishedClass}, $set_id) . ($editMode ? "" : $imageLink);
 1615   $fakeRecord{problems} = (FIELD_PERMS()->{problems} and not $authz->hasPermissions($user, FIELD_PERMS()->{problems}))
 1616           ? "$problems"
 1617           : CGI::a({href=>$problemListURL}, "$problems");
 1618   $fakeRecord{users} = (FIELD_PERMS()->{users} and not $authz->hasPermissions($user, FIELD_PERMS()->{users}))
 1619           ? "$users/$totalUsers"
 1620           : CGI::a({href=>$usersAssignedToSetURL}, "$users/$totalUsers");
 1621   $fakeRecord{filename} = CGI::input({-name => "set.$set_id", -value=>"set$set_id.def", -size=>60});
 1622 
 1623 
 1624   # Select
 1625   if ($editMode) {
 1626     # column not there
 1627   } else {
 1628     # selection checkbox
 1629     push @tableCells, CGI::checkbox(
 1630       -name => "selected_sets",
 1631       -value => $set_id,
 1632       -checked => $setSelected,
 1633       -label => "",
 1634     );
 1635   }
 1636 
 1637   # Set ID
 1638   push @tableCells, CGI::font({class=>$publishedClass}, $set_id . $imageLink);
 1639 
 1640   # Problems link
 1641   if ($editMode) {
 1642     # column not there
 1643   } else {
 1644     # "problem list" link
 1645     push @tableCells, CGI::a({href=>$problemListURL}, "$problems");
 1646   }
 1647 
 1648   # Users link
 1649   if ($editMode) {
 1650     # column not there
 1651   } else {
 1652     # "edit users assigned to set" link
 1653     push @tableCells, CGI::a({href=>$usersAssignedToSetURL}, "$users/$totalUsers");
 1654   }
 1655 
 1656   # Set Fields
 1657   foreach my $field ($Set->NONKEYFIELDS) {
 1658     my $fieldName = "set." . $set_id . "." . $field,
 1659     my $fieldValue = $Set->$field;
 1660     my %properties = %{ FIELD_PROPERTIES()->{$field} };
 1661     $properties{access} = "readonly" unless $editMode;
 1662     $fieldValue = formatDateTime($fieldValue) if $field =~ /_date/;
 1663     $fieldValue =~ s/ /&nbsp;/g unless $editMode;
 1664     $fieldValue = ($fieldValue) ? "Yes" : "No" if $field =~ /published/ and not $editMode;
 1665     push @tableCells, CGI::font({class=>$publishedClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1666     $fakeRecord{$field} = CGI::font({class=>$publishedClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1667   }
 1668 
 1669   my @fieldsToShow;
 1670   if ($editMode) {
 1671     @fieldsToShow = @{ EDIT_FIELD_ORDER() };
 1672   } else {
 1673     @fieldsToShow = @{ VIEW_FIELD_ORDER() };
 1674   }
 1675 
 1676   if ($exportMode) {
 1677     @fieldsToShow = @{ EXPORT_FIELD_ORDER() };
 1678   }
 1679 
 1680   @tableCells = map { $fakeRecord{$_} } @fieldsToShow;
 1681 
 1682   return CGI::Tr({}, CGI::td({}, \@tableCells));
 1683 }
 1684 
 1685 sub printTableHTML {
 1686   my ($self, $SetsRef, $fieldNamesRef, %options) = @_;
 1687   my $r                       = $self->r;
 1688   my $authz                   = $r->authz;
 1689   my $user                    = $r->param('user');
 1690   my $setTemplate             = $self->{setTemplate};
 1691   my @Sets                    = @$SetsRef;
 1692   my %fieldNames              = %$fieldNamesRef;
 1693 
 1694   my $editMode                = $options{editMode};
 1695   my $exportMode              = $options{exportMode};
 1696   my %selectedSetIDs          = map { $_ => 1 } @{ $options{selectedSetIDs} };
 1697   my $currentSort             = $options{currentSort};
 1698 
 1699   # names of headings:
 1700   my @realFieldNames = (
 1701       $setTemplate->KEYFIELDS,
 1702       $setTemplate->NONKEYFIELDS,
 1703   );
 1704 
 1705   if ($editMode) {
 1706     @realFieldNames = @{ EDIT_FIELD_ORDER() };
 1707   } else {
 1708     @realFieldNames = @{ VIEW_FIELD_ORDER() };
 1709   }
 1710 
 1711   if ($exportMode) {
 1712     @realFieldNames = @{ EXPORT_FIELD_ORDER() };
 1713   }
 1714 
 1715 
 1716   my %sortSubs = %{ SORT_SUBS() };
 1717 
 1718   # FIXME: should this always presume to use the templates directory?
 1719   my @headers = $self->recurseDirectory($self->{ce}->{courseDirs}->{templates}, '(?i)header.*?\\.pg$');
 1720   map { s|^$self->{ce}->{courseDirs}->{templates}/?|| } @headers;
 1721   @headers = sort @headers;
 1722   my %headers = map { $_ => $_ } @headers;
 1723   $headers{""} = "Use System Default";
 1724   $self->{headerFiles} = \%headers; # store these header files so we don't have to look for them later.
 1725 
 1726 
 1727   my @tableHeadings = map { $fieldNames{$_} } @realFieldNames;
 1728 
 1729   # prepend selection checkbox? only if we're NOT editing!
 1730 # unshift @tableHeadings, "Select", "Set", "Problems" unless $editMode;
 1731 
 1732   # print the table
 1733   if ($editMode or $exportMode) {
 1734     print CGI::start_table({});
 1735   } else {
 1736     print CGI::start_table({-border=>1});
 1737   }
 1738 
 1739   print CGI::Tr({}, CGI::th({}, \@tableHeadings));
 1740 
 1741 
 1742   for (my $i = 0; $i < @Sets; $i++) {
 1743     my $Set = $Sets[$i];
 1744 
 1745     print $self->recordEditHTML($Set,
 1746       editMode => $editMode,
 1747       exportMode => $exportMode,
 1748       setSelected => exists $selectedSetIDs{$Set->set_id}
 1749     );
 1750   }
 1751 
 1752   print CGI::end_table();
 1753   #########################################
 1754   # if there are no users shown print message
 1755   #
 1756   ##########################################
 1757 
 1758   print CGI::p(
 1759                       CGI::i("No sets shown.  Choose one of the options above to list the sets in the course.")
 1760   ) unless @Sets;
 1761 }
 1762 
 1763 1;
 1764 

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9