#!/usr/bin/perl -w #QED: Question Editing Database #(C) 2006-2007 by Carlo Angiuli use CGI::Carp 'fatalsToBrowser'; use strict; use warnings; use DBI; use CGI; use Digest::MD5 'md5_hex'; #use MIME::Lite; # These two are imported later if needed. #use Net::SMTP; my ($mail_from, $org_name, $mysql_database, $mysql_host, $mysql_port, $mysql_user, $mysql_pass, @types, @categories, $dtbu, $dtbq, $isadmin, $isround); ########################## ##### User Variables ##### ########################## $mail_from = ''; $org_name = ""; $mysql_database = ""; $mysql_host = ""; $mysql_port = 3306; $mysql_user = ""; $mysql_pass = ""; $dtbu = ""; $dtbq = ""; @types = ("Tossup", "Bonus"); @categories = ( "Fine Arts: Music", "Fine Arts: Visual Art", "Fine Arts: Other", "Literature: Language Arts", "Literature: Literature", "Literature: Mythology", "Math: Algebra", "Math: Calculus", "Math: General", "Math: Geometry", "Math: Other", "Miscellaneous: Entertainment", "Miscellaneous: Interdisciplinary", "Miscellaneous: Sports", "Miscellaneous: Technology", "Miscellaneous: Other", "Science: Astronomy", "Science: Biology", "Science: Chemistry", "Science: Earth Science", "Science: Physics", "Social Studies: Current Events", "Social Studies: Geography", "Social Studies: U.S. History", "Social Studies: World History", "Social Studies: Other" ); ########################## ###### General Code ###### ########################## my $prgm_version = "1.40"; sub login_screen; sub gui; sub gui_home; sub edit_question; sub new_question; sub gui_edit; sub gui_list; sub gui_list_table; sub search; sub gui_about; sub gui_delete; sub gui_delete_done; sub gui_search; sub admin_backup; sub admin_gui_config; sub admin_gui_users; sub admin_gui_stats; sub admin_gui_rounds; sub round_gui_options; sub round_gui_acquire; sub round_gui_acquire_table; sub round_acquire; sub round_unacquire; sub round_gui_organize; sub round_organize; sub round_gui_order; sub round_order; sub round_gui_stats; sub round_gui_output; sub round_output; sub order_string; sub rtrim; sub ceiling; sub parseget; sub output_question; print "Content-type: text/html\n\n"; my $cgi= new CGI; my $ip = $ENV{'REMOTE_ADDR'}; my $filename = $ENV{'SCRIPT_NAME'}; my $pagename = lc($ENV{'SCRIPT_URL'}); #follows qed.pl/ in URL $pagename =~ /$filename\/(.*)/; $pagename = $1; my $query; my ($username, $password, $round); for (split(';',$ENV{'HTTP_COOKIE'})) { if (/qed=([a-zA-Z0-9]+)\|([a-f0-9]+)/) { $username = $1; $password = $2; } elsif (/qed-rnd=([a-zA-Z0-9]+)/) { $round = $1; } } #CREATE TABLE users ( # user CHAR(10), # password CHAR(32), # flags INT(11)); #The password column contains the md5 hash of the password, #or if it's a round, the number of packets it contains. # #CREATE TABLE questions ( # id INTEGER AUTO_INCREMENT PRIMARY KEY, # type TEXT, # category TEXT, # question TEXT, # answer TEXT, # owner TEXT, # round TEXT, # comment TEXT,); my $dbh = DBI->connect("DBI:mysql:database=${mysql_database};host=${mysql_host};port=${mysql_port}",$mysql_user,$mysql_pass); $query = $dbh->prepare("SELECT * FROM $dtbu WHERE user=?"); $query->execute($username); my @sqluser = $query->fetchrow_array(); $query->finish(); #A cookie is set client-side when logging in, with the format qed=(username)|(md5hash), #where the hash is calculated client-side, so only the username is transmitted plaintext. #The hash is md5(ip . md5(pass)), so only the same IP address can use that hash. if ($password ne md5_hex($ENV{'REMOTE_ADDR'} . $sqluser[1])) { login_screen; } elsif ($pagename =~ /^output=(\d{1,2})/) { round_output($1); } elsif ($pagename =~ /^delete=(\d)/) { gui_delete; } elsif ($cgi->param("cgi") && $cgi->param("delete")) { gui_delete_done; } elsif ($sqluser[2] == 0) { $round = ""; gui; } elsif ($sqluser[2] == 1) { $isadmin = 1; gui; } ########################## #### Normal Interface #### ########################## sub login_screen { print qq` QED $prgm_version | Login
QED
Question Editing Database
Version $prgm_version
$org_name
Username:
Password:

