Parent Directory
|
Revision Log
Revision 2778 - (view) (download) (as text)
| 1 : | gage | 2240 | ################################################################################ |
| 2 : | # WeBWorK Online Homework Delivery System | ||
| 3 : | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ | ||
| 4 : | sh002i | 2778 | # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/Preflight.pm,v 1.3 2004/06/14 20:58:17 toenail Exp $ |
| 5 : | gage | 2240 | # |
| 6 : | # This program is free software; you can redistribute it and/or modify it under | ||
| 7 : | # the terms of either: (a) the GNU General Public License as published by the | ||
| 8 : | # Free Software Foundation; either version 2, or (at your option) any later | ||
| 9 : | # version, or (b) the "Artistic License" which comes with this package. | ||
| 10 : | # | ||
| 11 : | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 : | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 13 : | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the | ||
| 14 : | # Artistic License for more details. | ||
| 15 : | ################################################################################ | ||
| 16 : | |||
| 17 : | package WeBWorK::ContentGenerator::Instructor::Preflight; | ||
| 18 : | use base qw(WeBWorK::ContentGenerator::Instructor); | ||
| 19 : | |||
| 20 : | =head1 NAME | ||
| 21 : | |||
| 22 : | WeBWorK::ContentGenerator::Instructor::Preflight.pm -- display past answers of many students | ||
| 23 : | |||
| 24 : | =cut | ||
| 25 : | |||
| 26 : | use strict; | ||
| 27 : | use warnings; | ||
| 28 : | use CGI qw(); | ||
| 29 : | toenail | 2305 | use WeBWorK::HTML::OptionList qw/optionList/; |
| 30 : | use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/; | ||
| 31 : | gage | 2240 | |
| 32 : | sub initialize { | ||
| 33 : | my $self = shift; | ||
| 34 : | my $r = $self->r; | ||
| 35 : | my $urlpath = $r->urlpath; | ||
| 36 : | my $db = $r->db; | ||
| 37 : | my $ce = $r->ce; | ||
| 38 : | my $authz = $r->authz; | ||
| 39 : | my $courseName = $urlpath->arg("courseID"); | ||
| 40 : | my $user = $r->param('user'); | ||
| 41 : | |||
| 42 : | toenail | 2305 | # Check permissions |
| 43 : | return unless ($authz->hasPermissions($user, "access_instructor_tools")); | ||
| 44 : | gage | 2240 | } |
| 45 : | |||
| 46 : | |||
| 47 : | sub body { | ||
| 48 : | my $self = shift; | ||
| 49 : | my $r = $self->r; | ||
| 50 : | my $urlpath = $r->urlpath; | ||
| 51 : | my $db = $r->db; | ||
| 52 : | my $ce = $r->ce; | ||
| 53 : | my $authz = $r->authz; | ||
| 54 : | my $root = $ce->{webworkURLs}->{root}; | ||
| 55 : | my $courseName = $urlpath->arg('courseID'); | ||
| 56 : | toenail | 2305 | my $setName = $r->param('setID') || ""; # these are passed in the search args in this case |
| 57 : | my $problemNumber = $r->param('problemID') || ""; | ||
| 58 : | if ($problemNumber =~ /\!(\d+)/) { $problemNumber = $1 }; | ||
| 59 : | gage | 2240 | my $user = $r->param('user'); |
| 60 : | my $key = $r->param('key'); | ||
| 61 : | toenail | 2305 | my $studentUser = $r->param('studentUser') || ""; |
| 62 : | gage | 2240 | |
| 63 : | toenail | 2305 | # Check permissions |
| 64 : | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") | ||
| 65 : | unless $authz->hasPermissions($r->param("user"), "access_instructor_tools"); | ||
| 66 : | |||
| 67 : | gage | 2240 | |
| 68 : | my $showAnswersPage = $urlpath->newFromModule($urlpath->module, courseID => $courseName); | ||
| 69 : | my $showAnswersURL = $self->systemLink($showAnswersPage,authen => 0 ); | ||
| 70 : | |||
| 71 : | toenail | 2248 | my ($safeUser, $safeCourse) = (showHTML($studentUser), showHTML($courseName)); |
| 72 : | my ($safeSet, $safeProb) = (showHTML($setName), showHTML($problemNumber)); | ||
| 73 : | toenail | 2305 | |
| 74 : | my @defaultOrder = qw(user_id set_id problem_id date answers); | ||
| 75 : | |||
| 76 : | my %prettyFieldNames; | ||
| 77 : | gage | 2240 | |
| 78 : | toenail | 2305 | @prettyFieldNames{qw( |
| 79 : | user_id | ||
| 80 : | set_id | ||
| 81 : | problem_id | ||
| 82 : | date | ||
| 83 : | answers | ||
| 84 : | )} = ( | ||
| 85 : | "User ID", | ||
| 86 : | "Set Name", | ||
| 87 : | "Problem Number", | ||
| 88 : | "Date", | ||
| 89 : | "Answers", | ||
| 90 : | ); | ||
| 91 : | $prettyFieldNames{nofield} = ""; | ||
| 92 : | |||
| 93 : | gage | 2240 | ##################################################################### |
| 94 : | # print form | ||
| 95 : | ##################################################################### | ||
| 96 : | |||
| 97 : | toenail | 2305 | my @userIDs = grep /\w/, sort $db->listUsers(); |
| 98 : | my @Users = $db->getUsers(@userIDs); | ||
| 99 : | my %users = map { $_ => $db->getUser($_)->first_name } @userIDs; | ||
| 100 : | my @setIDs = sort $db->listGlobalSets(); | ||
| 101 : | my @GlobalSets = $db->getGlobalSets(@setIDs); | ||
| 102 : | my @GlobalProblems = $db->getAllGlobalProblems($setName); | ||
| 103 : | |||
| 104 : | my $scrolling_user_list = scrollingRecordList({ | ||
| 105 : | name => "studentUser", | ||
| 106 : | request => $r, | ||
| 107 : | default_sort => "lnfn", | ||
| 108 : | default_format => "lnfn_uid", | ||
| 109 : | default_filters => ["all"], | ||
| 110 : | default => "Select one or more users: ", | ||
| 111 : | # hide_sort => 1, | ||
| 112 : | # hide_format => 1, | ||
| 113 : | # hide_filter => 1, | ||
| 114 : | size => 10, | ||
| 115 : | # multiple => 0, | ||
| 116 : | }, @Users); | ||
| 117 : | |||
| 118 : | my $scrolling_set_list = scrollingRecordList({ | ||
| 119 : | name => "setID", | ||
| 120 : | request => $r, | ||
| 121 : | default_sort => "set_id", | ||
| 122 : | default_format => "set_id", | ||
| 123 : | default_filters => ["all"], | ||
| 124 : | default => "Select one or more sets: ", | ||
| 125 : | # hide_sort => 1, | ||
| 126 : | # hide_format => 1, | ||
| 127 : | # hide_filter => 1, | ||
| 128 : | size => 10, | ||
| 129 : | # multiple => 0, | ||
| 130 : | }, @GlobalSets); | ||
| 131 : | |||
| 132 : | my $scrolling_problem_list = scrollingRecordList({ | ||
| 133 : | name => "problemID", | ||
| 134 : | request => $r, | ||
| 135 : | default => "Select one or more problems: ", | ||
| 136 : | default_filters => ["all"], | ||
| 137 : | # hide_sort => 1, | ||
| 138 : | # hide_format => 1, | ||
| 139 : | # hide_filter => 1, | ||
| 140 : | size => 10, | ||
| 141 : | # multiple => 0, | ||
| 142 : | }, @GlobalProblems); | ||
| 143 : | |||
| 144 : | my @selected_fields = $r->param("selected_fields"); | ||
| 145 : | my @selected_answers = $r->param("selected_answers"); | ||
| 146 : | |||
| 147 : | toenail | 2248 | print join ("", |
| 148 : | CGI::br(), | ||
| 149 : | "\n\n", | ||
| 150 : | CGI::hr(), | ||
| 151 : | toenail | 2305 | CGI::start_form( |
| 152 : | -method => "post", | ||
| 153 : | -action => $showAnswersURL, | ||
| 154 : | -target => 'information', | ||
| 155 : | toenail | 2248 | ), |
| 156 : | toenail | 2305 | CGI::start_table( |
| 157 : | -border => "0", | ||
| 158 : | -cellpadding => "0", | ||
| 159 : | -cellspacing => "0", | ||
| 160 : | toenail | 2248 | ), |
| 161 : | toenail | 2305 | CGI::Tr( |
| 162 : | CGI::td({style=>"width:33%"}, $scrolling_user_list), | ||
| 163 : | CGI::td({style=>"width:33%"}, $scrolling_set_list), | ||
| 164 : | CGI::td({style=>"width:33%"}, $scrolling_problem_list), | ||
| 165 : | toenail | 2248 | ), |
| 166 : | toenail | 2305 | CGI::Tr({}, |
| 167 : | CGI::submit( | ||
| 168 : | -name => 'action', | ||
| 169 : | -value => 'Past Answers for', | ||
| 170 : | ), "\n", | ||
| 171 : | # $self->hidden_authen_fields, | ||
| 172 : | # " \n User: ", | ||
| 173 : | # CGI::textfield( | ||
| 174 : | # -name => 'studentUser1', | ||
| 175 : | # -value => $safeUser, | ||
| 176 : | # -size => 10, | ||
| 177 : | # ), | ||
| 178 : | # " \n Set: ", | ||
| 179 : | # CGI::textfield( | ||
| 180 : | # -name => 'setID1', | ||
| 181 : | # -value => $safeSet, | ||
| 182 : | # -size => 10, | ||
| 183 : | # ), | ||
| 184 : | # " \n Problem: ", | ||
| 185 : | # CGI::textfield( | ||
| 186 : | # -name => 'problemID', | ||
| 187 : | # -value => $safeProb, | ||
| 188 : | # -size => 10, | ||
| 189 : | # ), | ||
| 190 : | # " \n", | ||
| 191 : | # CGI::br(),CGI::br(), | ||
| 192 : | toenail | 2248 | ), |
| 193 : | toenail | 2305 | # CGI::Tr({}, |
| 194 : | # CGI::popup_menu( | ||
| 195 : | # -name => 'studentUser', | ||
| 196 : | # -size => 10, | ||
| 197 : | # -values => \@userIDs, | ||
| 198 : | # -multiple => 1, | ||
| 199 : | # ), | ||
| 200 : | # CGI::popup_menu( | ||
| 201 : | # -name => 'setID', | ||
| 202 : | # -values => \@setIDs, | ||
| 203 : | # -size => 10, | ||
| 204 : | # -multiple => 1, | ||
| 205 : | # ), | ||
| 206 : | # ), | ||
| 207 : | CGI::Tr({}, | ||
| 208 : | CGI::td({}, | ||
| 209 : | "Select which fields to show: " . CGI::br(), | ||
| 210 : | CGI::scrolling_list( | ||
| 211 : | -name => "selected_fields", | ||
| 212 : | -values => \@defaultOrder, | ||
| 213 : | -labels => \%prettyFieldNames, | ||
| 214 : | -default => \@selected_fields, | ||
| 215 : | -size => 5, | ||
| 216 : | -multiple => 1, | ||
| 217 : | ), | ||
| 218 : | ), | ||
| 219 : | CGI::td({}, | ||
| 220 : | "and which answers to show: " . CGI::br(), | ||
| 221 : | CGI::scrolling_list( | ||
| 222 : | -name => "selected_answers", | ||
| 223 : | -values => [1..100], | ||
| 224 : | -default => \@selected_answers, | ||
| 225 : | -size => 5, | ||
| 226 : | -multiple => 1, | ||
| 227 : | ) | ||
| 228 : | ), | ||
| 229 : | toenail | 2248 | ), |
| 230 : | toenail | 2305 | CGI::end_table({}), |
| 231 : | CGI::end_form({}), | ||
| 232 : | toenail | 2248 | ); |
| 233 : | toenail | 2305 | |
| 234 : | ##################################################################### | ||
| 235 : | # create ordering system | ||
| 236 : | ##################################################################### | ||
| 237 : | toenail | 2248 | |
| 238 : | toenail | 2305 | # FIXME: We need a way to choose the order as well as the fields! |
| 239 : | my (@fieldOrder) = @selected_fields ? @selected_fields : @defaultOrder; | ||
| 240 : | |||
| 241 : | gage | 2240 | if (defined($setName) and defined($problemNumber) ) { |
| 242 : | ##################################################################### | ||
| 243 : | # print result table of answers | ||
| 244 : | ##################################################################### | ||
| 245 : | my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'}; | ||
| 246 : | |||
| 247 : | $studentUser = $r->param('studentUser') if ( defined($r->param('studentUser')) ); | ||
| 248 : | toenail | 2248 | my ($safeUser, $safeCourse) = (showHTML($studentUser), showHTML($courseName)); |
| 249 : | my ($safeSet, $safeProb) = (showHTML($setName), showHTML($problemNumber)); | ||
| 250 : | gage | 2240 | |
| 251 : | |||
| 252 : | toenail | 2248 | print CGI::h3( "Past Answers for " . ($safeUser ? "user $safeUser " : '' ) . ($safeSet ? "set $safeSet " : '' ) . ($safeSet and $safeProb ? ', ' : '') . ($safeProb ? "problem $safeProb" : '')); |
| 253 : | gage | 2240 | |
| 254 : | toenail | 2305 | $studentUser = "[^|]*" if (not defined $studentUser or $studentUser eq "" or $studentUser eq "*"); |
| 255 : | gage | 2240 | $setName = "[^|]*" if ($setName eq "" or $setName eq "*"); |
| 256 : | $problemNumber = "[^|]*" if ($problemNumber eq "" or $problemNumber eq "*"); | ||
| 257 : | toenail | 2248 | |
| 258 : | #my $pattern = "^[[^]]*]|[^|]*\\|$setName\\|$problemNumber\\|"; | ||
| 259 : | my $pattern = "\\|$studentUser\\|$setName\\|$problemNumber\\|"; | ||
| 260 : | gage | 2240 | |
| 261 : | toenail | 2248 | our ($lastdate, $lasttime, $lastID, $lastn); |
| 262 : | gage | 2240 | |
| 263 : | |||
| 264 : | if (open(LOG,"$answer_log")) { | ||
| 265 : | toenail | 2248 | my $line; |
| 266 : | local ($lastdate, $lasttime, $lastID, $lastn) = ("",0,"",0); | ||
| 267 : | $self->{lastdate} = ''; | ||
| 268 : | $self->{lasttime} = ''; | ||
| 269 : | $self->{lastID} = ''; | ||
| 270 : | $self->{lastn} = ''; | ||
| 271 : | gage | 2240 | |
| 272 : | toenail | 2248 | # get data from file |
| 273 : | |||
| 274 : | my @lines = grep(/$pattern/,<LOG>); close(LOG); | ||
| 275 : | toenail | 2305 | chomp(@lines); |
| 276 : | |||
| 277 : | my $maxcount = 0; | ||
| 278 : | foreach my $newline (@lines) { | ||
| 279 : | my @words = split /\t/, $newline; | ||
| 280 : | my $count = @words; | ||
| 281 : | $maxcount = $count if $count > $maxcount; | ||
| 282 : | } | ||
| 283 : | @selected_answers = (1..$maxcount) unless @selected_answers; | ||
| 284 : | |||
| 285 : | toenail | 2248 | # print "<CENTER>\n"; |
| 286 : | print CGI::start_table({ | ||
| 287 : | -border => "1", | ||
| 288 : | toenail | 2305 | # -cellpadding => '3', |
| 289 : | # -cellspacing => '0', | ||
| 290 : | -onload => "", | ||
| 291 : | toenail | 2248 | }) . "\n"; |
| 292 : | |||
| 293 : | my @tableHeaders; | ||
| 294 : | foreach (@fieldOrder) { | ||
| 295 : | push @tableHeaders, $prettyFieldNames{$_} unless $_ eq "answers"; | ||
| 296 : | } | ||
| 297 : | print CGI::Tr({}, CGI::th({}, \@tableHeaders) , CGI::th({-colspan => 200}, $prettyFieldNames{answers})); | ||
| 298 : | |||
| 299 : | toenail | 2305 | my @Records; |
| 300 : | ##################################################################### | ||
| 301 : | # create array of records | ||
| 302 : | ##################################################################### | ||
| 303 : | toenail | 2248 | foreach $line ( @lines ) { |
| 304 : | toenail | 2305 | my %fakeRecord = (); |
| 305 : | toenail | 2248 | #print CGI::br() . $line; |
| 306 : | toenail | 2305 | next if not $line =~ /\|(\w+)\|([\w\d_-]+)\|(\d+)\|\s*(\d+)(.*?)\t?$/; |
| 307 : | toenail | 2248 | $fakeRecord{user_id} = "$1"; |
| 308 : | $fakeRecord{set_id} = "$2"; | ||
| 309 : | $fakeRecord{problem_id} = "$3"; | ||
| 310 : | sh002i | 2778 | $fakeRecord{date} = $4; #$self->formatDateTime($4); |
| 311 : | toenail | 2248 | $fakeRecord{answers} = [ split "\t", "$5", -1 ] if $5; # the -1 stops split from dropping any trailing null fields |
| 312 : | toenail | 2305 | my @answers = map { $_ ? showHTML($_) : CGI::small(CGI::i("empty")) } @{ $fakeRecord{answers} }; |
| 313 : | shift @answers; # first field is always empty | ||
| 314 : | $fakeRecord{answers} = \@answers; | ||
| 315 : | |||
| 316 : | toenail | 2248 | |
| 317 : | my @tableCells; | ||
| 318 : | foreach (@fieldOrder) { | ||
| 319 : | toenail | 2305 | |
| 320 : | #push @tableCells, showHTML($fakeRecord{$_}); | ||
| 321 : | toenail | 2248 | } |
| 322 : | |||
| 323 : | toenail | 2305 | push @Records, \%fakeRecord; |
| 324 : | |||
| 325 : | #print join " ", map { "$_ = $fakeRecord{$_}" } keys %fakeRecord; | ||
| 326 : | #print CGI::br(); | ||
| 327 : | # print CGI::Tr({}, CGI::td({}, \@tableCells)); | ||
| 328 : | toenail | 2248 | |
| 329 : | #print $self->tableRow(split("\t",$line."\tx")); | ||
| 330 : | } | ||
| 331 : | toenail | 2305 | |
| 332 : | ##################################################################### | ||
| 333 : | # sort array of records | ||
| 334 : | ##################################################################### | ||
| 335 : | |||
| 336 : | @Records = sort byUSPD @Records; | ||
| 337 : | |||
| 338 : | ##################################################################### | ||
| 339 : | # print array of records | ||
| 340 : | ##################################################################### | ||
| 341 : | |||
| 342 : | foreach my $record (@Records) { | ||
| 343 : | my @tableCells; | ||
| 344 : | foreach (@fieldOrder) { | ||
| 345 : | if ($_ eq "answers") { | ||
| 346 : | my $i = 0; | ||
| 347 : | my %answers = map { ++$i => $_ } @{ $record->{$_} }; | ||
| 348 : | push @tableCells, @answers{@selected_answers}; | ||
| 349 : | } elsif ($_ eq "date") { | ||
| 350 : | my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $record->{$_}; | ||
| 351 : | $wday = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")[$wday]; | ||
| 352 : | $mon = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[$mon]; | ||
| 353 : | $year += 1900; | ||
| 354 : | my $ampm = ("am", "pm")[$hour > 12]; | ||
| 355 : | $hour = $hour % 12; | ||
| 356 : | push @tableCells, showHTML("$wday $mday $mon $year $hour:$min $ampm"); | ||
| 357 : | } else { | ||
| 358 : | push @tableCells, $record->{$_}; | ||
| 359 : | } | ||
| 360 : | } | ||
| 361 : | |||
| 362 : | print CGI::Tr({}, CGI::td({}, \@tableCells)); | ||
| 363 : | } | ||
| 364 : | |||
| 365 : | toenail | 2248 | # print a horizontal line |
| 366 : | #print CGI::Tr({}, CGI::td({colspan => $lastn}, CGI::hr({size => 3}))); | ||
| 367 : | print CGI::end_table({}); | ||
| 368 : | # print "\n</CENTER>\n\n"; | ||
| 369 : | print CGI::p( | ||
| 370 : | CGI::i("No entries for " . ($safeUser ? "user $safeUser " : '' ) . ($safeSet ? "set $safeSet " : '' ) . ($safeSet and $safeProb ? ', ' : '') . ($safeProb ? "problem $safeProb" : '')) | ||
| 371 : | ) unless @lines; | ||
| 372 : | |||
| 373 : | gage | 2240 | } else { |
| 374 : | toenail | 2248 | print CGI::em("Can't open the access log $answer_log"); |
| 375 : | gage | 2240 | } |
| 376 : | } | ||
| 377 : | |||
| 378 : | |||
| 379 : | return ""; | ||
| 380 : | } | ||
| 381 : | |||
| 382 : | sub tableRow { | ||
| 383 : | toenail | 2248 | my $self = shift; |
| 384 : | my $lastID = $self->{lastID}; | ||
| 385 : | my $lastn = $self->{lastn}; | ||
| 386 : | my $lasttime = $self->{lasttime}; | ||
| 387 : | my $lastdate = $self->{lastdate}; | ||
| 388 : | my ($out,$answer,$studentUser,$set,$prob) = ""; | ||
| 389 : | my ($ID,$rtime,@answers) = @_; pop(@answers); | ||
| 390 : | my $date = scalar(localtime($rtime)); $date =~ s/\s+/ /g; | ||
| 391 : | my ($day,$month,$mdate,$time,$year) = split(" ",$date); | ||
| 392 : | $date = "$mdate $month $year"; | ||
| 393 : | my $n = 2*(scalar(@answers)+1); | ||
| 394 : | gage | 2240 | |
| 395 : | toenail | 2248 | if ($lastID ne $ID) { |
| 396 : | if ($lastn) { | ||
| 397 : | print qq{<TR><TD COLSPAN="$lastn"><HR SIZE="3"></TD></TR>\n<P>\n\n}; | ||
| 398 : | print '<TABLE BORDER="0" CELLPADDING="0" CELLSPACING="3">',"\n"; | ||
| 399 : | } | ||
| 400 : | ($studentUser,$set,$prob) = (split('\|',$ID))[1,2,3]; | ||
| 401 : | $out .= qq{<TR ALIGN="CENTER"><TD COLSPAN="$n"><HR SIZE="3"> | ||
| 402 : | User: <B>$studentUser</B> | ||
| 403 : | Set: <B>$set</B> | ||
| 404 : | Problem: <B>$prob</B></TD></TR>\n}; | ||
| 405 : | $lastID = $ID; $lasttime = 0; $lastdate = ""; | ||
| 406 : | } | ||
| 407 : | gage | 2240 | |
| 408 : | toenail | 2248 | $out .= qq{<TR><TD COLSPAN="$n"><HR SIZE="1"></TD></TR>\n} |
| 409 : | if ($rtime - $lasttime > 30*60); | ||
| 410 : | $lasttime = $rtime; $lastn = $n; | ||
| 411 : | gage | 2240 | |
| 412 : | toenail | 2248 | if ($lastdate ne $date) { |
| 413 : | $out .= qq{<TR><TD COLSPAN="$n"><SMALL><I>$date</I></SMALL></TD></TR>\n}; | ||
| 414 : | $lastdate = $date; | ||
| 415 : | } | ||
| 416 : | gage | 2240 | |
| 417 : | toenail | 2248 | $out .= '<TR><TD WIDTH="10"></TD>'. |
| 418 : | '<TD><FONT COLOR="#808080"><SMALL>'.$time.'</SMALL></FONT></TD>'; | ||
| 419 : | foreach $answer (@answers) { | ||
| 420 : | $answer =~ s/(^\s+|\s+$)//g; | ||
| 421 : | $answer = showHTML($answer); | ||
| 422 : | $answer = "<SMALL><I>empty</I></SMALL>" if ($answer eq ""); | ||
| 423 : | $out .= qq{<TD WIDTH="20"></TD><TD NOWRAP>$answer</TD>}; | ||
| 424 : | } | ||
| 425 : | $out .= "</TR>\n"; | ||
| 426 : | $out; | ||
| 427 : | gage | 2240 | } |
| 428 : | |||
| 429 : | toenail | 2305 | ################################################################################ |
| 430 : | # sorts | ||
| 431 : | ################################################################################ | ||
| 432 : | |||
| 433 : | sub byUserID { $a->{user_id} cmp $b->{user_id} } | ||
| 434 : | sub bySetID { lc($a->{set_id}) cmp lc($b->{set_id}) } | ||
| 435 : | sub byProblemID { $a->{problem_id} <=> $b->{problem_id} } | ||
| 436 : | sub byDate { $a->{date} cmp $b->{date} } | ||
| 437 : | |||
| 438 : | sub byUSPD { &byUserID || &bySetID || &byProblemID || &byDate } | ||
| 439 : | |||
| 440 : | gage | 2240 | ################################################## |
| 441 : | # | ||
| 442 : | # Make HTML symbols printable | ||
| 443 : | # | ||
| 444 : | sub showHTML { | ||
| 445 : | toenail | 2248 | my $string = shift; |
| 446 : | toenail | 2305 | return '' unless defined $string; |
| 447 : | toenail | 2248 | $string =~ s/&/\&/g; |
| 448 : | $string =~ s/</\</g; | ||
| 449 : | $string =~ s/>/\>/g; | ||
| 450 : | $string =~ s/ | ||
| 451 : | $string =~ s/ / /g; | ||
| 452 : | return $string; | ||
| 453 : | gage | 2240 | } |
| 454 : | |||
| 455 : | 1; |
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |