#!/usr/local/bin/webwork-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 $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";;
my $DELIM = $Global::delim;

# 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 <BR>\n";
	checkClasslistFile($Global::noOfFieldsInClasslist,$fileName);
	open(FILE, "$fileName") || wwerror($0, "Can't open $fileName");
	my @classList=<FILE>;
	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 <BR>\n";
		&backup($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


	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 <BR>\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 <BR>\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);
			&CL_putStudentLastName     ($lastName, $login_name)       if $update_lastName;
			&CL_putStudentFirstName    ($firstName, $login_name)      if $update_firstName;
			&CL_putComment             ($comment, $login_name)        if $update_comment;
			&CL_putClassSection        ($section,$login_name)         if $update_section;
			&CL_putClassRecitation     ($recitation,$login_name)      if $update_recitation;
			&CL_putStudentEmailAddress ($email_address, $login_name)  if $update_email_address;

			## 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 <BR>\n ";}
			elsif ($update_status)
				{&CL_putStudentStatus       ($status, $login_name);}

			$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_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')	{
		my $status = 'D';
		$status = $Global::statusDrop[0] if defined $Global::statusDrop[0];
		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 <BR>\n ";
			}
			else	{&CL_putStudentStatus($status, $login_name);}

			$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 <BR>\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 <BR>\n ";
				if ($second_pass) {
					removeRecord($login_name);
					$Global::over_ride_CLBD_lock = 1;
					deleteClassListRecord($login_name);
					$Global::over_ride_CLBD_lock = 0;
				}
			}
			else	{
				$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
	}

	unlock_CL_database();

	if (($remove_profs) and (!$second_pass)){
		$message .= "<BR>The following professors <FONT COLOR='#ff00aa'><b>HAVE NOT BEEN REMOVED</b></FONT>.
		If you really want to remove a professor, do this from the Edit Class Roster page where there are more
		options and safe guards.
		<BR><BR>";
		$message .= "\n $remove_profs<BR>";
	}
	if (($drop_profs) and (!$second_pass)) {
		$message .= "<BR>The following professors <FONT COLOR='#ff00aa'><b>HAVE NOT BEEN DROPPED</b></FONT>.
		If you really want to drop a professor, do this from the Edit Class Roster page where there are more
		options and safe guards.
		<BR><BR>";
		$message .= "\n $drop_profs<BR>";
	}
	if ($remove_students_with_sets) {
		if (!$second_pass) {
			$message .= "<BR>The following students <FONT COLOR='#ff00aa'><b>HAVE NOT BEEN REMOVED</b></FONT>.
			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.<P>
			<FONT COLOR='#ff00aa'><b>If you really want to remove all records for these students,
			click on 'Remove all records'.</b></FONT><BR><BR>";
		}
		else {
			$message .= "<BR>The following students <FONT COLOR='#ff00aa'><b>HAVE BEEN REMOVED</b></FONT>.
			All records for these students have been deleted.<BR><BR>";
		}
		$message .= "\n $remove_students_with_sets<BR>";
		$message .= &remove_students_form() unless $second_pass;

	}
	if ($errors) {
		$message .= "<BR>The following students <FONT COLOR='#ff00aa'><b>HAVE NOT BEEN ENTERED IN THE
		CLASSLIST DATABASE</b></FONT>
		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.<BR><BR>";
		$message .= "\n $errors<BR>";


	}
	$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<BR><BR> Modifying the password file: $passwordFile <BR>\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 .= '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'."$login_name not added because status is $status <BR>\n ";
		}
		elsif (&get_password($login_name, $passwordFile)) {
			$msg .= '&nbsp;&nbsp;&nbsp;'."$login_name not added because password already exists <BR>\n ";
		}
		else {
			&new_password($login_name, $studentID, $passwordFile);
			&put_permissions(0,$login_name,$permissionsFile);
			$msg .= "added: $login_name, $studentID <BR>\n ";
		}
	}

	my @pwStudents = &get_keys_from_db($passwordFile);
	my ($ans,$student);


	$msg .= "\n<BR<BR><BR> 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.<BR><BR>\n ";

	foreach $student (@pwStudents) {
		next if defined($studentsinclass{$student});

		&delete_password($student,$passwordFile);
		&delete_permissions($student,$permissionsFile);
		$msg .= "$student<BR>\n ";
	}

$msg = '';  ## returning too much info so don't return this
$msg;
}


sub uploadSuccess {
	my ($msg) = @_;
	print"content-type: text/html\n\n<H2>The classlist database has been updated. </H2>\n";
	print $msg;
	print &htmlBOTTOM("profImportClasslistDatabase.pl", \%inputs);
}

sub backup  {
	## 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 = "<FONT COLOR='#ff00aa'>WARNING</font>";}
	# print HTML text
	print &htmlTOP("Data for the classlist record for $studentLogin");

	print qq!<h3 align="left">WeBWorK $word message concerning user $studentLogin
	($studentFirstName $studentLastName $studentID)</h3>\n!;

	print qq!You have requested to remove the the classlist records and all associated data for the above user. <BR><BR>

	<P><FONT COLOR='#ff00aa'><B>NO CHANGES HAVE BEEN MADE YET. </B></font> <P>!;

	if ($User eq $studentLogin) {
		print qq!<FONT COLOR='#ff00aa'><B>YOU ARE TRYING TO REMOVE YOURSELF FROM THE COURSE. </B></font>\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.<P>\n!;
	}
	elsif (defined $studentPermissions and $studentPermissions == $Global::instructor_permissions){
		print qq!<FONT COLOR='#ff00aa'><B>YOU ARE TRYING TO REMOVE A PROFESSOR FROM THE COURSE. </B></font>\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.<P>\n!;
	}

	if ($setsExist) {
		print qq! Data exists in the WeBWorK databases for following sets for user <b>$studentLogin</b><BR>\n!;
		foreach my $set (@SetNumberKeys) {
			print "<BR> Set $set\n";
		}
		print "<BR> <BR>If you choose to remove all records for <b>$studentLogin</b>, all this data will be destroyed.  This action can not be undone.<BR>\n";
	}

	print qq! <BR><BR>Generally if the user <b>$studentLogin</b> 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! <BR><BR>If you have scored any of the above sets, scores for user <b>$studentLogin</b> will be contained in the
	${Course}_totals.csv file (and the other scoring files).  These scores will not be removed.\n!;

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


	print qq!<FORM ACTION="${cgiURL}profEditClasslistDB.pl" METHOD=POST>\n!;
	print &sessionKeyInputs(\%inputs);
	print qq!<INPUT TYPE='HIDDEN' NAME='studentLogin' VALUE="$studentLogin">\n
	<INPUT TYPE="HIDDEN" NAME="save" VALUE="ON">\n
	<INPUT TYPE="HIDDEN" NAME="firsttime" VALUE= 1>\n
	If you really want to remove all records for <b>$studentLogin</b>, click on\n
	<INPUT TYPE="SUBMIT" NAME = "action" VALUE="DO IT"> \n
	but think before you click since this data, once removed, can not be recovered.\n
	</FORM>!;

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;
}
