From: Sam H. v. a. <we...@ma...> - 2005-10-05 18:17:52
|
Log Message: ----------- Implemented status system as per bug #743. The status system consists of a new hash in the course environment, %statuses, that maps status names (like "Enrolled", "Drop", "Audit") to (a) a list of allowed abbreviations for the status and (b) a list of behaviors the system should have when dealing with users who have the status. I didn't want to have to mangle that hash-of-hashes-of-arrays myself in many modules, but I also didn't want to devote an entire module (Status.pm) to it. So, I'm trying a little experiment: I've added them as methods to WeBWorK::CourseEnvironment. My thinkint is that since all the data for these operations comes from the course environment, so why not have them be course environment methods? Here they are: status_abbrev_to_name($status_abbrev) Given the abbreviation for a status, return the name. Returns undef if the abbreviation is not found. status_name_to_abbrevs($status_name) Returns the list of abbreviations for a given status. Returns an empty list if the status is not found. status_has_behavior($status_name, $behavior) Return true if $status_name lists $behavior. status_abbrev_has_behavior($status_abbrev, $behavior) Return true if the status abbreviated by $status_abbrev lists $behavior. Since I removed the previous $siteDefaults{status} hash from global.conf, I have already switched modules that formerly used that hash over to using the new methods: Authen.pm: use status_abbrev_has_behavior($status, "allow_course_access") to determine if a user should be allowed to log in. Feedback.pm: look up status abbreviation using status_abbrev_to_name() and print the result for the status field in the email. Instructor.pm: use status_abbrev_has_behavior($status, "include_in_assignment") to determine if a user should be included in an assignment. UserList.pm and UsersAssignedToSet.pm: use status_abbrev_to_name() to get name of CSS class. Modified Files: -------------- webwork2/conf: global.conf.dist webwork2/lib/WeBWorK: Authen.pm CourseEnvironment.pm webwork2/lib/WeBWorK/ContentGenerator: Feedback.pm Instructor.pm webwork2/lib/WeBWorK/ContentGenerator/Instructor: UserList.pm UsersAssignedToSet.pm Revision Data ------------- Index: CourseEnvironment.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/CourseEnvironment.pm,v retrieving revision 1.28 retrieving revision 1.29 diff -Llib/WeBWorK/CourseEnvironment.pm -Llib/WeBWorK/CourseEnvironment.pm -u -r1.28 -r1.29 --- lib/WeBWorK/CourseEnvironment.pm +++ lib/WeBWorK/CourseEnvironment.pm @@ -21,15 +21,65 @@ WeBWorK::CourseEnvironment - Read configuration information from global.conf and course.conf files. +=head1 SYNOPSIS + + use WeBWorK::CourseEnvironment; + $ce = WeBWorK::CourseEnvironment->new({ + webwork_url => "/webwork2", + webwork_dir => "/opt/webwork2", + pg_dir => "/opt/pg", + webwork_htdocs_url => "/webwork2_files", + webwork_htdocs_dir => "/opt/webwork2/htdocs", + webwork_courses_url => "/webwork2_course_files", + webwork_courses_dir => "/opt/webwork2/courses", + courseName => "name_of_course", + }); + + my $timeout = $courseEnv->{sessionKeyTimeout}; + my $mode = $courseEnv->{pg}->{options}->{displayMode}; + # etc... + +=head1 DESCRIPTION + +The WeBWorK::CourseEnvironment module reads the system-wide F<global.conf> and +course-specific F<course.conf> files used by WeBWorK to calculate and store +settings needed throughout the system. The F<.conf> files are perl source files +that can contain any code allowed under the default safe compartment opset. +After evaluation of both files, any package variables are copied out of the +safe compartment into a hash. This hash becomes the course environment. + =cut use strict; use warnings; +use Carp qw/croak/; use Safe; use WeBWorK::Utils qw(readFile); use WeBWorK::Debug; use Opcode qw(empty_opset); +=head1 CONSTRUCTION + +=over + +=item new(HASHREF) + +HASHREF is a reference to a hash containing scalar variables with which to seed +the course environment. It must contain at least a value for the key +C<webworkRoot>. + +The C<new> method finds the file F<conf/global.conf> relative to the given +C<webwork_dir> directory. After reading this file, it uses the +C<$courseFiles{environment}> variable, if present, to locate the course +environment file. If found, the file is read and added to the environment. + +=item new(ROOT URLROOT PGROOT COURSENAME) + +A deprecated form of the constructor in which four seed variables are given +explicitly: C<webwork_dir>, C<webwork_url>, C<pg_dir>, and C<courseName>. + +=cut + # NEW SYNTAX # # new($invocant, $seedVarsRef) @@ -154,72 +204,107 @@ } bless $self, $class; + + # here is where we can do evil things to the course environment *sigh* + # anything changed has to be done here. after this, CE is considered read-only + # anything added must be prefixed with an underscore. + + # create reverse-lookup hash mapping status abbreviations to real names + $self->{_status_abbrev_to_name} = { + map { my $name = $_; map { $_ => $name } @{$self->{statuses}{$name}{abbrevs}} } + keys %{$self->{statuses}} + }; + + # now that we're done, we can go ahead and return... return $self; } -1; +=back -__END__ +=head1 ACCESS -=head1 SYNOPSIS +There are no formal accessor methods. However, since the course environemnt is +a hash of hashes and arrays, is exists as the self hash of an instance +variable: - use WeBWorK::CourseEnvironment; - $ce = WeBWorK::CourseEnvironment->new({ - webwork_url => "/webwork2", - webwork_dir => "/opt/webwork2", - pg_dir => "/opt/pg", - webwork_htdocs_url => "/webwork2_files", - webwork_htdocs_dir => "/opt/webwork2/htdocs", - webwork_courses_url => "/webwork2_course_files", - webwork_courses_dir => "/opt/webwork2/courses", - courseName => "name_of_course", - }); - - my $timeout = $courseEnv->{sessionKeyTimeout}; - my $mode = $courseEnv->{pg}->{options}->{displayMode}; - # etc... + $ce->{someKey}{someOtherKey}; -=head1 DESCRIPTION +=head1 EXPERIMENTAL ACCESS METHODS -The WeBWorK::CourseEnvironment module reads the system-wide F<global.conf> and -course-specific F<course.conf> files used by WeBWorK to calculate and store -settings needed throughout the system. The F<.conf> files are perl source files -that can contain any code allowed under the default safe compartment opset. -After evaluation of both files, any package variables are copied out of the -safe compartment into a hash. This hash becomes the course environment. +This is an experiment in extending CourseEnvironment to know a little more about +its contents, and perform useful operations for me. -=head1 CONSTRUCTION +There is a set of operations that require certain data from the course +environment. Most of these are un Utils.pm. I've been forced to pass $ce into +them, so that they can get their data out. But some things are so intrinsically +linked to the course environment that they might as well be methods in this +class. + +=head2 STATUS METHODS =over -=item new(HASHREF) +=item status_abbrev_to_name($status_abbrev) -HASHREF is a reference to a hash containing scalar variables with which to seed -the course environment. It must contain at least a value for the key -C<webworkRoot>. +Given the abbreviation for a status, return the name. Returns undef if the +abbreviation is not found. -The C<new> method finds the file F<conf/global.conf> relative to the given -C<webwork_dir> directory. After reading this file, it uses the -C<$courseFiles{environment}> variable, if present, to locate the course -environment file. If found, the file is read and added to the environment. +=cut -=item new(ROOT URLROOT PGROOT COURSENAME) +sub status_abbrev_to_name { + my ($ce, $status_abbrev) = @_; + return $ce->{_status_abbrev_to_name}{$status_abbrev}; +} -A deprecated form of the constructor in which four seed variables are given -explicitly: C<webwork_dir>, C<webwork_url>, C<pg_dir>, and C<courseName>. +=item status_name_to_abbrevs($status_name) -=back +Returns the list of abbreviations for a given status. Returns an empty list if +the status is not found. -=head1 ACCESS +=cut -There are no formal accessor methods. However, since the course environemnt is -a hash of hashes and arrays, is exists as the self hash of an instance -variable: +sub status_name_to_abbrevs { + my ($ce, $status_name) = @_; + return unless exists $ce->{statuses}{$status_name}; + return @{$ce->{statuses}{$status_name}{abbrevs}}; +} + +=item status_has_behavior($status_name, $behavior) + +Return true if $status_name lists $behavior. + +=cut - $ce->{someKey}->{someOtherKey}; +sub status_has_behavior { + my ($ce, $status_name, $behavior) = @_; + if (exists $ce->{statuses}{$status_name}) { + if (exists $ce->{statuses}{$status_name}{behaviors}) { + my $num_matches = grep { $_ eq $behavior } @{$ce->{statuses}{$status_name}{behaviors}}; + return $num_matches > 0; + } else { + return 0; # no behaviors + } + } else { + warn "status '$status_name' not found in \%statuses -- assuming no behaviors.\n"; + return 0; + } +} -=head1 AUTHOR +=item status_abbrev_has_behavior($status_abbrev, $behavior) -Written by Sam Hathaway, sh002i (at) math.rochester.edu. +Return true if the status abbreviated by $status_abbrev lists $behavior. =cut + +sub status_abbrev_has_behavior { + my ($ce, $status_abbrev, $behavior) = @_; + return $ce->status_has_behavior($ce->status_abbrev_to_name($status_abbrev), $behavior); +} + +=back + +=cut + +1; + +# perl doesn't look like line noise. line noise has way more alphanumerics. Index: Authen.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/Authen.pm,v retrieving revision 1.44 retrieving revision 1.45 diff -Llib/WeBWorK/Authen.pm -Llib/WeBWorK/Authen.pm -u -r1.44 -r1.45 --- lib/WeBWorK/Authen.pm +++ lib/WeBWorK/Authen.pm @@ -334,25 +334,26 @@ } # Make sure user is in the database - my $userRecord = $db->getUser($user); # checked - unless (defined $userRecord) { + my $User = $db->getUser($user); # checked + unless (defined $User) { # FIXME too much information! $error = "There is no account for $user in this course."; last VERIFY; } # fix invalid status values (FIXME this should be in DB!) - unless (defined $userRecord->status and - defined($ce->{siteDefaults}->{status}->{$userRecord->status}) - ) { - $userRecord-> status('C'); - # need to save this value to the database. - $db->putUser($userRecord); - warn "Setting status for user $user to C. It was previously undefined or miss defined."; + if (not defined $User->status or not defined $ce->status_abbrev_to_name($User->status)) { + my $default_status = $ce->{default_status}; + die "default_status not defined in course environment" unless defined $default_status; + my ($default_abbrev) = $ce->status_name_to_abbrevs($default_status); + die "default status has no abbrevs in course environment" unless defined $default_abbrev; + $User->status($default_abbrev); + $db->putUser($User); + warn "Setting status for user $user to '$default_abbrev'. It was previously unset or set to an invalid value."; } - # make sure the user hasn't been dropped from the course - if ($ce->{siteDefaults}->{status}->{$userRecord->status} eq "Drop") { + # make sure users with this user's status are allowed to access the course (jeez...) + unless ($ce->status_abbrev_has_behavior($User->status, "allow_course_access")) { # FIXME too much information! $error = "The user $user has been dropped from this course."; last VERIFY; @@ -523,111 +524,109 @@ # essentially the same as verify(), but pulls out the proctor data from the # form input and uses that with the appropriate database entry names to determine # whether the proctor is valid. -sub verifyProctor ($) { - my $self = shift(); - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - - my $user = $r->param('effectiveUser'); - my $proctorUser = $r->param('proctor_user'); - my $proctorPasswd = $r->param('proctor_passwd'); - my $proctorKey = $r->param('proctor_key'); -# we use the following to require a second proctor authorization to grade the -# test - my $submitAnswers = ( defined($r->param('submitAnswers')) ? - $r->param('submitAnswers') : '' ); - - my $failWithoutError = 0; - my $error = ''; - -# we define a key for "effectiveuser,proctoruser" to authorize a test, and -# "effectiveuser,proctoruser,g" to authorize grading. - my $prKeyIndex = ''; - - VERIFY: { - unless( ( defined($proctorUser) && $proctorUser ) or - ( defined($proctorPasswd) && $proctorPasswd ) or - ( defined($proctorKey) && $proctorKey ) ) { - $failWithoutError = 1; - last VERIFY; - } - - unless( defined($proctorUser) ) { - $error = 'Proctor username must be specified.'; - last VERIFY; - } - - my $proctorUserRecord = $db->getUser( $proctorUser ); - unless( defined( $proctorUserRecord ) ) { - $error = "There is no proctor account for $proctorUser in this course"; - last VERIFY; - } - - unless( ! defined( $proctorUserRecord->status() ) || - $proctorUserRecord->status() eq 'C' ) { - $error = "Proctor user $proctorUser does not have a valid status " . - "in this course."; - last VERIFY; - } - - if ( $proctorKey ) { - $r->param( 'proctor_password', '' ); - - $prKeyIndex = "$user,$proctorUser" . (($submitAnswers) ? ',g' : ''); - if ( $self->checkKey($prKeyIndex, $proctorKey) ) { - last VERIFY; - } else { - if ( $submitAnswers ) { - $error = 'Assignment requires valid proctor authorization ' . - 'for grading'; - } else { - $error = "Invalid or expired proctor session key."; - } - last VERIFY; - } - } - - if ( $proctorPasswd ) { - - if ( $self->checkPassword( $proctorUser, $proctorPasswd ) ) { - $prKeyIndex = "$user,$proctorUser" . - (($submitAnswers) ? ',g' : ''); - my $newKeyObject = $self->generateKey( $prKeyIndex ); - $r->param('proctor_passwd', ''); - - eval{ $db->deleteKey( $prKeyIndex ); }; - $db->addKey($newKeyObject); - - $r->param('proctor_key', $newKeyObject->key()); - - last VERIFY; - } else { - $error = 'Incorrect proctor username or password.'; - last VERIFY; - } - } - } - - if ( defined($error) && $error ) { - $r->notes("authen_error", $error); - return 0; - - } elsif ( $failWithoutError ) { - return 0; - - } else { - return 1; - } +sub verifyProctor { + my $self = shift(); + my $r = $self->{r}; + my $ce = $r->ce; + my $db = $r->db; + + my $user = $r->param('effectiveUser'); + my $proctorUser = $r->param('proctor_user'); + my $proctorPasswd = $r->param('proctor_passwd'); + my $proctorKey = $r->param('proctor_key'); + + # we use the following to require a second proctor authorization to grade the test + my $submitAnswers = defined($r->param('submitAnswers')) + ? $r->param('submitAnswers') + : ''; + + my $failWithoutError = 0; + my $error = ''; + + # we define a key for "effectiveuser,proctoruser" to authorize a test, and + # "effectiveuser,proctoruser,g" to authorize grading. + my $prKeyIndex = ''; + + VERIFY: { + unless( + defined $proctorUser && $proctorUser + or + defined $proctorPasswd && $proctorPasswd + or + defined $proctorKey && $proctorKey + ) { + $failWithoutError = 1; + last VERIFY; + } + + unless(defined $proctorUser) { + $error = 'Proctor username must be specified.'; + last VERIFY; + } + + my $Proctor = $db->getUser($proctorUser); + unless(defined $Proctor) { + # FIXME too much information + $error = "There is no proctor account for $proctorUser in this course"; + last VERIFY; + } + + unless( !defined($Proctor->status) or $Proctor->status() eq 'C' ) { + # FIXME too much information + $error = "Proctor user $proctorUser does not have a valid status in this course."; + last VERIFY; + } + + # make sure proctor has valid status + unless($ce->status_abbrev_has_behavior($Proctor->status, "allow_course_access")) { + # FIXME too much information + $error = "Proctor user $proctorUser does not have a valid status in this course."; + last VERIFY; + } + + if ($proctorKey) { + $r->param('proctor_password', ''); + + $prKeyIndex = "$user,$proctorUser" . (($submitAnswers) ? ',g' : ''); + if ($self->checkKey($prKeyIndex, $proctorKey)) { + last VERIFY; + } else { + if ($submitAnswers) { + $error = 'Assignment requires valid proctor authorization for grading'; + } else { + $error = "Invalid or expired proctor session key."; + } + last VERIFY; + } + } + + if ($proctorPasswd) { + if ($self->checkPassword($proctorUser, $proctorPasswd)) { + $prKeyIndex = "$user,$proctorUser" . (($submitAnswers) ? ',g' : ''); + my $newKeyObject = $self->generateKey( $prKeyIndex ); + $r->param('proctor_passwd', ''); + + eval{ $db->deleteKey($prKeyIndex); }; + $db->addKey($newKeyObject); + + $r->param('proctor_key', $newKeyObject->key); + + last VERIFY; + } else { + $error = 'Incorrect proctor username or password.'; + last VERIFY; + } + } + } + + if (defined $error and $error) { + $r->notes("authen_error", $error); + return 0; + } elsif ($failWithoutError) { + return 0; + } else { + return 1; + } } 1; - -__END__ - -=head1 AUTHOR - -Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam -Hathaway, sh002i (at) math.rochester.edu. - -=cut Index: Feedback.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/Feedback.pm,v retrieving revision 1.33 retrieving revision 1.34 diff -Llib/WeBWorK/ContentGenerator/Feedback.pm -Llib/WeBWorK/ContentGenerator/Feedback.pm -u -r1.33 -r1.34 --- lib/WeBWorK/ContentGenerator/Feedback.pm +++ lib/WeBWorK/ContentGenerator/Feedback.pm @@ -373,8 +373,11 @@ $result .= "Email: " . $User->email_address . "\n"; $result .= "Student ID: " . $User->student_id . "\n"; - my %status = %{$ce->{siteDefaults}{status}}; - $result .= "Status: " . (exists $status{$User->status} ? $status{$User->status} : $User->status) . "\n"; + my $status_name = $ce->status_abbrev_to_name($User->status); + my $status_string = defined $status_name + ? "$status_name ('" . $User->status . "')" + : $User->status . " (unknown status abbreviation)"; + $result .= "Status: $status_string\n"; $result .= "Section: " . $User->section . "\n"; $result .= "Recitation: " . $User->recitation . "\n"; Index: Instructor.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/Instructor.pm,v retrieving revision 1.50 retrieving revision 1.51 diff -Llib/WeBWorK/ContentGenerator/Instructor.pm -Llib/WeBWorK/ContentGenerator/Instructor.pm -u -r1.50 -r1.51 --- lib/WeBWorK/ContentGenerator/Instructor.pm +++ lib/WeBWorK/ContentGenerator/Instructor.pm @@ -298,7 +298,7 @@ my @results; foreach my $User (@userRecords) { - next if grep /$User->{status}/, @{$self->{r}->{ce}->{siteDefaults}->{statusDrop}}; + next unless $self->r->ce->status_abbrev_has_behavior($User->status, "include_in_assignment"); my $UserSet = $db->newUserSet; my $userID = $User->user_id; $UserSet->user_id($userID); Index: global.conf.dist =================================================================== RCS file: /webwork/cvs/system/webwork2/conf/global.conf.dist,v retrieving revision 1.145 retrieving revision 1.146 diff -Lconf/global.conf.dist -Lconf/global.conf.dist -u -r1.145 -r1.146 --- conf/global.conf.dist +++ conf/global.conf.dist @@ -429,22 +429,6 @@ # Site defaults (FIXME: what other things could be "site defaults"?) ################################################################################ -# Status strings -- lists valid status values and their names. If your site uses -# additional values, add them here. -$siteDefaults{status} = { - A => "Audit", - a => "Audit", - audit => "Audit", - D => "Drop", - d => "Drop", - drop => "Drop", - withdraw => "Drop", - C => "Enrolled", - c => "Enrolled", - current => "Enrolled", - enrolled => "Enrolled", -}; - # Set the default timezone of courses on this server. To get a list of valid # timezones, run: # @@ -566,6 +550,41 @@ ); ################################################################################ +# Status system +################################################################################ + +# The first abbreviation in the abbreviations list is the canonical +# abbreviation, and will be used when setting the status value in a user record +# or an exported classlist file. +# +# Results are undefined if more than one status has the same abbreviation. +# +# The four behaviors that are controlled by status are: +# allow_course_access => is this user allowed to log in? +# include_in_assignment => is this user included when assigning as set to "all" users? +# include_in_stats => is this user included in statistical reports? +# include_in_scoring => is this user included in score reports? + +%statuses = ( + Enrolled => { + abbrevs => [qw/ C c current enrolled /], + behaviors => [qw/ allow_course_access include_in_assignment include_in_stats include_in_scoring /], + }, + Audit => { + abbrevs => [qw/ A a audit /], + behaviors => [qw/ allow_course_access include_in_assignment include_in_stats /], + }, + Drop => { + abbrevs => [qw/ D d drop withdraw /], + behaviors => [qw/ /], + }, +); + +# This is the default status given to new students and students with invalid +# or missing statuses. +$default_status = "Enrolled"; + +################################################################################ # Session options ################################################################################ Index: UsersAssignedToSet.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm,v retrieving revision 1.18 retrieving revision 1.19 diff -Llib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm -Llib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm -u -r1.18 -r1.19 --- lib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm +++ lib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm @@ -153,7 +153,7 @@ foreach my $userRecord (@userRecords) { - my $statusClass = $ce->{siteDefaults}->{status}->{$userRecord->{status}} || ""; + my $statusClass = $ce->status_abbrev_to_name($userRecord->status) || ""; my $user = $userRecord->user_id; my $userSetRecord = $db->getUserSet($user, $setID); #checked Index: UserList.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm,v retrieving revision 1.71 retrieving revision 1.72 diff -Llib/WeBWorK/ContentGenerator/Instructor/UserList.pm -Llib/WeBWorK/ContentGenerator/Instructor/UserList.pm -u -r1.71 -r1.72 --- lib/WeBWorK/ContentGenerator/Instructor/UserList.pm +++ lib/WeBWorK/ContentGenerator/Instructor/UserList.pm @@ -1446,7 +1446,7 @@ my $passwordMode = $options{passwordMode}; my $userSelected = $options{userSelected}; - my $statusClass = $ce->{siteDefaults}->{status}->{$User->{status}}; + my $statusClass = $ce->status_abbrev_to_name($User->status); my $sets = $db->countUserSets($User->user_id); my $totalSets = $self->{totalSets}; |