[system] / trunk / webwork2 / lib / WeBWorK / Utils.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/Utils.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

1 : sh002i 440 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : gage 2893 # $CVSHeader: webwork-modperl/lib/WeBWorK/Utils.pm,v 1.57 2004/10/11 13:30:09 gage 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 : sh002i 440 ################################################################################
16 :    
17 : sh002i 410 package WeBWorK::Utils;
18 : sh002i 818 use base qw(Exporter);
19 : sh002i 410
20 : sh002i 455 =head1 NAME
21 :    
22 :     WeBWorK::Utils - useful utilities used by other WeBWorK modules.
23 :    
24 :     =cut
25 :    
26 : sh002i 412 use strict;
27 :     use warnings;
28 : sh002i 1257 #use Apache::DB;
29 : sh002i 2770 use DateTime;
30 : sh002i 2774 use Date::Parse;
31 : sh002i 2782 use Date::Format;
32 : sh002i 2858 use Time::Zone;
33 : sh002i 2774 #use Date::Manip;
34 :     #use DateTime::Format::DateManip;
35 : sh002i 1145 use Errno;
36 : sh002i 1150 use File::Path qw(rmtree);
37 : sh002i 2003 use Carp;
38 : sh002i 412
39 : sh002i 1145 use constant MKDIR_ATTEMPTS => 10;
40 :    
41 : sh002i 2770 # "standard" WeBWorK date/time format (for set definition files):
42 :     # %m/%d/%y at %I:%M%P
43 :     # where:
44 :     # %m = month number, starting with 01
45 :     # %d = numeric day of the month, with leading zeros (eg 01..31)
46 : sh002i 2858 # %Y = year (4 digits)
47 : sh002i 2770 # %I = hour, 12 hour clock, leading 0's)
48 :     # %M = minute, leading 0's
49 :     # %P = am or pm (Yes %p and %P are backwards :)
50 : sh002i 2858 use constant DATE_FORMAT => "%m/%d/%Y at %I:%M%P %Z";
51 : sh002i 2770
52 : sh002i 410 our @EXPORT = ();
53 : sh002i 424 our @EXPORT_OK = qw(
54 :     runtime_use
55 :     readFile
56 : sh002i 1150 readDirectory
57 : sh002i 2754 listFilesRecursive
58 : sh002i 2769 surePathToFile
59 :     makeTempDirectory
60 :     removeTempDirectory
61 : sh002i 424 formatDateTime
62 :     parseDateTime
63 : sh002i 2744 textDateTime
64 :     intDateTime
65 : sh002i 562 writeLog
66 : gage 1387 writeCourseLog
67 : sh002i 562 writeTimingLogEntry
68 : malsyned 970 list2hash
69 : sh002i 2769 ref2string
70 : sh002i 429 decodeAnswers
71 :     encodeAnswers
72 : sh002i 2769 max
73 : sh002i 1145 pretty_print_rh
74 : malsyned 1287 cryptPassword
75 : sh002i 1900 dequote
76 : sh002i 1945 undefstr
77 : sh002i 2769 sortByName
78 : sh002i 424 );
79 : sh002i 410
80 : sh002i 2769 ################################################################################
81 :     # Lowlevel thingies
82 :     ################################################################################
83 :    
84 :     sub runtime_use($) {
85 : sh002i 2003 croak "runtime_use: no module specified" unless $_[0];
86 : sh002i 424 eval "package Main; require $_[0]; import $_[0]";
87 : sh002i 410 die $@ if $@;
88 :     }
89 :    
90 : sh002i 2769 #sub backtrace($) {
91 : sh002i 1257 # my ($style) = @_;
92 :     # $style = "warn" unless $style;
93 :     # my @bt = DB->backtrace;
94 :     # shift @bt; # Remove "backtrace" from the backtrace;
95 :     # if ($style eq "die") {
96 :     # die join "\n", @bt;
97 :     # } elsif ($style eq "warn") {
98 :     # warn join "\n", @bt;
99 :     # } elsif ($style eq "print") {
100 :     # print join "\n", @bt;
101 :     # } elsif ($style eq "return") {
102 :     # return @bt;
103 :     # }
104 :     #}
105 : malsyned 1045
106 : sh002i 2769 ################################################################################
107 :     # Filesystem interaction
108 :     ################################################################################
109 :    
110 : sh002i 2754 # Convert Windows and Mac (classic) line endings to UNIX line endings in a string.
111 :     # Windows uses CRLF, Mac uses CR, UNIX uses LF. (CR is ASCII 15, LF if ASCII 12)
112 :     sub force_eoln($) {
113 :     my ($string) = @_;
114 :     $string =~ s/\015\012?/\012/g;
115 :     return $string;
116 :     }
117 :    
118 : sh002i 410 sub readFile($) {
119 :     my $fileName = shift;
120 : sh002i 1150 local $/ = undef; # slurp the whole thing into one string
121 :     open my $dh, "<", $fileName
122 :     or die "failed to read file $fileName: $!";
123 :     my $result = <$dh>;
124 :     close $dh;
125 : gage 2481 return force_eoln($result);
126 : sh002i 410 }
127 : sh002i 412
128 : malsyned 974 sub readDirectory($) {
129 : sh002i 1150 my $dirName = shift;
130 :     opendir my $dh, $dirName
131 : sh002i 1529 or die "Failed to read directory $dirName: $!";
132 : sh002i 1150 my @result = readdir $dh;
133 :     close $dh;
134 :     return @result;
135 : malsyned 974 }
136 :    
137 : sh002i 2754 =item @matches = listFilesRecusive($dir, $match_qr, $prune_qr, $match_full, $prune_full)
138 :    
139 :     Traverses the directory tree rooted at $dir, returning a list of files, named
140 :     pipes, and sockets matching the regular expression $match_qr. Directories
141 :     matching the regular expression $prune_qr are not visited.
142 :    
143 :     $match_full and $prune_full are boolean values that indicate whether $match_qr
144 :     and $prune_qr, respectively, should be applied to the bare directory entry
145 :     (false) or to the path to the directory entry relative to $dir.
146 :    
147 :     @matches is a list of paths relative to $dir.
148 :    
149 :     =cut
150 :    
151 :     sub listFilesRecursiveHelper($$$$$$);
152 :     sub listFilesRecursive($;$$$$) {
153 :     my ($dir, $match_qr, $prune_qr, $match_full, $prune_full) = @_;
154 :     return listFilesRecursiveHelper($dir, "", $match_qr, $prune_qr, $match_full, $prune_full);
155 :     }
156 :    
157 :     sub listFilesRecursiveHelper($$$$$$) {
158 :     my ($base_dir, $curr_dir, $match_qr, $prune_qr, $match_full, $prune_full) = @_;
159 :    
160 :     my $full_dir = "$base_dir/$curr_dir";
161 :    
162 :     my @dir_contents = readDirectory($full_dir);
163 :    
164 :     my @matches;
165 :    
166 :     foreach my $dir_entry (@dir_contents) {
167 :     my $full_path = "$full_dir/$dir_entry";
168 :     if (-d $full_path or -l $full_path) {
169 :     # standard things to skip
170 :     next if $dir_entry eq ".";
171 :     next if $dir_entry eq "..";
172 :    
173 :     # skip unreadable directories (and broken symlinks, incidentally)
174 :     unless (-r $full_path) {
175 :     warn "Directory/symlink $full_path not readable";
176 :     next;
177 :     }
178 :    
179 :     # check $prune_qr
180 :     my $subdir = ($curr_dir eq "") ? $dir_entry : "$curr_dir/$dir_entry";
181 :     if (defined $prune_qr) {
182 :     my $prune_string = $prune_full ? $subdir : $dir_entry;
183 :     next if $prune_string =~ m/$prune_qr/;
184 :     }
185 :    
186 :     # everything looks good, time to recurse!
187 :     push @matches, listFilesRecursiveHelper($base_dir, $subdir, $match_qr, $prune_qr, $match_full, $prune_full);
188 :     } elsif (-f $full_path or -p $full_path or -S $full_path) {
189 :     my $file = ($curr_dir eq "") ? $dir_entry : "$curr_dir/$dir_entry";
190 :     my $match_string = $match_full ? $file : $dir_entry;
191 : dpvc 2825 if (not defined $match_string or $match_string =~ m/$match_qr/) {
192 : sh002i 2754 push @matches, $file;
193 :     }
194 :     }
195 :     }
196 :    
197 :     return @matches;
198 :     }
199 :    
200 : sh002i 2769 # A very useful macro for making sure that all of the directories to a file have
201 :     # been constructed.
202 :     sub surePathToFile($$) {
203 :     # constructs intermediate
204 :     # the input path must be the path relative to this starting directory
205 :     my $start_directory = shift;
206 :     my $path = shift;
207 :     my $delim = "/"; #&getDirDelim();
208 :     unless ($start_directory and $path ) {
209 :     warn "missing directory<br> surePathToFile start_directory path ";
210 :     return '';
211 :     }
212 :     # use the permissions/group on the start directory itself as a template
213 :     my ($perms, $groupID) = (stat $start_directory)[2,5];
214 :     #warn "&urePathToTmpFile: perms=$perms groupID=$groupID\n";
215 :    
216 :     # if the path starts with $start_directory (which is permitted but optional) remove this initial segment
217 :     $path =~ s|^$start_directory|| if $path =~ m|^$start_directory|;
218 :     #$path = convertPath($path);
219 :    
220 :    
221 :     # find the nodes on the given path
222 :     my @nodes = split("$delim",$path);
223 :    
224 :     # create new path
225 :     $path = $start_directory; #convertPath("$tmpDirectory");
226 :    
227 :     while (@nodes>1) {
228 :     $path = $path . shift (@nodes) . "/"; #convertPath($path . shift (@nodes) . "/");
229 :     #FIXME this make directory command may not be fool proof.
230 :     unless (-e $path) {
231 :     mkdir($path, $perms)
232 :     or warn "Failed to create directory $path";
233 :     }
234 :    
235 :     }
236 :    
237 :     $path = $path . shift(@nodes); #convertPath($path . shift(@nodes));
238 :     return $path;
239 :     }
240 :    
241 :     sub makeTempDirectory($$) {
242 :     my ($parent, $basename) = @_;
243 :     # Loop until we're able to create a directory, or it fails for some
244 :     # reason other than there already being something there.
245 :     my $triesRemaining = MKDIR_ATTEMPTS;
246 :     my ($fullPath, $success);
247 :     do {
248 :     my $suffix = join "", map { ('A'..'Z','a'..'z','0'..'9')[int rand 62] } 1 .. 8;
249 :     $fullPath = "$parent/$basename.$suffix";
250 :     $success = mkdir $fullPath;
251 :     } until ($success or not $!{EEXIST});
252 :     die "Failed to create directory $fullPath: $!"
253 :     unless $success;
254 :     return $fullPath;
255 :     }
256 :    
257 :     sub removeTempDirectory($) {
258 :     my ($dir) = @_;
259 :     rmtree($dir, 0, 0);
260 :     }
261 :    
262 :     ################################################################################
263 :     # Date/time processing
264 :     ################################################################################
265 :    
266 : sh002i 2770 =head2 Date/time processing
267 :    
268 :     =over
269 :    
270 :     =item $dateTime = parseDateTime($string, $display_tz)
271 :    
272 :     Parses $string as a datetime. If $display_tz is given, $string is assumed to be
273 :     in that timezone. Otherwise, the server's timezone is used. The result,
274 :     $dateTime, is an integer UNIX datetime (epoch) in the server's timezone.
275 :    
276 :     =cut
277 :    
278 : sh002i 2858 # This is a modified version of the subroutine of the same name from WeBWorK
279 :     # 1.9.05's scripts/FILE.pl (v1.13). It has been modified to understand time
280 :     # zones. The time zone specification must appear at the end of the string and be
281 :     # preceded by whitespace. The return value is a list consisting of the following
282 :     # elements:
283 :     #
284 :     # ($second, $minute, $hour, $day, $month, $year, $zone)
285 :     #
286 :     # $second, $minute, $hour, $day, and $month are zero-indexed. $year is the
287 :     # number of years since 1900. $zone is a string (hopefully) representing the
288 :     # time zone.
289 :     #
290 :     # Error handling has also been improved. Exceptions are now thrown for errors,
291 :     # and more information is given abou the nature of errors.
292 :     #
293 :     sub unformatDateAndTime {
294 :     my ($string) = @_;
295 :     my $orgString =$string;
296 :     $string =~ s|^\s+||;
297 :     $string =~ s|\s+$||;
298 :     $string =~ s|at| at |i; ## OK if forget to enter spaces or use wrong case
299 :     $string =~ s|AM| AM|i; ## OK if forget to enter spaces or use wrong case
300 :     $string =~ s|PM| PM|i; ## OK if forget to enter spaces or use wrong case
301 :     $string =~ s|,| at |; ## start translating old form of date/time to new form
302 : gage 2893 if ($string =~ m|^\s*[\/\d]+\s+[:\d]+| ) { # case where the at is missing: MM/DD/YYYY at HH:MM AMPM ZONE
303 :     die "Incorrect date/time format \"$orgString\". The \"at\" appears to be missing.
304 :     Correct format is MM/DD/YYYY at HH:MM AMPM ZONE (e.g. \"03/29/2004 at 06:00am EST\")";
305 :     }
306 : sh002i 2858
307 : gage 2893 my($date,$at, $time,$AMPM,$TZ) = split(/\s+/,$string);
308 : sh002i 2858 unless ($time =~ /:/) {
309 :     { ##bare block for 'case" structure
310 :     $time =~ /(\d\d)(\d\d)/;
311 :     my $tmp_hour = $1;
312 :     my $tmp_min = $2;
313 :     if ($tmp_hour eq '00') {$time = "12:$tmp_min"; $AMPM = 'AM';last;}
314 :     if ($tmp_hour eq '12') {$time = "12:$tmp_min"; $AMPM = 'PM';last;}
315 :     if ($tmp_hour < 12) {$time = "$tmp_hour:$tmp_min"; $AMPM = 'AM';last;}
316 :     if ($tmp_hour < 24) {
317 :     $tmp_hour = $tmp_hour - 12;
318 :     $time = "$tmp_hour:$tmp_min";
319 :     $AMPM = 'PM';
320 :     }
321 :     } ##end of bare block for 'case" structure
322 :    
323 :     }
324 : gage 2893
325 : sh002i 2858 my ($mday, $mon, $year, $wday, $yday,$sec, $pm, $min, $hour);
326 :     $sec=0;
327 :     $time =~ /^([0-9]+)\s*\:\s*([0-9]*)/;
328 :     $min=$2;
329 :     $hour = $1;
330 :     if ($hour < 1 or $hour > 12) {
331 : gage 2868 die "Incorrect date/time format \"$orgString\". Hour must be in the range [1,12].
332 : gage 2893 Correct format is MM/DD/YYYY at HH:MM AMPM ZONE (e.g. \"03/29/2004 at 06:00am EST\")
333 :     date = $date
334 :     time = $time
335 :     ampm = $AMPM
336 :     zone = $TZ\n";
337 : sh002i 2858 }
338 :     if ($min < 0 or $min > 59) {
339 : gage 2893 die "Incorrect date/time format \"$orgString\". Minute must be in the range [0-59].
340 :     Correct format is MM/DD/YYYY at HH:MM AMPM ZONE
341 :     date = $date
342 :     time = $time
343 :     ampm = $AMPM
344 :     zone = $TZ\n";
345 : sh002i 2858 }
346 :     $pm = 0;
347 :     $pm = 12 if ($AMPM =~/PM/ and $hour < 12);
348 :     $hour += $pm;
349 :     $hour = 0 if ($AMPM =~/AM/ and $hour == 12);
350 :     $date =~ m!([0-9]+)\s*/\s*([0-9]+)/\s*([0-9]+)! ;
351 :     $mday =$2;
352 :     $mon=($1-1);
353 :     if ($mday < 1 or $mday > 31) {
354 : gage 2893 die "Incorrect date/time format \"$orgString\". Day must be in the range [1,31].
355 :     Correct format is MM/DD/YY at HH:MM AMPM ZONE
356 :     date = $date
357 :     time = $time
358 :     ampm = $AMPM
359 :     zone = $TZ\n";
360 : sh002i 2858 }
361 :     if ($mon < 0 or $mon > 11) {
362 : gage 2893 die "Incorrect date/time format \"$orgString\". Month must be in the range [1,12].
363 :     Correct format is MM/DD/YY at HH:MM AMPM ZONE
364 :     date = $date
365 :     time = $time
366 :     ampm = $AMPM
367 :     zone = $TZ\n";
368 : sh002i 2858 }
369 :     $year=$3;
370 :     $wday="";
371 :     $yday="";
372 :     return ($sec, $min, $hour, $mday, $mon, $year, $TZ);
373 :     }
374 :    
375 :    
376 : sh002i 2770 sub parseDateTime($;$) {
377 :     my ($string, $display_tz) = @_;
378 :     $display_tz ||= "local";
379 : sh002i 2774 #warn "parseDateTime('$string', '$display_tz')\n";
380 : sh002i 2770
381 : sh002i 2858 # use WeBWorK 1 date parsing routine
382 :     my ($second, $minute, $hour, $day, $month, $year, $zone) = unformatDateAndTime($string);
383 :     my $zone_str = defined $zone ? $zone : "UNDEF";
384 :     #warn "\tunformatDateAndTime: $second $minute $hour $day $month $year $zone_str\n";
385 : sh002i 2770
386 : sh002i 2858 # DateTime expects month 1-12, not 0-11
387 :     $month++;
388 : sh002i 2770
389 : sh002i 2858 # Do what Time::Local does to ambiguous years
390 :     {
391 :     my $ThisYear = (localtime())[5]; # FIXME: should be relative to $string's timezone
392 :     my $Breakpoint = ($ThisYear + 50) % 100;
393 :     my $NextCentury = $ThisYear - $ThisYear % 100;
394 :     $NextCentury += 100 if $Breakpoint < 50;
395 :     my $Century = $NextCentury - 100;
396 :     my $SecOff = 0;
397 :    
398 :     if ($year >= 1000) {
399 :     # leave alone
400 :     } elsif ($year < 100 and $year >= 0) {
401 :     $year += ($year > $Breakpoint) ? $Century : $NextCentury;
402 :     $year += 1900;
403 :     } else {
404 :     $year += 1900;
405 :     }
406 :     }
407 : sh002i 2770
408 : sh002i 2858 my $epoch;
409 :    
410 :     if (defined $zone and $zone ne "") {
411 :     if (DateTime::TimeZone->is_valid_name($zone)) {
412 :     #warn "\t\$zone is valid according to DateTime::TimeZone\n";
413 :    
414 :     my $dt = new DateTime(
415 :     year => $year,
416 :     month => $month,
417 :     day => $day,
418 :     hour => $hour,
419 :     minute => $minute,
420 :     second => $second,
421 :     time_zone => $zone,
422 :     );
423 :     #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
424 :    
425 :     $epoch = $dt->epoch;
426 :     #warn "\t\$dt->epoch = $epoch\n";
427 :     } else {
428 :     #warn "\t\$zone is invalid according to DateTime::TimeZone, so we ask Time::Zone\n";
429 :    
430 :     # treat the date/time as UTC
431 :     my $dt = new DateTime(
432 :     year => $year,
433 :     month => $month,
434 :     day => $day,
435 :     hour => $hour,
436 :     minute => $minute,
437 :     second => $second,
438 :     time_zone => "UTC",
439 :     );
440 :     #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
441 :    
442 :     # convert to an epoch value
443 :     my $utc_epoch = $dt->epoch
444 :     or die "Date/time '$string' not representable as an epoch. Get more bits!\n";
445 :     #warn "\t\$utc_epoch = $utc_epoch\n";
446 :    
447 :     # get offset for supplied timezone and utc_epoch
448 :     my $offset = tz_offset($zone, $utc_epoch) or die "Time zone '$zone' not recognized.\n";
449 :     #warn "\t\$zone is valid according to Time::Zone (\$offset = $offset)\n";
450 :    
451 :     #$epoch = $utc_epoch + $offset;
452 :     ##warn "\t\$epoch = \$utc_epoch + \$offset = $epoch\n";
453 :    
454 :     $dt->subtract(seconds => $offset);
455 :     #warn "\t\$dt - \$offset = ", $dt->strftime(DATE_FORMAT), "\n";
456 :    
457 :     $epoch = $dt->epoch;
458 :     #warn "\t\$epoch = $epoch\n";
459 :     }
460 :     } else {
461 :     #warn "\t\$zone not supplied, using \$display_tz\n";
462 :    
463 :     my $dt = new DateTime(
464 :     year => $year,
465 :     month => $month,
466 :     day => $day,
467 :     hour => $hour,
468 :     minute => $minute,
469 :     second => $second,
470 :     time_zone => $display_tz,
471 :     );
472 :     #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
473 :    
474 :     $epoch = $dt->epoch;
475 :     #warn "\t\$epoch = $epoch\n";
476 :     }
477 :    
478 :     return $epoch;
479 : sh002i 412 }
480 :    
481 : sh002i 2770 =item $string = formatDateTime($dateTime, $display_tz)
482 :    
483 :     Formats the UNIX datetime $dateTime in the standard WeBWorK datetime format.
484 :     $dateTime is assumed to be in the server's time zone. If $display_tz is given,
485 :     the datetime is converted from the server's timezone to the timezone specified.
486 :    
487 :     =cut
488 :    
489 :     sub formatDateTime($;$) {
490 :     my ($dateTime, $display_tz) = @_;
491 :     $display_tz ||= "local";
492 :     #warn "formatDateTime('$dateTime', '$display_tz')\n";
493 :    
494 :     my $dt = DateTime->from_epoch(epoch => $dateTime, time_zone => $display_tz);
495 : sh002i 2858 #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
496 : sh002i 2770 return $dt->strftime(DATE_FORMAT);
497 : sh002i 412 }
498 : sh002i 422
499 : sh002i 2770 =item $string = textDateTime($string_or_dateTime)
500 :    
501 :     Accepts a UNIX datetime or a formatted string, returns a formatted string.
502 :    
503 :     =cut
504 :    
505 : sh002i 2744 sub textDateTime($) {
506 :     return ($_[0] =~ m/^\d*$/) ? formatDateTime($_[0]) : $_[0];
507 :     }
508 :    
509 : sh002i 2770 =item $dateTIme = intDateTime($string_or_dateTime)
510 :    
511 :     Accepts a UNIX datetime or a formatted string, returns a UNIX datetime.
512 :    
513 :     =cut
514 :    
515 : sh002i 2744 sub intDateTime($) {
516 :     return ($_[0] =~ m/^\d*$/) ? $_[0] : parseDateTime($_[0]);
517 :     }
518 :    
519 : sh002i 2770 =back
520 :    
521 :     =cut
522 :    
523 : sh002i 2769 ################################################################################
524 :     # Logging
525 :     ################################################################################
526 :    
527 : sh002i 562 sub writeLog($$@) {
528 :     my ($ce, $facility, @message) = @_;
529 :     unless ($ce->{webworkFiles}->{logs}->{$facility}) {
530 :     warn "There is no log file for the $facility facility defined.\n";
531 :     return;
532 :     }
533 :     my $logFile = $ce->{webworkFiles}->{logs}->{$facility};
534 :     local *LOG;
535 :     if (open LOG, ">>", $logFile) {
536 :     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
537 :     close LOG;
538 :     } else {
539 :     warn "failed to open $logFile for writing: $!";
540 :     }
541 :     }
542 : sh002i 558
543 : gage 1387 sub writeCourseLog($$@) {
544 :     my ($ce, $facility, @message) = @_;
545 :     unless ($ce->{courseFiles}->{logs}->{$facility}) {
546 :     warn "There is no course log file for the $facility facility defined.\n";
547 :     return;
548 :     }
549 :     my $logFile = $ce->{courseFiles}->{logs}->{$facility};
550 :     local *LOG;
551 :     if (open LOG, ">>", $logFile) {
552 :     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
553 :     close LOG;
554 :     } else {
555 :     warn "failed to open $logFile for writing: $!";
556 :     }
557 :     }
558 :    
559 : sh002i 631 # $ce - a WeBWork::CourseEnvironment object
560 :     # $function - fully qualified function name
561 :     # $details - any information, do not use the characters '[' or ']'
562 : sh002i 692 # $beginEnd - the string "begin", "intermediate", or "end"
563 :     # use the intermediate step begun or completed for INTERMEDIATE
564 : sh002i 631 # use an empty string for $details when calling for END
565 : sh002i 562 sub writeTimingLogEntry($$$$) {
566 :     my ($ce, $function, $details, $beginEnd) = @_;
567 :     return unless defined $ce->{webworkFiles}->{logs}->{timing};
568 : sh002i 692 $beginEnd = ($beginEnd eq "begin") ? ">" : ($beginEnd eq "end") ? "<" : "-";
569 : sh002i 562 writeLog($ce, "timing", "$$ ".time." $beginEnd $function [$details]");
570 :     }
571 :    
572 : sh002i 2769 ################################################################################
573 :     # Data munging
574 :     ################################################################################
575 :    
576 :     sub list2hash(@) {
577 : malsyned 970 map {$_ => "0"} @_;
578 :     }
579 :    
580 : sh002i 2769 sub refBaseType($) {
581 :     my $ref = shift;
582 :     $ref =~ m/(\w+)\(/; # this might not be robust...
583 :     return $1;
584 : malsyned 970 }
585 :    
586 : sh002i 424 sub ref2string($;$);
587 :     sub ref2string($;$) {
588 :     my $ref = shift;
589 :     my $dontExpand = shift || {};
590 :     my $refType = ref $ref;
591 : sh002i 422 my $result;
592 : sh002i 424 if ($refType and not $dontExpand->{$refType}) {
593 :     my $baseType = refBaseType($ref);
594 :     $result .= '<font size="1" color="grey">' . $refType;
595 : sh002i 425 $result .= " ($baseType)" if $baseType and $refType ne $baseType;
596 : sh002i 424 $result .= ":</font><br>";
597 :     $result .= '<table border="1" cellpadding="2">';
598 :     if ($baseType eq "HASH") {
599 :     my %hash = %$ref;
600 :     foreach (sort keys %hash) {
601 :     $result .= '<tr valign="top">';
602 :     $result .= "<td>$_</td>";
603 :     $result .= "<td>" . ref2string($hash{$_}, $dontExpand) . "</td>";
604 :     $result .= "</tr>";
605 :     }
606 :     } elsif ($baseType eq "ARRAY") {
607 :     my @array = @$ref;
608 : sh002i 429 # special case for Problem, Set, and User objects, which are defined
609 :     # using lists and contain a @FIELDS package variable:
610 :     no strict 'refs';
611 :     my @FIELDS = eval { @{$refType."::FIELDS"} };
612 :     use strict 'refs';
613 :     undef @FIELDS unless scalar @FIELDS == scalar @array and not $@;
614 : sh002i 424 foreach (0 .. $#array) {
615 :     $result .= '<tr valign="top">';
616 :     $result .= "<td>$_</td>";
617 : sh002i 429 $result .= "<td>".$FIELDS[$_]."</td>" if @FIELDS;
618 : sh002i 424 $result .= "<td>" . ref2string($array[$_], $dontExpand) . "</td>";
619 :     $result .= "</tr>";
620 :     }
621 :     } elsif ($baseType eq "SCALAR") {
622 :     my $scalar = $$ref;
623 :     $result .= '<tr valign="top">';
624 :     $result .= "<td>$scalar</td>";
625 :     $result .= "</tr>";
626 : sh002i 422 } else {
627 : sh002i 424 # perhaps a coderef? in any case, i don't feel like dealing with it!
628 :     $result .= '<tr valign="top">';
629 :     $result .= "<td>$ref</td>";
630 :     $result .= "</tr>";
631 : sh002i 422 }
632 : sh002i 424 $result .= "</table>"
633 : sh002i 422 } else {
634 : sh002i 424 $result .= defined $ref ? $ref : '<font color="red">undef</font>';
635 :     }
636 : sh002i 422 }
637 :    
638 : sh002i 2769 sub decodeAnswers($) {
639 :     my $string = shift;
640 :     return unless defined $string and $string;
641 :     my @array = split m/##/, $string;
642 :     $array[$_] =~ s/\\#\\/#/g foreach 0 .. $#array;
643 :     push @array, "" if @array%2;
644 :     return @array; # it's actually a hash ;)
645 : sh002i 422 }
646 :    
647 : sh002i 2769 sub encodeAnswers(\%\@) {
648 :     my %hash = %{ shift() };
649 :     my @order = @{ shift() };
650 :     my $string = "";
651 :     foreach my $name (@order) {
652 :     my $value = defined $hash{$name} ? $hash{$name} : "";
653 :     $name =~ s/#/\\#\\/g; # this is a WEIRD way to escape things
654 :     $value =~ s/#/\\#\\/g; # and it's not my fault!
655 :     if ($value =~ m/\\$/) {
656 :     # if the value ends with a backslash, string2hash will
657 :     # interpret that as a normal escape sequence (not part
658 :     # of the weird pound escape sequence) if the next
659 :     # character is &. So we have to protect against this.
660 :     # will adding a spcae at the end of the last answer
661 :     # hurt anything? i don't think so...
662 :     $value .= " ";
663 : sh002i 1111 }
664 : sh002i 2769 $string .= "$name##$value##"; # this is also not my fault
665 :     }
666 :     $string =~ s/##$//; # remove last pair of hashs
667 :     return $string;
668 : sh002i 1111 }
669 :    
670 : sh002i 2769 sub max(@) {
671 :     my $soFar;
672 :     foreach my $item (@_) {
673 :     $soFar = $item unless defined $soFar;
674 :     if ($item > $soFar) {
675 :     $soFar = $item;
676 :     }
677 :     }
678 :     return defined $soFar ? $soFar : 0;
679 : sh002i 1145 }
680 :    
681 : sh002i 2769 sub pretty_print_rh($) {
682 : gage 1137 my $rh = shift;
683 :     foreach my $key (sort keys %{$rh}) {
684 :     warn " $key => ",$rh->{$key},"\n";
685 :     }
686 :     }
687 : sh002i 1145
688 : sh002i 2769 sub cryptPassword($) {
689 : malsyned 1287 my ($clearPassword) = @_;
690 :     my $salt = join("", ('.','/','0'..'9','A'..'Z','a'..'z')[rand 64, rand 64]);
691 :     my $cryptPassword = crypt($clearPassword, $salt);
692 :     return $cryptPassword;
693 :     }
694 :    
695 : sh002i 1900 # from the Perl Cookbook, first edition, page 25:
696 :     sub dequote($) {
697 :     local $_ = shift;
698 :     my ($white, $leader); # common whitespace and common leading string
699 :     if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {
700 :     ($white, $leader) = ($2, quotemeta($1));
701 :     } else {
702 :     ($white, $leader) = (/^(\s+)/, '');
703 :     }
704 :     s/^\s*?$leader(?:$white)?//gm;
705 :     return $_;
706 :     }
707 :    
708 : sh002i 1945 sub undefstr($@) {
709 :     map { defined $_ ? $_ : $_[0] } @_[1..$#_];
710 :     }
711 :    
712 : sh002i 2769 ################################################################################
713 :     # Sorting
714 :     ################################################################################
715 :    
716 :     # p. 101, Camel, 3rd ed.
717 :     # The <=> and cmp operators return -1 if the left operand is less than the
718 :     # right operand, 0 if they are equal, and +1 if the left operand is greater
719 :     # than the right operand.
720 :     sub sortByName($@) {
721 :     my ($field, @items) = @_;
722 :     return sort {
723 :     my @aParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, defined $field ? $a->$field : $a;
724 :     my @bParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, defined $field ? $b->$field : $b;
725 :     while (@aParts and @bParts) {
726 :     my $aPart = shift @aParts;
727 :     my $bPart = shift @bParts;
728 :     my $aNumeric = $aPart =~ m/^\d*$/;
729 :     my $bNumeric = $bPart =~ m/^\d*$/;
730 :    
731 :     # numbers should come before words
732 :     return -1 if $aNumeric and not $bNumeric;
733 :     return +1 if not $aNumeric and $bNumeric;
734 :    
735 :     # both have the same type
736 :     if ($aNumeric and $bNumeric) {
737 :     next if $aPart == $bPart; # check next pair
738 :     return $aPart <=> $bPart; # compare numerically
739 :     } else {
740 :     next if $aPart eq $bPart; # check next pair
741 :     return $aPart cmp $bPart; # compare lexicographically
742 :     }
743 :     }
744 :     return +1 if @aParts; # a has more sections, should go second
745 :     return -1 if @bParts; # a had fewer sections, should go first
746 :     } @items;
747 :     }
748 :    
749 : sh002i 422 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9