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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3972 - (download) (as text) (annotate)
Wed Jan 25 23:12:05 2006 UTC (7 years, 4 months ago) by sh002i
File size: 22561 byte(s)
update copyright date range -- 2000-2006. this is probably overkill,
since there are some files that were created after 2000 and some files
that were last modified before 2006.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9