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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2778 - (download) (as text) (annotate)
Mon Sep 13 19:35:12 2004 UTC (8 years, 8 months ago) by sh002i
File size: 17060 byte(s)
timezone support

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm,v 1.62 2004/09/10 02:35:19 sh002i 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 use constant SET_FIELDS => [qw(open_date due_date answer_date set_header hardcopy_header published)];
   37 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
   38 use constant PROBLEM_USER_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
   39 
   40 sub getSetName {
   41   my ($self, $pathSetName) = @_;
   42   if (ref $pathSetName eq "HASH") {
   43     $pathSetName = undef;
   44   }
   45   return $pathSetName;
   46 }
   47 
   48 # One wrinkle here: if $override is undefined, do the global thing,
   49 # otherwise, it's truth value determines the checkbox and the current fieldValue is not directly editable
   50 sub setRowHTML {
   51   my ($description, $fieldName, $fieldValue, $size, $override, $overrideValue) = @_;
   52 
   53   my $attributeHash = {type=>"text", name=>$fieldName, value=>$fieldValue};
   54   $attributeHash->{size} = $size if defined $size;
   55 
   56   my $input = (defined $override) ? $fieldValue : CGI::input($attributeHash);
   57 
   58   my $html = CGI::td({}, [$description, $input]);
   59 
   60   if (defined $override) {
   61     $attributeHash->{name}="${fieldName}_override";
   62     $attributeHash->{value}=($override ? $overrideValue : "" );
   63 
   64     $html .= CGI::td({}, [
   65       CGI::checkbox({
   66         type=>"checkbox",
   67         name=>"override",
   68         label=>"override with:",
   69         value=>$fieldName,
   70         checked=>($override ? 1 : 0)
   71       }),
   72       CGI::input($attributeHash)
   73     ]);
   74   }
   75 
   76   return $html;
   77 
   78 }
   79 
   80 # Initialize does all of the form processing.  It's extensive, and could probably be cleaned up and
   81 # consolidated with a little abstraction.
   82 sub initialize {
   83   my ($self)      = @_;
   84   my $r           = $self->r;
   85   my $db          = $r->db;
   86   my $ce          = $r->ce;
   87   my $authz       = $r->authz;
   88   my $user        = $r->param('user');
   89   #my $setName    = $self->getSetName(@components);
   90   my $setName     = $r->urlpath->arg("setID");
   91   my $setRecord   = $db->getGlobalSet($setName); #checked
   92   die "global set $setName not found." unless $setRecord;
   93 
   94   $self->{set}    = $setRecord;
   95   my @editForUser = $r->param('editForUser');
   96   # some useful booleans
   97   my $forUsers    = scalar(@editForUser);
   98   my $forOneUser  = $forUsers == 1;
   99 
  100   # build a quick lookup table
  101   my %overrides = list2hash $r->param('override');
  102 
  103   # Check permissions
  104   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  105   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  106 
  107   ###################################################
  108   # The set form was submitted with the save button pressed
  109   # Save changes to the set
  110   ###################################################
  111 
  112   if (defined($r->param('submit_set_changes'))) {
  113 
  114     if (!$forUsers) {
  115       foreach (@{SET_FIELDS()}) {
  116         if (defined($r->param($_))) {
  117           if (m/_date$/) {
  118             $setRecord->$_($self->parseDateTime($r->param($_)));
  119           } else {
  120             $setRecord->$_($r->param($_)) unless ($_ eq 'set_header' and $r->param($_) eq "Use System Default");
  121 
  122             if($_ eq 'set_header') {
  123               # be nice and copy the default file here if it doesn't exist yet
  124               # empty set headers lead to trouble
  125               my $set_header = ($r->param($_) eq "Use System Default") ? $setRecord->set_header : $r->param($_);
  126 
  127               my $newheaderpath = $r->{ce}->{courseDirs}->{templates} . '/'. $set_header;
  128               unless(($set_header !~ /\S/) or -e $newheaderpath) {
  129                 my $default_header = $ce->{webworkFiles}->{screenSnippets}->{setHeader};
  130                 File::Copy::copy($default_header, $newheaderpath);
  131               }
  132             }
  133           }
  134         } else {
  135           if (m/published$/) {
  136             $setRecord->$_(0);
  137           }
  138         }
  139       }
  140 
  141 
  142 
  143 
  144       ###################################################
  145       # Check that the open, due and answer dates are in increasing order.
  146       # Bail if this is not correct.
  147       ###################################################
  148       if ($setRecord->open_date > $setRecord->due_date)  {
  149         $self->addbadmessage('Error: Due date must come after open date');
  150         return;
  151       }
  152       if ($setRecord->due_date > $setRecord->answer_date) {
  153         $self->addbadmessage('Error: Answer date must come after due date');
  154         return;
  155       }
  156       ###################################################
  157       # End date check section.
  158       ###################################################
  159       $self->addgoodmessage("Changes to set $setName were successfully saved.");
  160       $db->putGlobalSet($setRecord);
  161     } else {
  162 
  163       my $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
  164       die "set $setName not found for $editForUser[0]." unless $userSetRecord;
  165       foreach my $field (@{SET_FIELDS()}) {
  166         if (defined $r->param("${field}_override")) {
  167           if (exists $overrides{$field}) {
  168             if ($field =~ m/_date$/) {
  169               $userSetRecord->$field($self->parseDateTime($r->param("${field}_override")));
  170             } else {
  171               $userSetRecord->$field($r->param("${field}_override"));
  172             }
  173           } else {
  174             $userSetRecord->$field(undef);
  175           }
  176         }
  177       }
  178       ###################################################
  179       # Check that the open, due and answer dates are in increasing order.
  180       # Bail if this is not correct.
  181       ###################################################
  182       my $active_open_date   = $userSetRecord->open_date   ? $userSetRecord->open_date   : $setRecord->open_date;
  183       my $active_due_date    = $userSetRecord->due_date    ? $userSetRecord->due_date    : $setRecord->due_date;
  184       my $active_answer_date = $userSetRecord->answer_date ? $userSetRecord->answer_date : $setRecord->answer_date;
  185       if ( $active_open_date > $active_due_date ) {
  186         $self->addbadmessage('Error: Due date override must come after open date');
  187         return;
  188       }
  189       if ( $active_due_date > $active_answer_date ) {
  190         $self->addbadmessage('Error: Answer date override must come after due date');
  191         return;
  192       }
  193       ###################################################
  194       # End date check section.
  195       ###################################################
  196       $self->addgoodmessage("Changes to set $setName for user ", CGI::b($editForUser[0]), "were successfully saved.");
  197       $db->putUserSet($userSetRecord);
  198     }
  199 
  200   }
  201 
  202   ###################################################
  203   # The set form was submitted with the export button pressed
  204   # Export the set structure to a set definition file
  205   ###################################################
  206 
  207   if (  defined($r->param('export_set'))  ) {
  208     my $fileName = $r->param('export_file_name');
  209     die "Please specify a file name for saving the set definition" unless $fileName;
  210     $fileName    .= '.def' unless $fileName =~ /\.def$/;
  211     my $filePath  = $ce->{courseDirs}->{templates}.'/'.$fileName;
  212     # back up existing file
  213     if(-e $filePath) {
  214         rename($filePath,"$filePath.bak") or
  215                die "Can't rename $filePath to $filePath.bak ",
  216                    "Check permissions for webserver on directories. $!";
  217     }
  218       my $openDate     = $self->formatDateTime($setRecord->open_date);
  219       my $dueDate      = $self->formatDateTime($setRecord->due_date);
  220       my $answerDate   = $self->formatDateTime($setRecord->answer_date);
  221       my $setHeader    = $setRecord->set_header;
  222 
  223       my @problemList = $db->listGlobalProblems($setName);
  224       my $problemList  = '';
  225       foreach my $prob (sort {$a <=> $b} @problemList) {
  226         my $problemRecord = $db->getGlobalProblem($setName, $prob); # checked
  227         die "global problem $prob for set $setName not found" unless defined($problemRecord);
  228         my $source_file   = $problemRecord->source_file();
  229       my $value         = $problemRecord->value();
  230       my $max_attempts  = $problemRecord->max_attempts();
  231         $problemList     .= "$source_file, $value, $max_attempts \n";
  232       }
  233       my $fileContents = <<EOF;
  234 
  235 openDate          = $openDate
  236 dueDate           = $dueDate
  237 answerDate        = $answerDate
  238 paperHeaderFile   = $setHeader
  239 screenHeaderFile  = $setHeader
  240 problemList       =
  241 
  242 $problemList
  243 
  244 
  245 
  246 EOF
  247 
  248 
  249       $self->saveProblem($fileContents, $filePath);
  250       $self->addgoodmessage(CGI::p("Set definition saved to $filePath"));
  251 
  252   }
  253 }
  254 
  255 
  256 sub body {
  257   my ($self, @components) = @_;
  258   my $r                   = $self->r;
  259   my $urlpath             = $r->urlpath;
  260   my $db                  = $r->db;
  261   my $ce                  = $r->ce;
  262   my $authz               = $r->authz;
  263   my $user                = $r->param('user');
  264   my $courseName          = $urlpath->arg("courseID");
  265   my $setName             = $urlpath->arg("setID");
  266   my $setRecord           = $db->getGlobalSet($setName);  # checked
  267   die "global set $setName not found." unless $setRecord;
  268   my @editForUser         = $r->param('editForUser');
  269   # some useful booleans
  270   my $forUsers            = scalar(@editForUser);
  271   my $forOneUser          = $forUsers == 1;
  272 
  273   # Check permissions
  274   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  275     unless $authz->hasPermissions($r->param("user"), "access_instructor_tools");
  276 
  277   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problem sets.")
  278     unless $authz->hasPermissions($r->param("user"), "modify_problem_sets");
  279 
  280 
  281   ## Set Form ##
  282   my $userSetRecord;
  283   my %overrideArgs;
  284   if ($forOneUser) {
  285     $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
  286     die "set $setName not found for user $editForUser[0]." unless $userSetRecord;
  287     foreach my $field (@{SET_FIELDS()}) {
  288       $overrideArgs{$field} = [defined $userSetRecord->$field && $userSetRecord->$field ne "", ($field =~ /_date$/ ? $self->formatDateTime($userSetRecord->$field) : $userSetRecord->$field)];
  289     }
  290   } else {
  291     foreach my $field (@{SET_FIELDS()}) {
  292       $overrideArgs{$field} = [undef, undef];
  293     }
  294   }
  295   print CGI::h2({}, "Set Data"), "\n";
  296   if (@editForUser) {
  297     print CGI::p("Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
  298   }
  299 
  300   my $templates_dir = $r->ce->{courseDirs}->{templates};
  301   my %probLibs = %{ $r->ce->{courseFiles}->{problibs} };
  302   my $exempt_dirs = join("|", keys %probLibs);
  303   my @headers = listFilesRecursive(
  304     $templates_dir,
  305     qr/header.*\.pg$/i, # match these files
  306     qr/^(?:$exempt_dirs|CVS)$/, # prune these directories
  307     0, # match against file name only
  308     1, # prune against path relative to $templates_dir
  309   );
  310 
  311   @headers = sort @headers;
  312   unshift (@headers, "Use System Default");
  313 
  314   print CGI::start_form({method=>"post", action=>$r->uri}), "\n";
  315   print CGI::table({},
  316     CGI::Tr({}, [
  317       setRowHTML( "Open Date:",
  318             "open_date",
  319             $self->formatDateTime($setRecord->open_date),
  320             undef,
  321             @{$overrideArgs{open_date}})."\n",
  322       setRowHTML( "Due Date:",
  323             "due_date",
  324             $self->formatDateTime($setRecord->due_date),
  325             undef,
  326             @{$overrideArgs{due_date}})."\n",
  327       setRowHTML( "Answer Date:",
  328             "answer_date",
  329             $self->formatDateTime($setRecord->answer_date),
  330             undef,
  331             @{$overrideArgs{answer_date}})."\n",
  332 #     setRowHTML( "Set Header:", "set_header",
  333 #           $setRecord->set_header,
  334 #           32,
  335 #           @{$overrideArgs{set_header}})."\n",
  336 # 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.
  337 #       setRowHTML( "Problem Header:",
  338 #             "hardcopy_header",
  339 #             $setRecord->hardcopy_header,
  340 #             undef,
  341 #             @{$overrideArgs{hardcopy_header}})."\n",
  342       CGI::td({}, [ "Set Header:" ,
  343           ($forOneUser)
  344             ? $setRecord->set_header || "None selected."
  345             : CGI::popup_menu(
  346               -name=>'set_header',
  347               -values=>\@headers,
  348               -default=>0) .
  349             "(currently: " . ($setRecord->set_header || "None selected.") . ")" . "\n",
  350         ])
  351     ])
  352   );
  353 
  354   if (@editForUser) {
  355     my $publishedClass = ($setRecord->published) ? "Published" : "Unpublished";
  356     my $publishedText = ($setRecord->published) ? "visible to students" : "hidden from students";
  357     print CGI::p("This set is currently", CGI::font({class=>$publishedClass}, $publishedText),
  358     CGI::br(), "(You cannot hide or make a set visible for specific users.)");
  359   } else {
  360     print CGI::checkbox({type=>"checkbox", name=>"published", label=>"Visible to students", value=>"1", checked=>(($setRecord->published) ? 1 : 0)}), CGI::br();
  361 
  362   }
  363 
  364   print $self->hiddenEditForUserFields(@editForUser),
  365         $self->hidden_authen_fields,
  366         CGI::input({type=>"submit", name=>"submit_set_changes", value=>"Save Set", style=>"{width: 13ex}"}),
  367         '&nbsp;';
  368 
  369     #### link to edit setHeader
  370     my $PGProblemEditor    = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
  371                                                      courseID  => $courseName,
  372                                                      setID     => $setName,
  373                                                      problemID => '0'
  374     );
  375     my $setHeaderEditLink = $self->systemLink($PGProblemEditor);
  376   if (defined($setRecord) and $setRecord->set_header) {
  377     print CGI::a({-href=>$setHeaderEditLink},'Edit set header: '.$setRecord->set_header);
  378   }
  379 
  380   print CGI::br(),
  381         CGI::submit({ name=>"export_set", label=>"Export Set",  style=>"{width: 13ex}"} ),
  382         ' as ',
  383         CGI::input({type=>'text',name=>'export_file_name',value=>"set$setName.def",size=>32});
  384 
  385   print CGI::br();
  386 
  387 
  388 
  389   print CGI::end_form();
  390 
  391   my $problemCount = $db->listGlobalProblems($setName);
  392   print CGI::h2({}, "Problems"), "\n";
  393   print CGI::p({}, "This set contains $problemCount problem" . ($problemCount == 1 ? "" : "s").".");
  394   #FIXME
  395   # the code below doesn't work ---
  396   # get message
  397   #no type matches module WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser with args at
  398   # /home/gage/webwork/webwork-modperl/lib/WeBWorK/URLPath.pm line 497.
  399     # error in URLPath.pm??????
  400   my $problemSetListPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemList",
  401                                                     courseID => $courseName,
  402                                                     setID    => $setName
  403   );
  404 
  405   my $editProblemsURL        = $self->systemLink($problemSetListPage,
  406                                                  params => ['editForUser']   # include all editForUser parameters
  407   );
  408   my $usersAssignedToSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
  409                                                     courseID => $courseName,
  410                                                     setID    => $setName
  411   );
  412 
  413   my $editUsersAssignedToSetURL        = $self->systemLink($usersAssignedToSetPage,
  414 
  415   );
  416   print CGI::a({href=>$editProblemsURL},
  417    (@editForUser) ? "Edit the list of problems in this set for ". CGI::b(join ", ", @editForUser) :
  418                     "Edit the list of problems in this set");
  419 
  420   unless (@editForUser) {      # this is not needed when we are editing details for a user
  421     my $userCount = $db->listUsers;
  422     my $usersOfSet = $db->countSetUsers($setName);
  423     print CGI::h2({}, "Users"), "\n";
  424     print CGI::p({}, "This set is assigned to ".$self->userCountMessage($usersOfSet, $userCount).".");
  425     print CGI::a({href=>$editUsersAssignedToSetURL}, "Determine who this set is assigned to");
  426   }
  427 
  428   return "";
  429 }
  430 ###########################################################################
  431 # utility
  432 ###########################################################################
  433 sub saveProblem {
  434     my $self      = shift;
  435   my ($body, $probFileName)= @_;
  436   local(*PROBLEM);
  437   open (PROBLEM, ">$probFileName") ||
  438     $self->addbadmessage(CGI::p("Could not open $probFileName for writing. Check that the  permissions for this problem are 660 (-rw-rw----)"));
  439   print PROBLEM $body;
  440   close PROBLEM;
  441   chmod 0660, "$probFileName" ||
  442     $self->addbadmessage(CGI::p("CAN'T CHANGE PERMISSIONS ON FILE $probFileName"));
  443 }
  444 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9