Annotation of loncom/metadata_database/LONCAPA/lonmetadata.pm, revision 1.10
1.1 matthew 1: # The LearningOnline Network with CAPA
2: #
1.10 ! matthew 3: # $Id: lonmetadata.pm,v 1.9 2004/04/23 20:30:07 matthew Exp $
1.1 matthew 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: ######################################################################
28:
29: package LONCAPA::lonmetadata;
30:
31: use strict;
32: use DBI;
33:
34: ######################################################################
35: ######################################################################
36:
37: =pod
38:
39: =head1 Name
40:
41: lonmetadata
42:
43: =head1 Synopsis
44:
45: lonmetadata holds a description of the metadata table and provides
46: wrappers for the storage and retrieval of metadata to/from the database.
47:
48: =head1 Description
49:
50: =head1 Methods
51:
52: =over 4
53:
54: =cut
55:
56: ######################################################################
57: ######################################################################
58:
59: =pod
60:
61: =item Old table creation command
62:
63: CREATE TABLE IF NOT EXISTS metadata
64: (title TEXT,
65: author TEXT,
66: subject TEXT,
67: url TEXT,
68: keywords TEXT,
69: version TEXT,
70: notes TEXT,
71: abstract TEXT,
72: mime TEXT,
73: language TEXT,
74: creationdate DATETIME,
75: lastrevisiondate DATETIME,
76: owner TEXT,
77: copyright TEXT,
78:
79: FULLTEXT idx_title (title),
80: FULLTEXT idx_author (author),
81: FULLTEXT idx_subject (subject),
82: FULLTEXT idx_url (url),
83: FULLTEXT idx_keywords (keywords),
84: FULLTEXT idx_version (version),
85: FULLTEXT idx_notes (notes),
86: FULLTEXT idx_abstract (abstract),
87: FULLTEXT idx_mime (mime),
88: FULLTEXT idx_language (language),
89: FULLTEXT idx_owner (owner),
90: FULLTEXT idx_copyright (copyright))
91:
92: TYPE=MYISAM;
93:
94: =cut
95:
96: ######################################################################
97: ######################################################################
98: my @Metadata_Table_Description =
99: (
100: { name => 'title', type=>'TEXT'},
101: { name => 'author', type=>'TEXT'},
102: { name => 'subject', type=>'TEXT'},
103: { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' },
104: { name => 'keywords', type=>'TEXT'},
105: { name => 'version', type=>'TEXT'},
106: { name => 'notes', type=>'TEXT'},
107: { name => 'abstract', type=>'TEXT'},
108: { name => 'mime', type=>'TEXT'},
109: { name => 'language', type=>'TEXT'},
110: { name => 'creationdate', type=>'DATETIME'},
111: { name => 'lastrevisiondate', type=>'DATETIME'},
112: { name => 'owner', type=>'TEXT'},
113: { name => 'copyright', type=>'TEXT'},
114: #--------------------------------------------------
115: { name => 'dependencies', type=>'TEXT'},
116: { name => 'modifyinguser', type=>'TEXT'},
117: { name => 'authorspace', type=>'TEXT'},
118: { name => 'lowestgradelevel', type=>'INT'},
119: { name => 'highestgradelevel', type=>'INT'},
120: { name => 'standards', type=>'TEXT'},
121: { name => 'count', type=>'INT'},
122: { name => 'course', type=>'INT'},
123: { name => 'course_list', type=>'TEXT'},
124: { name => 'goto', type=>'INT'},
125: { name => 'goto_list', type=>'TEXT'},
126: { name => 'comefrom', type=>'INT'},
127: { name => 'comefrom_list', type=>'TEXT'},
128: { name => 'sequsage', type=>'INT'},
129: { name => 'sequsage_list', type=>'TEXT'},
130: { name => 'stdno', type=>'INT'},
131: { name => 'stdno_list', type=>'TEXT'},
132: { name => 'avetries', type=>'FLOAT'},
133: { name => 'avetries_list', type=>'TEXT'},
134: { name => 'difficulty', type=>'FLOAT'},
135: { name => 'difficulty_list',type=>'TEXT'},
1.9 matthew 136: { name => 'disc', type=>'FLOAT'},
137: { name => 'disc_list', type=>'TEXT'},
1.1 matthew 138: { name => 'clear', type=>'FLOAT'},
139: { name => 'technical', type=>'FLOAT'},
140: { name => 'correct', type=>'FLOAT'},
141: { name => 'helpful', type=>'FLOAT'},
142: { name => 'depth', type=>'FLOAT'},
143: { name => 'hostname', type=> 'TEXT'},
144: #--------------------------------------------------
145: );
146:
147: my @Fulltext_indicies = qw/
148: title
149: author
150: subject
151: url
152: keywords
153: version
154: notes
155: abstract
156: mime
157: language
158: owner
159: copyright/;
160:
161: ######################################################################
162: ######################################################################
163:
164: =pod
165:
166: =item &describe_metadata_storage
167:
168: Input: None
169:
1.2 matthew 170: Returns: An array of hash references describing the columns and indicies
171: of the metadata table(s).
1.1 matthew 172:
173: =cut
174:
175: ######################################################################
176: ######################################################################
177: sub describe_metadata_storage {
178: return (\@Metadata_Table_Description,\@Fulltext_indicies);
179: }
180:
181: ######################################################################
182: ######################################################################
183:
184: =pod
185:
186: =item create_metadata_storage()
187:
1.3 matthew 188: Inputs: table name (optional): the name of the table. Default is 'metadata'.
1.1 matthew 189:
190: Returns: A perl string which, when executed by MySQL, will cause the
191: metadata storage to be initialized.
192:
193: =cut
194:
195: ######################################################################
196: ######################################################################
197: sub create_metadata_storage {
1.3 matthew 198: my ($tablename) = @_;
199: $tablename = 'metadata' if (! defined($tablename));
1.1 matthew 200: my $request = "CREATE TABLE IF NOT EXISTS ".$tablename." ";
201: #
202: # Process the columns (this code is stolen from lonmysql.pm)
203: my @Columns;
204: my $col_des; # mysql column description
205: foreach my $coldata (@Metadata_Table_Description) {
206: my $column = $coldata->{'name'};
207: $col_des = '';
208: if (lc($coldata->{'type'}) =~ /(enum|set)/) { # 'enum' or 'set'
209: $col_des.=$column." ".$coldata->{'type'}."('".
210: join("', '",@{$coldata->{'values'}})."')";
211: } else {
212: $col_des.=$column." ".$coldata->{'type'};
213: if (exists($coldata->{'size'})) {
214: $col_des.="(".$coldata->{'size'}.")";
215: }
216: }
217: # Modifiers
218: if (exists($coldata->{'restrictions'})){
219: $col_des.=" ".$coldata->{'restrictions'};
220: }
221: if (exists($coldata->{'default'})) {
222: $col_des.=" DEFAULT '".$coldata->{'default'}."'";
223: }
224: $col_des.=' AUTO_INCREMENT' if (exists($coldata->{'auto_inc'}) &&
225: ($coldata->{'auto_inc'} eq 'yes'));
226: $col_des.=' PRIMARY KEY' if (exists($coldata->{'primary_key'}) &&
227: ($coldata->{'primary_key'} eq 'yes'));
228: } continue {
229: # skip blank items.
230: push (@Columns,$col_des) if ($col_des ne '');
231: }
232: foreach my $colname (@Fulltext_indicies) {
233: my $text = 'FULLTEXT idx_'.$colname.' ('.$colname.')';
234: push (@Columns,$text);
235: }
1.3 matthew 236: $request .= "(".join(", ",@Columns).") TYPE=MyISAM";
1.1 matthew 237: return $request;
238: }
239:
240: ######################################################################
241: ######################################################################
242:
243: =pod
244:
245: =item store_metadata()
246:
1.4 matthew 247: Inputs: database handle ($dbh), a table name, and a hash or hash reference
248: containing the metadata for a single resource.
1.1 matthew 249:
250: Returns: 1 on success, 0 on failure to store.
251:
252: =cut
253:
254: ######################################################################
255: ######################################################################
1.2 matthew 256: {
257: ##
258: ## WARNING: The following cleverness may cause trouble in cases where
259: ## the dbi connection is dropped and recreated - a stale statement
260: ## handler may linger around and cause trouble.
261: ##
262: ## In most scripts, this will work fine. If the dbi is going to be
263: ## dropped and (possibly) later recreated, call &clear_sth. Yes it
264: ## is annoying but $sth appearantly does not have a link back to the
265: ## $dbh, so we can't check our validity.
266: ##
267: my $sth = undef;
1.4 matthew 268: my $sth_table = undef;
1.2 matthew 269:
270: sub create_statement_handler {
271: my $dbh = shift();
1.4 matthew 272: my $tablename = shift();
273: $tablename = 'metadata' if (! defined($tablename));
274: $sth_table = $tablename;
275: my $request = 'INSERT INTO '.$tablename.' VALUES(';
1.2 matthew 276: foreach (@Metadata_Table_Description) {
277: $request .= '?,';
278: }
279: chop $request;
280: $request.= ')';
281: $sth = $dbh->prepare($request);
282: return;
283: }
284:
1.4 matthew 285: sub clear_sth { $sth=undef; $sth_table=undef;}
1.2 matthew 286:
1.1 matthew 287: sub store_metadata {
1.10 ! matthew 288: my ($dbh,$tablename,@Metadata)=@_;
1.2 matthew 289: my $errors = '';
1.4 matthew 290: if (! defined($sth) ||
291: ( defined($tablename) && ($sth_table ne $tablename)) ||
292: (! defined($tablename) && $sth_table ne 'metadata')) {
293: &create_statement_handler($dbh,$tablename);
1.2 matthew 294: }
295: my $successcount = 0;
1.10 ! matthew 296: foreach my $mdata (@Metadata) {
1.2 matthew 297: next if (ref($mdata) ne "HASH");
298: my @MData;
299: foreach my $field (@Metadata_Table_Description) {
1.10 ! matthew 300: my $fname = $field->{'name'};
! 301: if (exists($mdata->{$fname}) &&
! 302: defined($mdata->{$fname}) &&
! 303: $mdata->{$fname} ne '') {
! 304: if ($mdata->{$fname} eq 'nan' ||
! 305: $mdata->{$fname} eq '') {
1.5 matthew 306: push(@MData,'NULL');
307: } else {
1.10 ! matthew 308: push(@MData,$mdata->{$fname});
1.5 matthew 309: }
1.2 matthew 310: } else {
311: push(@MData,undef);
312: }
313: }
314: $sth->execute(@MData);
315: if (! $sth->err) {
316: $successcount++;
317: } else {
318: $errors = join(',',$errors,$sth->errstr);
319: }
1.10 ! matthew 320: $errors =~ s/^,//;
1.2 matthew 321: }
322: if (wantarray()) {
323: return ($successcount,$errors);
324: } else {
325: return $successcount;
326: }
327: }
1.1 matthew 328:
329: }
330:
331: ######################################################################
332: ######################################################################
333:
334: =pod
335:
336: =item lookup_metadata()
337:
338: Inputs: database handle ($dbh) and a hash or hash reference containing
339: metadata which will be used for a search.
340:
1.2 matthew 341: Returns: scalar with error string on failure, array reference on success.
342: The array reference is the same one returned by $sth->fetchall_arrayref().
1.1 matthew 343:
344: =cut
345:
346: ######################################################################
347: ######################################################################
1.2 matthew 348: sub lookup_metadata {
1.10 ! matthew 349: my ($dbh,$condition,$fetchparameter,$tablename) = @_;
! 350: $tablename = 'metadata' if (! defined($tablename));
1.2 matthew 351: my $error;
352: my $returnvalue=[];
1.10 ! matthew 353: my $request = 'SELECT * FROM '.$tablename;
1.2 matthew 354: if (defined($condition)) {
355: $request .= ' WHERE '.$condition;
356: }
357: my $sth = $dbh->prepare($request);
358: if ($sth->err) {
359: $error = $sth->errstr;
360: }
361: if (! $error) {
362: $sth->execute();
363: if ($sth->err) {
364: $error = $sth->errstr;
365: } else {
366: $returnvalue = $sth->fetchall_arrayref($fetchparameter);
367: if ($sth->err) {
368: $error = $sth->errstr;
369: }
370: }
371: }
372: return ($error,$returnvalue);
373: }
1.1 matthew 374:
375: ######################################################################
376: ######################################################################
377:
378: =pod
379:
380: =item delete_metadata()
381:
1.10 ! matthew 382: Removes a single metadata record, based on its url.
! 383:
! 384: Inputs: $dbh, the database handler.
! 385: $tablename, the name of the metadata table to remove from. default: 'metadata'
! 386: $url, the url of the resource to remove from the metadata database.
! 387:
! 388: Returns: undef on success, dbh errorstr on failure.
! 389:
! 390: =cut
! 391:
! 392: ######################################################################
! 393: ######################################################################
! 394: sub delete_metadata {
! 395: my ($dbh,$tablename,$url) = @_;
! 396: $tablename = 'metadata' if (! defined($tablename));
! 397: my $error;
! 398: my $delete_command = 'DELETE FROM '.$tablename.' WHERE url='.
! 399: $dbh->quote($url);
! 400: $dbh->do($delete_command);
! 401: if ($dbh->err) {
! 402: $error = $dbh->errstr();
! 403: }
! 404: return $error;
! 405: }
! 406:
! 407: ######################################################################
! 408: ######################################################################
! 409:
! 410: =pod
! 411:
! 412: =item update_metadata
! 413:
! 414: Updates metadata record in mysql database. It does not matter if the record
! 415: currently exists. Fields not present in the new metadata will be taken
! 416: from the current record, if it exists. To delete an entry for a key, set
! 417: it to "" or undef.
! 418:
! 419: Inputs:
! 420: $dbh, database handle
! 421: $newmetadata, hash reference containing the new metadata
! 422: $tablename, metadata table name. Defaults to 'metadata'.
! 423:
! 424: Returns:
! 425: $error on failure. undef on success.
1.1 matthew 426:
427: =cut
428:
429: ######################################################################
430: ######################################################################
1.10 ! matthew 431: sub update_metadata {
! 432: my ($dbh,$tablename,$newmetadata)=@_;
! 433: my $error;
! 434: $tablename = 'metadata' if (! defined($tablename));
! 435: if (! exists($newmetadata->{'url'})) {
! 436: $error = 'Unable to update: no url specified';
! 437: }
! 438: return $error if (defined($error));
! 439: #
! 440: # Retrieve current values
! 441: my $row;
! 442: ($error,$row) = &lookup_metadata($dbh,
! 443: ' url='.$dbh->quote($newmetadata->{'url'}),
! 444: undef,$tablename);
! 445: return $error if ($error);
! 446: my %metadata = &LONCAPA::lonmetadata::metadata_col_to_hash(@{$row->[0]});
! 447: #
! 448: # Update metadata values
! 449: while (my ($key,$value) = each(%$newmetadata)) {
! 450: $metadata{$key} = $value;
! 451: }
! 452: #
! 453: # Delete old data (deleting a nonexistant record does not produce an error.
! 454: $error = &delete_metadata($dbh,$tablename,$newmetadata->{'url'});
! 455: return $error if (defined($error));
! 456: #
! 457: # Store updated metadata
! 458: my $success;
! 459: ($success,$error) = &store_metadata($dbh,$tablename,\%metadata);
! 460: return $error;
! 461: }
1.1 matthew 462:
463: ######################################################################
464: ######################################################################
1.5 matthew 465:
1.6 matthew 466: =pod
467:
468: =item metdata_col_to_hash
469:
470: Input: Array of metadata columns
471:
472: Return: Hash with the metadata columns as keys and the array elements
473: passed in as values
474:
475: =cut
476:
477: ######################################################################
478: ######################################################################
479: sub metadata_col_to_hash {
480: my @cols=@_;
481: my %hash=();
482: for (my $i=0; $i<=$#Metadata_Table_Description;$i++) {
483: $hash{$Metadata_Table_Description[$i]->{'name'}}=$cols[$i];
484: }
485: return %hash;
486: }
1.5 matthew 487:
488: ######################################################################
489: ######################################################################
490:
491: =pod
492:
1.8 matthew 493: =item nohist_resevaldata.db data structure
494:
495: The nohist_resevaldata.db file has the following possible keys:
496:
497: Statistics Data (values are integers, perl times, or real numbers)
498: ------------------------------------------
499: $course___$resource___avetries
500: $course___$resource___count
501: $course___$resource___difficulty
502: $course___$resource___stdno
503: $course___$resource___timestamp
504:
505: Evaluation Data (values are on a 1 to 5 scale)
506: ------------------------------------------
507: $username@$dom___$resource___clear
508: $username@$dom___$resource___comments
509: $username@$dom___$resource___depth
510: $username@$dom___$resource___technical
511: $username@$dom___$resource___helpful
512:
513: Course Context Data
514: ------------------------------------------
515: $course___$resource___course course id
516: $course___$resource___comefrom resource preceeding this resource
517: $course___$resource___goto resource following this resource
518: $course___$resource___usage resource containing this resource
519:
520: New statistical data storage
521: ------------------------------------------
522: $course&$sec&$numstud___$resource___stats
523: $sec is a string describing the sections: all, 1 2, 1 2 3,...
524: Value is a '&' deliminated list of key=value pairs.
525: Possible keys are (currently) disc,course,sections,difficulty,
526: stdno, timestamp
527:
528: =cut
529:
530: ######################################################################
531: ######################################################################
532:
533: =pod
534:
1.5 matthew 535: =item &process_reseval_data
536:
537: Process a nohist_resevaldata hash into a more complex data structure.
538:
539: Input: Hash reference containing reseval data
540:
541: Returns: Hash with the following structure:
542:
543: $hash{$url}->{'statistics'}->{$courseid}->{'avetries'} = $value
544: $hash{$url}->{'statistics'}->{$courseid}->{'count'} = $value
545: $hash{$url}->{'statistics'}->{$courseid}->{'difficulty'} = $value
546: $hash{$url}->{'statistics'}->{$courseid}->{'stdno'} = $value
547: $hash{$url}->{'statistics'}->{$courseid}->{'timestamp'} = $value
548:
549: $hash{$url}->{'evaluation'}->{$username}->{'clear'} = $value
550: $hash{$url}->{'evaluation'}->{$username}->{'comments'} = $value
551: $hash{$url}->{'evaluation'}->{$username}->{'depth'} = $value
552: $hash{$url}->{'evaluation'}->{$username}->{'technical'} = $value
553: $hash{$url}->{'evaluation'}->{$username}->{'helpful'} = $value
554:
555: $hash{$url}->{'course'} = \@Courses
556: $hash{$url}->{'comefrom'} = \@Resources
557: $hash{$url}->{'goto'} = \@Resources
558: $hash{$url}->{'usage'} = \@Resources
559:
560: $hash{$url}->{'stats'}->{$courseid\_$section}->{$key} = $value
561:
562: =cut
563:
564: ######################################################################
565: ######################################################################
566: sub process_reseval_data {
567: my ($evaldata) = @_;
568: my %DynamicData;
569: #
570: # Process every stored element
571: while (my ($storedkey,$value) = each(%{$evaldata})) {
572: my ($source,$file,$type) = split('___',$storedkey);
573: $source = &unescape($source);
574: $file = &unescape($file);
575: $value = &unescape($value);
576: " got ".$file."\n ".$type." ".$source."\n";
577: if ($type =~ /^(avetries|count|difficulty|stdno|timestamp)$/) {
578: #
579: # Statistics: $source is course id
580: $DynamicData{$file}->{'statistics'}->{$source}->{$type}=$value;
581: } elsif ($type =~ /^(clear|comments|depth|technical|helpful)$/){
582: #
583: # Evaluation $source is username, check if they evaluated it
584: # more than once. If so, pad the entry with a space.
585: while(exists($DynamicData{$file}->{'evaluation'}->{$type}->{$source})) {
586: $source .= ' ';
587: }
588: $DynamicData{$file}->{'evaluation'}->{$type}->{$source}=$value;
589: } elsif ($type =~ /^(course|comefrom|goto|usage)$/) {
590: #
591: # Context $source is course id or resource
592: push(@{$DynamicData{$file}->{$type}},&unescape($source));
593: } elsif ($type eq 'stats') {
594: #
595: # Statistics storage...
596: # $source is $cid\_$sec\_$stdno
597: # $value is stat1=value&stat2=value&stat3=value,....
598: #
1.8 matthew 599: my ($cid,$sec,$stdno)=split('&',$source);
600: my $crssec = $cid.'&'.$sec;
1.5 matthew 601: my @Data = split('&',$value);
602: my %Statistics;
603: while (my ($key,$value) = split('=',pop(@Data))) {
604: $Statistics{$key} = $value;
605: }
1.8 matthew 606: $sec =~ s:("$|^")::g;
607: $Statistics{'sections'} = $sec;
1.5 matthew 608: #
609: # Only store the data if the number of students is greater
610: # than the data already stored
611: if (! exists($DynamicData{$file}->{'stats'}->{$crssec}) ||
612: $DynamicData{$file}->{'stats'}->{$crssec}->{'stdno'}<$stdno){
613: $DynamicData{$file}->{'stats'}->{$crssec}=\%Statistics;
614: }
615: }
616: }
617: return %DynamicData;
618: }
619:
620:
621: ######################################################################
622: ######################################################################
623:
624: =pod
625:
626: =item &process_dynamic_metadata
627:
628: Inputs: $url: the url of the item to process
629: $DynamicData: hash reference for the results of &process_reseval_data
630:
631: Returns: Hash containing the following keys:
632: avetries, avetries_list, difficulty, difficulty_list, stdno, stdno_list,
633: course, course_list, goto, goto_list, comefrom, comefrom_list,
634: usage, clear, technical, correct, helpful, depth, comments
635:
636: Each of the return keys is associated with either a number or a string
637: The *_list items are comma-seperated strings. 'comments' is a string
638: containing generically marked-up comments.
639:
640: =cut
641:
642: ######################################################################
643: ######################################################################
644: sub process_dynamic_metadata {
645: my ($url,$DynamicData) = @_;
646: my %data;
647: my $resdata = $DynamicData->{$url};
648: #
1.8 matthew 649: # Get the statistical data - Use a weighted average
650: foreach my $type (qw/avetries difficulty disc/) {
651: my $studentcount;
1.5 matthew 652: my $sum;
653: my @Values;
1.8 matthew 654: my @Students;
1.5 matthew 655: #
1.8 matthew 656: # Old data
1.5 matthew 657: foreach my $coursedata (values(%{$resdata->{'statistics'}}),
658: values(%{$resdata->{'stats'}})) {
659: if (ref($coursedata) eq 'HASH' && exists($coursedata->{$type})) {
1.8 matthew 660: $studentcount += $coursedata->{'stdno'};
661: $sum += ($coursedata->{$type}*$coursedata->{'stdno'});
1.5 matthew 662: push(@Values,$coursedata->{$type});
1.8 matthew 663: push(@Students,$coursedata->{'stdno'});
1.5 matthew 664: }
665: }
1.8 matthew 666: if (exists($resdata->{'stats'})) {
667: foreach my $identifier (sort(keys(%{$resdata->{'stats'}}))) {
668: my $coursedata = $resdata->{'stats'}->{$identifier};
669: $studentcount += $coursedata->{'stdno'};
670: $sum += $coursedata->{$type}*$coursedata->{'stdno'};
671: push(@Values,$coursedata->{$type});
672: push(@Students,$coursedata->{'stdno'});
673: }
674: }
675: #
676: # New data
677: if (defined($studentcount) && $studentcount>0) {
678: $data{$type} = $sum/$studentcount;
1.5 matthew 679: $data{$type.'_list'} = join(',',@Values);
680: }
681: }
682: #
1.8 matthew 683: # Find out the number of students who have completed the resource...
684: my $stdno;
685: foreach my $coursedata (values(%{$resdata->{'statistics'}}),
686: values(%{$resdata->{'stats'}})) {
687: if (ref($coursedata) eq 'HASH' && exists($coursedata->{'stdno'})) {
688: $stdno += $coursedata->{'stdno'};
689: }
690: }
691: if (exists($resdata->{'stats'})) {
692: #
693: # For the number of students, take the maximum found for the class
694: my $current_course;
695: my $coursemax=0;
696: foreach my $identifier (sort(keys(%{$resdata->{'stats'}}))) {
697: my $coursedata = $resdata->{'stats'}->{$identifier};
698: if (! defined($current_course)) {
699: $current_course = $coursedata->{'course'};
700: }
701: if ($current_course ne $coursedata->{'course'}) {
702: $stdno += $coursemax;
703: $coursemax = 0;
704: $current_course = $coursedata->{'course'};
705: }
706: if ($coursemax < $coursedata->{'stdno'}) {
707: $coursemax = $coursedata->{'stdno'};
708: }
709: }
710: $stdno += $coursemax; # pick up the final course in the list
711: }
712: $data{'stdno'}=$stdno;
713: #
1.5 matthew 714: # Get the context data
715: foreach my $type (qw/course goto comefrom/) {
716: if (defined($resdata->{$type}) &&
717: ref($resdata->{$type}) eq 'ARRAY') {
718: $data{$type} = scalar(@{$resdata->{$type}});
719: $data{$type.'_list'} = join(',',@{$resdata->{$type}});
720: }
721: }
722: if (defined($resdata->{'usage'}) &&
723: ref($resdata->{'usage'}) eq 'ARRAY') {
724: $data{'sequsage'} = scalar(@{$resdata->{'usage'}});
725: $data{'sequsage_list'} = join(',',@{$resdata->{'usage'}});
726: }
727: #
728: # Get the evaluation data
729: foreach my $type (qw/clear technical correct helpful depth/) {
730: my $count;
731: my $sum;
732: foreach my $evaluator (keys(%{$resdata->{'evaluation'}->{$type}})){
733: $sum += $resdata->{'evaluation'}->{$type}->{$evaluator};
734: $count++;
735: }
736: if ($count > 0) {
737: $data{$type}=$sum/$count;
738: }
739: }
740: #
741: # put together comments
742: my $comments = '<div class="LCevalcomments">';
743: foreach my $evaluator (keys(%{$resdata->{'evaluation'}->{'comments'}})){
1.7 matthew 744: $comments .=
745: '<p>'.
746: '<b>'.$evaluator.'</b>:'.
747: $resdata->{'evaluation'}->{'comments'}->{$evaluator}.
748: '</p>';
1.5 matthew 749: }
750: $comments .= '</div>';
1.7 matthew 751: $data{'comments'} = $comments;
1.5 matthew 752: #
1.8 matthew 753: if (exists($resdata->{'stats'})) {
754: $data{'stats'} = $resdata->{'stats'};
755: }
756: #
1.5 matthew 757: return %data;
758: }
759:
1.8 matthew 760: sub dynamic_metadata_storage {
761: my ($data) = @_;
762: my %Store;
763: my $courseid = $data->{'course'};
764: my $sections = $data->{'sections'};
765: my $numstu = $data->{'num_students'};
766: my $urlres = $data->{'urlres'};
767: my $key = $courseid.'&'.$sections.'&'.$numstu.'___'.$urlres.'___stats';
768: $Store{$key} =
769: 'course='.$courseid.'&'.
770: 'sections='.$sections.'&'.
771: 'timestamp='.time.'&'.
772: 'stdno='.$data->{'num_students'}.'&'.
773: 'avetries='.$data->{'mean_tries'}.'&'.
774: 'difficulty='.$data->{'deg_of_diff'};
775: if (exists($data->{'deg_of_disc'})) {
776: $Store{$key} .= '&'.'disc='.$data->{'deg_of_disc'};
777: }
778: return %Store;
779: }
1.6 matthew 780:
1.5 matthew 781: ######################################################################
782: ######################################################################
783: ##
784: ## The usual suspects, repeated here to reduce dependency hell
785: ##
786: ######################################################################
787: ######################################################################
788: sub unescape {
789: my $str=shift;
790: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
791: return $str;
792: }
793:
794: sub escape {
795: my $str=shift;
796: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
797: return $str;
798: }
1.6 matthew 799:
1.1 matthew 800: 1;
801:
802: __END__;
803:
804: =pod
805:
806: =back
807:
808: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>