From 9c8cd3c176e64c9ef4fffdb2a0f57475e068abbf Mon Sep 17 00:00:00 2001
From: Romain Lebbadi-Breteau <romain@lebbadi.fr>
Date: Tue, 29 Aug 2023 23:32:01 -0400
Subject: [PATCH] Small fixes

---
 .gitignore         |   3 +-
 data/update        |   4 +-
 db/empty.sql       |  10 +-
 db/updatedb.pl     |  90 +++++++--------
 emailer/emailer.py | 269 +++++++++++++++++++++++----------------------
 refresh_semester   |  14 ++-
 web/lib.php        |  28 +++--
 7 files changed, 212 insertions(+), 206 deletions(-)

diff --git a/.gitignore b/.gitignore
index 62b388c..587bb48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,8 @@ count.txt
 *.pyc
 *.o
 .svn/
-sopti/triplet/xml/
+triplet/xml/
+data/*.csv
 
 # http://www.gnu.org/software/automake
 
diff --git a/data/update b/data/update
index 2c11db9..597e5e1 100755
--- a/data/update
+++ b/data/update
@@ -8,8 +8,8 @@ fi
 
 touch lock;
 
-COURSE_URL="https://www.polymtl.ca/public/Horaire/horsage.csv"
-CLOSED_URL="https://www.polymtl.ca/public/Horaire/fermes.csv"
+COURSE_URL="https://cours.polymtl.ca/Horaire/public/horsage.csv"
+CLOSED_URL="https://cours.polymtl.ca/Horaire/public/fermes.csv"
 WGETFAIL=0  # false
 
 wget -O "courses.csv.new" $COURSE_URL 2> /dev/null || WGETFAIL=1;
diff --git a/db/empty.sql b/db/empty.sql
index 19d7dd6..1b146da 100644
--- a/db/empty.sql
+++ b/db/empty.sql
@@ -14,7 +14,7 @@ CREATE TABLE `courses` (
   `title` char(100) default NULL,
   PRIMARY KEY  (`unique`),
   KEY `symbol` (`symbol`)
-) TYPE=MyISAM;
+);
 
 --
 -- Dumping data for table `courses`
@@ -33,7 +33,7 @@ CREATE TABLE `courses_semester` (
   PRIMARY KEY  (`unique`),
   KEY `semester_index` (`semester`),
   KEY `course_index` (`course`)
-) TYPE=MyISAM;
+);
 
 --
 -- Dumping data for table `courses_semester`
@@ -58,7 +58,7 @@ CREATE TABLE `groups` (
   KEY `name_index` (`name`),
   KEY `tol_index` (`theory_or_lab`),
   KEY `course_semester_index` (`course_semester`)
-) TYPE=MyISAM;
+);
 
 --
 -- Dumping data for table `groups`
@@ -79,7 +79,7 @@ CREATE TABLE `periods` (
   `weekday` char(10) NOT NULL default '',
   PRIMARY KEY  (`unique`),
   KEY `group` (`group`)
-) TYPE=MyISAM;
+);
 
 --
 -- Dumping data for table `periods`
@@ -95,7 +95,7 @@ CREATE TABLE `semesters` (
   `code` char(10) NOT NULL default '',
   `pretty_name` char(15) NOT NULL default '',
   PRIMARY KEY  (`unique`)
-) TYPE=MyISAM;
+);
 
 --
 -- Dumping data for table `semesters`
diff --git a/db/updatedb.pl b/db/updatedb.pl
index 7142df1..0ec4f73 100755
--- a/db/updatedb.pl
+++ b/db/updatedb.pl
@@ -13,7 +13,7 @@ use Unicode::String qw(utf8 latin1 utf16);
 @fields_periods = ('room', 'weekday', 'time', 'week');
 
 $CONFIG_DIR='..';
-$CURRENT_SEMESTER='H2005';
+$CURRENT_SEMESTER='A2023';
 %CONFIG=();
 
 sub warning {
@@ -43,17 +43,17 @@ sub retrieve_closed {
 
 		$_ =~ s/[\n\r]*$//s;
 		my @fields = split(/;/);
-		
+
 		if(scalar(@fields) != 6) {
 			die("bad field count");
 		}
-		
+
 		$closed_sections->{$fields[1]}->{$fields[2]}->{$fields[3]} = 1;
 	}
-	
+
 	print("Closing Closed CSV...\n");
 	close CLOSEDFILE;
-	
+
 	return $closed_sections;
 }
 
@@ -71,38 +71,38 @@ sub retrieve_teachers {
 		$_ = utf8($_)->latin1;
 		$_ =~ s/[\n\r]*$//s;
 		my @fields = split(/;/);
-		
+
 		if(scalar(@fields) != 4) {
 			die("bad field count");
 		}
-		
+
 		$teacher_data->{$fields[0]}->{$fields[1]}->{$fields[2]} = $fields[3];
 	}
-	
+
 	print("Closing teachers CSV...\n");
 	close TEACHERFILE;
-	
+
 	return $teacher_data;
 }
 
 sub read_config {
 	print "Opening config file...\n";
 	open(CONFIGFILE, "<" . $CONFIG_DIR . "/sopti.conf") or die("error opening config file");
-	
+
 	while(<CONFIGFILE>) {
 		chomp;
 		$line = $_;
-		
+
 		# remove comments
 		$line =~ s/([^#]*)#.*/\1/;
