[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / Scoring.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/Scoring.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2778 - (view) (download) (as text)

1 : malsyned 1386 ################################################################################
2 : sh002i 1663 # 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/Scoring.pm,v 1.35 2004/06/14 22:18:16 toenail Exp $
5 : sh002i 1663 #
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 : malsyned 1386 ################################################################################
16 :    
17 :     package WeBWorK::ContentGenerator::Instructor::Scoring;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 :     WeBWorK::ContentGenerator::Instructor::Scoring - Generate scoring data files
23 :    
24 :     =cut
25 :    
26 :     use strict;
27 :     use warnings;
28 :     use CGI qw();
29 : sh002i 2778 use WeBWorK::Utils qw(readFile);
30 : malsyned 1449 use WeBWorK::DB::Utils qw(initializeUserProblem);
31 : malsyned 1427 use WeBWorK::Timing;
32 : malsyned 1386
33 : malsyned 1413 sub initialize {
34 : gage 1609 my ($self) = @_;
35 : gage 1928 my $r = $self->r;
36 :     my $urlpath = $r->urlpath;
37 :     my $ce = $r->ce;
38 :     my $db = $r->db;
39 :     my $authz = $r->authz;
40 : malsyned 1413 my $scoringDir = $ce->{courseDirs}->{scoring};
41 : gage 1928 my $courseName = $urlpath->arg("courseID");
42 : gage 1609 my $user = $r->param('user');
43 : malsyned 1448
44 : toenail 2308 # Check permission
45 :     return unless $authz->hasPermissions($user, "access_instructor_tools");
46 :     return unless $authz->hasPermissions($user, "score_sets");
47 : gage 1609
48 : malsyned 1413 if (defined $r->param('scoreSelected')) {
49 : gage 1609 my @selected = $r->param('selectedSet');
50 :     my @totals = ();
51 :     my $recordSingleSetScores = $r->param('recordSingleSetScores');
52 :    
53 : toenail 2079 $self->addmessage(CGI::div({class=>'ResultsWithError'},"You must select one or more sets for scoring")) unless @selected;
54 : gage 2055
55 : sh002i 1675 # pre-fetch users
56 :     $WeBWorK::timer->continue("pre-fetching users") if defined($WeBWorK::timer);
57 :     my @Users = $db->getUsers($db->listUsers);
58 : sh002i 1680 my %Users;
59 : sh002i 1675 foreach my $User (@Users) {
60 :     next unless $User;
61 : sh002i 1680 $Users{$User->user_id} = $User;
62 : sh002i 1675 }
63 : sh002i 1680 my @sortedUserIDs = sort { $Users{$a}->student_id cmp $Users{$b}->student_id }
64 :     keys %Users;
65 :     my @userInfo = (\%Users, \@sortedUserIDs);
66 : sh002i 1675 $WeBWorK::timer->continue("done pre-fetching users") if defined($WeBWorK::timer);
67 : sh002i 1680
68 : gage 1609 my $scoringType = ($recordSingleSetScores) ?'everything':'totals';
69 :     my (@everything, @normal,@full,@info,@totalsColumn);
70 : gage 2055 @info = $self->scoreSet($selected[0], "info", undef, @userInfo) if defined($selected[0]);
71 :     @totals = @info;
72 :     my $showIndex = defined($r->param('includeIndex')) ? defined($r->param('includeIndex')) : 0;
73 : gage 1609
74 : malsyned 1413 foreach my $setID (@selected) {
75 : gage 2055 next unless defined $setID;
76 : gage 1609 if ($scoringType eq 'everything') {
77 : sh002i 1675 @everything = $self->scoreSet($setID, "everything", $showIndex, @userInfo);
78 : gage 1609 @normal = $self->everything2normal(@everything);
79 :     @full = $self->everything2full(@everything);
80 :     @info = $self->everything2info(@everything);
81 :     @totalsColumn = $self->everything2totals(@everything);
82 :     $self->appendColumns(\@totals, \@totalsColumn);
83 :     $self->writeCSV("$scoringDir/s${setID}scr.csv", @normal);
84 :     $self->writeCSV("$scoringDir/s${setID}ful.csv", @full);
85 :     } else {
86 : sh002i 1675 @totalsColumn = $self->scoreSet($setID, "totals", $showIndex, @userInfo);
87 : gage 1609 $self->appendColumns(\@totals, \@totalsColumn);
88 :     }
89 : malsyned 1413 }
90 : malsyned 1441 $self->writeCSV("$scoringDir/${courseName}_totals.csv", @totals);
91 : gage 1609 }
92 :    
93 :     # Obtaining list of sets:
94 : sh002i 1675 #$WeBWorK::timer->continue("Begin listing sets") if defined $WeBWorK::timer;
95 : gage 1609 my @setNames = $db->listGlobalSets();
96 : sh002i 1675 #$WeBWorK::timer->continue("End listing sets") if defined $WeBWorK::timer;
97 : gage 1609 my @set_records = ();
98 : sh002i 1675 #$WeBWorK::timer->continue("Begin obtaining sets") if defined $WeBWorK::timer;
99 : gage 1667 @set_records = $db->getGlobalSets( @setNames);
100 : sh002i 1675 #$WeBWorK::timer->continue("End obtaining sets: ".@set_records) if defined $WeBWorK::timer;
101 : gage 1609
102 :    
103 :     # store data
104 : sh002i 1675 $self->{ra_sets} = \@setNames; # ra_sets IS NEVER USED AGAIN!!!!!
105 : gage 1609 $self->{ra_set_records} = \@set_records;
106 : malsyned 1413 }
107 :    
108 : sh002i 1681
109 : malsyned 1448 sub body {
110 : gage 1928 my ($self) = @_;
111 :     my $r = $self->r;
112 :     my $urlpath = $r->urlpath;
113 :     my $ce = $r->ce;
114 :     my $authz = $r->authz;
115 :     my $scoringDir = $ce->{courseDirs}->{scoring};
116 :     my $courseName = $urlpath->arg("courseID");
117 :     my $user = $r->param('user');
118 : malsyned 1448
119 : gage 1938 my $scoringPage = $urlpath->newFromModule($urlpath->module, courseID => $courseName);
120 : gage 1947 my $scoringURL = $self->systemLink($scoringPage, authen=>0);
121 : gage 1609
122 : gage 1938 my $scoringDownloadPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ScoringDownload",
123 :     courseID => $courseName
124 :     );
125 : toenail 2308
126 :     # Check permissions
127 :     return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
128 :     unless $authz->hasPermissions($r->param("user"), "access_instructor_tools");
129 :    
130 :     return CGI::div({class=>"ResultsWithError"}, "You are not authorized to score sets.")
131 :     unless $authz->hasPermissions($r->param("user"), "score_sets");
132 :    
133 : gage 1609 print join("",
134 : gage 1938 CGI::start_form(-method=>"POST", -action=>$scoringURL),"\n",
135 : gage 1609 $self->hidden_authen_fields,"\n",
136 :     CGI::hidden({-name=>'scoreSelected', -value=>1}),
137 :     $self->popup_set_form,
138 :     CGI::br(),
139 :     CGI::checkbox({ -name=>'includeIndex',
140 :     -value=>1,
141 :     -label=>'IncludeIndex',
142 :     -checked=>1,
143 :     },
144 :     'Include Index'
145 :     ),
146 :     CGI::br(),
147 :     CGI::checkbox({ -name=>'recordSingleSetScores',
148 :     -value=>1,
149 :     -label=>'Record Scores for Single Sets',
150 :     -checked=>0,
151 :     },
152 :     'Record Scores for Single Sets'
153 :     ),
154 :     CGI::br(),
155 :     CGI::input({type=>'submit',value=>'Score selected set(s)...',name=>'score-sets'}),
156 :    
157 :     );
158 :    
159 :    
160 : malsyned 1448 if ($authz->hasPermissions($user, "score_sets")) {
161 :     my @selected = $r->param('selectedSet');
162 : gage 2055 if (@selected) {
163 :     print CGI::p("All of these files will also be made available for mail merge");
164 :     }
165 : malsyned 1448 foreach my $setID (@selected) {
166 : gage 1938
167 : sh002i 1675 my @validFiles;
168 : malsyned 1448 foreach my $type ("scr", "ful") {
169 :     my $filename = "s$setID$type.csv";
170 :     my $path = "$scoringDir/$filename";
171 : sh002i 1675 push @validFiles, $filename if -f $path;
172 :     }
173 :     if (@validFiles) {
174 :     print CGI::h2("$setID");
175 :     foreach my $filename (@validFiles) {
176 : gage 1938 #print CGI::a({href=>"../scoringDownload/?getFile=${filename}&".$self->url_authen_args}, $filename);
177 :     print CGI::a({href=>$self->systemLink($scoringDownloadPage,
178 :     params=>{getFile => $filename } )}, $filename);
179 : malsyned 1448 print CGI::br();
180 :     }
181 : sh002i 1675 print CGI::hr();
182 : malsyned 1448 }
183 : sh002i 1675 }
184 :     if (-f "$scoringDir/${courseName}_totals.csv") {
185 :     print CGI::h2("Totals");
186 : gage 1938 #print CGI::a({href=>"../scoringDownload/?getFile=${courseName}_totals.csv&".$self->url_authen_args}, "${courseName}_totals.csv");
187 :     print CGI::a({href=>$self->systemLink($scoringDownloadPage,
188 :     params=>{getFile => "${courseName}_totals.csv" } )}, "${courseName}_totals.csv");
189 : malsyned 1448 print CGI::hr();
190 : gage 1768 print CGI::pre({style=>'font-size:smaller'},WeBWorK::Utils::readFile("$scoringDir/${courseName}_totals.csv"));
191 : malsyned 1448 }
192 :     }
193 :    
194 :     return "";
195 :     }
196 :    
197 : malsyned 1401 # If, some day, it becomes possible to assign a different number of problems to each student, this code
198 :     # will have to be rewritten some.
199 : malsyned 1441 # $format can be any of "normal", "full", "everything", "info", or "totals". An undefined value defaults to "normal"
200 : malsyned 1403 # normal: student info, the status of each problem in the set, and a "totals" column
201 :     # full: student info, the status of each problem, and the number of correct and incorrect attempts
202 : malsyned 1441 # everything: "full" plus a totals column
203 : malsyned 1403 # info: student info columns only
204 :     # totals: total column only
205 : malsyned 1401 sub scoreSet {
206 : sh002i 1680 my ($self, $setID, $format, $showIndex, $UsersRef, $sortedUserIDsRef) = @_;
207 : gage 1928 my $r = $self->r;
208 :     my $db = $r->db;
209 : malsyned 1401 my @scoringData;
210 : gage 1609 my $scoringItems = { info => 0,
211 :     successIndex => 0,
212 :     setTotals => 0,
213 :     problemScores => 0,
214 :     problemAttempts => 0,
215 :     header => 0,
216 :     };
217 : malsyned 1413 $format = "normal" unless defined $format;
218 : malsyned 1441 $format = "normal" unless $format eq "full" or $format eq "everything" or $format eq "totals" or $format eq "info";
219 :     my $columnsPerProblem = ($format eq "full" or $format eq "everything") ? 3 : 1;
220 : gage 2055
221 : gage 1667 my $setRecord = $db->getGlobalSet($setID); #checked
222 :     die "global set $setID not found. " unless $setRecord;
223 : sh002i 1675 #my %users;
224 :     #my %userStudentID=();
225 :     #$WeBWorK::timer->continue("Begin getting users for set $setID") if defined($WeBWorK::timer);
226 :     #foreach my $userID ($db->listUsers()) {
227 :     # my $userRecord = $db->getUser($userID); # checked
228 :     # die "user record for $userID not found" unless $userID;
229 :     # # FIXME: if two users have the same student ID, the second one will
230 :     # # clobber the first one. this is bad!
231 :     # # The key is what we'd like to sort by.
232 :     # $users{$userRecord->student_id} = $userRecord;
233 :     # $userStudentID{$userID} = $userRecord->student_id;
234 :     #}
235 :     #$WeBWorK::timer->continue("End getting users for set $setID") if defined($WeBWorK::timer);
236 : gage 1629
237 : sh002i 1680 my %Users = %$UsersRef; # user objects hashed on user ID
238 :     my @sortedUserIDs = @$sortedUserIDsRef; # user IDs sorted by student ID
239 :    
240 : malsyned 1401 my @problemIDs = $db->listGlobalProblems($setID);
241 : malsyned 1402
242 : gage 1609 # determine what information will be returned
243 :     if ($format eq 'normal') {
244 :     $scoringItems = { info => 1,
245 :     successIndex => $showIndex,
246 :     setTotals => 1,
247 :     problemScores => 1,
248 :     problemAttempts => 0,
249 :     header => 1,
250 :     };
251 :     } elsif ($format eq 'full') {
252 :     $scoringItems = { info => 1,
253 :     successIndex => $showIndex,
254 :     setTotals => 0,
255 :     problemScores => 1,
256 :     problemAttempts => 1,
257 :     header => 1,
258 :     };
259 :     } elsif ($format eq 'everything') {
260 :     $scoringItems = { info => 1,
261 :     successIndex => $showIndex,
262 :     setTotals => 1,
263 :     problemScores => 1,
264 :     problemAttempts => 1,
265 :     header => 1,
266 :     };
267 :     } elsif ($format eq 'totals') {
268 :     $scoringItems = { info => 0,
269 :     successIndex => $showIndex,
270 :     setTotals => 1,
271 :     problemScores => 0,
272 :     problemAttempts => 0,
273 :     header => 0,
274 :     };
275 :     } elsif ($format eq 'info') {
276 :     $scoringItems = { info => 0,
277 :     successIndex => 0,
278 :     setTotals => 0,
279 :     problemScores => 0,
280 :     problemAttempts => 0,
281 :     header => 1,
282 :     };
283 :     } else {
284 :     warn "unrecognized format";
285 :     }
286 :    
287 : malsyned 1402 # Initialize a two-dimensional array of the proper size
288 : sh002i 1680 for (my $i = 0; $i < @sortedUserIDs + 7; $i++) { # 7 is how many descriptive fields there are in each column
289 : malsyned 1401 push @scoringData, [];
290 :     }
291 :    
292 : gage 1819 my @userInfoColumnHeadings = ("STUDENT ID", "LAST NAME", "FIRST NAME", "SECTION", "RECITATION");
293 :     my @userInfoFields = ("student_id", "last_name", "first_name", "section", "recitation");
294 : sh002i 1680 #my @userKeys = sort keys %users; # list of "student IDs" NOT user IDs
295 : gage 1609
296 :     if ($scoringItems->{header}) {
297 : malsyned 1403 $scoringData[0][0] = "NO OF FIELDS";
298 :     $scoringData[1][0] = "SET NAME";
299 :     $scoringData[2][0] = "PROB NUMBER";
300 :     $scoringData[3][0] = "DUE DATE";
301 :     $scoringData[4][0] = "DUE TIME";
302 :     $scoringData[5][0] = "PROB VALUE";
303 : gage 1609
304 : malsyned 1403
305 : malsyned 1401
306 : malsyned 1402 # Write identifying information about the users
307 : gage 1609
308 : malsyned 1403 for (my $field=0; $field < @userInfoFields; $field++) {
309 :     if ($field > 0) {
310 :     for (my $i = 0; $i < 6; $i++) {
311 :     $scoringData[$i][$field] = "";
312 :     }
313 : malsyned 1401 }
314 : malsyned 1403 $scoringData[6][$field] = $userInfoColumnHeadings[$field];
315 : sh002i 1680 for (my $user = 0; $user < @sortedUserIDs; $user++) {
316 : malsyned 1403 my $fieldName = $userInfoFields[$field];
317 : sh002i 1680 $scoringData[$user + 7][$field] = $Users{$sortedUserIDs[$user]}->$fieldName;
318 : malsyned 1403 }
319 : malsyned 1401 }
320 :     }
321 : malsyned 1403 return @scoringData if $format eq "info";
322 : malsyned 1401
323 : sh002i 1675 # pre-fetch global problems
324 :     $WeBWorK::timer->continue("pre-fetching global problems for set $setID") if defined($WeBWorK::timer);
325 :     my %GlobalProblems = map { $_->problem_id => $_ }
326 :     $db->getAllGlobalProblems($setID);
327 :     $WeBWorK::timer->continue("done pre-fetching global problems for set $setID") if defined($WeBWorK::timer);
328 :    
329 :     # pre-fetch user problems
330 :     $WeBWorK::timer->continue("pre-fetching user problems for set $setID") if defined($WeBWorK::timer);
331 :     my %UserProblems; # $UserProblems{$userID}{$problemID}
332 : sh002i 1680 foreach my $userID (@sortedUserIDs) {
333 : sh002i 1675 my %CurrUserProblems = map { $_->problem_id => $_ }
334 :     $db->getAllUserProblems($userID, $setID);
335 :     $UserProblems{$userID} = \%CurrUserProblems;
336 :     }
337 :     $WeBWorK::timer->continue("done pre-fetching user problems for set $setID") if defined($WeBWorK::timer);
338 :    
339 : malsyned 1402 # Write the problem data
340 : sh002i 2778 my $dueDateString = $self->formatDateTime($setRecord->due_date);
341 : malsyned 1401 my ($dueDate, $dueTime) = $dueDateString =~ m/^([^\s]*)\s*([^\s]*)$/;
342 : malsyned 1402 my $valueTotal = 0;
343 :     my %userStatusTotals = ();
344 : gage 1609 my %userSuccessIndex = ();
345 :     my %numberOfAttempts = ();
346 :     my $num_of_problems = @problemIDs;
347 : malsyned 1401 for (my $problem = 0; $problem < @problemIDs; $problem++) {
348 : sh002i 1675
349 :     #my $globalProblem = $db->getGlobalProblem($setID, $problemIDs[$problem]); #checked
350 :     my $globalProblem = $GlobalProblems{$problemIDs[$problem]};
351 : gage 1667 die "global problem $problemIDs[$problem] not found for set $setID" unless $globalProblem;
352 : sh002i 1675
353 : malsyned 1403 my $column = 5 + $problem * $columnsPerProblem;
354 : gage 1609 if ($scoringItems->{header}) {
355 : malsyned 1403 $scoringData[0][$column] = "";
356 :     $scoringData[1][$column] = $setRecord->set_id;
357 :     $scoringData[2][$column] = $globalProblem->problem_id;
358 :     $scoringData[3][$column] = $dueDate;
359 :     $scoringData[4][$column] = $dueTime;
360 :     $scoringData[5][$column] = $globalProblem->value;
361 :     $scoringData[6][$column] = "STATUS";
362 : gage 1609 if ($scoringItems->{header} and $scoringItems->{problemAttempts}) { # Fill in with blanks, or maybe the problem number
363 : malsyned 1403 for (my $row = 0; $row < 6; $row++) {
364 :     for (my $col = $column+1; $col <= $column + 2; $col++) {
365 :     if ($row == 2) {
366 :     $scoringData[$row][$col] = $globalProblem->problem_id;
367 :     } else {
368 :     $scoringData[$row][$col] = "";
369 :     }
370 :     }
371 :     }
372 :     $scoringData[6][$column + 1] = "#corr";
373 :     $scoringData[6][$column + 2] = "#incorr";
374 :     }
375 :     }
376 : malsyned 1402 $valueTotal += $globalProblem->value;
377 : gage 1629
378 : sh002i 1675 #my @userLoginIDs = $db->listUsers();
379 :     #$WeBWorK::timer->continue("Begin getting user problems for set $setID, problem $problemIDs[$problem]") if defined($WeBWorK::timer);
380 :     ##my @userProblems = $db->getMergedProblems( map { [ $_, $setID, $problemIDs[$problem] ] } @userLoginIDs );
381 :     #my @userProblems = $db->getUserProblems( map { [ $_, $setID, $problemIDs[$problem] ] } @userLoginIDs ); # checked
382 :     #my %userProblems;
383 :     #foreach my $item (@userProblems) {
384 :     # $userProblems{$item->user_id} = $item if ref $item;
385 :     #}
386 :     #$WeBWorK::timer->continue("End getting user problems for set $setID, problem $problemIDs[$problem]") if defined($WeBWorK::timer);
387 :    
388 : sh002i 1680 for (my $user = 0; $user < @sortedUserIDs; $user++) {
389 : sh002i 1675 #my $userProblem = $userProblems{ $users{$userKeys[$user]}->user_id };
390 : sh002i 1680 #my $userProblem = $UserProblems{$sers{$userKeys[$user]}->user_id}{$problemIDs[$problem]};
391 :     my $userProblem = $UserProblems{$sortedUserIDs[$user]}{$problemIDs[$problem]};
392 : malsyned 1449 unless (defined $userProblem) { # assume an empty problem record if the problem isn't assigned to this user
393 :     $userProblem = $db->newUserProblem;
394 :     $userProblem->status(0);
395 :     $userProblem->value(0);
396 :     $userProblem->num_correct(0);
397 :     $userProblem->num_incorrect(0);
398 :     }
399 : malsyned 1402 $userStatusTotals{$user} = 0 unless exists $userStatusTotals{$user};
400 : gage 1629 #$userStatusTotals{$user} += $userProblem->status * $userProblem->value;
401 :     $userStatusTotals{$user} += $userProblem->status * $globalProblem->value;
402 : gage 1609 if ($scoringItems->{successIndex}) {
403 :     $numberOfAttempts{$user} = 0 unless defined($numberOfAttempts{$user});
404 :     my $num_correct = $userProblem->num_correct;
405 :     my $num_incorrect = $userProblem->num_incorrect;
406 :     $num_correct = ( defined($num_correct) and $num_correct) ? $num_correct : 0;
407 :     $num_incorrect = ( defined($num_incorrect) and $num_incorrect) ? $num_incorrect : 0;
408 :     $numberOfAttempts{$user} += $num_correct + $num_incorrect;
409 :     }
410 :     if ($scoringItems->{problemScores}) {
411 : malsyned 1403 $scoringData[7 + $user][$column] = $userProblem->status;
412 : gage 1609 if ($scoringItems->{problemAttempts}) {
413 : malsyned 1403 $scoringData[7 + $user][$column + 1] = $userProblem->num_correct;
414 :     $scoringData[7 + $user][$column + 2] = $userProblem->num_incorrect;
415 :     }
416 :     }
417 : malsyned 1401 }
418 :     }
419 : gage 1609 if ($scoringItems->{successIndex}) {
420 : sh002i 1680 for (my $user = 0; $user < @sortedUserIDs; $user++) {
421 : gage 1609 my $avg_num_attempts = ($num_of_problems) ? $numberOfAttempts{$user}/$num_of_problems : 0;
422 : gage 1629 $userSuccessIndex{$user} = ($avg_num_attempts) ? ($userStatusTotals{$user}/$valueTotal)**2/$avg_num_attempts : 0;
423 : gage 1609 }
424 :     }
425 : malsyned 1402 # write the status totals
426 : gage 1609 if ($scoringItems->{setTotals}) { # Ironic, isn't it?
427 : malsyned 1403 my $totalsColumn = $format eq "totals" ? 0 : 5 + @problemIDs * $columnsPerProblem;
428 : gage 1629 $scoringData[0][$totalsColumn] = "";
429 :     $scoringData[1][$totalsColumn] = $setRecord->set_id;
430 :     $scoringData[1][$totalsColumn+1] = $setRecord->set_id if $scoringItems->{successIndex};
431 :     $scoringData[2][$totalsColumn] = "";
432 :     $scoringData[3][$totalsColumn] = "";
433 :     $scoringData[4][$totalsColumn] = "";
434 :     $scoringData[5][$totalsColumn] = $valueTotal;
435 :     $scoringData[6][$totalsColumn] = "total";
436 :     $scoringData[6][$totalsColumn+1] = "index" if $scoringItems->{successIndex};
437 : sh002i 1680 for (my $user = 0; $user < @sortedUserIDs; $user++) {
438 : gage 1687 $scoringData[7+$user][$totalsColumn] = sprintf("%4.1f",$userStatusTotals{$user});
439 :     $scoringData[7+$user][$totalsColumn+1] = sprintf("%4.1f",$userSuccessIndex{$user}) if $scoringItems->{successIndex};
440 : malsyned 1403 }
441 : malsyned 1402 }
442 : gage 1629 $WeBWorK::timer->continue("End set $setID") if defined($WeBWorK::timer);
443 : malsyned 1401 return @scoringData;
444 :     }
445 :    
446 : malsyned 1441 # Often it's more efficient to just get everything out of the database
447 :     # and then pick out what you want later. Hence, these "everything2*" functions
448 :     sub everything2info {
449 :     my ($self, @everything) = @_;
450 :     my @result = ();
451 :     foreach my $row (@everything) {
452 :     push @result, [@{$row}[0..4]];
453 :     }
454 :     return @result;
455 :     }
456 :    
457 :     sub everything2normal {
458 :     my ($self, @everything) = @_;
459 :     my @result = ();
460 :     foreach my $row (@everything) {
461 :     my @row = @$row;
462 :     my @newRow = ();
463 :     push @newRow, @row[0..4];
464 :     for (my $i = 5; $i < @row; $i+=3) {
465 :     push @newRow, $row[$i];
466 :     }
467 : malsyned 1448 #push @newRow, $row[$#row];
468 : malsyned 1441 push @result, [@newRow];
469 :     }
470 :     return @result;
471 :     }
472 :    
473 :     sub everything2full {
474 :     my ($self, @everything) = @_;
475 :     my @result = ();
476 :     foreach my $row (@everything) {
477 :     push @result, [@{$row}[0..($#{$row}-1)]];
478 :     }
479 :     return @result;
480 :     }
481 :    
482 :     sub everything2totals {
483 :     my ($self, @everything) = @_;
484 :     my @result = ();
485 :     foreach my $row (@everything) {
486 :     push @result, [${$row}[$#{$row}]];
487 :     }
488 : malsyned 1448 return @result;
489 : malsyned 1441 }
490 :    
491 :     sub appendColumns {
492 :     my ($self, $a1, $a2) = @_;
493 :     my @a1 = @$a1;
494 :     my @a2 = @$a2;
495 : malsyned 1448 for (my $i = 0; $i < @a1; $i++) {
496 :     push @{$a1[$i]}, @{$a2[$i]};
497 :     }
498 : malsyned 1441 }
499 :    
500 : malsyned 1386 # Reads a CSV file and returns an array of arrayrefs, each containing a
501 : malsyned 1391 # row of data:
502 : malsyned 1386 # (["c1r1", "c1r2", "c1r3"], ["c2r1", "c2r2", "c2r3"])
503 :     sub readCSV {
504 : malsyned 1391 my ($self, $fileName) = @_;
505 : malsyned 1386 my @result = ();
506 : malsyned 1391 my @rows = split m/\n/, readFile($fileName);
507 :     foreach my $row (@rows) {
508 :     push @result, [split m/\s*,\s*/, $row];
509 : malsyned 1386 }
510 :     return @result;
511 :     }
512 :    
513 : malsyned 1389 # Write a CSV file from an array in the same format that readCSV produces
514 : malsyned 1386 sub writeCSV {
515 : malsyned 1388 my ($self, $filename, @csv) = @_;
516 : malsyned 1393
517 :     my @lengths = ();
518 :     for (my $row = 0; $row < @csv; $row++) {
519 :     for (my $column = 0; $column < @{$csv[$row]}; $column++) {
520 :     $lengths[$column] = 0 unless defined $lengths[$column];
521 : gage 1562 $lengths[$column] = length $csv[$row][$column] if defined($csv[$row][$column]) and length $csv[$row][$column] > $lengths[$column];
522 : malsyned 1393 }
523 :     }
524 :    
525 : gage 1562 open my $fh, ">", $filename or warn "Unable to open $filename for writing";
526 : malsyned 1391 foreach my $row (@csv) {
527 : malsyned 1393 my @rowPadded = ();
528 :     foreach (my $column = 0; $column < @$row; $column++) {
529 :     push @rowPadded, $self->pad($row->[$column], $lengths[$column] + 1);
530 :     }
531 :     print $fh join(",", @rowPadded);
532 : malsyned 1391 print $fh "\n";
533 : malsyned 1388 }
534 :     close $fh;
535 : malsyned 1386 }
536 :    
537 : malsyned 1389 # As soon as backwards compatability is no longer a concern and we don't expect to have
538 :     # to use old ww1.x code to read the output anymore, I recommend switching to using
539 :     # these routines, which are more versatile and compatable with other programs which
540 :     # deal with CSV files.
541 : malsyned 1386 sub readStandardCSV {
542 : malsyned 1391 my ($self, $fileName) = @_;
543 : malsyned 1386 my @result = ();
544 : malsyned 1391 my @rows = split m/\n/, readFile($fileName);
545 :     foreach my $row (@rows) {
546 : malsyned 1394 push @result, [$self->splitQuoted($row)];
547 : malsyned 1386 }
548 :     return @result;
549 :     }
550 :    
551 :     sub writeStandardCSV {
552 : malsyned 1388 my ($self, $filename, @csv) = @_;
553 :     open my $fh, ">", $filename;
554 : malsyned 1391 foreach my $row (@csv) {
555 : malsyned 1394 print $fh (join ",", map {$self->quote($_)} @$row);
556 :     print $fh "\n";
557 : malsyned 1388 }
558 :     close $fh;
559 : malsyned 1386 }
560 :    
561 :     ###
562 :    
563 :     # This particular unquote method unquotes (optionally) quoted strings in the
564 :     # traditional CSV style (double-quote for literal quote, etc.)
565 :     sub unquote {
566 :     my ($self, $string) = @_;
567 :     if ($string =~ m/^"(.*)"$/) {
568 :     $string = $1;
569 :     $string =~ s/""/"/;
570 :     }
571 :     return $string;
572 :     }
573 :    
574 :     # Should you wish to treat whitespace differently, this routine has been designed
575 :     # to make it easy to do so.
576 :     sub splitQuoted {
577 :     my ($self, $string) = @_;
578 :     my ($leadingSpace, $preText, $quoted, $postText, $trailingSpace, $result);
579 :     my @result = ();
580 : malsyned 1391 my $continue = 1;
581 : malsyned 1386 while ($continue) {
582 : malsyned 1394 $string =~ m/\G(\s*)/gc;
583 : malsyned 1386 $leadingSpace = $1;
584 : malsyned 1394 $string =~ m/\G([^",]*)/gc;
585 : malsyned 1386 $preText = $1;
586 : malsyned 1394 if ($string =~ m/\G"((?:[^"]|"")*)"/gc) {
587 : malsyned 1386 $quoted = $1;
588 :     }
589 : malsyned 1394 $string =~ m/\G([^,]*?)(\s*)(,?)/gc;
590 : malsyned 1391 ($postText, $trailingSpace, $continue) = ($1, $2, $3);
591 : malsyned 1394
592 :     $preText = "" unless defined $preText;
593 :     $postText = "" unless defined $postText;
594 :     $quoted = "" unless defined $quoted;
595 :    
596 :     if ($quoted and (not $preText and not $postText)) {
597 :     $quoted =~ s/""/"/;
598 : malsyned 1386 $result = $quoted;
599 :     } else {
600 :     $result = "$preText$quoted$postText";
601 :     }
602 :     push @result, $result;
603 :     }
604 :     return @result;
605 :     }
606 :    
607 :     # This particular quoting method does CSV-style (double a quote to escape it) quoting when necessary.
608 :     sub quote {
609 :     my ($self, $string) = @_;
610 :     if ($string =~ m/[", ]/) {
611 :     $string =~ s/"/""/;
612 : malsyned 1393 $string = "\"$string\"";
613 : malsyned 1386 }
614 : malsyned 1393 return $string;
615 : malsyned 1386 }
616 : malsyned 1388
617 :     sub pad {
618 :     my ($self, $string, $padTo) = @_;
619 : gage 1562 $string = '' unless defined $string;
620 : malsyned 1388 my $spaces = $padTo - length $string;
621 :     return $string . " "x$spaces;
622 :     }
623 :    
624 :     sub maxLength {
625 :     my ($self, $arrayRef) = @_;
626 :     my $max = 0;
627 :     foreach my $cell (@$arrayRef) {
628 :     $max = length $cell unless length $cell < $max;
629 :     }
630 :     return $max;
631 :     }
632 : malsyned 1391
633 : gage 1609 sub popup_set_form {
634 :     my $self = shift;
635 : gage 1928 my $r = $self->r;
636 :     my $db = $r->db;
637 :     my $ce = $r->ce;
638 :     my $authz = $r->authz;
639 :     my $user = $r->param('user');
640 :    
641 : gage 1609 my $root = $ce->{webworkURLs}->{root};
642 :     my $courseName = $ce->{courseName};
643 :    
644 :     # return CGI::em("You are not authorized to access the Instructor tools.") unless $authz->hasPermissions($user, "access_instructor_tools");
645 :    
646 :     # This code will require changing if the permission and user tables ever have different keys.
647 :     my @setNames = ();
648 :     my $ra_set_records = $self->{ra_set_records};
649 :     my %setLabels = ();# %$hr_classlistLabels;
650 :     my @set_records = sort {$a->set_id cmp $b->set_id } @{$ra_set_records};
651 :     foreach my $sr (@set_records) {
652 :     $setLabels{$sr->set_id} = $sr->set_id;
653 :     push(@setNames, $sr->set_id); # reorder sets
654 :     }
655 :     return CGI::popup_menu(-name=>'selectedSet',
656 :     -values=>\@setNames,
657 :     -labels=>\%setLabels,
658 :     -size => 10,
659 :     -multiple => 1,
660 :     #-default=>$user
661 :     ),
662 :    
663 :    
664 :     }
665 : malsyned 1391 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9