#!/usr/bin/env perl ## This file is profExportClasslistDatabase.pl ## #################################################################### # Copyright @ 1995-2000 University of Rochester # All Rights Reserved #################################################################### use lib '.'; use webworkInit; # WeBWorKInitLine use CGI qw(:standard); use Global; use Auth; use strict; use GDBM_File; my $cgi = new CGI; my %inputs = $cgi->Vars(); # get information from CGI inputs (see also below for additional information) my $Course = $inputs{'course'}; my $User = $inputs{'user'}; my $Session_key = $inputs{'key'}; # verify that information has been received unless($Course && $User && $Session_key) { &wwerror("$0","The script did not receive the proper input data.","",""); } # establish environment for this script &Global::getCourseEnvironment($Course); my $scriptsDirectory = getWebworkScriptDirectory; my $databaseDirectory = getCourseDatabaseDirectory; my $scoringDirectory = getCourseScoringDirectory($Course);; my $templateDirectory = getCourseTemplateDirectory; my $cgiURL = getWebworkCgiURL; my $CL_Database = $Global::CL_Database; my $path_to_CL_DB = "${databaseDirectory}$CL_Database"; # File names require "${scriptsDirectory}$Global::HTMLglue_pl"; require "${scriptsDirectory}$Global::DBglue_pl"; require "${scriptsDirectory}$Global::classlist_DBglue_pl"; require "${scriptsDirectory}$Global::FILE_pl"; require "${scriptsDirectory}$Global::SCRtools_pl"; my $DAT = &getDat; my $DELIM = &getDelim(); # log access &Global::log_info('', query_string); my $passwordFile = &Global::getCoursePasswordFile($Course); my $permissionsFile = &Global::getCoursePermissionsFile($Course); my $permissions = &get_permissions($inputs{'user'}, $permissionsFile); my $keyFile = &Global::getCourseKeyFile($Course); #verify session key &verify_key($inputs{'user'}, $inputs{'key'}, $keyFile, $Course); # verify permissions are correct if ($permissions != $Global::instructor_permissions ) { print "permissions = $permissions instructor_permissions = $Global::instructor_permissions\n"; print &html_NO_PERMISSION; exit(0); } # get the rest of the information from the submitted form my $classlistFilename = $inputs{'classList'}; my $update_firstName = $inputs{'update_firstName'}; my $update_lastName = $inputs{'update_lastName'}; my $update_status = $inputs{'update_status'}; my $update_comment = $inputs{'update_comment'}; my $update_section = $inputs{'update_section'}; my $update_recitation = $inputs{'update_recitation'}; my $update_email_address = $inputs{'update_email_address'}; my $update_drop = $inputs{'update_drop'}; ## either 'drop', 'leave', or 'remove' $update_firstName = 0 unless defined $update_firstName; $update_lastName = 0 unless defined $update_lastName; $update_status = 0 unless defined $update_status; $update_comment = 0 unless defined $update_comment; $update_section = 0 unless defined $update_section; $update_recitation = 0 unless defined $update_recitation; $update_email_address = 0 unless defined $update_email_address; my $second_pass = 0; # $second_pass is 1 if we are removing all records for some students who have existing sets. $second_pass = 1 if (defined $inputs{'action'} and $inputs{'action'} =~ /Remove/); lock_CL_database() if $second_pass; ## the first pass unlocked the database my $CL_status = get_CL_database_status(); wwerror('Classlist Database is unlocked', 'You must go back and lock the classlist database before you can import it from an ascii file.') unless $CL_status eq 'locked'; wwerror('No classlist file selected', 'You must go back and select a classlist file.') unless $classlistFilename =~ /\w/; my $msg1 = updateClasslistDB($classlistFilename); my $msg2 = initial_passwords(); my $msg3 = "$msg1" . "$msg2"; uploadSuccess("$msg3"); exit; ## end of main script sub updateClasslistDB { ## builds the classlist DB and returns a message my ($classlistFilename) = @_; #get data from class list. my $fileName="${templateDirectory}$classlistFilename"; ## e.g. fileName=m161.lst my $message = "\nGetting classlist file from: $fileName
\n"; checkClasslistFile($Global::noOfFieldsInClasslist,$fileName); open(FILE, "$fileName") || wwerror($0, "Can't open $fileName"); my @classList=; close(FILE); ################################### # Before updating the database we back it up ################################### if (( -e "$path_to_CL_DB" ) and (!$second_pass)){ $message .= "Backing up current classlist database to: ${path_to_CL_DB}_bak1
\n"; &back_up($path_to_CL_DB); } my %loginName_StudentID_Hash = %{getLoginName_StudentID_Hash()}; my %studentID_LoginName_Hash =%{getStudentID_LoginName_Hash()}; # my $WW_DB_exists = 0; # $WW_DB_exists = 1 if ( -e "${databaseDirectory}$Global::database" ); # my %loginName_StudentID_Hash_from_WW_DB =(); # my %studentID_LoginName_Hash_from_WW_DB =(); # if ($WW_DB_exists) { # %loginName_StudentID_Hash_from_WW_DB =%{getLoginName_StudentID_Hash_from_WW_DB()}; # %studentID_LoginName_Hash_from_WW_DB = reverse %loginName_StudentID_Hash_from_WW_DB; # } my $errors =''; my $drop_profs = ''; ## profs who will not be dropped my $remove_profs = ''; ## profs who will not be removed my $remove_students_with_sets = ''; ## students for whom sets exits who will not ## not be removed on first pass my %new_good_classlist_students =(); ## students in new classlist without conflicts my %new_bad_classlist_students =(); ## students in new classlist with conflicts my %totals_file_update_required =(); ## students for whom we have to update totals file my %totals_last_name =(); ## new last names for totals update my %totals_first_name =(); ## new first names for totals update my %totals_rec =(); ## new rec for totals update my %totals_sec =(); ## new sec for totals update my %totals_status =(); ## new status for totals update my %totals_file_remove_required =(); ## students for whom we have to update totals since ## their classlist record is removed foreach (@classList) { ## read through classlist and create ## class list database unless ($_ =~ /\S/) {next;} ## skip blank lines chomp; my @classListRecord=&getRecord($_); my ($studentID, $lastName, $firstName, $status, $comment, $section, $recitation, $email_address, $login_name) = @classListRecord; ## First we get a list of any conflicts with current students if ((defined $loginName_StudentID_Hash{$login_name}) and ($loginName_StudentID_Hash{$login_name} ne $studentID)) { $errors .= "$firstName $lastName, $login_name, $studentID
\n "; $new_bad_classlist_students{$login_name} =1; next; } if ((defined $studentID_LoginName_Hash{$studentID}) and ($studentID_LoginName_Hash{$studentID} ne $login_name)) { $errors .= "$firstName $lastName, $login_name, $studentID
\n "; $new_bad_classlist_students{$login_name} =1; next; } ## OK, the student record has no conflicts $new_good_classlist_students{$login_name} =1; ## Handle students already in classlist DB if (defined $loginName_StudentID_Hash{$login_name}) { &attachCLRecord($login_name); if ($update_lastName) { &CL_putStudentLastName($lastName, $login_name); $totals_file_update_required{$studentID} =1; $totals_last_name{$studentID} = $lastName; } if ($update_firstName) { &CL_putStudentFirstName($firstName, $login_name); $totals_file_update_required{$studentID} =1; $totals_first_name{$studentID} = $firstName; } if ($update_comment) {&CL_putComment($comment, $login_name);} if ($update_section) { &CL_putClassSection($section,$login_name); $totals_file_update_required{$studentID} =1; $totals_sec{$studentID} = $section; } if ($update_recitation) { &CL_putClassRecitation($recitation,$login_name); $totals_file_update_required{$studentID} =1; $totals_rec{$studentID} = $recitation; } if ($update_email_address) {&CL_putStudentEmailAddress($email_address, $login_name);} ## test for dropping a professor my $studentPermissions = &get_permissions($login_name, $permissionsFile); my $orgStudentStatus = &CL_getStudentStatus($login_name); if ($update_status and defined $studentPermissions and $studentPermissions == $Global::instructor_permissions and &dropStatus($status) != &dropStatus($orgStudentStatus) and &dropStatus($status) ) {$drop_profs .= "$firstName $lastName, $login_name, $studentID
\n ";} elsif ($update_status) { &CL_putStudentStatus($status, $login_name); $totals_file_update_required{$studentID} =1; $totals_status{$studentID} = $status; } $Global::over_ride_CLBD_lock = 1; &saveCLRecord($login_name); $Global::over_ride_CLBD_lock = 0; } else { ## Handle new students &CL_putStudentID ($studentID, $login_name); &CL_putStudentLastName ($lastName, $login_name); &CL_putStudentFirstName ($firstName, $login_name); &CL_putStudentStatus ($status, $login_name); &CL_putComment ($comment, $login_name); &CL_putClassSection ($section,$login_name); &CL_putClassRecitation ($recitation,$login_name); &CL_putStudentEmailAddress ($email_address, $login_name); $Global::over_ride_CLBD_lock = 1; &saveCLRecord($login_name); $Global::over_ride_CLBD_lock = 0; } } ## Now we take care of students who are in the current classlist database but are not in ## the classlist file. my $drop_status = 'D'; $drop_status = $Global::statusDrop[0] if defined $Global::statusDrop[0]; my %drop_list =(); my $login_name; %loginName_StudentID_Hash = %{getLoginName_StudentID_Hash()}; foreach $login_name (keys %loginName_StudentID_Hash) { $drop_list{$login_name} = 1 unless ( (defined ($new_good_classlist_students{$login_name})) or (defined ($new_bad_classlist_students{$login_name})) ); } if ($update_drop eq 'drop') { foreach $login_name (keys %drop_list) { &attachCLRecord($login_name); ## test for dropping a professor. If not a prof, drop them. my $studentPermissions = &get_permissions($login_name, $permissionsFile); my $orgStudentStatus = &CL_getStudentStatus($login_name); if (defined $studentPermissions and $studentPermissions == $Global::instructor_permissions and !&dropStatus($orgStudentStatus) ) { my $lastName = &CL_getStudentLastName($login_name); my $firstName = &CL_getStudentFirstName($login_name); my $studentID = &CL_getStudentID($login_name); $drop_profs .= "$firstName $lastName, $login_name, $studentID
\n "; } else { &CL_putStudentStatus($drop_status, $login_name); my $studentID = &CL_getStudentID($login_name); $totals_file_update_required{$studentID} =1; $totals_status{$studentID} = $drop_status; } $Global::over_ride_CLBD_lock = 1; &saveCLRecord($login_name); $Global::over_ride_CLBD_lock = 0; } } elsif ($update_drop eq 'remove') { $remove_profs = ''; $remove_students_with_sets = ''; foreach $login_name (keys %drop_list) { &attachCLRecord($login_name); ## test for removing a professor. my $studentPermissions = &get_permissions($login_name, $permissionsFile); my $orgStudentStatus = &CL_getStudentStatus($login_name); if (defined $studentPermissions and $studentPermissions == $Global::instructor_permissions and !&dropStatus($orgStudentStatus) ) { my $lastName = &CL_getStudentLastName($login_name); my $firstName = &CL_getStudentFirstName($login_name); my $studentID = &CL_getStudentID($login_name); $remove_profs .= "$firstName $lastName, $login_name, $studentID
\n "; } ## test for removing a students with existing problem sets. elsif (check_Record($login_name)) { my $lastName = &CL_getStudentLastName($login_name); my $firstName = &CL_getStudentFirstName($login_name); my $studentID = &CL_getStudentID($login_name); $remove_students_with_sets .= "$firstName $lastName, $login_name, $studentID
\n "; if ($second_pass) { removeRecord($login_name); $totals_file_remove_required{$studentID}=1; $Global::over_ride_CLBD_lock = 1; deleteClassListRecord($login_name); $Global::over_ride_CLBD_lock = 0; } } else { my $studentID = &CL_getStudentID($login_name); $totals_file_remove_required{$studentID}=1; $Global::over_ride_CLBD_lock = 1; deleteClassListRecord($login_name); $Global::over_ride_CLBD_lock = 0; } } } else { ## if this case $update_drop eq 'leave' and we do nothing } ## Now update the totals file my $fullTotalsFileName = "${scoringDirectory}${Course}_totals.${DAT}"; my $totalsFileName = "${Course}_totals"; if (-e "$fullTotalsFileName") { if (-w "$fullTotalsFileName") { my %dbaArray = &dat2aa("$totalsFileName"); foreach my $studentID ( keys %totals_file_update_required) { my $record = $dbaArray{$studentID}; if (defined $record) { $record = put_LN($record, $totals_last_name{$studentID}) if defined $totals_last_name{$studentID}; $record = put_FN($record, $totals_first_name{$studentID}) if defined $totals_first_name{$studentID}; $record = put_sec($record, $totals_sec{$studentID}) if defined $totals_sec{$studentID}; $record = put_rec($record, $totals_rec{$studentID}) if defined $totals_rec{$studentID}; ## Make sure Enroll Status is defined in totals file before updating status my $temp_record = $dbaArray{'STUDENT ID'}; if (get_status($temp_record) =~ /STATUS/){ $record = put_status($record, $totals_status{$studentID}) if defined $totals_status{$studentID}; } $dbaArray{$studentID} = $record; } } ## Now handle students who have been removed ## Make sure Enroll Status is defined in totals file before updating status my $temp_record = $dbaArray{'STUDENT ID'}; if (get_status($temp_record) =~ /STATUS/){ foreach my $studentID (keys %totals_file_remove_required) { my $record = $dbaArray{$studentID}; if (defined $record) { $record = put_status($record, $drop_status); $dbaArray{$studentID} = $record; } } } &aa2dat(\%dbaArray,"$totalsFileName"); } else {warn("Can't update the file $fullTotalsFileName since it is not writable.");} } unlock_CL_database(); if (($remove_profs) and (!$second_pass)){ $message .= "
The following professors HAVE NOT BEEN REMOVED. If you really want to remove a professor, do this from the Edit Class Roster page where there are more options and safe guards.

"; $message .= "\n $remove_profs
"; } if (($drop_profs) and (!$second_pass)) { $message .= "
The following professors HAVE NOT BEEN DROPPED. If you really want to drop a professor, do this from the Edit Class Roster page where there are more options and safe guards.

"; $message .= "\n $drop_profs
"; } if ($remove_students_with_sets) { if (!$second_pass) { $message .= "
The following students HAVE NOT BEEN REMOVED. There are existing sets for these students. If you choose to remove all records for these students, all this data will be destroyed. This action can not be undone. Generally it is preferable to change their \"Enrollment Status\" to \"D\" (for Drop) rather than to totally remove all records. To change their \"Enrollment Status\" to \"D\", use your brower's back button and select \"Change student's status to 'drop' in the classlist database\". If you want details on which sets exist for which students, go to the Edit Class Roster page, select the student and then click \"Remove THIS RECORD\". You will see a list of existing sets and then be given the oportunity to remove them if you wish.

If you really want to remove all records for these students, click on 'Remove all records'.

"; } else { $message .= "
The following students HAVE BEEN REMOVED. All records for these students have been deleted.

"; } $message .= "\n $remove_students_with_sets
"; $message .= &remove_students_form() unless $second_pass; } if ($errors) { $message .= "
The following students HAVE NOT BEEN ENTERED IN THE CLASSLIST DATABASE because of a conflict with entries in the WeBWorK problem set database or the classlist database. These students have a studentID or a loginName that conflicts with a current student. Enter this information again from the Add Student(s) Page to get a more detailed error message and instructions on how to correct the problem.

"; $message .= "\n $errors
"; } $message; } sub initial_passwords { my %studentsinclass=(); my @classListRecord=(); my $msg =''; # Check that the files exist: # The permissions file must exist and have both read and write privilages. # The password file must exist and have both read and write privilages. unless ( -r $passwordFile and -w $passwordFile) { wwerror ($0, "Permissions set incorrectly on $passwordFile or its directory. Cannot access file to both read and write."); } unless ( -r $permissionsFile and -w $permissionsFile) { wwerror ($0, "Permissions set incorrectly on $permissionsFile or its directory. Cannot access file to both read and write."); } my $login_name; my @classList = @{getAllLoginNames()}; $msg .= "\n

Modifying the password file: $passwordFile
\n "; foreach $login_name (@classList) { ## read through classlist database and create ## passwords for all active students ## except if passwords already exist for student attachCLRecord($login_name); my $status = CL_getStudentStatus($login_name); my $studentID = CL_getStudentID($login_name); $studentsinclass{$login_name}++ unless(&dropStatus($status)); if(&dropStatus($status)) { $msg .= '       '."$login_name not added because status is $status
\n "; } elsif (&get_password($login_name, $passwordFile)) { $msg .= '   '."$login_name not added because password already exists
\n "; } else { &new_password($login_name, $studentID, $passwordFile); &put_permissions(0,$login_name,$permissionsFile); $msg .= "added: $login_name, $studentID
\n "; } } my @pwStudents = &get_keys_from_db($passwordFile); my ($ans,$student); $msg .= "\n
The following login's (if any) in the password and permissions databases are either\n "; $msg .= "(1) not listed in the new class list database file \n"; $msg .= "or (2) have DROP status in the new class list database file.\n"; $msg .= "They will all be removed from the password and permissions databases.

\n "; foreach $student (@pwStudents) { next if defined($studentsinclass{$student}); &delete_password($student,$passwordFile); &delete_permissions($student,$permissionsFile); $msg .= "$student
\n "; } $msg = ''; ## returning too much info so don't return this $msg; } sub uploadSuccess { my ($msg) = @_; print"content-type: text/html\n\n

The classlist database has been updated.

\n"; print $msg; print &htmlBOTTOM("profImportClasslistDatabase.pl", \%inputs); } sub back_up { ## takes as a parameter the full path name ## makes upto two backups of the file with _bak1, or _bak2 ## appended to filename where _bak1 is the most recent backup use File::Copy; my $fileName =$_[0]; if (-e "${fileName}_bak1") { rename("${fileName}_bak1","${fileName}_bak2") or &wwerror("$0","can't rename ${fileName}_bak1"); } if (-e "${fileName}") { copy("${fileName}","${fileName}_bak1") or &wwerror("$0","can't copy ${fileName}"); } } sub check_Record { my $studentLogin = shift @_; my $setsExist = 0; # check to see if there is data for this student in the WW DB if ( -e "${databaseDirectory}$Global::database" ){ $setsExist = &setsExistForStudentLogin($studentLogin); } return $setsExist; } sub removeRecordWarningPage { my ($inputref, $studentLogin, $setsExist, $SetNumberKeysref) = @_; my @SetNumberKeys = @$SetNumberKeysref; my %inputs = %$inputref; attachCLRecord($studentLogin); my $studentLastName = CL_getStudentLastName($studentLogin); my $studentFirstName = CL_getStudentFirstName($studentLogin); my $studentID = CL_getStudentID($studentLogin); my $studentPermissions = &get_permissions($studentLogin, $permissionsFile); my $word = 'informational'; if ($setsExist or (defined $studentPermissions and $studentPermissions == $Global::instructor_permissions)) {$word = "WARNING";} # print HTML text print &htmlTOP("Data for the classlist record for $studentLogin"); print qq!

WeBWorK $word message concerning user $studentLogin ($studentFirstName $studentLastName $studentID)

\n!; print qq!You have requested to remove the the classlist records and all associated data for the above user.

NO CHANGES HAVE BEEN MADE YET.

!; if ($User eq $studentLogin) { print qq!YOU ARE TRYING TO REMOVE YOURSELF FROM THE COURSE. \n If you do this, you will immediately be locked out of the course. You will no longer be able to\n login and/or administer the course. Removing yourself is something you almost never want to do.\n Use your browser's "Back" button to cancel this action.

\n!; } elsif (defined $studentPermissions and $studentPermissions == $Global::instructor_permissions){ print qq!YOU ARE TRYING TO REMOVE A PROFESSOR FROM THE COURSE. \n If you do this, $studentFirstName $studentLastName will imediately be locked out of the course. He or she will no longer be able to login and/or administer the course. Use your browser's "Back" button to cancel this action.

\n!; } if ($setsExist) { print qq! Data exists in the WeBWorK databases for following sets for user $studentLogin
\n!; foreach my $set (@SetNumberKeys) { print "
Set $set\n"; } print "

If you choose to remove all records for $studentLogin, all this data will be destroyed. This action can not be undone.
\n"; } print qq!

Generally if the user $studentLogin is a real user, it is preferable to change his or her "Enrollment Status"\n to "D" (for Drop) rather than to totally remove all records. That way records are not destroyed\n and also the student can be reactivated simply by changing his or her "Enrollment Status" back to "C" (for Current).\n!; print qq!

If you have scored any of the above sets, scores for user $studentLogin will be contained in the ${Course}_totals.csv file (and the other scoring files). These scores will not be removed but the students Enrollment Status will be changed to "Drop".\n!; print qq!

If you want to change the "Enrollment Status" for $studentLogin to "D" (for Drop), use your browser's "Back" button.
\n!; print qq!

\n!; print &sessionKeyInputs(\%inputs); print qq!\n \n \n If you really want to remove all records for $studentLogin, click on\n \n but think before you click since this data, once removed, can not be recovered.\n
!; print &htmlBOTTOM('profEditClasslistDB.pl', \%inputs); } #end of removeRecordWarningPage sub removeRecord { my $studentLogin = shift @_; my $setsExist = 0; my @SetNumberKeys = (); my %setNumberHash = (); # check to see if there is data for this student in the WW DB if ( -e "${databaseDirectory}$Global::database" ){ $setsExist = &setsExistForStudentLogin($studentLogin); if ($setsExist) { %setNumberHash=&getAllSetNumbersForStudentLoginHash($studentLogin); @SetNumberKeys = keys(%setNumberHash); } } ## Now remove all that data in the WeBWorK database, the .sco files, ## dvipng images, and any LaTeX2HTML images my ($setName, $psvn); if ($setsExist) { foreach $setName (@SetNumberKeys) { $psvn = $setNumberHash{$setName}; &attachProbSetRecord($psvn); &deleteProbSetRecord($psvn); # remove .sco file if it exists system ("rm ${databaseDirectory}S${setName}-${psvn}.sco") if (-e "${databaseDirectory}S${setName}-${psvn}.sco"); # remove any l2h files my $l2hDir = getCoursel2hDirectory(); my $tempDir = convertPath("${l2hDir}set${setName}/*-$psvn"); system ("rm -rf $tempDir"); # remove any dvipng images -- reuse the variable names $l2hDir = getCourseTempDirectory(); $tempDir = convertPath("${l2hDir}png/${setName}/$psvn"); system ("rm -rf $tempDir"); } } ## Next remove password and permission data delete_password($studentLogin,$passwordFile); delete_permissions($studentLogin,$permissionsFile); } sub remove_students_form { my $form = ''; $form .= $cgi->startform(-action=>"${cgiURL}profImportClasslistDatabase.pl")."\n"; $form .= $cgi->hidden(-name=>'classList', -value=>"$inputs{'classList'}")."\n"; $form .= $cgi->hidden(-name=>'course', -value=>"$inputs{'course'}")."\n"; $form .= $cgi->hidden(-name=>'user', -value=>"$inputs{'user'}")."\n"; $form .= $cgi->hidden(-name=>'key', -value=>"$inputs{'key'}")."\n"; $form .= $cgi->hidden(-name=>'update_drop', -value=>'remove')."\n"; $form .= $cgi->submit(-name=>'action', -value=>'Remove all records')."\n"; $form .= $cgi->endform()."\n"; return $form; } sub get_position { my $pos = shift; my $string = shift; my @array = getRecord($string); return $array[$pos - 1]; } sub get_LN { my $string = shift; get_position( 1, $string); } sub get_FN { my $string = shift; get_position( 2, $string); } sub get_sec { my $string = shift; get_position( 3, $string); } sub get_rec { my $string = shift; get_position( 4, $string); } sub get_status { my $string = shift; get_position( 5, $string); } sub put_position { my $pos = shift; my $string = shift; my $item = shift; my @array = getRecord($string); $array[$pos - 1] = $item; return join $DELIM, @array; } sub put_LN { my $string = shift; my $item = shift; put_position( 1, $string, $item); } sub put_FN { my $string = shift; my $item = shift; put_position( 2, $string, $item); } sub put_sec { my $string = shift; my $item = shift; put_position( 3, $string, $item); } sub put_rec { my $string = shift; my $item = shift; put_position( 4, $string, $item); } sub put_status { my $string = shift; my $item = shift; put_position( 5, $string, $item); }