-		
+
 		if($line =~ /^[ \t]*$/) {
 			next;
 		}
-		
+
 		my $varname;
 		my $varval;
-		
+
 		if($line =~ /^[ \t]*[^ \t]+[ \t]+[^ \t"]+[ \t]*$/) {
 			$varname = $line;
 			$varval = $line;
@@ -119,14 +119,14 @@ sub read_config {
 		else {
 			die("invalid config file line: $line\n");
 		}
-		
+
 		$CONFIG{$varname} = $varval;
 	}
 
-	
+
 	print "Closing config file...\n";
 	close CONFIGFILE;
-	
+
 	open(SEMFILE, "<" . $CONFIG_DIR . "/semester.conf") or die error("opening semester file");
 	$sem = <SEMFILE>;
 	$sem = trim($sem);
@@ -136,13 +136,13 @@ sub read_config {
 
 sub main() {
 	print("DATABASE UPDATE\n");
-	
+
 	read_config;
 	$CURRENT_SEMESTER=$CONFIG{'default_semester'};
-	
+
 	print("Connecting to database...\n");
 	$dbh = DBI->connect('dbi:mysql:database=' . $CONFIG{'db.schema'}, $CONFIG{'db.username'}, $CONFIG{'db.password'}) or die(DBI->errstr);
-	
+
 # 	print("Creating replacement tables...\n");
 # 	$dbh->do('CREATE TABLE courses_new LIKE courses') or die $dbh->errstr;
 # 	$dbh->do('CREATE TABLE courses_semester_new LIKE courses_semester') or die $dbh->errstr;
@@ -158,11 +158,11 @@ sub main() {
 	else {
 		$current_semester_unique = $semester_result->[0][0];
 	}
-	
+
 	# Download the courses table
 	print("Downloading courses table...\n");
 	$courses_table_ref = $dbh->selectall_hashref('SELECT * FROM courses', 'symbol');
-	
+
 	# Download the courses_semester table
 	# Index the hash by course symbol (ex: ING1040)
 	print("Downloading courses_semester table...\n");
@@ -177,7 +177,7 @@ sub main() {
 		my $course_semester = $row->{'course_semester'};
 		my $name = $row->{'name'};
 		my $theory_or_lab = $row->{'theory_or_lab'};
-		
+
 		$groups_table_ref->{$course_semester}->{$name}->{$theory_or_lab} = $row;
 	}
 
@@ -189,13 +189,13 @@ sub main() {
 	foreach $row (@{$periods_table_array}) {
 		my $group = $row->{'group'};
 		my $period_code = $row->{'period_code'};
-		
+
 		$periods_table_ref->{$group}->{$period_code} = $row;
 	}
-	
+
 	my $closed_sections = retrieve_closed();
 	my $teacher_data = retrieve_teachers();
-	
+
 	print("Opening CSV...\n");
 	open(DATAFILE, "<../data/courses.csv") or die("error opening data file");
 	$i=0;
@@ -211,17 +211,17 @@ sub main() {
 
 		$_ =~ s/[\n\r]*$//s;
 		@fields = split(/;/);
-	
+
 		if(scalar(@fields) != scalar(@fields_csv)) {
 			print(scalar(@fields), " != ", scalar(@fields_csv), "\n");
 			die("bad field count");
 		}
-		
+
 		# Transfer current line in a hash
 		for($j=0; $j<scalar(@fields); $j++) {
 			$current_line{$fields_csv[$j]} = $fields[$j];
 		}
-		
+
 		# Modifiy the CSV data
 		if($current_line{'week'} eq 'I') {
 			$current_line{'week'}='B1';
@@ -243,7 +243,7 @@ sub main() {
 		else {
 			$current_line{'closed'} = 0;
 		}
-		
+
 		# Merge the teacher data
 		if(exists $teacher_data->{$current_line{'symbol'}}->{$current_line{'group'}}->{$current_line{'theory_or_lab'}}) {
 			$current_line{'teacher'} = $teacher_data->{$current_line{'symbol'}}->{$current_line{'group'}}->{$current_line{'theory_or_lab'}};
@@ -251,7 +251,7 @@ sub main() {
 		else {
 			$current_line{'teacher'} = "";
 		}
-		
+
 		# Update the courses table
 		$current_course_entry = $courses_table_ref->{$current_line{'symbol'}};
 		if($courses_done{$current_line{'symbol'}} != 1) {
@@ -277,11 +277,11 @@ sub main() {
 					}
 				}
 			}
-			
+
 			$courses_done{$current_line{'symbol'}} = 1;
 			$current_course_entry->{'used'} = 1; # Mark as used because it was in the CSV
 		}
-		
+
 		# Update the courses_semester table
 		$current_course_semester_entry = $courses_semester_table_ref->{$current_line{'symbol'}};
 		if($courses_semester_done{$current_line{'symbol'}} != 1) {
@@ -309,11 +309,11 @@ sub main() {
 					}
 				}
 			}
-			
+
 			$courses_semester_done{$current_line{'symbol'}} = 1;
 			$current_course_semester_entry->{'used'} = 1; # Mark as used because it was in the CSV
 		}
-		
+
 		# Update the groups table
 		$current_course_semester_unique = $current_course_semester_entry->{'unique'};
 		$current_group_entry = $groups_table_ref->{$current_course_semester_unique}->{$current_line{'group'}}->{$current_line{'theory_or_lab'}};
@@ -322,7 +322,7 @@ sub main() {
 				# course not in DB, add it
 				warning("[INSERT][groups] Group $current_line{'symbol'}, $current_line{'group'}, $current_line{'theory_or_lab'} was not in groups table; adding it");
 				$dbh->do("INSERT INTO groups (course_semester, name, theory_or_lab, places_room, places_group, closed, teacher) VALUES (\"$current_course_semester_unique\", \"$current_line{'group'}\", \"$current_line{'theory_or_lab'}\", \"$current_line{'places_room'}\", \"$current_line{'places_group'}\", \"$current_line{'closed'}\", \"$current_line{'teacher'}\")") or die $dbh->errstr;
-				
+
 				# get the unique of the new entry
 				my $unique = $dbh->selectall_arrayref("SELECT groups.unique FROM groups WHERE course_semester=\"$current_course_semester_unique\" AND name=\"$current_line{'group'}\" AND theory_or_lab=\"$current_line{'theory_or_lab'}\"");
 				if(scalar(@$unique) != 1) {
@@ -342,11 +342,11 @@ sub main() {
 					}
 				}
 			}
-			
+
 			$groups_done{$current_line{'symbol'}}->{$current_line{'group'}}->{$current_line{'theory_or_lab'}} = 1;
 			$current_group_entry->{'used'} = 1; # Mark as used because it was in the CSV
 		}
-		
+
 		# Update the periods table
 		$current_group_unique = $current_group_entry->{'unique'};
 		$current_period_entry = $periods_table_ref->{$current_group_unique}->{$current_line{'period_code'}};
@@ -363,10 +363,10 @@ sub main() {
 					$dbh->do("UPDATE periods SET $field=\"$current_line{$field}\" WHERE periods.unique=\"$current_period_entry->{'unique'}\"") or die $dbh->errstr;
 				}
 			}
-			
+
 			$current_period_entry->{'used'} = 1;
 		}
-		
+
 # 		if($course_sem_done{$course_symbol} != '1') {
 # 			$dbh->do("INSERT INTO courses_new (symbol, title) VALUES (\"$course_symbol\", \"$course_title\")") or die $dbh->errstr;
 # 			$course_sem_done{$course_symbol} = '1';
@@ -374,7 +374,7 @@ sub main() {
 		$i++;
 	}
 	close DATAFILE;
-	
+
 	# Check for unused courses
 	while ( my ($symbol, $entry) = each(%{$courses_table_ref}) ) {
 		if($entry->{'used'} != 1) {
@@ -390,7 +390,7 @@ sub main() {
 			$dbh->do("DELETE FROM courses_semester WHERE courses_semester.unique=\"$entry->{'unique'}\"") or die $dbh->errstr;
 		}
 	}
-	
+
 	# Check for unused groups
 	while ( my ($symbol, $entry1) = each(%{$groups_table_ref}) ) {
 		while ( my ($group_unique, $entry2) = each(%{$entry1}) ) {
@@ -402,7 +402,7 @@ sub main() {
 			}
 		}
 	}
-	
+
 	# Check for unused periods
 	while ( my ($group_unique, $entry1) = each(%{$periods_table_ref}) ) {
 		while ( my ($period_code, $entry2) = each(%{$entry1}) ) {
@@ -412,7 +412,7 @@ sub main() {
 			}
 		}
 	}
-	
+
 # 	print("Commiting changes...\n");
 # 	$dbh->do(q{RENAME TABLE
 # 		courses TO courses_old,
@@ -422,7 +422,7 @@ sub main() {
 # 		}) or die $dbh->errstr;
 # 	# Race condition if other update process starts here
 # 	$dbh->do(q{DROP TABLE courses_old, courses_semester_old});
-	
+
 	print("Disconnecting...\n");
 	$dbh->disconnect();
 	print("done.\n");
diff --git a/emailer/emailer.py b/emailer/emailer.py
index f15f4c9..f9d4743 100644
--- a/emailer/emailer.py
+++ b/emailer/emailer.py
@@ -19,16 +19,16 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
-Emailer.py is a program to send emails from a database of notification 
+Emailer.py is a program to send emails from a database of notification
 requests.
-It is part of the SOPTI (School Schedule Optimizer) package written 
+It is part of the SOPTI (School Schedule Optimizer) package written
 by Pierre-Marc Fournier <pmf@users.sf.net>
 
-Emailer.py reads notification requests from a table in the sopti 
-database, checks if the groups for those requests have space 
-available and if it is the case notifies by email the person who 
-made the request that they can go change their schedule. After 
-sending the email, the program removes that entry from the database. 
+Emailer.py reads notification requests from a table in the sopti
+database, checks if the groups for those requests have space
+available and if it is the case notifies by email the person who
+made the request that they can go change their schedule. After
+sending the email, the program removes that entry from the database.
 
 The program can log and print relevant parts of its operation.
 See `emailer.py -h` for information on this.
@@ -36,7 +36,7 @@ See `emailer.py -h` for information on this.
 Some aspects of its operation can also be changed via a configuration
 file (see the example config file in the distribution)
 
-The table that contains the notfications is filled via a php script 
+The table that contains the notfications is filled via a php script
 embeded in the SOPTI web interface.
 
 CVS information:
@@ -67,7 +67,7 @@ def main():
 	configuration= Configuration.getInstance()
 	logging= configuration["loggingModule"]
 	random.seed()
-	
+
 	configuration["stats"]= {
 		"emailsSuccessfully" : 0, # emails were sent
 		"emailsFailed" : 0, # emails could not be sent
@@ -76,40 +76,41 @@ def main():
 		"remainingNotifications" : 0,  # notifications are still remaining in the database
 		"remainingGroups" : 0,  # notifications are still remaining in the database
 		"remainingEmails" : 0, } # notifications are still remaining in the database
-	
+
 	# The notifications to be sent are fetched from the database
 	logging.debug("Connecting to the database server \"%s\"..." % (configuration["DB"]["host"],))
-	
+
 	try:
 		connection= MySQLdb.connect(**configuration["DB"])
-		
+		connection.autocommit(True)
+
 		connection.query("""
-			select 
-				`notifications`.`unique`, 
-				`notifications`.`email`, 
-				`groups`.`unique`, 
-				`groups`.`name`, 
-				`groups`.`theory_or_lab`, 
-				`groupsT`.`teacher`, 
-				`groupsL`.`teacher`, 
-				`courses_semester`.`course_type`, 
-				`courses`.`symbol`, 
-				`courses`.`title` 
-			from `notifications` 
-			inner join `groups` on 
-				`notifications`.`group` = `groups`.`unique` 
-			left join `groups` as `groupsT` on 
-				`groups`.`course_semester` = `groupsT`.`course_semester` and 
-				`groups`.`name` = `groupsT`.`name` and 
-				`groupsT`.`theory_or_lab` = 'C' 
-			left join `groups` as `groupsL` on 
-				`groups`.`course_semester` = `groupsL`.`course_semester` and 
-				`groups`.`name` = `groupsL`.`name` and 
-				`groupsL`.`theory_or_lab` = 'L' 
-			inner join `courses_semester` on 
-				`groups`.`course_semester` = `courses_semester`.`unique` 
-			inner join `courses` on 
-				`courses_semester`.`course` = `courses`.`unique` 
+			select
+				`notifications`.`unique`,
+				`notifications`.`email`,
+				`groups`.`unique`,
+				`groups`.`name`,
+				`groups`.`theory_or_lab`,
+				`groupsT`.`teacher`,
+				`groupsL`.`teacher`,
+				`courses_semester`.`course_type`,
+				`courses`.`symbol`,
+				`courses`.`title`
+			from `notifications`
+			inner join `groups` on
+				`notifications`.`group` = `groups`.`unique`
+			left join `groups` as `groupsT` on
+				`groups`.`course_semester` = `groupsT`.`course_semester` and
+				`groups`.`name` = `groupsT`.`name` and
+				`groupsT`.`theory_or_lab` = 'C'
+			left join `groups` as `groupsL` on
+				`groups`.`course_semester` = `groupsL`.`course_semester` and
+				`groups`.`name` = `groupsL`.`name` and
+				`groupsL`.`theory_or_lab` = 'L'
+			inner join `courses_semester` on
+				`groups`.`course_semester` = `courses_semester`.`unique`
+			inner join `courses` on
+				`courses_semester`.`course` = `courses`.`unique`
 			where `groups`.`closed` = '0'
 			order by `notifications`.`email`
 		""")
@@ -119,7 +120,7 @@ def main():
 	else:
 		logging.debug("OK")
 		notifications= list(connection.store_result().fetch_row(maxrows= 0, how= 2))
-	
+
 	# The emails are sent
 	try:
 		logging.debug("Connecting to SMTP server \"%s:%d\"..." % (configuration["SMTP"]["host"], int(configuration["SMTP"]["port"]),))
@@ -130,14 +131,14 @@ def main():
 		return 1
 	else:
 		logging.debug("OK")
-		
+
 		# the list of the notification entries that were successfully sent
 		successfulNotifications= list()
 		# the list of the notification entries that could not be sent because of recepient refused errors
 		refusedNotifications= list()
 		# a group of notifications that are to be sent to the same email address
 		notificationGroup= list()
-		
+
 		while len(notifications) or len(notificationGroup):
 			#TODO: verifier si c'est slow enlever le premier element au lieu du dernier
 			if not len(notificationGroup) or (len(notifications) and notifications[0]["notifications.email"] == notificationGroup[0]["notifications.email"]):
@@ -145,39 +146,39 @@ def main():
 				continue
 			else:
 				hasher= hmac.new(
-					configuration["general"]["pepper"], 
-					notificationGroup[0]["notifications.email"], 
+					configuration["general"]["pepper"],
+					notificationGroup[0]["notifications.email"],
 					digestmod=hashlib.sha1)
 				# build the notification email
 				if len(notificationGroup) == 1:
 					# for a single notification
 					msg= email.MIMEText.MIMEText(
-						configuration["emailIntro"]["singular"] + 
+						configuration["emailIntro"]["singular"] +
 						configuration["emailCourse"]["text"] % getTemplateValues(notificationGroup[0]) +
 						(configuration["emailOutro"]["singular"] % {
-							"email" : notificationGroup[0]["notifications.email"], 
+							"email" : notificationGroup[0]["notifications.email"],
 							"hash" : hasher.hexdigest(),
 							"baseurl" : configuration["general"]["baseurl"],}))
-					
+
 					msg["Subject"]= configuration["emailSubject"]["singular"]
 					msg["From"]= "%s <%s>" % (configuration["sender"]["name"], configuration["sender"]["address"],)
 					msg["To"]= notificationGroup[0]["notifications.email"]
 				else:
-					# for many notifications that are to be sent to 
+					# for many notifications that are to be sent to
 					# the same address
 					msg= email.MIMEText.MIMEText(
-						configuration["emailIntro"]["plural"] + 
+						configuration["emailIntro"]["plural"] +
 						"".join([configuration["emailCourse"]["text"] % getTemplateValues(notification) for notification in notificationGroup]) +
 						configuration["emailOutro"]["plural"] % {
-							"email" : urllib.quote(notificationGroup[0]["notifications.email"]), 
+							"email" : urllib.quote(notificationGroup[0]["notifications.email"]),
 							# a period (.) at the end of the line is also replaced otherwise it is left out of the link by some mail readers
 							"hash" : hasher.hexdigest(),
 							"baseurl" : configuration["general"]["baseurl"],})
-					
+
 					msg["Subject"]= configuration["emailSubject"]["plural"]
 					msg["From"]= "%s <%s>" % (configuration["sender"]["name"], configuration["sender"]["address"],)
 					msg["To"]= notificationGroup[0]["notifications.email"]
-			
+
 			# send the notification email
 			infoMesg= "Sending a notification to %s" % (msg["To"],)
 			if len(notificationGroup) == 1:
@@ -185,79 +186,79 @@ def main():
 			else:
 				infoMesg += " for groups " + ", ".join([str(notification["groups.unique"]) for notification in notificationGroup])
 			logging.info(infoMesg)
-			
+
 			try:
 				if not configuration["general"]["dry-run"]: smtp.sendmail(configuration["sender"]["address"], msg["To"], msg.as_string())
 			except smtplib.SMTPSenderRefused, message:
 				logging.error("SMTP error %d: %s" % (message[0], message[1]))
 				logging.error("The program will not try sending any more emails, consider changing your \"from\" address or you SMTP server")
-				
+
 				smtp.quit()
 				connection.close()
 				logging.shutdown()
-				
+
 				return 1
 			except smtplib.SMTPRecipientsRefused, message:
 				message= message[0].values()[0]
 				logging.error("SMTP Recipient refused error %d: %s" % (message[0], message[1]))
-				
+
 				configuration["stats"]["groupsFailed"]+= len(notificationGroup)
 				configuration["stats"]["emailsFailed"]+= 1
-				
+
 				if len(notificationGroup) == 1:
 					refusedNotifications.append({
-						"notifications.unique" : notificationGroup[0]["notifications.unique"], 
-						"notifications.email" : notificationGroup[0]["notifications.email"], 
+						"notifications.unique" : notificationGroup[0]["notifications.unique"],
+						"notifications.email" : notificationGroup[0]["notifications.email"],
 						"notifications.group" : notificationGroup[0]["groups.unique"],})
 				else:
 					refusedNotifications.extend([{
-						"notifications.unique" : notification["notifications.unique"], 
-						"notifications.email" : notification["notifications.email"], 
-						"notifications.group" : notification["groups.unique"],} 
+						"notifications.unique" : notification["notifications.unique"],
+						"notifications.email" : notification["notifications.email"],
+						"notifications.group" : notification["groups.unique"],}
 						for notification in notificationGroup])
-			
+
 			except smtplib.SMTPDataError, message:
 				logging.error("SMTP Data error %d: %s" % (message[0], message[1]))
-				
+
 				configuration["stats"]["groupsFailed"]+= len(notificationGroup)
 				configuration["stats"]["emailsFailed"]+= 1
 			else:
 				if len(notificationGroup) == 1:
 					successfulNotifications.append({
-						"notifications.unique" : notificationGroup[0]["notifications.unique"], 
-						"notifications.email" : notificationGroup[0]["notifications.email"], 
+						"notifications.unique" : notificationGroup[0]["notifications.unique"],
+						"notifications.email" : notificationGroup[0]["notifications.email"],
 						"notifications.group" : notificationGroup[0]["groups.unique"],})
 				else:
 					successfulNotifications.extend([{
-						"notifications.unique" : notification["notifications.unique"], 
-						"notifications.email" : notification["notifications.email"], 
-						"notifications.group" : notification["groups.unique"],} 
+						"notifications.unique" : notification["notifications.unique"],
+						"notifications.email" : notification["notifications.email"],
+						"notifications.group" : notification["groups.unique"],}
 						for notification in notificationGroup])
-				
+
 				configuration["stats"]["emailsSuccessfully"]+= 1
-			
+
 			notificationGroup= list()
-			
+
 		smtp.quit()
-	
+
 	configuration["stats"]["groupsSuccessfully"]= len(successfulNotifications)
-	
-	# The notifications that have been successfully sent are 
+
+	# The notifications that have been successfully sent are
 	# cleared from the database
 	# The notifications that generated a recipient refused message may
 	# be cleared as well depending on the configuration
 	logging.info("Cleaning the DB...")
-	
+
 	if configuration["general"]["clearRecipientsRefused"]:
 		successfulNotifications.extend(refusedNotifications)
-	
+
 	if len(successfulNotifications):
 		if not configuration["general"]["dry-run"]:
 			try:
 				connection.query("""
-					delete 
-					from `notifications` 
-					where `unique` in (""" + ", ".join([str(notification["notifications.unique"]) for notification in successfulNotifications]) + """) 
+					delete
+					from `notifications`
+					where `unique` in (""" + ", ".join([str(notification["notifications.unique"]) for notification in successfulNotifications]) + """)
 					limit """ + str(len(successfulNotifications)))
 			except MySQLdb.MySQLError, message:
 				logging.error("MySQL error %d: %s" % (message[0], message[1]))
@@ -265,125 +266,125 @@ def main():
 				for notification in successfulNotifications:
 					logging.dbentries(
 						"erased from `notifications` values(%d, %d, '%s')" % (
-						notification["notifications.unique"], 
-						notification["notifications.group"], 
+						notification["notifications.unique"],
+						notification["notifications.group"],
 						notification["notifications.email"],))
 				logging.info("%d rows have been removed" % (connection.affected_rows(), ))
 		else:
 			logging.info("0 rows have been removed")
 	else:
 		logging.info("nothing to be done")
-	
+
 	# statistics are processed
 	try:
 		connection.query("""
-			select 
-				count(*) 
+			select
+				count(*)
 				from `notifications`
 			""")
 	except MySQLdb.MySQLError, message:
 		logging.error("MySQL error %d: %s" % (message[0], message[1]))
 	else:
 		configuration["stats"]["remainingNotifications"]= int(connection.store_result().fetch_row()[0][0])
-	
+
 	try:
 		# subquerry version
 		# if your database server supports subqueries you might want to use this block instead of the next
-		#~ connection.query("""
-			#~ select 
-				#~ count(*) 
-				#~ from (
-					#~ select 
-						#~ count(*) 
-					#~ from `notifications`
-					#~ group by `group`)
-				#~ as `subTable`
-			#~ """)
+		connection.query("""
+			select
+				count(*)
+				from (
+					select
+						count(*)
+					from `notifications`
+					group by `group`)
+				as `subTable`
+			""")
 		# safe version
 		#~ connection.query("""create temporary table if not exists `_groups` (`group` int(11) not null)""")
 		#~ connection.query("""truncate table `_groups`""")
 		#~ connection.query("""insert into `_groups` (`group`) select count(*) from `notifications` group by `group`""")
 		#~ connection.query("""select count(*) from `_groups`""")
 		# temporary table version
-		connection.query("""create temporary table `_groups` (`group` int(11) not null) select count(*) from `notifications` group by `group`""")
-		connection.query("""select count(*) from `_groups`""")
+		# connection.query("""create temporary table `_groups` (`group` int(11) not null) select count(*) from `notifications` group by `group`""")
+		# connection.query("""select count(*) from `_groups`""")
 	except MySQLdb.MySQLError, message:
 		logging.error("MySQL error %d: %s" % (message[0], message[1]))
 	else:
 		configuration["stats"]["remainingGroups"]= int(connection.store_result().fetch_row()[0][0])
-	
+
 	try:
 		# subquerry version
 		# if your database server supports subqueries you might want to use this block instead of the next
-		#~ connection.query("""
-			#~ select 
-				#~ count(*) 
-				#~ from (
-					#~ select 
-						#~ count(*) 
-					#~ from `notifications`
-					#~ group by `email`)
-				#~ as `subTable`
-			#~ """)
+		connection.query("""
+			select
+				count(*)
+				from (
+					select
+						count(*)
+					from `notifications`
+					group by `email`)
+				as `subTable`
+			""")
 		# temporary table version
-		connection.query("""create temporary table `_emails` (`email` varchar(60) not null) select count(*) from `notifications` group by `email`""")
-		connection.query("""select count(*) from `_emails`""")
+		# connection.query("""create temporary table `_emails` (`email` varchar(60) not null) select count(*) from `notifications` group by `email`""")
+		# connection.query("""select count(*) from `_emails`""")
 	except MySQLdb.MySQLError, message:
 		logging.error("MySQL error %d: %s" % (message[0], message[1]))
 	else:
 		configuration["stats"]["remainingEmails"]= int(connection.store_result().fetch_row()[0][0])
-	
+
 	logging.summary("Summary for emailer run #%s:" % (configuration["runNumber"],))
 	logging.summary("Started at %s" % (time.asctime(configuration["startTime"]), ))
 	logging.summary("Finished at %s" % (time.asctime(), ))
 	if configuration["stats"]["groupsSuccessfully"]:
 		logging.summary(
-			("%d group notification" + 
-			(configuration["stats"]["groupsSuccessfully"] > 1 and "s were " or " was ") + 
+			("%d group notification" +
+			(configuration["stats"]["groupsSuccessfully"] > 1 and "s were " or " was ") +
 			"sent successfully in %d email" +
-			(configuration["stats"]["emailsSuccessfully"] > 1 and ["s"] or [""])[0]) % 
+			(configuration["stats"]["emailsSuccessfully"] > 1 and ["s"] or [""])[0]) %
 			(configuration["stats"]["groupsSuccessfully"], configuration["stats"]["emailsSuccessfully"],))
 	if configuration["stats"]["groupsFailed"]:
 		logging.summary(
-			("%d group notification" + 
-			(configuration["stats"]["groupsFailed"] > 1 and "s " or " ") + 
-			"distributed in %d email" + 
-			(configuration["stats"]["emailsFailed"] > 1 and "s " or " ") + 
-			"FAILED to be sent") % 
+			("%d group notification" +
+			(configuration["stats"]["groupsFailed"] > 1 and "s " or " ") +
+			"distributed in %d email" +
+			(configuration["stats"]["emailsFailed"] > 1 and "s " or " ") +
+			"FAILED to be sent") %
 			(configuration["stats"]["groupsFailed"], configuration["stats"]["emailsFailed"],))
 	logging.summary(
-		("%d notification" + 
-		(configuration["stats"]["remainingNotifications"] > 1 and "s are " or " is ") + 
-		"still in the database spreaded over %d group" + 
-		(configuration["stats"]["remainingGroups"] > 1 and "s " or " ") + 
-		"and %d email" + 
-		(configuration["stats"]["remainingEmails"] > 1 and ["s"] or [""])[0]) % 
+		("%d notification" +
+		(configuration["stats"]["remainingNotifications"] > 1 and "s are " or " is ") +
+		"still in the database spreaded over %d group" +
+		(configuration["stats"]["remainingGroups"] > 1 and "s " or " ") +
+		"and %d email" +
+		(configuration["stats"]["remainingEmails"] > 1 and ["s"] or [""])[0]) %
 		(configuration["stats"]["remainingNotifications"], configuration["stats"]["remainingGroups"], configuration["stats"]["remainingEmails"],))
-	
+
 	connection.close()
 	logging.shutdown()
 
 
 def getTemplateValues(notification):
 	configuration= Configuration.getInstance()
-	
+
 	values= {
-		"symbol" : notification["courses.symbol"], 
-		"title" : notification["courses.title"], 
+		"symbol" : notification["courses.symbol"],
+		"title" : notification["courses.title"],
 		"name" : notification["groups.name"],}
-	
+
 	if notification["courses_semester.course_type"] == "TL":
-		values["theory_or_lab"]= "combinée théorique et laboratoire"
-		values["teacher"]= "\n\tThéorie: %s\n\tLab: %s" % (notification["groupsT.teacher"], notification["groupsL.teacher"],)
+		values["theory_or_lab"]= "combin�e th�orique et laboratoire"
+		values["teacher"]= "\n\tTh�orie: %s\n\tLab: %s" % (notification["groupsT.teacher"], notification["groupsL.teacher"],)
 	elif notification["groups.theory_or_lab"] == "C":
-		values["theory_or_lab"]= "théorique"
+		values["theory_or_lab"]= "th�orique"
 		values["teacher"]= notification["groupsT.teacher"]
 	elif notification["groups.theory_or_lab"] == "L":
 		values["theory_or_lab"]= "laboratoire"
 		values["teacher"]= notification["groupsL.teacher"]
 	else:
 		raise(Exception("Unknown course type"))
-	
+
 	return values
 
 
diff --git a/refresh_semester b/refresh_semester
index ec0f784..3c0422b 100755
--- a/refresh_semester
+++ b/refresh_semester
@@ -1,8 +1,13 @@
 #!/bin/bash
 
+if [ "$EUID" -ne 0 ]; then
+    echo -e "\033[31mERROR : You must run this as the root user 😡"
+    exit -1
+fi
+
 function mailbadmonth()
 {
-	echo -e "Hi, this is sopti at $(hostname). I just refused to update the semester to $1 because this is not the right time of the year.\n\nHave a nice day." | mail -s "Refusing to change semester" pierre-marc.fournier@polymtl.ca
+	echo -e "Hi, this is sopti at $(hostname). I just refused to update the semester to $1 because this is not the right time of the year.\n\nHave a nice day."
 }
 
 SOPTI_DIR=$(dirname $0)
@@ -43,7 +48,7 @@ if [ "$SEM" != "$(<$SOPTI_DIR/semester.conf)" ]; then
 			mailbadmonth "$SEM"
 			exit 0
 		fi
-		PRETTY="Été"
+		PRETTY="Été"
 	fi
 	if [ "${SEM:0:1}" = "A" ]; then
 		if [ "$month" -lt "6" -o "$month" -gt "9" ]; then
@@ -64,10 +69,11 @@ if [ "$SEM" != "$(<$SOPTI_DIR/semester.conf)" ]; then
 	echo "Writing new semester to config file"
 	echo "$SEM" >$SOPTI_DIR/semester.conf
 
-	$SOPTI_DIR/db/updatedb.pl
+	cd $SOPTI_DIR/db
+	./updatedb.pl
 
 	echo -e "Hi, this is sopti at $(hostname). This is to let you know I just updated the semester to $SEM.\n\nHave a nice day." | mail -s "Semester updated to $SEM" infra@exec.step.polymtl.ca
-	
+
 fi
 
 
diff --git a/web/lib.php b/web/lib.php
index f544d7f..3e5f1e0 100644
--- a/web/lib.php
+++ b/web/lib.php
@@ -1,6 +1,4 @@
 <?php
-error_reporting(0);
-
 require_once('config.php');
 
 $CONFIG_VARS=array();
@@ -34,7 +32,7 @@ function admin_error($msg)
 <html>
 
 <head>
-  <title>Générateur d'horaires - Erreur interne</title>
+  <title>G�n�rateur d'horaires - Erreur interne</title>
   <link rel="stylesheet" type="text/css" href="sopti.css">
         <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
 </head>
@@ -43,8 +41,8 @@ function admin_error($msg)
 
 <center>
 
-<img src="genhor_sm.png" alt="Générateur d'horaires">
-<p style="font-size: 15px;">Générateur d'horaires</p>
+<img src="genhor_sm.png" alt="G�n�rateur d'horaires">
+<p style="font-size: 15px;">G�n�rateur d'horaires</p>
 </center>
 
 <div style="background-color: #ffbbbb;">
@@ -52,7 +50,7 @@ function admin_error($msg)
 <p align="center"><?php echo $msg; ?></p>
 </div>
 
-<p align="center">L'administrateur a été informé de cette erreur.
+<p align="center">L'administrateur a �t� inform� de cette erreur.
 </body>
 </html>
 
@@ -72,7 +70,7 @@ function error($msg)
 <html>
 
 <head>
-  <title>Générateur d'horaires - Erreur</title>
+  <title>G�n�rateur d'horaires - Erreur</title>
   <link rel="stylesheet" type="text/css" href="sopti.css">
         <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
 </head>
@@ -81,8 +79,8 @@ function error($msg)
 
 <center>
 
-<img src="genhor_sm.png" alt="Générateur d'horaires">
-<p><font size="+2">Générateur d'horaires</font>
+<img src="genhor_sm.png" alt="G�n�rateur d'horaires">
+<p><font size="+2">G�n�rateur d'horaires</font>
 </center>
 
 <div style="background-color: #ffbbbb;">
@@ -90,7 +88,7 @@ function error($msg)
 <p align="center"><?php echo $msg; ?></p>
 </div>
 
-<p align="center">Si possible, utiliser le bouton précédent de votre navigateur pour revenir en arrière et corriger l'erreur.
+<p align="center">Si possible, utiliser le bouton pr�c�dent de votre navigateur pour revenir en arri�re et corriger l'erreur.
 </body>
 </html>
 
@@ -240,8 +238,8 @@ function print_schedule($sch, $schedno)
 			</tr>
 		</table>
 	</td></tr>
-	<tr><th rowspan="2">Sigle</th><th rowspan="2">Titre</th><th colspan="2">Théorie</th><th colspan="2">Lab</th></tr>
-	<tr><th class="subheader">Section</th><th class="subheader">Chargé</th><th class="subheader">Section</th><th class="subheader">Chargé</th><th class="subheader"></th></tr>
+	<tr><th rowspan="2">Sigle</th><th rowspan="2">Titre</th><th colspan="2">Th�orie</th><th colspan="2">Lab</th></tr>
+	<tr><th class="subheader">Section</th><th class="subheader">Charg�</th><th class="subheader">Section</th><th class="subheader">Charg�</th><th class="subheader"></th></tr>
 	<tr><td colspan="7" style="background-color: black; height: 1px;"></td></tr>
 	
 <?php
@@ -264,11 +262,11 @@ function print_schedule($sch, $schedno)
 		}
 		elseif($group_data[$req['symbol']]['course_type'] == 'TL') {
 		        if($req['th_grp'] != $req['lab_grp']) {
-				error("Horaire illégal; th_grp != lab_grp pour un cours TL");
+				error("Horaire ill�gal; th_grp != lab_grp pour un cours TL");
 			}
 
 			if(!isset($group_data[$req['symbol']]['theory'][$req['th_grp']])) {
-				error("Groupe théorique introuvable: " . $req['symbol'] . "/" . $req['th_grp']);
+				error("Groupe th�orique introuvable: " . $req['symbol'] . "/" . $req['th_grp']);
 			}
 			if(!isset($group_data[$req['symbol']]['lab'][$req['lab_grp']])) {
 				error("Groupe lab introuvable: " . $req['symbol'] . "/" . $req['lab_grp']);
@@ -278,7 +276,7 @@ function print_schedule($sch, $schedno)
 		}
 		elseif($group_data[$req['symbol']]['course_type'] == 'TLS') {
 			if(!isset($group_data[$req['symbol']]['theory'][$req['th_grp']])) {
-				error("Groupe théorique introuvable: " . $req['symbol'] . "/" . $req['th_grp']);
+				error("Groupe th�orique introuvable: " . $req['symbol'] . "/" . $req['th_grp']);
 			}
 			if(!isset($group_data[$req['symbol']]['lab'][$req['lab_grp']])) {
 				error("Groupe lab introuvable: " . $req['symbol'] . "/" . $req['lab_grp']);
-- 
GitLab