![]() ![]() | ![]() |
- recommiting changes made in 1.38 and 1.39
1: # The LearningOnline Network with CAPA 2: # a pile of common routines 3: # 4: # $Id: loncommon.pm,v 1.41 2002/06/25 17:09:38 ng Exp $ 5: # 6: # Copyright Michigan State University Board of Trustees 7: # 8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 9: # 10: # LON-CAPA is free software; you can redistribute it and/or modify 11: # it under the terms of the GNU General Public License as published by 12: # the Free Software Foundation; either version 2 of the License, or 13: # (at your option) any later version. 14: # 15: # LON-CAPA is distributed in the hope that it will be useful, 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18: # GNU General Public License for more details. 19: # 20: # You should have received a copy of the GNU General Public License 21: # along with LON-CAPA; if not, write to the Free Software 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23: # 24: # /home/httpd/html/adm/gpl.txt 25: # 26: # http://www.lon-capa.org/ 27: # 28: # YEAR=2001 29: # 2/13-12/7 Guy Albertelli 30: # 12/11,12/12,12/17 Scott Harrison 31: # 12/21 Gerd Kortemeyer 32: # 12/21 Scott Harrison 33: # 12/25,12/28 Gerd Kortemeyer 34: # YEAR=2002 35: # 1/4 Gerd Kortemeyer 36: 37: # Makes a table out of the previous attempts 38: # Inputs result_from_symbread, user, domain, course_id 39: # Reads in non-network-related .tab files 40: 41: # POD header: 42: 43: =head1 NAME 44: 45: Apache::loncommon - pile of common routines 46: 47: =head1 SYNOPSIS 48: 49: Referenced by other mod_perl Apache modules. 50: 51: Invocation: 52: &Apache::loncommon::SUBROUTINENAME(ARGUMENTS); 53: 54: =head1 INTRODUCTION 55: 56: Common collection of used subroutines. This collection helps remove 57: redundancy from other modules and increase efficiency of memory usage. 58: 59: Current things done: 60: 61: Makes a table out of the previous homework attempts 62: Inputs result_from_symbread, user, domain, course_id 63: Reads in non-network-related .tab files 64: 65: This is part of the LearningOnline Network with CAPA project 66: described at http://www.lon-capa.org. 67: 68: =head2 General Subroutines 69: 70: =over 4 71: 72: =cut 73: 74: # End of POD header 75: package Apache::loncommon; 76: 77: use strict; 78: use Apache::lonnet(); 79: use POSIX qw(strftime); 80: use Apache::Constants qw(:common); 81: use Apache::lonmsg(); 82: my $readit; 83: 84: # ----------------------------------------------- Filetypes/Languages/Copyright 85: my %language; 86: my %cprtag; 87: my %fe; my %fd; 88: my %category_extensions; 89: 90: # -------------------------------------------------------------- Thesaurus data 91: my @therelated; 92: my @theword; 93: my @thecount; 94: my %theindex; 95: my $thetotalcount; 96: my $thefuzzy=2; 97: my $thethreshold=0.1/$thefuzzy; 98: my $theavecount; 99: 100: # ----------------------------------------------------------------------- BEGIN 101: 102: =pod 103: 104: =item BEGIN() 105: 106: Initialize values from language.tab, copyright.tab, filetypes.tab, 107: and filecategories.tab. 108: 109: =cut 110: # ----------------------------------------------------------------------- BEGIN 111: 112: BEGIN { 113: 114: unless ($readit) { 115: # ------------------------------------------------------------------- languages 116: { 117: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. 118: '/language.tab'); 119: if ($fh) { 120: while (<$fh>) { 121: next if /^\#/; 122: chomp; 123: my ($key,$val)=(split(/\s+/,$_,2)); 124: $language{$key}=$val; 125: } 126: } 127: } 128: # ------------------------------------------------------------------ copyrights 129: { 130: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonIncludes'}. 131: '/copyright.tab'); 132: if ($fh) { 133: while (<$fh>) { 134: next if /^\#/; 135: chomp; 136: my ($key,$val)=(split(/\s+/,$_,2)); 137: $cprtag{$key}=$val; 138: } 139: } 140: } 141: # ------------------------------------------------------------- file categories 142: { 143: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. 144: '/filecategories.tab'); 145: if ($fh) { 146: while (<$fh>) { 147: next if /^\#/; 148: chomp; 149: my ($extension,$category)=(split(/\s+/,$_,2)); 150: push @{$category_extensions{lc($category)}},$extension; 151: } 152: } 153: } 154: # ------------------------------------------------------------------ file types 155: { 156: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. 157: '/filetypes.tab'); 158: if ($fh) { 159: while (<$fh>) { 160: next if (/^\#/); 161: chomp; 162: my ($ending,$emb,$descr)=split(/\s+/,$_,3); 163: if ($descr ne '') { 164: $fe{$ending}=lc($emb); 165: $fd{$ending}=$descr; 166: } 167: } 168: } 169: } 170: # -------------------------------------------------------------- Thesaurus data 171: { 172: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. 173: '/thesaurus.dat'); 174: if ($fh) { 175: while (<$fh>) { 176: my ($tword,$tindex,$tcount,$trelated)=split(/\@/,$_); 177: $theindex{$tword}=$tindex; 178: $theword[$tindex]=$tword; 179: $thecount[$tindex]=$tcount; 180: $thetotalcount+=$tcount; 181: $therelated[$tindex]=$trelated; 182: } 183: } 184: $theavecount=$thetotalcount/$#thecount; 185: } 186: &Apache::lonnet::logthis( 187: "<font color=yellow>INFO: Read file types and thesaurus</font>"); 188: $readit=1; 189: } 190: 191: } 192: # ============================================================= END BEGIN BLOCK 193: 194: =item linked_select_forms(...) 195: 196: linked_select_forms returns a string containing a <script></script> block 197: and html for two <select> menus. The select menus will be linked in that 198: changing the value of the first menu will result in new values being placed 199: in the second menu. The values in the select menu will appear in alphabetical 200: order. 201: 202: linked_select_forms takes the following ordered inputs: 203: 204: =over 4 205: 206: =item $formname, the name of the <form> tag 207: 208: =item $middletext, the text which appears between the <select> tags 209: 210: =item $firstdefault, the default value for the first menu 211: 212: =item $firstselectname, the name of the first <select> tag 213: 214: =item $secondselectname, the name of the second <select> tag 215: 216: =item $hashref, a reference to a hash containing the data for the menus. 217: 218: =back 219: 220: Below is an example of such a hash. Only the 'text', 'default', and 221: 'select2' keys must appear as stated. keys(%menu) are the possible 222: values for the first select menu. The text that coincides with the 223: first menu value is given in $menu{$choice1}->{'text'}. The values 224: and text for the second menu are given in the hash pointed to by 225: $menu{$choice1}->{'select2'}. 226: 227: my %menu = ( A1 => { text =>"Choice A1" , 228: default => "B3", 229: select2 => { 230: B1 => "Choice B1", 231: B2 => "Choice B2", 232: B3 => "Choice B3", 233: B4 => "Choice B4" 234: } 235: }, 236: A2 => { text =>"Choice A2" , 237: default => "C2", 238: select2 => { 239: C1 => "Choice C1", 240: C2 => "Choice C2", 241: C3 => "Choice C3" 242: } 243: }, 244: A3 => { text =>"Choice A3" , 245: default => "D6", 246: select2 => { 247: D1 => "Choice D1", 248: D2 => "Choice D2", 249: D3 => "Choice D3", 250: D4 => "Choice D4", 251: D5 => "Choice D5", 252: D6 => "Choice D6", 253: D7 => "Choice D7" 254: } 255: } 256: ); 257: 258: =back 259: 260: =cut 261: 262: # ------------------------------------------------ 263: 264: sub linked_select_forms { 265: my ($formname, 266: $middletext, 267: $firstdefault, 268: $firstselectname, 269: $secondselectname, 270: $hashref 271: ) = @_; 272: my $second = "document.$formname.$secondselectname"; 273: my $first = "document.$formname.$firstselectname"; 274: # output the javascript to do the changing 275: my $result = ''; 276: $result.="<script>\n"; 277: $result.="var select2data = new Object();\n"; 278: $" = '","'; 279: my $debug = ''; 280: foreach my $s1 (sort(keys(%$hashref))) { 281: $result.="select2data.d_$s1 = new Object();\n"; 282: $result.="select2data.d_$s1.def = new String('". 283: $hashref->{$s1}->{'default'}."');\n"; 284: $result.="select2data.d_$s1.values = new Array("; 285: my @s2values = sort(keys( %{ $hashref->{$s1}->{'select2'} } )); 286: $result.="\"@s2values\");\n"; 287: $result.="select2data.d_$s1.texts = new Array("; 288: my @s2texts; 289: foreach my $value (@s2values) { 290: push @s2texts, $hashref->{$s1}->{'select2'}->{$value}; 291: } 292: $result.="\"@s2texts\");\n"; 293: } 294: $"=' '; 295: $result.= <<"END"; 296: 297: function select1_changed() { 298: // Determine new choice 299: var newvalue = "d_" + $first.value; 300: // update select2 301: var values = select2data[newvalue].values; 302: var texts = select2data[newvalue].texts; 303: var select2def = select2data[newvalue].def; 304: var i; 305: // out with the old 306: for (i = 0; i < $second.options.length; i++) { 307: $second.options[i] = null; 308: } 309: // in with the nuclear 310: for (i=0;i<values.length; i++) { 311: $second.options[i] = new Option(values[i]); 312: $second.options[i].text = texts[i]; 313: if (values[i] == select2def) { 314: $second.options[i].selected = true; 315: } 316: } 317: } 318: </script> 319: END 320: # output the initial values for the selection lists 321: $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1_changed()\">\n"; 322: foreach my $value (sort(keys(%$hashref))) { 323: $result.=" <option value=\"$value\" "; 324: $result.=" selected=\"true\" " if ($value eq $firstdefault); 325: $result.=">$hashref->{$value}->{'text'}</option>\n"; 326: } 327: $result .= "</select>\n"; 328: my %select2 = %{$hashref->{$firstdefault}->{'select2'}}; 329: $result .= $middletext; 330: $result .= "<select size=\"1\" name=\"$secondselectname\">\n"; 331: my $seconddefault = $hashref->{$firstdefault}->{'default'}; 332: foreach my $value (sort(keys(%select2))) { 333: $result.=" <option value=\"$value\" "; 334: $result.=" selected=\"true\" " if ($value eq $seconddefault); 335: $result.=">$select2{$value}</option>\n"; 336: } 337: $result .= "</select>\n"; 338: # return $debug; 339: return $result; 340: } # end of sub linked_select_forms { 341: 342: ############################################################### 343: 344: =item csv_translate($text) 345: 346: Translate $text to allow it to be output as a 'comma seperated values' 347: format. 348: 349: =cut 350: 351: sub csv_translate { 352: my $text = shift; 353: $text =~ s/\"/\"\"/g; 354: $text =~ s/\n//g; 355: return $text; 356: } 357: 358: ############################################################### 359: 360: ############################################################### 361: ## Home server <option> list generating code ## 362: ############################################################### 363: #------------------------------------------- 364: 365: =item get_domains() 366: 367: Returns an array containing each of the domains listed in the hosts.tab 368: file. 369: 370: =cut 371: 372: #------------------------------------------- 373: sub get_domains { 374: # The code below was stolen from "The Perl Cookbook", p 102, 1st ed. 375: my @domains; 376: my %seen; 377: foreach (sort values(%Apache::lonnet::hostdom)) { 378: push (@domains,$_) unless $seen{$_}++; 379: } 380: return @domains; 381: } 382: 383: #------------------------------------------- 384: 385: =item select_dom_form($defdom,$name) 386: 387: Returns a string containing a <select name='$name' size='1'> form to 388: allow a user to select the domain to preform an operation in. 389: See loncreateuser.pm for an example invocation and use. 390: 391: =cut 392: 393: #------------------------------------------- 394: sub select_dom_form { 395: my ($defdom,$name) = @_; 396: my @domains = get_domains(); 397: my $selectdomain = "<select name=\"$name\" size=\"1\">\n"; 398: foreach (@domains) { 399: $selectdomain.="<option value=\"$_\" ". 400: ($_ eq $defdom ? 'selected' : ''). 401: ">$_</option>\n"; 402: } 403: $selectdomain.="</select>"; 404: return $selectdomain; 405: } 406: 407: #------------------------------------------- 408: 409: =item get_home_servers($domain) 410: 411: Returns a hash which contains keys like '103l3' and values like 412: 'kirk.lite.msu.edu'. All of the keys will be for machines in the 413: given $domain. 414: 415: =cut 416: 417: #------------------------------------------- 418: sub get_home_servers { 419: my $domain = shift; 420: my %home_servers; 421: foreach (keys(%Apache::lonnet::libserv)) { 422: if ($Apache::lonnet::hostdom{$_} eq $domain) { 423: $home_servers{$_} = $Apache::lonnet::hostname{$_}; 424: } 425: } 426: return %home_servers; 427: } 428: 429: #------------------------------------------- 430: 431: =item home_server_option_list($domain) 432: 433: returns a string which contains an <option> list to be used in a 434: <select> form input. See loncreateuser.pm for an example. 435: 436: =cut 437: 438: #------------------------------------------- 439: sub home_server_option_list { 440: my $domain = shift; 441: my %servers = &get_home_servers($domain); 442: my $result = ''; 443: foreach (sort keys(%servers)) { 444: $result.= 445: '<option value="'.$_.'">'.$_.' '.$servers{$_}."</option>\n"; 446: } 447: return $result; 448: } 449: ############################################################### 450: ## End of home server <option> list generating code ## 451: ############################################################### 452: 453: ############################################################### 454: ## Authentication changing form generation subroutines ## 455: ############################################################### 456: ## 457: ## All of the authform_xxxxxxx subroutines take their inputs in a 458: ## hash, and have reasonable default values. 459: ## 460: ## formname = the name given in the <form> tag. 461: #------------------------------------------- 462: 463: =item authform_xxxxxx 464: 465: The authform_xxxxxx subroutines provide javascript and html forms which 466: handle some of the conveniences required for authentication forms. 467: This is not an optimal method, but it works. 468: 469: See loncreateuser.pm for invocation and use examples. 470: 471: =over 4 472: 473: =item authform_header 474: 475: =item authform_authorwarning 476: 477: =item authform_nochange 478: 479: =item authform_kerberos 480: 481: =item authform_internal 482: 483: =item authform_filesystem 484: 485: =back 486: 487: =cut 488: 489: #------------------------------------------- 490: sub authform_header{ 491: my %in = ( 492: formname => 'cu', 493: kerb_def_dom => 'MSU.EDU', 494: @_, 495: ); 496: $in{'formname'} = 'document.' . $in{'formname'}; 497: my $result=''; 498: $result.=<<"END"; 499: var current = new Object(); 500: current.radiovalue = 'nochange'; 501: current.argfield = null; 502: 503: function changed_radio(choice,currentform) { 504: var choicearg = choice + 'arg'; 505: // If a radio button in changed, we need to change the argfield 506: if (current.radiovalue != choice) { 507: current.radiovalue = choice; 508: if (current.argfield != null) { 509: currentform.elements[current.argfield].value = ''; 510: } 511: if (choice == 'nochange') { 512: current.argfield = null; 513: } else { 514: current.argfield = choicearg; 515: switch(choice) { 516: case 'krb': 517: currentform.elements[current.argfield].value = 518: "$in{'kerb_def_dom'}"; 519: break; 520: default: 521: break; 522: } 523: } 524: } 525: return; 526: } 527: 528: function changed_text(choice,currentform) { 529: var choicearg = choice + 'arg'; 530: if (currentform.elements[choicearg].value !='') { 531: switch (choice) { 532: case 'krb': currentform.elements[choicearg].value = 533: currentform.elements[choicearg].value.toUpperCase(); 534: break; 535: default: 536: } 537: // clear old field 538: if ((current.argfield != choicearg) && (current.argfield != null)) { 539: currentform.elements[current.argfield].value = ''; 540: } 541: current.argfield = choicearg; 542: } 543: set_auth_radio_buttons(choice,currentform); 544: return; 545: } 546: 547: function set_auth_radio_buttons(newvalue,currentform) { 548: var i=0; 549: while (i < currentform.login.length) { 550: if (currentform.login[i].value == newvalue) { break; } 551: i++; 552: } 553: if (i == currentform.login.length) { 554: return; 555: } 556: current.radiovalue = newvalue; 557: currentform.login[i].checked = true; 558: return; 559: } 560: END 561: return $result; 562: } 563: 564: sub authform_authorwarning{ 565: my $result=''; 566: $result=<<"END"; 567: <i>As a general rule, only authors or co-authors should be filesystem 568: authenticated (which allows access to the server filesystem).</i> 569: END 570: return $result; 571: } 572: 573: sub authform_nochange{ 574: my %in = ( 575: formname => 'document.cu', 576: kerb_def_dom => 'MSU.EDU', 577: @_, 578: ); 579: my $result=''; 580: $result.=<<"END"; 581: <input type="radio" name="login" value="nochange" checked="checked" 582: onclick="javascript:changed_radio('nochange',$in{'formname'});"> 583: Do not change login data 584: END 585: return $result; 586: } 587: 588: sub authform_kerberos{ 589: my %in = ( 590: formname => 'document.cu', 591: kerb_def_dom => 'MSU.EDU', 592: @_, 593: ); 594: my $result=''; 595: $result.=<<"END"; 596: <input type="radio" name="login" value="krb" 597: onclick="javascript:changed_radio('krb',$in{'formname'});" 598: onchange="javascript:changed_radio('krb',$in{'formname'});"> 599: Kerberos authenticated with domain 600: <input type="text" size="10" name="krbarg" value="" 601: onchange="javascript:changed_text('krb',$in{'formname'});"> 602: END 603: return $result; 604: } 605: 606: sub authform_internal{ 607: my %args = ( 608: formname => 'document.cu', 609: kerb_def_dom => 'MSU.EDU', 610: @_, 611: ); 612: my $result=''; 613: $result.=<<"END"; 614: <input type="radio" name="login" value="int" 615: onchange="javascript:changed_radio('int',$args{'formname'});" 616: onclick="javascript:changed_radio('int',$args{'formname'});"> 617: Internally authenticated (with initial password 618: <input type="text" size="10" name="intarg" value="" 619: onchange="javascript:changed_text('int',$args{'formname'});"> 620: END 621: return $result; 622: } 623: 624: sub authform_local{ 625: my %in = ( 626: formname => 'document.cu', 627: kerb_def_dom => 'MSU.EDU', 628: @_, 629: ); 630: my $result=''; 631: $result.=<<"END"; 632: <input type="radio" name="login" value="loc" 633: onchange="javascript:changed_radio('loc',$in{'formname'});" 634: onclick="javascript:changed_radio('loc',$in{'formname'});"> 635: Local Authentication with argument 636: <input type="text" size="10" name="locarg" value="" 637: onchange="javascript:changed_text('loc',$in{'formname'});"> 638: END 639: return $result; 640: } 641: 642: sub authform_filesystem{ 643: my %in = ( 644: formname => 'document.cu', 645: kerb_def_dom => 'MSU.EDU', 646: @_, 647: ); 648: my $result=''; 649: $result.=<<"END"; 650: <input type="radio" name="login" value="fsys" 651: onchange="javascript:changed_radio('fsys',$in{'formname'});" 652: onclick="javascript:changed_radio('fsys',$in{'formname'});"> 653: Filesystem authenticated (with initial password 654: <input type="text" size="10" name="fsysarg" value="" 655: onchange="javascript:changed_text('fsys',$in{'formname'});"> 656: END 657: return $result; 658: } 659: 660: ############################################################### 661: ## End Authentication changing form generation functions ## 662: ############################################################### 663: 664: 665: 666: # ---------------------------------------------------------- Is this a keyword? 667: 668: sub keyword { 669: my $newword=shift; 670: $newword=~s/\W//g; 671: $newword=~tr/A-Z/a-z/; 672: my $tindex=$theindex{$newword}; 673: if ($tindex) { 674: if ($thecount[$tindex]>$theavecount) { 675: return 1; 676: } 677: } 678: return 0; 679: } 680: # -------------------------------------------------------- Return related words 681: 682: sub related { 683: my $newword=shift; 684: $newword=~s/\W//g; 685: $newword=~tr/A-Z/a-z/; 686: my $tindex=$theindex{$newword}; 687: if ($tindex) { 688: my %found=(); 689: foreach (split(/\,/,$therelated[$tindex])) { 690: # - Related word found 691: my ($ridx,$rcount)=split(/\:/,$_); 692: # - Direct relation index 693: my $directrel=$rcount/$thecount[$tindex]; 694: if ($directrel>$thethreshold) { 695: foreach (split(/\,/,$therelated[$ridx])) { 696: my ($rridx,$rrcount)=split(/\:/,$_); 697: if ($rridx==$tindex) { 698: # - Determine reverse relation index 699: my $revrel=$rrcount/$thecount[$ridx]; 700: # - Calculate full index 701: $found{$ridx}=$directrel*$revrel; 702: if ($found{$ridx}>$thethreshold) { 703: foreach (split(/\,/,$therelated[$ridx])) { 704: my ($rrridx,$rrrcount)=split(/\:/,$_); 705: unless ($found{$rrridx}) { 706: my $revrevrel=$rrrcount/$thecount[$ridx]; 707: if ( 708: $directrel*$revrel*$revrevrel>$thethreshold 709: ) { 710: $found{$rrridx}= 711: $directrel*$revrel*$revrevrel; 712: } 713: } 714: } 715: } 716: } 717: } 718: } 719: } 720: } 721: return (); 722: } 723: 724: # ---------------------------------------------------------------- Language IDs 725: sub languageids { 726: return sort(keys(%language)); 727: } 728: 729: # -------------------------------------------------------- Language Description 730: sub languagedescription { 731: return $language{shift(@_)}; 732: } 733: 734: # --------------------------------------------------------------- Copyright IDs 735: sub copyrightids { 736: return sort(keys(%cprtag)); 737: } 738: 739: # ------------------------------------------------------- Copyright Description 740: sub copyrightdescription { 741: return $cprtag{shift(@_)}; 742: } 743: 744: # ------------------------------------------------------------- File Categories 745: sub filecategories { 746: return sort(keys(%category_extensions)); 747: } 748: 749: # -------------------------------------- File Types within a specified category 750: sub filecategorytypes { 751: return @{$category_extensions{lc($_[0])}}; 752: } 753: 754: # ------------------------------------------------------------------ File Types 755: sub fileextensions { 756: return sort(keys(%fe)); 757: } 758: 759: # ------------------------------------------------------------- Embedding Style 760: sub fileembstyle { 761: return $fe{lc(shift(@_))}; 762: } 763: 764: # ------------------------------------------------------------ Description Text 765: sub filedescription { 766: return $fd{lc(shift(@_))}; 767: } 768: 769: # ------------------------------------------------------------ Description Text 770: sub filedescriptionex { 771: my $ex=shift; 772: return '.'.$ex.' '.$fd{lc($ex)}; 773: } 774: 775: # ---- Retrieve attempts by students 776: # input 777: # $symb - problem including path 778: # $username,$domain - that of the student 779: # $course - course name 780: # $getattempt - leave blank if want all attempts, else put something. 781: # 782: # output 783: # formatted as a table all the attempts, if any. 784: # 785: sub get_previous_attempt { 786: my ($symb,$username,$domain,$course,$getattempt)=@_; 787: my $prevattempts=''; 788: if ($symb) { 789: my (%returnhash)= 790: &Apache::lonnet::restore($symb,$course,$domain,$username); 791: if ($returnhash{'version'}) { 792: my %lasthash=(); 793: my $version; 794: for ($version=1;$version<=$returnhash{'version'};$version++) { 795: foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) { 796: $lasthash{$_}=$returnhash{$version.':'.$_}; 797: } 798: } 799: $prevattempts='<table border="0" width="100%"><tr><td bgcolor="#000000">'; 800: $prevattempts.='<table border="0" width="100%"><tr bgcolor="#e6ffff"><td>History</td>'; 801: foreach (sort(keys %lasthash)) { 802: my ($ign,@parts) = split(/\./,$_); 803: if ($#parts > 0) { 804: my $data=$parts[-1]; 805: pop(@parts); 806: $prevattempts.='<td>Part '.join('.',@parts).'<br />'.$data.' </td>'; 807: } else { 808: if ($#parts == 0) { 809: $prevattempts.='<th>'.$parts[0].'</th>'; 810: } else { 811: $prevattempts.='<th>'.$ign.'</th>'; 812: } 813: } 814: } 815: if ($getattempt eq '') { 816: for ($version=1;$version<=$returnhash{'version'};$version++) { 817: $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Transaction '.$version.'</td>'; 818: foreach (sort(keys %lasthash)) { 819: my $value; 820: if ($_ =~ /timestamp/) { 821: $value=scalar(localtime($returnhash{$version.':'.$_})); 822: } else { 823: $value=$returnhash{$version.':'.$_}; 824: } 825: $prevattempts.='<td>'.$value.' </td>'; 826: } 827: } 828: } 829: $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Current</td>'; 830: foreach (sort(keys %lasthash)) { 831: my $value; 832: if ($_ =~ /timestamp/) { 833: $value=scalar(localtime($lasthash{$_})); 834: } else { 835: $value=$lasthash{$_}; 836: } 837: $prevattempts.='<td>'.$value.' </td>'; 838: } 839: $prevattempts.='</tr></table></td></tr></table>'; 840: } else { 841: $prevattempts='Nothing submitted - no attempts.'; 842: } 843: } else { 844: $prevattempts='No data.'; 845: } 846: } 847: 848: sub get_student_view { 849: my ($symb,$username,$domain,$courseid) = @_; 850: my ($map,$id,$feedurl) = split(/___/,$symb); 851: my (%old,%moreenv); 852: my @elements=('symb','courseid','domain','username'); 853: foreach my $element (@elements) { 854: $old{$element}=$ENV{'form.grade_'.$element}; 855: $moreenv{'form.grade_'.$element}=eval '$'.$element #' 856: } 857: &Apache::lonnet::appenv(%moreenv); 858: my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); 859: &Apache::lonnet::delenv('form.grade_'); 860: foreach my $element (@elements) { 861: $ENV{'form.grade_'.$element}=$old{$element}; 862: } 863: $userview=~s/\<body[^\>]*\>//gi; 864: $userview=~s/\<\/body\>//gi; 865: $userview=~s/\<html\>//gi; 866: $userview=~s/\<\/html\>//gi; 867: $userview=~s/\<head\>//gi; 868: $userview=~s/\<\/head\>//gi; 869: $userview=~s/action\s*\=/would_be_action\=/gi; 870: return $userview; 871: } 872: 873: sub get_student_answers { 874: my ($symb,$username,$domain,$courseid) = @_; 875: my ($map,$id,$feedurl) = split(/___/,$symb); 876: my (%old,%moreenv); 877: my @elements=('symb','courseid','domain','username'); 878: foreach my $element (@elements) { 879: $old{$element}=$ENV{'form.grade_'.$element}; 880: $moreenv{'form.grade_'.$element}=eval '$'.$element #' 881: } 882: $moreenv{'form.grade_target'}='answer'; 883: &Apache::lonnet::appenv(%moreenv); 884: my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); 885: &Apache::lonnet::delenv('form.grade_'); 886: foreach my $element (@elements) { 887: $ENV{'form.grade_'.$element}=$old{$element}; 888: } 889: return $userview; 890: } 891: 892: ############################################### 893: 894: ############################################### 895: 896: sub get_unprocessed_cgi { 897: my ($query,$possible_names)= @_; 898: # $Apache::lonxml::debug=1; 899: foreach (split(/&/,$query)) { 900: my ($name, $value) = split(/=/,$_); 901: $name = &Apache::lonnet::unescape($name); 902: if (!defined($possible_names) || (grep {$_ eq $name} @$possible_names)) { 903: $value =~ tr/+/ /; 904: $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; 905: &Apache::lonxml::debug("Seting :$name: to :$value:"); 906: unless (defined($ENV{'form.'.$name})) { &add_to_env('form.'.$name,$value) }; 907: } 908: } 909: } 910: 911: sub cacheheader { 912: unless ($ENV{'request.method'} eq 'GET') { return ''; } 913: my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); 914: my $output .='<meta HTTP-EQUIV="Expires" CONTENT="'.$date.'" /> 915: <meta HTTP-EQUIV="Cache-control" CONTENT="no-cache" /> 916: <meta HTTP-EQUIV="Pragma" CONTENT="no-cache" />'; 917: return $output; 918: } 919: 920: sub no_cache { 921: my ($r) = @_; 922: unless ($ENV{'request.method'} eq 'GET') { return ''; } 923: #my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); 924: $r->no_cache(1); 925: $r->header_out("Pragma" => "no-cache"); 926: #$r->header_out("Expires" => $date); 927: } 928: 929: sub add_to_env { 930: my ($name,$value)=@_; 931: if (defined($ENV{$name})) { 932: if (ref($ENV{$name})) { 933: #already have multiple values 934: push(@{ $ENV{$name} },$value); 935: } else { 936: #first time seeing multiple values, convert hash entry to an arrayref 937: my $first=$ENV{$name}; 938: undef($ENV{$name}); 939: push(@{ $ENV{$name} },$first,$value); 940: } 941: } else { 942: $ENV{$name}=$value; 943: } 944: } 945: 946: =pod 947: 948: =head2 CSV Upload/Handling functions 949: 950: =over 4 951: 952: =item upfile_store($r) 953: 954: Store uploaded file, $r should be the HTTP Request object, 955: needs $ENV{'form.upfile'} 956: returns $datatoken to be put into hidden field 957: 958: =cut 959: 960: sub upfile_store { 961: my $r=shift; 962: $ENV{'form.upfile'}=~s/\r/\n/gs; 963: $ENV{'form.upfile'}=~s/\f/\n/gs; 964: $ENV{'form.upfile'}=~s/\n+/\n/gs; 965: $ENV{'form.upfile'}=~s/\n+$//gs; 966: 967: my $datatoken=$ENV{'user.name'}.'_'.$ENV{'user.domain'}. 968: '_enroll_'.$ENV{'request.course.id'}.'_'.time.'_'.$$; 969: { 970: my $fh=Apache::File->new('>'.$r->dir_config('lonDaemons'). 971: '/tmp/'.$datatoken.'.tmp'); 972: print $fh $ENV{'form.upfile'}; 973: } 974: return $datatoken; 975: } 976: 977: =item load_tmp_file($r) 978: 979: Load uploaded file from tmp, $r should be the HTTP Request object, 980: needs $ENV{'form.datatoken'}, 981: sets $ENV{'form.upfile'} to the contents of the file 982: 983: =cut 984: 985: sub load_tmp_file { 986: my $r=shift; 987: my @studentdata=(); 988: { 989: my $fh; 990: if ($fh=Apache::File->new($r->dir_config('lonDaemons'). 991: '/tmp/'.$ENV{'form.datatoken'}.'.tmp')) { 992: @studentdata=<$fh>; 993: } 994: } 995: $ENV{'form.upfile'}=join('',@studentdata); 996: } 997: 998: =item upfile_record_sep() 999: 1000: Separate uploaded file into records 1001: returns array of records, 1002: needs $ENV{'form.upfile'} and $ENV{'form.upfiletype'} 1003: 1004: =cut 1005: 1006: sub upfile_record_sep { 1007: if ($ENV{'form.upfiletype'} eq 'xml') { 1008: } else { 1009: return split(/\n/,$ENV{'form.upfile'}); 1010: } 1011: } 1012: 1013: =item record_sep($record) 1014: 1015: Separate a record into fields $record should be an item from the upfile_record_sep(), needs $ENV{'form.upfiletype'} 1016: 1017: =cut 1018: 1019: sub record_sep { 1020: my $record=shift; 1021: my %components=(); 1022: if ($ENV{'form.upfiletype'} eq 'xml') { 1023: } elsif ($ENV{'form.upfiletype'} eq 'space') { 1024: my $i=0; 1025: foreach (split(/\s+/,$record)) { 1026: my $field=$_; 1027: $field=~s/^(\"|\')//; 1028: $field=~s/(\"|\')$//; 1029: $components{$i}=$field; 1030: $i++; 1031: } 1032: } elsif ($ENV{'form.upfiletype'} eq 'tab') { 1033: my $i=0; 1034: foreach (split(/\t+/,$record)) { 1035: my $field=$_; 1036: $field=~s/^(\"|\')//; 1037: $field=~s/(\"|\')$//; 1038: $components{$i}=$field; 1039: $i++; 1040: } 1041: } else { 1042: my @allfields=split(/\,/,$record); 1043: my $i=0; 1044: my $j; 1045: for ($j=0;$j<=$#allfields;$j++) { 1046: my $field=$allfields[$j]; 1047: if ($field=~/^\s*(\"|\')/) { 1048: my $delimiter=$1; 1049: while (($field!~/$delimiter$/) && ($j<$#allfields)) { 1050: $j++; 1051: $field.=','.$allfields[$j]; 1052: } 1053: $field=~s/^\s*$delimiter//; 1054: $field=~s/$delimiter\s*$//; 1055: } 1056: $components{$i}=$field; 1057: $i++; 1058: } 1059: } 1060: return %components; 1061: } 1062: 1063: =item upfile_select_html() 1064: 1065: return HTML code to select file and specify its type 1066: 1067: =cut 1068: 1069: sub upfile_select_html { 1070: return (<<'ENDUPFORM'); 1071: <input type="file" name="upfile" size="50"> 1072: <br />Type: <select name="upfiletype"> 1073: <option value="csv">CSV (comma separated values, spreadsheet)</option> 1074: <option value="space">Space separated</option> 1075: <option value="tab">Tabulator separated</option> 1076: <option value="xml">HTML/XML</option> 1077: </select> 1078: ENDUPFORM 1079: } 1080: 1081: =item csv_print_samples($r,$records) 1082: 1083: Prints a table of sample values from each column uploaded $r is an 1084: Apache Request ref, $records is an arrayref from 1085: &Apache::loncommon::upfile_record_sep 1086: 1087: =cut 1088: 1089: sub csv_print_samples { 1090: my ($r,$records) = @_; 1091: my (%sone,%stwo,%sthree); 1092: %sone=&record_sep($$records[0]); 1093: if (defined($$records[1])) {%stwo=&record_sep($$records[1]);} 1094: if (defined($$records[2])) {%sthree=&record_sep($$records[2]);} 1095: 1096: $r->print('Samples<br /><table border="2"><tr>'); 1097: foreach (sort({$a <=> $b} keys(%sone))) { $r->print('<th>Column '.($_+1).'</th>'); } 1098: $r->print('</tr>'); 1099: foreach my $hash (\%sone,\%stwo,\%sthree) { 1100: $r->print('<tr>'); 1101: foreach (sort({$a <=> $b} keys(%sone))) { 1102: $r->print('<td>'); 1103: if (defined($$hash{$_})) { $r->print($$hash{$_}); } 1104: $r->print('</td>'); 1105: } 1106: $r->print('</tr>'); 1107: } 1108: $r->print('</tr></table><br />'."\n"); 1109: } 1110: 1111: =item csv_print_select_table($r,$records,$d) 1112: 1113: Prints a table to create associations between values and table columns. 1114: $r is an Apache Request ref, 1115: $records is an arrayref from &Apache::loncommon::upfile_record_sep, 1116: $d is an array of 2 element arrays (internal name, displayed name) 1117: 1118: =cut 1119: 1120: sub csv_print_select_table { 1121: my ($r,$records,$d) = @_; 1122: my $i=0;my %sone; 1123: %sone=&record_sep($$records[0]); 1124: $r->print('Associate columns with student attributes.'."\n". 1125: '<table border="2"><tr><th>Attribute</th><th>Column</th></tr>'."\n"); 1126: foreach (@$d) { 1127: my ($value,$display)=@{ $_ }; 1128: $r->print('<tr><td>'.$display.'</td>'); 1129: 1130: $r->print('<td><select name=f'.$i. 1131: ' onchange="javascript:flip(this.form,'.$i.');">'); 1132: $r->print('<option value="none"></option>'); 1133: foreach (sort({$a <=> $b} keys(%sone))) { 1134: $r->print('<option value="'.$_.'">Column '.($_+1).'</option>'); 1135: } 1136: $r->print('</select></td></tr>'."\n"); 1137: $i++; 1138: } 1139: $i--; 1140: return $i; 1141: } 1142: 1143: =item csv_samples_select_table($r,$records,$d) 1144: 1145: Prints a table of sample values from the upload and can make associate samples to internal names. 1146: 1147: $r is an Apache Request ref, 1148: $records is an arrayref from &Apache::loncommon::upfile_record_sep, 1149: $d is an array of 2 element arrays (internal name, displayed name) 1150: 1151: =cut 1152: 1153: sub csv_samples_select_table { 1154: my ($r,$records,$d) = @_; 1155: my %sone; my %stwo; my %sthree; 1156: my $i=0; 1157: 1158: $r->print('<table border=2><tr><th>Field</th><th>Samples</th></tr>'); 1159: %sone=&record_sep($$records[0]); 1160: if (defined($$records[1])) {%stwo=&record_sep($$records[1]);} 1161: if (defined($$records[2])) {%sthree=&record_sep($$records[2]);} 1162: 1163: foreach (sort keys %sone) { 1164: $r->print('<tr><td><select name=f'.$i. 1165: ' onchange="javascript:flip(this.form,'.$i.');">'); 1166: foreach (@$d) { 1167: my ($value,$display)=@{ $_ }; 1168: $r->print('<option value='.$value.'>'.$display.'</option>'); 1169: } 1170: $r->print('</select></td><td>'); 1171: if (defined($sone{$_})) { $r->print($sone{$_}."</br>\n"); } 1172: if (defined($stwo{$_})) { $r->print($stwo{$_}."</br>\n"); } 1173: if (defined($sthree{$_})) { $r->print($sthree{$_}."</br>\n"); } 1174: $r->print('</td></tr>'); 1175: $i++; 1176: } 1177: $i--; 1178: return($i); 1179: } 1180: 1; 1181: __END__; 1182: 1183: =pod 1184: 1185: =back 1186: 1187: =head2 Access .tab File Data 1188: 1189: =over 4 1190: 1191: =item languageids() 1192: 1193: returns list of all language ids 1194: 1195: =item languagedescription() 1196: 1197: returns description of a specified language id 1198: 1199: =item copyrightids() 1200: 1201: returns list of all copyrights 1202: 1203: =item copyrightdescription() 1204: 1205: returns description of a specified copyright id 1206: 1207: =item filecategories() 1208: 1209: returns list of all file categories 1210: 1211: =item filecategorytypes() 1212: 1213: returns list of file types belonging to a given file 1214: category 1215: 1216: =item fileembstyle() 1217: 1218: returns embedding style for a specified file type 1219: 1220: =item filedescription() 1221: 1222: returns description for a specified file type 1223: 1224: =item filedescriptionex() 1225: 1226: returns description for a specified file type with 1227: extra formatting 1228: 1229: =back 1230: 1231: =head2 Alternate Problem Views 1232: 1233: =over 4 1234: 1235: =item get_previous_attempt() 1236: 1237: return string with previous attempt on problem 1238: 1239: =item get_student_view() 1240: 1241: show a snapshot of what student was looking at 1242: 1243: =item get_student_answers() 1244: 1245: show a snapshot of how student was answering problem 1246: 1247: =back 1248: 1249: =head2 HTTP Helper 1250: 1251: =over 4 1252: 1253: =item get_unprocessed_cgi($query,$possible_names) 1254: 1255: Modify the %ENV hash to contain unprocessed CGI form parameters held in 1256: $query. The parameters listed in $possible_names (an array reference), 1257: will be set in $ENV{'form.name'} if they do not already exist. 1258: 1259: Typically called with $ENV{'QUERY_STRING'} as the first parameter. 1260: $possible_names is an ref to an array of form element names. As an example: 1261: get_unprocessed_cgi($ENV{'QUERY_STRING'},['uname','udom']); 1262: will result in $ENV{'form.uname'} and $ENV{'form.udom'} being set. 1263: 1264: =item cacheheader() 1265: 1266: returns cache-controlling header code 1267: 1268: =item nocache() 1269: 1270: specifies header code to not have cache 1271: 1272: =item add_to_env($name,$value) 1273: 1274: adds $name to the %ENV hash with value 1275: $value, if $name already exists, the entry is converted to an array 1276: reference and $value is added to the array. 1277: 1278: =back 1279: 1280: =cut