`; exit; } sub gui { my $usertype = ($isadmin ? "Admin" : "Writer"); my $usercolor = ($isadmin ? "#ff0000" : "#000000"); my $usercss = ($isadmin ? "#menu a.admin {color: #ee6897;} #menu a.admin:hover {background-color: #f4c5cb;} #menu a.round {color: #2e8b57;} #menu a.round:hover {background-color: #bce9bf;} #adminusers td {padding-right:1em;} .roundlist {text-decoration: none; color:#000000; font-size:1.2em;} .roundlist:hover {text-decoration:underline;} .orgqs td {padding-right:.7em;} " : "#questions {width: 85%;}"); my $userjs = ($pagename =~ /^round-acquire/ ? qq` onload="count();"` : ""); print qq` QED $prgm_version | $usertype
QED $prgm_version | $org_name | $username ($usertype) | Log out
`; admin_backup if $cgi->param("backup-yes") && $isadmin; edit_question if $cgi->param("cgi") && $cgi->param("edit"); new_question if $cgi->param("cgi") && not($cgi->param("edit")); if ($cgi->param("acquire") && $isadmin) { $cgi->delete("acquire"); round_acquire($cgi->param()); } if ($isadmin && $pagename =~ /^admin-config/) {admin_gui_config;} elsif ($isadmin && $pagename =~ /^admin-users/) {admin_gui_users;} elsif ($isadmin && $pagename =~ /^admin-stats/) {admin_gui_stats;} elsif ($isadmin && $pagename =~ /^admin-rounds/) {admin_gui_rounds;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-stats/) {round_gui_stats;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-options/) {round_gui_options;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-acquire/) {round_gui_acquire;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-organize/) {round_gui_organize;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-order/) {round_gui_order;} elsif ($isadmin && $round ne "" && $pagename =~ /^round-output/) {round_gui_output;} elsif ($pagename =~ /^list/) {gui_list;} elsif ($pagename =~ /^about/) {gui_about;} elsif ($pagename =~ /^edit/) {gui_edit;} else {gui_home;} print qq`
`; exit; } sub gui_home { $query = $dbh->prepare("SELECT count(*) FROM $dtbq WHERE owner = '$username'"); $query->execute(); my $questions = $query->fetchrow_array(); $query->finish(); $query = $dbh->prepare("SELECT count(*) FROM $dtbq WHERE owner = '$username' and comment != ''"); $query->execute(); my $flagged = $query->fetchrow_array(); $query->finish(); print qq`

Welcome, $username!

Hello, $username! You currently have $questions questions. `; if ($flagged > 1) {print qq`You have $flagged questions flagged for attention.`;} elsif ($flagged == 1) {print qq`You have 1 question flagged for attention.`;} else {print qq`You have no questions flagged for attention.`;} print qq`

Having problems? Check the help file or email Carlo.

`; } sub edit_question { my $iscomp = ""; $iscomp = " (Comp)" if $cgi->param("computational") eq "Computational"; my $id = $cgi->param("id"); my $type = $dbh->quote($cgi->param("type")); my $category = $dbh->quote($cgi->param("category") . $iscomp); my $question = $dbh->quote(rtrim($cgi->param("question"))); my $answer = $dbh->quote(rtrim($cgi->param("answer"))); my $comment = $dbh->quote($cgi->param("comment")); $dbh->do("UPDATE $dtbq SET type=$type , category=$category , question=$question , answer=$answer , comment=$comment WHERE id = $id"); } sub new_question { my $iscomp = ""; $iscomp = " (Comp)" if $cgi->param("computational") eq "Computational"; my $type = $dbh->quote($cgi->param("type")); my $category = $dbh->quote($cgi->param("category") . $iscomp); my $question = $dbh->quote(rtrim($cgi->param("question"))); my $answer = $dbh->quote(rtrim($cgi->param("answer"))); my $comment = $dbh->quote($cgi->param("comment")); $dbh->do("INSERT INTO $dtbq (id, type, category, question, answer, owner, round, comment) VALUES(NULL, $type , $category , $question , $answer , '$username', '', $comment);"); } sub gui_edit { $pagename =~ /edit=(\d*)/; print qq` `; if ($1 eq "") { print qq`

New Question

`; print qq`` foreach (@types[1..$#types]); print qq`
$types[0]   $_   Computational

Category:

Question:

Answer:


(Comment/request for attention. Leave blank if none.)
`; } else { $pagename =~ /^edit=(\d+)/; my $id = $dbh->quote($1); if ($isadmin or $isround) {$query = $dbh->prepare("SELECT * FROM $dtbq WHERE id=$id"); } else {$query = $dbh->prepare("SELECT * FROM $dtbq WHERE id=$id AND owner='$username'")} $query->execute(); my @question = $query->fetchrow_array(); if ($question[5] eq "") {print "

Question not found.

"; exit;} print qq`

Edit Question

`; print qq``; } print qq`
`; foreach (@types) { print qq` $_   Computational

Category:

Question:

Answer:


(Comment/request for attention. Leave blank if none.)
`; $query->finish(); } } sub gui_list { print qq`

List Questions

Containing exact phrase:    in

per page, ordered by   ↑   

`; if ($isadmin) {print qq`

By users (separated by spaces):    or view all users

View questions in rounds

`}; gui_search; } sub gui_list_table { my $questionquery = shift; my @questions = @$questionquery; $isadmin ? print "IDTypeCategoryQuestionAnswerOwner" : print "TypeCategoryQuestionAnswer"; foreach (@questions) { my $question = substr(@$_[3],0,50); $question .= "..." if length(@$_[3]) > 50; my $answer = substr(@$_[4],0,50); $answer .= "..." if length(@$_[4]) > 50; $question =~ s/>/>/g; $question =~ s/@$_[0]@$_[1]@$_[2]$question$answer@$_[5]` : print qq`">@$_[1]@$_[2]$question$answer`; } } sub search { my ($search,$querystring,$ownerstring,$setstring,$orderstring,$limitstring,$infields,$squery); my ($allresults,$perpage,$page,$offset); my %get = parseget; if ($pagename =~ /\?q\=/) { $querystring = " FROM $dtbq WHERE owner='$username'"; $limitstring = " LIMIT 0,25"; } else { $squery = $dbh->quote($get{"q"}) if exists $get{"q"}; $squery =~ s/^'//; $squery =~ s/'$//; $perpage = (exists $get{"num"} ? $get{"num"} : 25); $perpage =~ s/[^\d]//g; $page = (exists $get{"pg"} ? $get{"pg"} : 1); $page =~ s/[^\d]//g; $offset = ($page - 1) * $perpage; $infields = (exists $get{"in"} ? $get{"in"} : "qa"); if (exists $get{"of"} and exists $get{"ob"}) { $orderstring = order_string($get{"of"} . $get{"ob"}); } else {$orderstring = "order by id desc";} $pagename =~ s/\&of+$//;} if ($squery eq "" && $infields ne "f") {$search = ""} else { if ($infields eq "qa") {$search="(question regexp '$squery' or answer regexp '$squery') and"} elsif ($infields eq "q") {$search="(question regexp '$squery') and"} elsif ($infields eq "a") {$search="(answer regexp '$squery') and"} elsif ($infields eq "c") {$search="(category regexp '$squery') and"} elsif ($infields eq "f") {$search="(comment != '') and"}} if ($isadmin and exists $get{"aua"} and $get{"aua"} eq "y") { $ownerstring = "1"; } elsif ($isadmin and exists $get{"au"} and $get{"au"} ne "") { my @searchusers = split / /, $get{"au"}; $ownerstring = "owner=''"; $ownerstring .= " or owner like '%%$_%%'" foreach @searchusers; } else {$ownerstring = "owner='$username'"} if ($isadmin and exists $get{"ar"} and $get{"ar"} eq "y") { $setstring = "and 1"; } else {$setstring = "and ((round is null) or (round=''))"} $querystring = " FROM $dtbq WHERE $search $ownerstring $setstring $orderstring"; $limitstring = " LIMIT $offset,$perpage"; $query = $dbh->prepare("SELECT COUNT(*)" . $querystring); $query->execute(); $allresults = $query->fetchrow_array(); $query->finish(); return ("SELECT *" . $querystring . $limitstring, $offset + 1, $allresults, $perpage, $page); } sub gui_about { print qq`

About QED

Welcome to QED, Question Editing Database. QED is a full-featured quizbowl question management program designed to allow users secure access to a question database. It allows users to input and edit questions, and collects them to be compiled into packets.

Running QED requires a web server with Perl/CGI capabilities, the Sendmail MTA (or an SMTP server), as well as MySQL to store the databases. If you are interested in running QED but do not have access to these resources, Aegis Questions is offering to host your QED installation; contact Carlo at the address below. Using QED requires only a modern browser with JavaScript and CSS. (Firefox recommended.)

Questions? Comments? Contact Carlo Angiuli at carlo\@aegisquestions.com. QED is free to use, and you may modify the source code as long as the original name and author are maintained. QED is ©2006-2007 Carlo Angiuli.

Note: QED uses a JavaScript implementation of the RSA Data Security, Inc. MD5 Message-Digest Algorithm. JavaScript MD5 is released under the BSD License and is copyright ©1998 - 2002, Paul Johnston & Contributors, all rights reserved. You can find it at http://pajhome.org.uk/crypt/md5.

`; } sub gui_delete { $pagename =~ /^delete=(\d+)/; my $id = $1; print qq` Confirm Delete

Are you sure you want to delete this question permanently?

`; } sub gui_delete_done { my $id = $dbh->quote($cgi->param("id")); $dbh->do("DELETE FROM $dtbq WHERE id=$id"); $dbh->do("ALTER TABLE $dtbq AUTO_INCREMENT=1"); print qq` Question Deleted

Deleted.

`; } sub gui_search { my $orderstring = order_string($pagename); my ($search,$first,$allresults,$perpage,$page) = search; $query = $dbh->prepare($search); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; my $last = @questions + $first - 1; my $pages = ceiling($allresults / $perpage); my $pg = $ENV{'QUERY_STRING'}; $pg =~ s/&?pg=(\d*)//; print qq`\n

Page:   `; print ($_ == $page ? qq`  $_  ` : qq`  $_  `) foreach (1..$pages); print qq`

Questions $first - $last of $allresults

`; if ($pagename =~ /^list/) {gui_list_table($questionquery); } elsif ($pagename =~ /^round-acquire/) {round_gui_acquire_table($questionquery);} $query->finish(); print qq`

Page:   `; print ($_ == $page ? qq`  $_  ` : qq`  $_  `) foreach (1..$pages); print qq`

`; } ########################## ####### Admin Code ####### ########################## sub admin_backup { use MIME::Lite; use Net::SMTP; open BACKUP, "> backup.csv"; print BACKUP "id,type,category,question,answer,owner,round,comment\n"; $query = $dbh->prepare("SELECT * FROM $dtbq"); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; foreach (@questions) { my $question = @$_[3]; my $answer = @$_[4]; my $comment = @$_[7]; $question =~ s/\r\n/\\r\\n/g; $answer =~ s/\r\n/\\r\\n/g; $comment =~ s/\r\n/\\r\\n/g; $question =~ s/"/""/g; $answer =~ s/"/""/g; $comment =~ s/"/""/g; print BACKUP qq`@$_[0],@$_[1],@$_[2],"$question","$answer",@$_[5],@$_[6],"$comment"\n`; } $query->finish(); close BACKUP; my @time = localtime(time); my $datestr = ($time[5]+1900) . "-" . ($time[4]+1) . "-" . $time[3]; my $msg = MIME::Lite->new ( From => $mail_from, To => $cgi->param("backup-email"), Subject => "QED Database Backup", Type => "multipart/mixed"); $msg->attach ( Type => "TEXT", Data => "As requested, here is a CSV backup of your QED question database.\n\nEmailed from QED $prgm_version."); $msg->attach ( Type => "text/plain", Path => "backup.csv", Filename => "$datestr.csv", Disposition => "attachment"); #I use sendmail. If you need an SMTP server instead, uncomment the next line. #MIME::Lite->send("smtp", "mail.yourmailserver.com", Timeout => 60, AuthUser => "username", AuthPass => "password"); $msg->send; unlink "backup.csv"; } sub admin_gui_config { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbu"); $query->execute(); my $usernum = $query->fetchrow_array(); $query->finish(); $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq"); $query->execute(); my $questionnum = $query->fetchrow_array(); $query->finish(); print qq`

Administrator Panel

Back Up Database

Email address:

QED Statistics

Users: $usernum
Questions: $questionnum

QED Settings

\$mail_from = $mail_from
\$org_name = $org_name
\$mysql_database = $mysql_database
\$mysql_host = $mysql_host
\$mysql_port = $mysql_port
\$mysql_user = $mysql_user
\$mysql_pass = (hidden)
\$dtbu = $dtbu
\$dtbq = $dtbq
\@types = (`; print "$_, " foreach (@types[0..@types-2]); print @types[@types-1] . ")
"; print qq`\@categories = (`; print "$_, " foreach (@categories[0..@categories-2]); print @categories[@categories-1] . ")"; } sub admin_gui_users { #Run admin-action commands from admin-users screen before displaying page if ($cgi->param("admin-action") eq "Create User") { my $newuser = $dbh->quote($cgi->param("newuser")); my $newpass = $dbh->quote(md5_hex($cgi->param("newpass"))); $query = $dbh->do("INSERT INTO $dtbu (user, password) VALUES($newuser, $newpass)"); } elsif ($cgi->param("admin-action") eq "Delete User(s)") { foreach ($cgi->param()) { if ($cgi->param($_) eq "on" && $_ ne "newuser" && $_ ne "newpass") { my $deleteuser = $dbh->quote($_); $query = $dbh->do("DELETE FROM $dtbu WHERE user=$deleteuser"); } } } elsif ($cgi->param("admin-action") eq "Grant Admin Privileges") { foreach ($cgi->param()) { if ($cgi->param($_) eq "on" && $_ ne "newuser" && $_ ne "newpass") { my $adminuser = $dbh->quote($_); $query = $dbh->do("UPDATE $dtbu SET flags='1' WHERE user=$adminuser AND (NOT flags='2')"); } } } elsif ($cgi->param("admin-action") eq "Revoke Admin Privileges") { foreach ($cgi->param()) { if ($cgi->param($_) eq "on" && $_ ne "newuser" && $_ ne "newpass") { my $notadminuser = $dbh->quote($_); $query = $dbh->do("UPDATE $dtbu SET flags='0' WHERE user=$notadminuser AND (NOT flags='2')"); } } } $query = $dbh->prepare("SELECT * FROM $dtbu"); $query->execute(); my $userquery = $query->fetchall_arrayref([]); my @users = @$userquery; print qq`

Manage Users

`; foreach (@users) { print qq``} else {print qq``} } $query->finish(); print qq`
 user 
`; if (@$_[2] == 0) {print @$_[0]} elsif (@$_[2] == 1) {print qq`@$_[0] (Admin)`} elsif (@$_[2] == 2) {print qq`@$_[0] (Question Set)`} unless (@$_[2] == 2) {print qq`Log in
 


User: Pass:

`; } sub admin_gui_stats { $query = $dbh->prepare("SELECT * FROM $dtbu WHERE flags <> 2"); $query->execute(); my $userquery = $query->fetchall_arrayref([]); my @users = @$userquery; $query->finish(); my ($usrname,$usrquery); if ($ENV{'QUERY_STRING'} eq "") { $usrname = "All Users"; $usrquery = "1";} else { $usrname = $ENV{'QUERY_STRING'}; $usrquery = "owner = '$usrname'";} print qq`

View Stats: $usrname

`; print "" foreach (@types); print ""; foreach (@categories) { my $thiscat = $_; print ""; foreach (@types) { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE category like '%%$thiscat%%' AND type='$_' AND $usrquery"); $query->execute(); print ""; $query->finish(); } print ""; } print qq``; foreach (@types) { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE type='$_' AND $usrquery"); $query->execute(); print ""; $query->finish(); } print qq`
Category $_  
$_  " . $query->fetchrow_array() . "
Total: " . $query->fetchrow_array() . "
View stats for:
`; } sub admin_gui_rounds { if ($cgi->param("admin-action") eq "Create Set") { my $newuser = $dbh->quote($cgi->param("newuser")); $query = $dbh->do("INSERT INTO $dtbu (user, password, flags) VALUES($newuser, '0', '2')"); } $query = $dbh->prepare("SELECT * FROM $dtbu WHERE flags='2'"); $query->execute(); my $userquery = $query->fetchall_arrayref([]); my @users = @$userquery; print qq`

Manage Rounds

`; foreach (@users) { my $color = (@$_[0] eq $round ? ' style="background-color:#bce9bf;font-weight:bold"' : ""); print qq``; } print qq``; print qq`
Round 
@$_[0]Edit
(none)Edit

Set Name:

`; } ########################## ####### Round Code ####### ########################## sub round_gui_options { if ($cgi->param("roundoptions")) { if ($cgi->param("acquireids") ne "") {round_acquire(split / /, $cgi->param("acquireids"));} if ($cgi->param("unacquireids") ne "") {round_unacquire(split / /, $cgi->param("unacquireids"));} } print qq`

Advanced Options for “$round”

Mass acquire questions by ID, separated by spaces:

Unacquire questions by ID, separated by spaces:

`; } sub round_gui_acquire { my $querystring = $ENV{'QUERY_STRING'}; print qq`

Acquire Questions for “$round”

Containing exact phrase:    in

per page, ordered by   ↑   

By users (separated by spaces):    or view all users

View questions in rounds

`; print qq`
`; gui_search; print qq`
`; } sub round_gui_acquire_table { my $questionquery = shift; my @questions = @$questionquery; print " IDTypeCategoryQuestionAnswerOwner"; foreach (@questions) { my $question = substr(@$_[3],0,50); $question .= "..." if length(@$_[3]) > 50; my $answer = substr(@$_[4],0,50); $answer .= "..." if length(@$_[4]) > 50; $question =~ s/>/>/g; $question =~ s/@$_[0]@$_[1]@$_[2]$question$answer@$_[5]`; } } sub round_acquire { $dbh->do("UPDATE $dtbq SET round=CONCAT(round,'|$round-00/00-') WHERE (id='$_') AND NOT (round like '%%|$round-%%')") foreach (@_); } sub round_unacquire { $dbh->do("UPDATE $dtbq SET round=CONCAT(MID(round,1,LOCATE('|$round-',round)-1),MID(round,LOCATE('|$round-',round)+LENGTH('$round')+8)) WHERE (id='$_') AND (round like '%%|$round-%%')") foreach (@_); } sub round_gui_organize { if ($cgi->param("roundorganize") ne "") {round_organize;} $query = $dbh->prepare("SELECT password FROM $dtbu WHERE user='$round'"); $query->execute(); my $roundnum = $query->fetchrow_array(); $query->finish(); if ($cgi->param("organizeminus") ne "") { my $roundnumzp = ($roundnum<10 ? "0$roundnum" : "$roundnum"); $roundnum--; $dbh->do("UPDATE $dtbu SET password ='$roundnum' WHERE user='$round'"); $dbh->do("UPDATE $dtbq SET round=CONCAT(MID(round,1,LOCATE('|$round-',round)-1),'|$round-00/00-',MID(round,LOCATE('|$round-',round)+LENGTH('$round')+8)) WHERE (round like '%%|$round-$roundnumzp/%%')"); } if ($cgi->param("organizeplus") ne "" or $roundnum == 0) { $roundnum++ unless $roundnum == 99; $dbh->do("UPDATE $dtbu SET password ='$roundnum' WHERE user='$round'") } $ENV{'QUERY_STRING'} =~ /^(\d{1,2})/; my $lround = (($1 > 0 and $roundnum <= $1) ? $1 : 0); my $lroundzp = ($lround<10 ? "0$lround" : "$lround"); print qq`

Organize “$round” Into Packets

$round has $roundnum packet(s).  

» Questions in `; print ($lround == 0 ? "Unassigned

" : "Packet $lround"); $query = $dbh->prepare("SELECT * FROM $dtbq WHERE round LIKE '%%|$round-$lroundzp/%%'"); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; print qq``; foreach (@questions) { my $answer = substr(@$_[4],0,50); $answer .= "..." if length(@$_[4]) > 50; $answer =~ s/>/>/g; $answer =~ s/`; } print qq`
 IDTypeCategoryAnswerOwner
@$_[0]@$_[1] @$_[2] $answer @$_[5]

» Move to / View

`; foreach (0..$roundnum) { my $roundnumzp = ($_<10 ? "0$_" : "$_"); $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE round LIKE '%%|$round-$roundnumzp/%%'"); $query->execute(); my $numin = $query->fetchrow_array(); print qq`

` . ($_ == 0 ? "Unassigned" : "Packet $_") . qq`($numin)

`; } print qq`
`; } sub round_organize { my $intoround = $cgi->param("intoround"); $cgi->delete("roundorganize"); $cgi->delete("intoround"); my $intoroundzp = ($_<10 ? "0$intoround" : "$intoround"); foreach ($cgi->param()) { $dbh->do("UPDATE $dtbq SET round=CONCAT(MID(round,1,LOCATE('|$round-',round)-1),'|$round-$intoroundzp/00-',MID(round,LOCATE('|$round-',round)+LENGTH('$round')+8)) WHERE id like '$_' and not round like '%%|$round-$intoroundzp/%%'"); } } sub round_gui_order { if ($cgi->param("roundorder") ne "") {round_order;} $query = $dbh->prepare("SELECT password FROM $dtbu WHERE user='$round'"); $query->execute(); my $roundnum = $query->fetchrow_array(); $query->finish(); $ENV{'QUERY_STRING'} =~ /^(\d{1,2})/; my $thisround = ($1 > 0 ? $1 : 1); my $thisroundzp = ($thisround<10 ? "0$thisround" : "$thisround"); print qq`

Order Packet $thisround

`; print qq`

Choose packet: `; print qq`$_ - ` foreach (1..$roundnum-1); print qq`$roundnum`; print qq`

The next ID you click on will be assigned number:

`; $query = $dbh->prepare("SELECT * FROM $dtbq WHERE round LIKE '%%|$round-$thisroundzp/%%' ORDER BY MID(round,LOCATE('|$round-',round)+LENGTH('$round')+5,2) ASC"); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; print qq``; foreach (@questions) { my $answer = substr(@$_[4],0,50); $answer .= "..." if length(@$_[4]) > 50; $answer =~ s/>/>/g; $answer =~ s/`; } print qq`
#IDTypeCategoryAnswerOwner
@$_[0] @$_[1] @$_[2] $answer @$_[5]
`; } sub round_order { $ENV{'QUERY_STRING'} =~ /^(\d{1,2})/; my $thisround = ($1 > 0 ? $1 : 1); my $thisroundzp = ($thisround<10 ? "0$thisround" : "$thisround"); $cgi->delete("roundorder"); foreach ($cgi->param()) { my $num = $cgi->param($_); $num = 0 if $num eq ""; my $numzp = ($num<10 ? "0$num" : "$num"); $dbh->do("UPDATE $dtbq SET round=CONCAT(MID(round,1,LOCATE('|$round-',round)+LENGTH('$round')+4),'$numzp-',MID(round,LOCATE('|$round-',round)+LENGTH('$round')+8)) WHERE id='$_'"); } } sub round_gui_stats { print qq`

Stats for “$round”

`; $query = $dbh->prepare("SELECT * FROM $dtbq WHERE round LIKE '%%|$round-%%'"); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; $query = $dbh->prepare("SELECT password FROM $dtbu WHERE user='$round'"); $query->execute(); my $roundnum = $query->fetchrow_array(); $query->finish(); print qq`

View stats for: Whole Set, Unassigned, or Packet `; print qq`$_  ` foreach (1..$roundnum); print "

"; my ($roundquery, $roundname); if ($ENV{'QUERY_STRING'} eq "") { $roundquery = "round LIKE '%%|$round-%%'"; $roundname = "Whole Set";} else { $ENV{'QUERY_STRING'} =~ /^(\d{1,2})/; my $thisround = (($1 > 0 and $roundnum <= $1) ? $1 : 0); my $thisroundzp = ($thisround<10 ? "0$thisround" : "$thisround"); $roundquery = "round LIKE '%%|$round-$thisroundzp/%%'"; $roundname = ($thisround == 0 ? "Unassigned" : "Packet $thisround"); } print qq`

$roundname

`; print "" foreach (@types); print ""; foreach (@categories) { my $thiscat = $_; print ""; foreach (@types) { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE category like '%%$thiscat%%' AND type='$_' AND $roundquery"); $query->execute(); print ""; $query->finish(); } print ""; } print qq``; foreach (@types) { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE type='$_' AND $roundquery"); $query->execute(); print ""; $query->finish(); } print qq`
Category $_  
$_  " . $query->fetchrow_array() . "
Total: " . $query->fetchrow_array() . "
`; $query = $dbh->prepare("SELECT * FROM $dtbu WHERE (NOT flags='2')"); $query->execute(); my $userquery = $query->fetchall_arrayref([]); my @userq = @$userquery; my @users; push @users, @$_[0] foreach @userq; $query->finish(); print qq``; print "" foreach (@types); print ""; foreach (@users) { my $thisauthor = $_; print ""; foreach (@types) { $query = $dbh->prepare("SELECT COUNT(*) FROM $dtbq WHERE type='$_' AND owner='$thisauthor' AND $roundquery"); $query->execute(); print ""; $query->finish(); } print ""; } print qq`
Author $_  
$_  " . $query->fetchrow_array() . "
`; } sub round_gui_output { print "

Output Packets for “$round”

"; $query = $dbh->prepare("SELECT password FROM $dtbu WHERE user='$round'"); $query->execute(); my $roundnum = $query->fetchrow_array(); $query->finish(); print qq`

Output packet: `; print qq`$_  ` foreach (1..$roundnum); print "

"; } sub round_output { my $packet = shift; my $packetzp = ($packet<10 ? "0$packet" : "$packet"); print qq` QED $prgm_version | Set $round, Packet $packet `; $query = $dbh->prepare("SELECT * FROM $dtbq WHERE round LIKE '%%|$round-$packetzp/%%' AND NOT (round LIKE '%%|$round-$packetzp/00-%%') ORDER BY MID(round,LOCATE('|$round-',round)+LENGTH('$round')+5,2) ASC"); $query->execute(); my $questionquery = $query->fetchall_arrayref([]); my @questions = @$questionquery; foreach (@questions) { @$_[6] =~ /\|$round\-$packetzp\/(\d\d)\-/; output_question($1,@$_[1],@$_[2],@$_[3],@$_[4]); } print qq``; exit; } ########################### ##### Misc. Functions ##### ########################### sub order_string { my $string = shift; my $orderstring; if ($string =~ m/([0-9])([a-z])$/) { $orderstring = "ORDER BY "; if ($1 == 0) {$orderstring .= "id";} elsif ($1 == 1) {$orderstring .= "type";} elsif ($1 == 2) {$orderstring .= "category";} elsif ($1 == 3) {$orderstring .= "question";} elsif ($1 == 4) {$orderstring .= "answer";} elsif ($1 == 5) {$orderstring .= "owner";} if ($2 eq "d") {$orderstring .= " DESC";} elsif ($2 eq "a") {$orderstring .= " ASC";} } return $orderstring; } sub rtrim { my $string = shift; $string =~ s/\s+$//; return $string; } sub ceiling { my $num = shift; return ($num == int($num) ? $num : int($num) + 1); } sub parseget { my %formdata; my @getpairs = split(/&/,$ENV{'QUERY_STRING'}); #read (STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); #my @postpairs = split(/&/,$buffer); #my @pairs = (@getpairs, @postpairs); foreach (@getpairs) { my ($key, $value) = split /=/; $key =~ tr/+/ /; $key =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; if ($formdata{$key}) { $formdata{$key} .= ", $value"; } else { $formdata{$key} = $value; } } return %formdata; } ########################### ####### Output Code ####### ########################### sub output_question { my @question = @_; # @question[0..4] has format: position, type, category, question, answer. #Here's some parsing that should change depending on format and taste. #I'm simply translating markup, not checking whether it's right. #Convert < and > and & into HTML entities. $question[3] =~ s/>/>/g; $question[3] =~ s//>/g; $question[4] =~ s/(/g; $question[3] =~ s/\]/)<\/em>/g; $question[4] =~ s/\[/<\/strong>(/g; $question[4] =~ s/\]/)<\/em>/g; #Make new lines in the question into line breaks. Preface each one after the first #with A:, B:, C:, D:, etc. my @alphabet = qw(A B C D E); my @qparts = split /\n/, $question[3]; $qparts[$_] = "
\n" . $alphabet[$_-1] . ": " . $qparts[$_] for (1..$#qparts); $question[3] = join "", @qparts if $question[1] eq "Bonus"; #Same with the answer, but without line breaks. my @aparts = split /\n/, $question[4]; $aparts[$_] ne "" ? $aparts[$_] = "
" . $alphabet[$_] . ": " . $aparts[$_] : "" for (0..@aparts); $question[4] = join "", @aparts if $question[1] eq "Bonus"; #If it's a bonus answer that starts with a parenthetical, put it before the A:, not after. $question[4] =~ s/^<\/strong> A: <\/strong>\(([\w\W]+?)\)<\/em>/<\/strong>\($1\)<\/em> A: /; #Make _ and _ start and end bold underlining in the answer. $question[4] =~ s/_([^_]+)_/$1<\/span>/g; #If it's a bonus and it has three or five parts, say so. my $isnotfour; $isnotfour = " -- Three Parts" if $question[1] eq "Bonus" && @aparts == 3; $isnotfour = " -- Five Parts" if $question[1] eq "Bonus" && @aparts == 5; #If it's a tossup and it's computational, say so. my $iscomp; if ($question[1] eq "Tossup" and $question[2] =~ / \(Comp\)/) { $category[1] =~ s/ \(Comp\)//; $iscomp = " -- Computational (30 Seconds)";} print qq`

$question[1] $pairnumber: $category[0] ($category[1])$isnotfour$iscomp
$question[3]
Answer${isbonus}: $question[4]

`; }