--- loncom/interface/loncommon.pm 2002/07/12 14:36:16 1.46 +++ loncom/interface/loncommon.pm 2003/10/27 21:21:08 1.136 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.46 2002/07/12 14:36:16 matthew Exp $ +# $Id: loncommon.pm,v 1.136 2003/10/27 21:21:08 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -27,9 +27,7 @@ # # YEAR=2001 # 2/13-12/7 Guy Albertelli -# 12/11,12/12,12/17 Scott Harrison # 12/21 Gerd Kortemeyer -# 12/21 Scott Harrison # 12/25,12/28 Gerd Kortemeyer # YEAR=2002 # 1/4 Gerd Kortemeyer @@ -49,29 +47,15 @@ Apache::loncommon - pile of common routi =head1 SYNOPSIS -Referenced by other mod_perl Apache modules. +Common routines for manipulating connections, student answers, + domains, common Javascript fragments, etc. -Invocation: - &Apache::loncommon::SUBROUTINENAME(ARGUMENTS); +=head1 OVERVIEW -=head1 INTRODUCTION - -Common collection of used subroutines. This collection helps remove +A collection of commonly used subroutines that don't have a natural +home anywhere else. This collection helps remove redundancy from other modules and increase efficiency of memory usage. -Current things done: - - Makes a table out of the previous homework attempts - Inputs result_from_symbread, user, domain, course_id - Reads in non-network-related .tab files - -This is part of the LearningOnline Network with CAPA project -described at http://www.lon-capa.org. - -=head2 General Subroutines - -=over 4 - =cut # End of POD header @@ -80,57 +64,72 @@ package Apache::loncommon; use strict; use Apache::lonnet(); use GDBM_File; -use POSIX qw(strftime); -use Apache::Constants qw(:common); +use POSIX qw(strftime mktime); +use Apache::Constants qw(:common :http :methods); use Apache::lonmsg(); +use Apache::lonmenu(); +use Apache::lonlocal; + my $readit; =pod -=item Global Variables - -=over 4 +=head1 Global Variables =cut + # ----------------------------------------------- Filetypes/Languages/Copyright my %language; +my %supported_language; my %cprtag; my %fe; my %fd; my %category_extensions; +# ---------------------------------------------- Designs + +my %designhash; + # ---------------------------------------------- Thesaurus variables +# FIXME: I don't think it's necessary to document these things; +# they're privately used - Jeremy + =pod -=item %Keywords +=over 4 + +=item * %Keywords A hash used by &keyword to determine if a word is considered a keyword. -=item $thesaurus_db_file +=item * $thesaurus_db_file Scalar containing the full path to the thesaurus database. +=back + =cut my %Keywords; my $thesaurus_db_file; +# ----------------------------------------------------------------------- BEGIN +# FIXME: I don't think this needs to be documented, it prepares +# private data structures - Jeremy =pod -=back - -=cut - -# ----------------------------------------------------------------------- BEGIN +=head1 General Subroutines -=pod +=over 4 -=item BEGIN() +=item * BEGIN() Initialize values from language.tab, copyright.tab, filetypes.tab, thesaurus.tab, and filecategories.tab. +=back + =cut # ----------------------------------------------------------------------- BEGIN @@ -148,8 +147,11 @@ BEGIN { while (<$fh>) { next if /^\#/; chomp; - my ($key,$val)=(split(/\s+/,$_,2)); - $language{$key}=$val; + my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$_)); + $language{$key}=$val.' - '.$enc; + if ($sup) { + $supported_language{$key}=$sup; + } } } } @@ -166,6 +168,30 @@ BEGIN { } } } + +# -------------------------------------------------------------- domain designs + + my $filename; + my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors'; + opendir(DIR,$designdir); + while ($filename=readdir(DIR)) { + my ($domain)=($filename=~/^(\w+)\./); + { + my $fh=Apache::File->new($designdir.'/'.$filename); + if ($fh) { + while (<$fh>) { + next if /^\#/; + chomp; + my ($key,$val)=(split(/\=/,$_)); + if ($val) { $designhash{$domain.'.'.$key}=$val; } + } + } + } + + } + closedir(DIR); + + # ------------------------------------------------------------- file categories { my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. @@ -201,21 +227,27 @@ BEGIN { } # end of unless($readit) } -# ============================================================= END BEGIN BLOCK + ############################################################### ## HTML and Javascript Helper Functions ## ############################################################### =pod -=item browser_and_searcher_javascript +=head1 HTML and Javascript Functions + +=over 4 + +=item * browser_and_searcher_javascript () -Returns scalar containing javascript to open a browser window -or a searcher window. Also creates +X<browsing, javascript>X<searching, javascript>Returns a string +containing javascript with two functions, C<openbrowser> and +C<opensearcher>. Returned string does not contain E<lt>scriptE<gt> +tags. =over 4 -=item openbrowser(formname,elementname,only,omit) [javascript] +=item * openbrowser(formname,elementname,only,omit) [javascript] inputs: formname, elementname, only, omit @@ -228,7 +260,7 @@ with the given extension. Can be a comm Specifying 'omit' will restrict the browser to NOT displaying files with the given extension. Can be a comma seperated list. -=item opensearcher(formname, elementname) [javascript] +=item * opensearcher(formname, elementname) [javascript] Inputs: formname, elementname @@ -239,11 +271,10 @@ of the element the selection from the se =cut -############################################################### sub browser_and_searcher_javascript { return <<END; - var editbrowser; - function openbrowser(formname,elementname,only,omit) { + var editbrowser = null; + function openbrowser(formname,elementname,only,omit,titleelement) { var url = '/res/?'; if (editbrowser == null) { url += 'launch=1&'; @@ -257,6 +288,9 @@ sub browser_and_searcher_javascript { if (omit != null) { url += 'omit=' + omit + '&'; } + if (titleelement != null) { + url += 'titleelement=' + titleelement + '&'; + } url += 'element=' + elementname + ''; var title = 'Browser'; var options = 'scrollbars=1,resizable=1,menubar=0'; @@ -265,7 +299,7 @@ sub browser_and_searcher_javascript { editbrowser.focus(); } var editsearcher; - function opensearcher(formname,elementname) { + function opensearcher(formname,elementname,titleelement) { var url = '/adm/searchcat?'; if (editsearcher == null) { url += 'launch=1&'; @@ -273,6 +307,9 @@ sub browser_and_searcher_javascript { url += 'catalogmode=interactive&'; url += 'mode=edit&'; url += 'form=' + formname + '&'; + if (titleelement != null) { + url += 'titleelement=' + titleelement + '&'; + } url += 'element=' + elementname + ''; var title = 'Search'; var options = 'scrollbars=1,resizable=1,menubar=0'; @@ -283,13 +320,93 @@ sub browser_and_searcher_javascript { END } +sub studentbrowser_javascript { + unless ( + (($ENV{'request.course.id'}) && + (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'}))) + || ($ENV{'request.role'}=~/^(au|dc|su)/) + ) { return ''; } + return (<<'ENDSTDBRW'); +<script type="text/javascript" language="Javascript" > + var stdeditbrowser; + function openstdbrowser(formname,uname,udom,roleflag) { + var url = '/adm/pickstudent?'; + var filter; + eval('filter=document.'+formname+'.'+uname+'.value;'); + if (filter != null) { + if (filter != '') { + url += 'filter='+filter+'&'; + } + } + url += 'form=' + formname + '&unameelement='+uname+ + '&udomelement='+udom; + if (roleflag) { url+="&roles=1"; } + var title = 'Student_Browser'; + var options = 'scrollbars=1,resizable=1,menubar=0'; + options += ',width=700,height=600'; + stdeditbrowser = open(url,title,options,'1'); + stdeditbrowser.focus(); + } +</script> +ENDSTDBRW +} +sub selectstudent_link { + my ($form,$unameele,$udomele)=@_; + if ($ENV{'request.course.id'}) { + unless (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'})) { + return ''; + } + return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele. + '","'.$udomele.'");'."'>".&mt('Select User')."</a>"; + } + if ($ENV{'request.role'}=~/^(au|dc|su)/) { + return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele. + '","'.$udomele.'",1);'."'>".&mt('Select User')."</a>"; + } + return ''; +} + +sub coursebrowser_javascript { + my ($domainfilter)=@_; + return (<<ENDSTDBRW); +<script type="text/javascript" language="Javascript" > + var stdeditbrowser; + function opencrsbrowser(formname,uname,udom) { + var url = '/adm/pickcourse?'; + var filter; + if (filter != null) { + if (filter != '') { + url += 'filter='+filter+'&'; + } + } + var domainfilter='$domainfilter'; + if (domainfilter != null) { + if (domainfilter != '') { + url += 'domainfilter='+domainfilter+'&'; + } + } + url += 'form=' + formname + '&cnumelement='+uname+ + '&cdomelement='+udom; + var title = 'Course_Browser'; + var options = 'scrollbars=1,resizable=1,menubar=0'; + options += ',width=700,height=600'; + stdeditbrowser = open(url,title,options,'1'); + stdeditbrowser.focus(); + } +</script> +ENDSTDBRW +} -############################################################### +sub selectcourse_link { + my ($form,$unameele,$udomele)=@_; + return "<a href='".'javascript:opencrsbrowser("'.$form.'","'.$unameele. + '","'.$udomele.'");'."'>".&mt('Select Course')."</a>"; +} =pod -=item linked_select_forms(...) +=item * linked_select_forms(...) linked_select_forms returns a string containing a <script></script> block and html for two <select> menus. The select menus will be linked in that @@ -301,17 +418,17 @@ linked_select_forms takes the following =over 4 -=item $formname, the name of the <form> tag +=item * $formname, the name of the <form> tag -=item $middletext, the text which appears between the <select> tags +=item * $middletext, the text which appears between the <select> tags -=item $firstdefault, the default value for the first menu +=item * $firstdefault, the default value for the first menu -=item $firstselectname, the name of the first <select> tag +=item * $firstselectname, the name of the first <select> tag -=item $secondselectname, the name of the second <select> tag +=item * $secondselectname, the name of the second <select> tag -=item $hashref, a reference to a hash containing the data for the menus. +=item * $hashref, a reference to a hash containing the data for the menus. =back @@ -322,41 +439,39 @@ first menu value is given in $menu{$choi and text for the second menu are given in the hash pointed to by $menu{$choice1}->{'select2'}. -my %menu = ( A1 => { text =>"Choice A1" , - default => "B3", - select2 => { - B1 => "Choice B1", - B2 => "Choice B2", - B3 => "Choice B3", - B4 => "Choice B4" - } - }, - A2 => { text =>"Choice A2" , - default => "C2", - select2 => { - C1 => "Choice C1", - C2 => "Choice C2", - C3 => "Choice C3" - } - }, - A3 => { text =>"Choice A3" , - default => "D6", - select2 => { - D1 => "Choice D1", - D2 => "Choice D2", - D3 => "Choice D3", - D4 => "Choice D4", - D5 => "Choice D5", - D6 => "Choice D6", - D7 => "Choice D7" - } - } - ); + my %menu = ( A1 => { text =>"Choice A1" , + default => "B3", + select2 => { + B1 => "Choice B1", + B2 => "Choice B2", + B3 => "Choice B3", + B4 => "Choice B4" + } + }, + A2 => { text =>"Choice A2" , + default => "C2", + select2 => { + C1 => "Choice C1", + C2 => "Choice C2", + C3 => "Choice C3" + } + }, + A3 => { text =>"Choice A3" , + default => "D6", + select2 => { + D1 => "Choice D1", + D2 => "Choice D2", + D3 => "Choice D3", + D4 => "Choice D4", + D5 => "Choice D5", + D6 => "Choice D6", + D7 => "Choice D7" + } + } + ); =cut -# ------------------------------------------------ - sub linked_select_forms { my ($formname, $middletext, @@ -418,7 +533,7 @@ END foreach my $value (sort(keys(%$hashref))) { $result.=" <option value=\"$value\" "; $result.=" selected=\"true\" " if ($value eq $firstdefault); - $result.=">$hashref->{$value}->{'text'}</option>\n"; + $result.=">".&mt($hashref->{$value}->{'text'})."</option>\n"; } $result .= "</select>\n"; my %select2 = %{$hashref->{$firstdefault}->{'select2'}}; @@ -428,62 +543,102 @@ END foreach my $value (sort(keys(%select2))) { $result.=" <option value=\"$value\" "; $result.=" selected=\"true\" " if ($value eq $seconddefault); - $result.=">$select2{$value}</option>\n"; + $result.=">".&mt($select2{$value})."</option>\n"; } $result .= "</select>\n"; # return $debug; return $result; } # end of sub linked_select_forms { -############################################################### - =pod -=item help_open_topic($topic, $stayOnPage, $width, $height) - -Returns a string corresponding to an HTML link to the given help $topic, where $topic corresponds to the name of a .tex file in /home/httpd/html/adm/help/tex, with underscores replaced by spaces. - -$stayOnPage is a value that will be interpreted as a boolean. If true, the link will not open a new window. If false, the link will open a new window using Javascript. (Default is false.) +=item * help_open_topic($topic, $text, $stayOnPage, $width, $height) -$width and $height are optional numerical parameters that will override the width and height of the popped up window, which may be useful for certain help topics with big pictures included. +Returns a string corresponding to an HTML link to the given help +$topic, where $topic corresponds to the name of a .tex file in +/home/httpd/html/adm/help/tex, with underscores replaced by +spaces. + +$text will optionally be linked to the same topic, allowing you to +link text in addition to the graphic. If you do not want to link +text, but wish to specify one of the later parameters, pass an +empty string. + +$stayOnPage is a value that will be interpreted as a boolean. If true, +the link will not open a new window. If false, the link will open +a new window using Javascript. (Default is false.) + +$width and $height are optional numerical parameters that will +override the width and height of the popped up window, which may +be useful for certain help topics with big pictures included. =cut sub help_open_topic { - my ($topic, $stayOnPage, $width, $height) = @_; + my ($topic, $text, $stayOnPage, $width, $height) = @_; + $text = "" if (not defined $text); $stayOnPage = 0 if (not defined $stayOnPage); + if ($ENV{'browser.interface'} eq 'textual' || + $ENV{'environment.remote'} eq 'off' ) { + $stayOnPage=1; + } $width = 350 if (not defined $width); $height = 400 if (not defined $height); my $filename = $topic; $filename =~ s/ /_/g; - my $template; + my $template = ""; + my $link; if (!$stayOnPage) { - $template = <<"ENDTEMPLATE"; -<a href="javascript:void(open('/adm/help/${filename}.hlp', 'Help for $topic', 'menubar=0,s -crollbars=1,width=$width,height=$height'))"><image - src="/adm/help/gif/smallHelp.gif" - border="0" alt="(Help: $topic)"></a> -ENDTEMPLATE + $link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))"; } else { - $template = <<"ENDTEMPLATE"; -<a href="/adm/help/${filename}.hlp"><image - src="/adm/help/gif/smallHelp.gif" - border="0" alt="(Help: $topic)"></a> -ENDTEMPLATE + $link = "/adm/help/${filename}.hlp"; } + # Add the text + if ($text ne "") + { + $template .= + "<table bgcolor='#3333AA' cellspacing='1' cellpadding='1' border='0'><tr>". + "<td bgcolor='#5555FF'><a href=\"$link\"><font color='#FFFFFF' size='2'>$text</font></a>"; + } + + # Add the graphic + $template .= <<"ENDTEMPLATE"; + <a href="$link"><image src="/adm/help/gif/smallHelp.gif" border="0" alt="(Help: $topic)" /></a> +ENDTEMPLATE + if ($text ne '') { $template.='</td></tr></table>' }; return $template; } +# This is a quicky function for Latex cheatsheet editing, since it +# appears in at least four places +sub helpLatexCheatsheet { + my $other = shift; + my $addOther = ''; + if ($other) { + $addOther = Apache::loncommon::help_open_topic($other, shift, + undef, undef, 600) . + '</td><td>'; + } + return '<table><tr><td>'. + $addOther . + &Apache::loncommon::help_open_topic("Greek_Symbols",'Greek Symbols', + undef,undef,600) + .'</td><td>'. + &Apache::loncommon::help_open_topic("Other_Symbols",'Other Symbols', + undef,undef,600) + .'</td></tr></table>'; +} + =pod -=item csv_translate($text) +=item * csv_translate($text) Translate $text to allow it to be output as a 'comma seperated values' format. @@ -497,14 +652,102 @@ sub csv_translate { return $text; } +=pod + +=item * change_content_javascript(): + +This and the next function allow you to create small sections of an +otherwise static HTML page that you can update on the fly with +Javascript, even in Netscape 4. + +The Javascript fragment returned by this function (no E<lt>scriptE<gt> tag) +must be written to the HTML page once. It will prove the Javascript +function "change(name, content)". Calling the change function with the +name of the section +you want to update, matching the name passed to C<changable_area>, and +the new content you want to put in there, will put the content into +that area. + +B<Note>: Netscape 4 only reserves enough space for the changable area +to contain room for the original contents. You need to "make space" +for whatever changes you wish to make, and be B<sure> to check your +code in Netscape 4. This feature in Netscape 4 is B<not> powerful; +it's adequate for updating a one-line status display, but little more. +This script will set the space to 100% width, so you only need to +worry about height in Netscape 4. + +Modern browsers are much less limiting, and if you can commit to the +user not using Netscape 4, this feature may be used freely with +pretty much any HTML. + +=cut + +sub change_content_javascript { + # If we're on Netscape 4, we need to use Layer-based code + if ($ENV{'browser.type'} eq 'netscape' && + $ENV{'browser.version'} =~ /^4\./) { + return (<<NETSCAPE4); + function change(name, content) { + doc = document.layers[name+"___escape"].layers[0].document; + doc.open(); + doc.write(content); + doc.close(); + } +NETSCAPE4 + } else { + # Otherwise, we need to use semi-standards-compliant code + # (technically, "innerHTML" isn't standard but the equivalent + # is really scary, and every useful browser supports it + return (<<DOMBASED); + function change(name, content) { + element = document.getElementById(name); + element.innerHTML = content; + } +DOMBASED + } +} + +=pod + +=item * changable_area($name, $origContent): + +This provides a "changable area" that can be modified on the fly via +the Javascript code provided in C<change_content_javascript>. $name is +the name you will use to reference the area later; do not repeat the +same name on a given HTML page more then once. $origContent is what +the area will originally contain, which can be left blank. + +=cut + +sub changable_area { + my ($name, $origContent) = @_; + + if ($ENV{'browser.type'} eq 'netscape' && + $ENV{'browser.version'} =~ /^4\./) { + # If this is netscape 4, we need to use the Layer tag + return "<ilayer width='100%' id='${name}___escape' overflow='none'><layer width='100%' id='$name' overflow='none'>$origContent</layer></ilayer>"; + } else { + return "<span id='$name'>$origContent</span>"; + } +} + +=pod + +=back + +=cut + ############################################################### ## Home server <option> list generating code ## ############################################################### -#------------------------------------------- =pod -=item get_domains() +=head1 Home Server option list generating code + +=over 4 + +=item * get_domains() Returns an array containing each of the domains listed in the hosts.tab file. @@ -526,18 +769,54 @@ sub get_domains { =pod -=item select_dom_form($defdom,$name) +=item * select_form($defdom,$name,%hash) + +Returns a string containing a <select name='$name' size='1'> form to +allow a user to select options from a hash option_name => displayed text. +See lonrights.pm for an example invocation and use. + +=cut + +#------------------------------------------- +sub select_form { + my ($def,$name,%hash) = @_; + my $selectform = "<select name=\"$name\" size=\"1\">\n"; + my @keys; + if (exists($hash{'select_form_order'})) { + @keys=@{$hash{'select_form_order'}}; + } else { + @keys=sort(keys(%hash)); + } + foreach (@keys) { + $selectform.="<option value=\"$_\" ". + ($_ eq $def ? 'selected' : ''). + ">".&mt($hash{$_})."</option>\n"; + } + $selectform.="</select>"; + return $selectform; +} + + +#------------------------------------------- + +=pod + +=item * select_dom_form($defdom,$name,$includeempty) Returns a string containing a <select name='$name' size='1'> form to allow a user to select the domain to preform an operation in. See loncreateuser.pm for an example invocation and use. +If the $includeempty flag is set, it also includes an empty choice ("no domain +selected"); + =cut #------------------------------------------- sub select_dom_form { - my ($defdom,$name) = @_; + my ($defdom,$name,$includeempty) = @_; my @domains = get_domains(); + if ($includeempty) { @domains=('',@domains); } my $selectdomain = "<select name=\"$name\" size=\"1\">\n"; foreach (@domains) { $selectdomain.="<option value=\"$_\" ". @@ -552,7 +831,7 @@ sub select_dom_form { =pod -=item get_home_servers($domain) +=item * get_library_servers($domain) Returns a hash which contains keys like '103l3' and values like 'kirk.lite.msu.edu'. All of the keys will be for machines in the @@ -561,22 +840,22 @@ given $domain. =cut #------------------------------------------- -sub get_home_servers { +sub get_library_servers { my $domain = shift; - my %home_servers; + my %library_servers; foreach (keys(%Apache::lonnet::libserv)) { if ($Apache::lonnet::hostdom{$_} eq $domain) { - $home_servers{$_} = $Apache::lonnet::hostname{$_}; + $library_servers{$_} = $Apache::lonnet::hostname{$_}; } } - return %home_servers; + return %library_servers; } #------------------------------------------- =pod -=item home_server_option_list($domain) +=item * home_server_option_list($domain) returns a string which contains an <option> list to be used in a <select> form input. See loncreateuser.pm for an example. @@ -586,7 +865,7 @@ returns a string which contains an <opti #------------------------------------------- sub home_server_option_list { my $domain = shift; - my %servers = &get_home_servers($domain); + my %servers = &get_library_servers($domain); my $result = ''; foreach (sort keys(%servers)) { $result.= @@ -594,10 +873,89 @@ sub home_server_option_list { } return $result; } + +=pod + +=back + +=cut + ############################################################### -## End of home server <option> list generating code ## +## Decoding User Agent ## ############################################################### +=pod + +=head1 Decoding the User Agent + +=over 4 + +=item * &decode_user_agent() + +Inputs: $r + +Outputs: + +=over 4 + +=item * $httpbrowser + +=item * $clientbrowser + +=item * $clientversion + +=item * $clientmathml + +=item * $clientunicode + +=item * $clientos + +=back + +=cut + +############################################################### +############################################################### +sub decode_user_agent { + my @browsertype=split(/\&/,$Apache::lonnet::perlvar{"lonBrowsDet"}); + my %mathcap=split(/\&/,$$Apache::lonnet::perlvar{"lonMathML"}); + my $httpbrowser=$ENV{"HTTP_USER_AGENT"}; + my $clientbrowser='unknown'; + my $clientversion='0'; + my $clientmathml=''; + my $clientunicode='0'; + for (my $i=0;$i<=$#browsertype;$i++) { + my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\:/,$browsertype[$i]); + if (($httpbrowser=~/$match/i) && ($httpbrowser!~/$notmatch/i)) { + $clientbrowser=$bname; + $httpbrowser=~/$vreg/i; + $clientversion=$1; + $clientmathml=($clientversion>=$minv); + $clientunicode=($clientversion>=$univ); + } + } + my $clientos='unknown'; + if (($httpbrowser=~/linux/i) || + ($httpbrowser=~/unix/i) || + ($httpbrowser=~/ux/i) || + ($httpbrowser=~/solaris/i)) { $clientos='unix'; } + if (($httpbrowser=~/vax/i) || + ($httpbrowser=~/vms/i)) { $clientos='vms'; } + if ($httpbrowser=~/next/i) { $clientos='next'; } + if (($httpbrowser=~/mac/i) || + ($httpbrowser=~/powerpc/i)) { $clientos='mac'; } + if ($httpbrowser=~/win/i) { $clientos='win'; } + if ($httpbrowser=~/embed/i) { $clientos='pda'; } + return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml, + $clientunicode,$clientos,); +} + +=pod + +=back + +=cut + ############################################################### ## Authentication changing form generation subroutines ## ############################################################### @@ -610,7 +968,11 @@ sub home_server_option_list { =pod -=item authform_xxxxxx +=head1 Authentication Routines + +=over 4 + +=item * authform_xxxxxx The authform_xxxxxx subroutines provide javascript and html forms which handle some of the conveniences required for authentication forms. @@ -620,17 +982,17 @@ See loncreateuser.pm for invocation and =over 4 -=item authform_header +=item * authform_header -=item authform_authorwarning +=item * authform_authorwarning -=item authform_nochange +=item * authform_nochange -=item authform_kerberos +=item * authform_kerberos -=item authform_internal +=item * authform_internal -=item authform_filesystem +=item * authform_filesystem =back @@ -640,11 +1002,27 @@ See loncreateuser.pm for invocation and sub authform_header{ my %in = ( formname => 'cu', - kerb_def_dom => 'MSU.EDU', + kerb_def_dom => '', @_, ); $in{'formname'} = 'document.' . $in{'formname'}; my $result=''; + +#---------------------------------------------- Code for upper case translation + my $Javascript_toUpperCase; + unless ($in{kerb_def_dom}) { + $Javascript_toUpperCase =<<"END"; + switch (choice) { + case 'krb': currentform.elements[choicearg].value = + currentform.elements[choicearg].value.toUpperCase(); + break; + default: + } +END + } else { + $Javascript_toUpperCase = ""; + } + $result.=<<"END"; var current = new Object(); current.radiovalue = 'nochange'; @@ -678,12 +1056,7 @@ function changed_radio(choice,currentfor function changed_text(choice,currentform) { var choicearg = choice + 'arg'; if (currentform.elements[choicearg].value !='') { - switch (choice) { - case 'krb': currentform.elements[choicearg].value = - currentform.elements[choicearg].value.toUpperCase(); - break; - default: - } + $Javascript_toUpperCase // clear old field if ((current.argfield != choicearg) && (current.argfield != null)) { currentform.elements[current.argfield].value = ''; @@ -729,7 +1102,7 @@ sub authform_nochange{ my $result=''; $result.=<<"END"; <input type="radio" name="login" value="nochange" checked="checked" - onclick="javascript:changed_radio('nochange',$in{'formname'});"> + onclick="javascript:changed_radio('nochange',$in{'formname'});" /> Do not change login data END return $result; @@ -739,16 +1112,26 @@ sub authform_kerberos{ my %in = ( formname => 'document.cu', kerb_def_dom => 'MSU.EDU', + kerb_def_auth => 'krb4', @_, ); my $result=''; + my $check4; + my $check5; + if ($in{'kerb_def_auth'} eq 'krb5') { + $check5 = " checked=\"on\""; + } else { + $check4 = " checked=\"on\""; + } $result.=<<"END"; <input type="radio" name="login" value="krb" onclick="javascript:changed_radio('krb',$in{'formname'});" - onchange="javascript:changed_radio('krb',$in{'formname'});"> + onchange="javascript:changed_radio('krb',$in{'formname'});" /> Kerberos authenticated with domain -<input type="text" size="10" name="krbarg" value="" - onchange="javascript:changed_text('krb',$in{'formname'});"> +<input type="text" size="10" name="krbarg" value="$in{'kerb_def_dom'}" + onchange="javascript:changed_text('krb',$in{'formname'});" /> +<input type="radio" name="krbver" value="4" $check4 />Version 4 +<input type="radio" name="krbver" value="5" $check5 />Version 5 END return $result; } @@ -763,10 +1146,10 @@ sub authform_internal{ $result.=<<"END"; <input type="radio" name="login" value="int" onchange="javascript:changed_radio('int',$args{'formname'});" - onclick="javascript:changed_radio('int',$args{'formname'});"> + onclick="javascript:changed_radio('int',$args{'formname'});" /> Internally authenticated (with initial password <input type="text" size="10" name="intarg" value="" - onchange="javascript:changed_text('int',$args{'formname'});"> + onchange="javascript:changed_text('int',$args{'formname'});" />) END return $result; } @@ -781,10 +1164,10 @@ sub authform_local{ $result.=<<"END"; <input type="radio" name="login" value="loc" onchange="javascript:changed_radio('loc',$in{'formname'});" - onclick="javascript:changed_radio('loc',$in{'formname'});"> + onclick="javascript:changed_radio('loc',$in{'formname'});" /> Local Authentication with argument <input type="text" size="10" name="locarg" value="" - onchange="javascript:changed_text('loc',$in{'formname'});"> + onchange="javascript:changed_text('loc',$in{'formname'});" /> END return $result; } @@ -799,25 +1182,107 @@ sub authform_filesystem{ $result.=<<"END"; <input type="radio" name="login" value="fsys" onchange="javascript:changed_radio('fsys',$in{'formname'});" - onclick="javascript:changed_radio('fsys',$in{'formname'});"> + onclick="javascript:changed_radio('fsys',$in{'formname'});" /> Filesystem authenticated (with initial password <input type="text" size="10" name="fsysarg" value="" - onchange="javascript:changed_text('fsys',$in{'formname'});"> + onchange="javascript:changed_text('fsys',$in{'formname'});">) END return $result; } +=pod + +=back + +=cut + +############################################################### +## Get Authentication Defaults for Domain ## +############################################################### + +=pod + +=head1 Domains and Authentication + +Returns default authentication type and an associated argument as +listed in file 'domain.tab'. + +=over 4 + +=item * get_auth_defaults + +get_auth_defaults($target_domain) returns the default authentication +type and an associated argument (initial password or a kerberos domain). +These values are stored in lonTabs/domain.tab + +($def_auth, $def_arg) = &get_auth_defaults($target_domain); + +If target_domain is not found in domain.tab, returns nothing (''). + +=cut + +#------------------------------------------- +sub get_auth_defaults { + my $domain=shift; + return ($Apache::lonnet::domain_auth_def{$domain},$Apache::lonnet::domain_auth_arg_def{$domain}); +} ############################################################### -## End Authentication changing form generation functions ## +## End Get Authentication Defaults for Domain ## ############################################################### ############################################################### +## Get Kerberos Defaults for Domain ## +############################################################### +## +## Returns default kerberos version and an associated argument +## as listed in file domain.tab. If not listed, provides +## appropriate default domain and kerberos version. +## +#------------------------------------------- + +=pod + +=item * get_kerberos_defaults + +get_kerberos_defaults($target_domain) returns the default kerberos +version and domain. If not found in domain.tabs, it defaults to +version 4 and the domain of the server. + +($def_version, $def_krb_domain) = &get_kerberos_defaults($target_domain); + +=cut + +#------------------------------------------- +sub get_kerberos_defaults { + my $domain=shift; + my ($krbdef,$krbdefdom) = + &Apache::loncommon::get_auth_defaults($domain); + unless ($krbdef =~/^krb/ && $krbdefdom) { + $ENV{'SERVER_NAME'}=~/(\w+\.\w+)$/; + my $krbdefdom=$1; + $krbdefdom=~tr/a-z/A-Z/; + $krbdef = "krb4"; + } + return ($krbdef,$krbdefdom); +} + +=pod + +=back + +=cut + +############################################################### ## Thesaurus Functions ## ############################################################### =pod -=item initialize_keywords +=head1 Thesaurus Functions + +=over 4 + +=item * initialize_keywords Initializes the package variable %Keywords if it is empty. Uses the package variable $thesaurus_db_file. @@ -838,7 +1303,7 @@ sub initialize_keywords { # Set up the hash as a database my %thesaurus_db; if (! tie(%thesaurus_db,'GDBM_File', - $thesaurus_db_file,&GDBM_READER,0640)){ + $thesaurus_db_file,&GDBM_READER(),0640)){ &Apache::lonnet::logthis("Could not tie \%thesaurus_db to ". $thesaurus_db_file); return 0; @@ -862,7 +1327,7 @@ sub initialize_keywords { =pod -=item keyword($word) +=item * keyword($word) Returns true if $word is a keyword. A keyword is a word that appears more than the average number of times in the thesaurus database. Calls @@ -879,57 +1344,11 @@ sub keyword { return exists($Keywords{$word}); } -################################################### -# Old code, to be removed soon # -################################################### -# -------------------------------------------------------- Return related words -#sub related { -# my $newword=shift; -# $newword=~s/\W//g; -# $newword=~tr/A-Z/a-z/; -# my $tindex=$theindex{$newword}; -# if ($tindex) { -# my %found=(); -# foreach (split(/\,/,$therelated[$tindex])) { -## - Related word found -# my ($ridx,$rcount)=split(/\:/,$_); -## - Direct relation index -# my $directrel=$rcount/$thecount[$tindex]; -# if ($directrel>$thethreshold) { -# foreach (split(/\,/,$therelated[$ridx])) { -# my ($rridx,$rrcount)=split(/\:/,$_); -# if ($rridx==$tindex) { -## - Determine reverse relation index -# my $revrel=$rrcount/$thecount[$ridx]; -## - Calculate full index -# $found{$ridx}=$directrel*$revrel; -# if ($found{$ridx}>$thethreshold) { -# foreach (split(/\,/,$therelated[$ridx])) { -# my ($rrridx,$rrrcount)=split(/\:/,$_); -# unless ($found{$rrridx}) { -# my $revrevrel=$rrrcount/$thecount[$ridx]; -# if ( -# $directrel*$revrel*$revrevrel>$thethreshold -# ) { -# $found{$rrridx}= -# $directrel*$revrel*$revrevrel; -# } -# } -# } -# } -# } -# } -# } -# } -# } -# return (); -#} - ############################################################### =pod -=item get_related_words +=item * get_related_words Look up a word in the thesaurus. Takes a scalar arguement and returns an array of words. If the keyword is not in the thesaurus, an empty array @@ -941,7 +1360,6 @@ Uses global $thesaurus_db_file. =cut ############################################################### - sub get_related_words { my $keyword = shift; my %thesaurus_db; @@ -951,7 +1369,7 @@ sub get_related_words { return (); } if (! tie(%thesaurus_db,'GDBM_File', - $thesaurus_db_file,&GDBM_READER,0640)){ + $thesaurus_db_file,&GDBM_READER(),0640)){ return (); } my @Words=(); @@ -967,73 +1385,350 @@ sub get_related_words { return @Words; } +=pod + +=back + +=cut + +# -------------------------------------------------------------- Plaintext name +=pod + +=head1 User Name Functions + +=over 4 + +=item * plainname($uname,$udom) + +Takes a users logon name and returns it as a string in +"first middle last generation" form + +=cut + ############################################################### -## End Thesaurus Functions ## -############################################################### +sub plainname { + my ($uname,$udom)=@_; + my %names=&Apache::lonnet::get('environment', + ['firstname','middlename','lastname','generation'], + $udom,$uname); + my $name=$names{'firstname'}.' '.$names{'middlename'}.' '. + $names{'lastname'}.' '.$names{'generation'}; + $name=~s/\s+$//; + $name=~s/\s+/ /g; + return $name; +} + +# -------------------------------------------------------------------- Nickname +=pod + +=item * nickname($uname,$udom) + +Gets a users name and returns it as a string as + +""nickname"" + +if the user has a nickname or + +"first middle last generation" + +if the user does not + +=cut + +sub nickname { + my ($uname,$udom)=@_; + my %names=&Apache::lonnet::get('environment', + ['nickname','firstname','middlename','lastname','generation'],$udom,$uname); + my $name=$names{'nickname'}; + if ($name) { + $name='"'.$name.'"'; + } else { + $name=$names{'firstname'}.' '.$names{'middlename'}.' '. + $names{'lastname'}.' '.$names{'generation'}; + $name=~s/\s+$//; + $name=~s/\s+/ /g; + } + return $name; +} + + +# ------------------------------------------------------------------ Screenname + +=pod + +=item * screenname($uname,$udom) + +Gets a users screenname and returns it as a string + +=cut + +sub screenname { + my ($uname,$udom)=@_; + my %names= + &Apache::lonnet::get('environment',['screenname'],$udom,$uname); + return $names{'screenname'}; +} + +# ------------------------------------------------------------- Message Wrapper + +sub messagewrapper { + my ($link,$un,$do)=@_; + return +"<a href='/adm/email?compose=individual&recname=$un&recdom=$do'>$link</a>"; +} +# --------------------------------------------------------------- Notes Wrapper + +sub noteswrapper { + my ($link,$un,$do)=@_; + return +"<a href='/adm/email?recordftf=retrieve&recname=$un&recdom=$do'>$link</a>"; +} +# ------------------------------------------------------------- Aboutme Wrapper + +sub aboutmewrapper { + my ($link,$username,$domain)=@_; + return "<a href='/adm/$domain/$username/aboutme'>$link</a>"; +} + +# ------------------------------------------------------------ Syllabus Wrapper + + +sub syllabuswrapper { + my ($linktext,$coursedir,$domain,$fontcolor)=@_; + if ($fontcolor) { + $linktext='<font color="'.$fontcolor.'">'.$linktext.'</font>'; + } + return "<a href='/public/$domain/$coursedir/syllabus'>$linktext</a>"; +} + +=pod + +=back + +=head1 Access .tab File Data + +=over 4 + +=item * languageids() + +returns list of all language ids + +=cut -# ---------------------------------------------------------------- Language IDs sub languageids { return sort(keys(%language)); } -# -------------------------------------------------------- Language Description +=pod + +=item * languagedescription() + +returns description of a specified language id + +=cut + sub languagedescription { - return $language{shift(@_)}; + my $code=shift; + return ($supported_language{$code}?'* ':''). + $language{$code}. + ($supported_language{$code}?' ('.&mt('interface available').')':''); } -# --------------------------------------------------------------- Copyright IDs +=pod + +=item * copyrightids() + +returns list of all copyrights + +=cut + sub copyrightids { return sort(keys(%cprtag)); } -# ------------------------------------------------------- Copyright Description +=pod + +=item * copyrightdescription() + +returns description of a specified copyright id + +=cut + sub copyrightdescription { return $cprtag{shift(@_)}; } -# ------------------------------------------------------------- File Categories +=pod + +=item * filecategories() + +returns list of all file categories + +=cut + sub filecategories { return sort(keys(%category_extensions)); } -# -------------------------------------- File Types within a specified category +=pod + +=item * filecategorytypes() + +returns list of file types belonging to a given file +category + +=cut + sub filecategorytypes { return @{$category_extensions{lc($_[0])}}; } -# ------------------------------------------------------------------ File Types -sub fileextensions { - return sort(keys(%fe)); -} +=pod + +=item * fileembstyle() + +returns embedding style for a specified file type + +=cut -# ------------------------------------------------------------- Embedding Style sub fileembstyle { return $fe{lc(shift(@_))}; } -# ------------------------------------------------------------ Description Text +=pod + +=item * filedescription() + +returns description for a specified file type + +=cut + sub filedescription { return $fd{lc(shift(@_))}; } -# ------------------------------------------------------------ Description Text +=pod + +=item * filedescriptionex() + +returns description for a specified file type with +extra formatting + +=cut + sub filedescriptionex { my $ex=shift; return '.'.$ex.' '.$fd{lc($ex)}; } -# ---- Retrieve attempts by students -# input -# $symb - problem including path -# $username,$domain - that of the student -# $course - course name -# $getattempt - leave blank if want all attempts, else put something. -# $regexp - regular expression. If string matches regexp send to -# $gradesub - routine that process the string if it matches regexp -# -# output -# formatted as a table all the attempts, if any. +# End of .tab access +=pod + +=back + +=cut + +# ------------------------------------------------------------------ File Types +sub fileextensions { + return sort(keys(%fe)); +} + +# ----------------------------------------------------------- Display Languages +# returns a hash with all desired display languages # + +sub display_languages { + my %languages=(); + foreach (&preferred_languages()) { + $languages{$_}=1; + } + &get_unprocessed_cgi($ENV{'QUERY_STRING'},['displaylanguage']); + if ($ENV{'form.displaylanguage'}) { + foreach (split(/\s*(\,|\;|\:)\s*/,$ENV{'form.displaylanguage'})) { + $languages{$_}=1; + } + } + return %languages; +} + +sub preferred_languages { + my @languages=(); + if ($ENV{'environment.languages'}) { + @languages=split(/\s*(\,|\;|\:)\s*/,$ENV{'environment.languages'}); + } + if ($ENV{'course.'.$ENV{'request.course.id'}.'.languages'}) { + @languages=(@languages,split(/\s*(\,|\;|\:)\s*/, + $ENV{'course.'.$ENV{'request.course.id'}.'.languages'})); + } + my $browser=(split(/\;/,$ENV{'HTTP_ACCEPT_LANGUAGE'}))[0]; + if ($browser) { + @languages=(@languages,split(/\s*(\,|\;|\:)\s*/,$browser)); + } + if ($Apache::lonnet::domain_lang_def{$ENV{'user.domain'}}) { + @languages=(@languages, + $Apache::lonnet::domain_lang_def{$ENV{'user.domain'}}); + } + if ($Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}}) { + @languages=(@languages, + $Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}}); + } + if ($Apache::lonnet::domain_lang_def{ + $Apache::lonnet::perlvar{'lonDefDomain'}}) { + @languages=(@languages, + $Apache::lonnet::domain_lang_def{ + $Apache::lonnet::perlvar{'lonDefDomain'}}); + } +# turn "en-ca" into "en-ca,en" + my @genlanguages; + foreach (@languages) { + unless ($_=~/\w/) { next; } + push (@genlanguages,$_); + if ($_=~/(\-|\_)/) { + push (@genlanguages,(split(/(\-|\_)/,$_))[0]); + } + } + return @genlanguages; +} + +############################################################### +## Student Answer Attempts ## +############################################################### + +=pod + +=head1 Alternate Problem Views + +=over 4 + +=item * get_previous_attempt($symb, $username, $domain, $course, + $getattempt, $regexp, $gradesub) + +Return string with previous attempt on problem. Arguments: + +=over 4 + +=item * $symb: Problem, including path + +=item * $username: username of the desired student + +=item * $domain: domain of the desired student + +=item * $course: Course ID + +=item * $getattempt: Leave blank for all attempts, otherwise put + something + +=item * $regexp: if string matches this regexp, the string will be + sent to $gradesub + +=item * $gradesub: routine that processes the string if it matches $regexp + +=back + +The output string is a table containing all desired attempts, if any. + +=cut + sub get_previous_attempt { my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_; my $prevattempts=''; @@ -1087,7 +1782,7 @@ sub get_previous_attempt { } else { $value=$lasthash{$_}; } - if ($_ =~/$regexp$/) {$value = &$gradesub($value)} + if ($_ =~/$regexp$/ && (defined &$gradesub)) {$value = &$gradesub($value)} $prevattempts.='<td>'.$value.' </td>'; } $prevattempts.='</tr></table></td></tr></table>'; @@ -1099,17 +1794,62 @@ sub get_previous_attempt { } } +sub relative_to_absolute { + my ($url,$output)=@_; + my $parser=HTML::TokeParser->new(\$output); + my $token; + my $thisdir=$url; + my @rlinks=(); + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + if ($token->[1] eq 'a') { + if ($token->[2]->{'href'}) { + $rlinks[$#rlinks+1]=$token->[2]->{'href'}; + } + } elsif ($token->[1] eq 'img' || $token->[1] eq 'embed' ) { + $rlinks[$#rlinks+1]=$token->[2]->{'src'}; + } elsif ($token->[1] eq 'base') { + $thisdir=$token->[2]->{'href'}; + } + } + } + $thisdir=~s-/[^/]*$--; + foreach (@rlinks) { + unless (($_=~/^http:\/\//i) || + ($_=~/^\//) || + ($_=~/^javascript:/i) || + ($_=~/^mailto:/i) || + ($_=~/^\#/)) { + my $newlocation=&Apache::lonnet::hreflocation($thisdir,$_); + $output=~s/(\"|\'|\=\s*)$_(\"|\'|\s|\>)/$1$newlocation$2/; + } + } +# -------------------------------------------------- Deal with Applet codebases + $output=~s/(\<applet[^\>]+)(codebase\=[^\S\>]+)*([^\>]*)\>/$1.($2?$2:' codebase="'.$thisdir.'"').$3.'>'/gei; + return $output; +} + +=pod + +=item * get_student_view + +show a snapshot of what student was looking at + +=cut + sub get_student_view { - my ($symb,$username,$domain,$courseid) = @_; - my ($map,$id,$feedurl) = split(/___/,$symb); + my ($symb,$username,$domain,$courseid,$target) = @_; + my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb); my (%old,%moreenv); my @elements=('symb','courseid','domain','username'); foreach my $element (@elements) { $old{$element}=$ENV{'form.grade_'.$element}; $moreenv{'form.grade_'.$element}=eval '$'.$element #' } + if ($target eq 'tex') {$moreenv{'form.grade_target'} = 'tex';} &Apache::lonnet::appenv(%moreenv); - my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); + $feedurl=&Apache::lonnet::clutter($feedurl); + my $userview=&Apache::lonnet::ssi_body($feedurl); &Apache::lonnet::delenv('form.grade_'); foreach my $element (@elements) { $ENV{'form.grade_'.$element}=$old{$element}; @@ -1121,12 +1861,21 @@ sub get_student_view { $userview=~s/\<head\>//gi; $userview=~s/\<\/head\>//gi; $userview=~s/action\s*\=/would_be_action\=/gi; + $userview=&relative_to_absolute($feedurl,$userview); return $userview; } +=pod + +=item * get_student_answers() + +show a snapshot of how student was answering problem + +=cut + sub get_student_answers { - my ($symb,$username,$domain,$courseid) = @_; - my ($map,$id,$feedurl) = split(/___/,$symb); + my ($symb,$username,$domain,$courseid,%form) = @_; + my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb); my (%old,%moreenv); my @elements=('symb','courseid','domain','username'); foreach my $element (@elements) { @@ -1135,7 +1884,7 @@ sub get_student_answers { } $moreenv{'form.grade_target'}='answer'; &Apache::lonnet::appenv(%moreenv); - my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); + my $userview=&Apache::lonnet::ssi('/res/'.$feedurl,%form); &Apache::lonnet::delenv('form.grade_'); foreach my $element (@elements) { $ENV{'form.grade_'.$element}=$old{$element}; @@ -1143,10 +1892,412 @@ sub get_student_answers { return $userview; } +=pod + +=item * &submlink() + +Inputs: $text $uname $udom $symb + +Returns: A link to grades.pm such as to see the SUBM view of a student + +=cut + +############################################### +sub submlink { + my ($text,$uname,$udom,$symb)=@_; + if (!($uname && $udom)) { + (my $cursymb, my $courseid,$udom,$uname)= + &Apache::lonxml::whichuser($symb); + if (!$symb) { $symb=$cursymb; } + } + if (!$symb) { $symb=&symbread(); } + return '<a href="/adm/grades?symb='.$symb.'&student='.$uname. + '&userdom='.$udom.'&command=submission">'.$text.'</a>'; +} +############################################## + +=pod + +=back + +=cut + +############################################### + + +sub timehash { + my @ltime=localtime(shift); + return ( 'seconds' => $ltime[0], + 'minutes' => $ltime[1], + 'hours' => $ltime[2], + 'day' => $ltime[3], + 'month' => $ltime[4]+1, + 'year' => $ltime[5]+1900, + 'weekday' => $ltime[6], + 'dayyear' => $ltime[7]+1, + 'dlsav' => $ltime[8] ); +} + +sub maketime { + my %th=@_; + return POSIX::mktime( + ($th{'seconds'},$th{'minutes'},$th{'hours'}, + $th{'day'},$th{'month'}-1,$th{'year'}-1900,0,0,$th{'dlsav'})); +} + + +######################################### +# +# Retro-fixing of un-backward-compatible time format + +sub unsqltime { + my $timestamp=shift; + if ($timestamp=~/^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/) { + $timestamp=&maketime( + 'year'=>$1,'month'=>$2,'day'=>$3, + 'hours'=>$4,'minutes'=>$5,'seconds'=>$6); + } + return $timestamp; +} + +######################################### + +sub findallcourses { + my %courses=(); + my $now=time; + foreach (keys %ENV) { + if ($_=~/^user\.role\.\w+\.\/(\w+)\/(\w+)/) { + my ($starttime,$endtime)=$ENV{$_}; + my $active=1; + if ($starttime) { + if ($now<$starttime) { $active=0; } + } + if ($endtime) { + if ($now>$endtime) { $active=0; } + } + if ($active) { $courses{$1.'_'.$2}=1; } + } + } + return keys %courses; +} + +############################################### +############################################### + +=pod + +=head1 Domain Template Functions + +=over 4 + +=item * &determinedomain() + +Inputs: $domain (usually will be undef) + +Returns: Determines which domain should be used for designs + +=cut + +############################################### +sub determinedomain { + my $domain=shift; + if (! $domain) { + # Determine domain if we have not been given one + $domain = $Apache::lonnet::perlvar{'lonDefDomain'}; + if ($ENV{'user.domain'}) { $domain=$ENV{'user.domain'}; } + if ($ENV{'request.role.domain'}) { + $domain=$ENV{'request.role.domain'}; + } + } + return $domain; +} ############################################### +=pod + +=item * &domainlogo() + +Inputs: $domain (usually will be undef) + +Returns: A link to a domain logo, if the domain logo exists. +If the domain logo does not exist, a description of the domain. + +=cut + +############################################### +sub domainlogo { + my $domain = &determinedomain(shift); + # See if there is a logo + if (-e '/home/httpd/html/adm/lonDomLogos/'.$domain.'.gif') { + my $lonhttpdPort=$Apache::lonnet::perlvar{'lonhttpdPort'}; + if (!defined($lonhttpdPort)) { $lonhttpdPort='8080'; } + return '<img src="http://'.$ENV{'HTTP_HOST'}.':'.$lonhttpdPort. + '/adm/lonDomLogos/'.$domain.'.gif" />'; + } elsif(exists($Apache::lonnet::domaindescription{$domain})) { + return $Apache::lonnet::domaindescription{$domain}; + } else { + return ''; + } +} +############################################## + +=pod + +=item * &designparm() + +Inputs: $which parameter; $domain (usually will be undef) + +Returns: value of designparamter $which + +=cut + +############################################## +sub designparm { + my ($which,$domain)=@_; + if ($ENV{'browser.blackwhite'} eq 'on') { + if ($which=~/\.(font|alink|vlink|link)$/) { + return '#000000'; + } + if ($which=~/\.(pgbg|sidebg)$/) { + return '#FFFFFF'; + } + if ($which=~/\.tabbg$/) { + return '#CCCCCC'; + } + } + if ($ENV{'environment.color.'.$which}) { + return $ENV{'environment.color.'.$which}; + } + $domain=&determinedomain($domain); + if ($designhash{$domain.'.'.$which}) { + return $designhash{$domain.'.'.$which}; + } else { + return $designhash{'default.'.$which}; + } +} + +############################################### +############################################### + +=pod + +=back + +=head1 HTTP Helpers + +=over 4 + +=item * &bodytag() + +Returns a uniform header for LON-CAPA web pages. + +Inputs: + +=over 4 + +=item * $title, A title to be displayed on the page. + +=item * $function, the current role (can be undef). + +=item * $addentries, extra parameters for the <body> tag. + +=item * $bodyonly, if defined, only return the <body> tag. + +=item * $domain, if defined, force a given domain. + +=item * $forcereg, if page should register as content page (relevant for + text interface only) + +=back + +Returns: A uniform header for LON-CAPA web pages. +If $bodyonly is nonzero, a string containing a <body> tag will be returned. +If $bodyonly is undef or zero, an html string containing a <body> tag and +other decorations will be returned. + +=cut + +sub bodytag { + my ($title,$function,$addentries,$bodyonly,$domain,$forcereg)=@_; + $title=&mt($title); + unless ($function) { + $function='student'; + if ($ENV{'request.role'}=~/^(cc|in|ta|ep)/) { + $function='coordinator'; + } + if ($ENV{'request.role'}=~/^(su|dc|ad|li)/) { + $function='admin'; + } + if (($ENV{'request.role'}=~/^(au|ca)/) || + ($ENV{'REQUEST_URI'}=~/^(\/priv|\~)/)) { + $function='author'; + } + } + my $img=&designparm($function.'.img',$domain); + my $pgbg=&designparm($function.'.pgbg',$domain); + my $tabbg=&designparm($function.'.tabbg',$domain); + my $font=&designparm($function.'.font',$domain); + my $link=&designparm($function.'.link',$domain); + my $alink=&designparm($function.'.alink',$domain); + my $vlink=&designparm($function.'.vlink',$domain); + my $sidebg=&designparm($function.'.sidebg',$domain); +# Accessibility font enhance + unless ($addentries) { $addentries=''; } + if ($ENV{'browser.fontenhance'} eq 'on') { + $addentries.=' style="font-size: x-large"'; + } + # role and realm + my ($role,$realm) + =&Apache::lonnet::plaintext((split(/\./,$ENV{'request.role'}))[0]); +# realm + if ($ENV{'request.course.id'}) { + $realm= + $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + } + unless ($realm) { $realm=' '; } +# Set messages + my $messages=&domainlogo($domain); +# Port for miniserver + my $lonhttpdPort=$Apache::lonnet::perlvar{'lonhttpdPort'}; + if (!defined($lonhttpdPort)) { $lonhttpdPort='8080'; } +# construct main body tag + my $bodytag = <<END; +<body bgcolor="$pgbg" text="$font" alink="$alink" vlink="$vlink" link="$link" +$addentries> +END + my $upperleft='<img src="http://'.$ENV{'HTTP_HOST'}.':'. + $lonhttpdPort.$img.'" />'; + if ($bodyonly) { + return $bodytag; + } elsif ($ENV{'browser.interface'} eq 'textual') { +# Accessibility + return $bodytag.&Apache::lonmenu::menubuttons($forcereg,'web', + $forcereg). + '<h1>LON-CAPA: '.$title.'</h1>'; + } elsif ($ENV{'environment.remote'} eq 'off') { +# No Remote + return $bodytag.&Apache::lonmenu::menubuttons($forcereg,'web', + $forcereg). + '<table bgcolor="'.$pgbg.'" width="100%" border="0" cellspacing="3" cellpadding="3"><tr><td bgcolor="'.$tabbg.'"><font size="+3" color="'.$font.'"><b>'.$title. +'</b></font></td></tr></table>'; + } + +# +# Top frame rendering, Remote is up +# + return(<<ENDBODY); +$bodytag +<table width="100%" cellspacing="0" border="0" cellpadding="0"> +<tr><td bgcolor="$sidebg"> +$upperleft</td> +<td bgcolor="$sidebg" align="right">$messages </td> +</tr> +<tr> +<td rowspan="3" bgcolor="$tabbg"> + <font size="5"><b>$title</b></font> +<td bgcolor="$tabbg" align="right"> +<font size="2"> + $ENV{'environment.firstname'} + $ENV{'environment.middlename'} + $ENV{'environment.lastname'} + $ENV{'environment.generation'} + </font> +</td> +</tr> +<tr><td bgcolor="$tabbg" align="right"> +<font size="2">$role</font> +</td></tr> +<tr> +<td bgcolor="$tabbg" align="right"><font size="2">$realm</font> </td></tr> +</table><br> +ENDBODY +} ############################################### +sub get_posted_cgi { + my $r=shift; + + my $buffer; + + $r->read($buffer,$r->header_in('Content-length'),0); + unless ($buffer=~/^(\-+\w+)\s+Content\-Disposition\:\s*form\-data/si) { + my @pairs=split(/&/,$buffer); + my $pair; + foreach $pair (@pairs) { + my ($name,$value) = split(/=/,$pair); + $value =~ tr/+/ /; + $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; + $name =~ tr/+/ /; + $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; + &add_to_env("form.$name",$value); + } + } else { + my $contentsep=$1; + my @lines = split (/\n/,$buffer); + my $name=''; + my $value=''; + my $fname=''; + my $fmime=''; + my $i; + for ($i=0;$i<=$#lines;$i++) { + if ($lines[$i]=~/^$contentsep/) { + if ($name) { + chomp($value); + if ($fname) { + $ENV{"form.$name.filename"}=$fname; + $ENV{"form.$name.mimetype"}=$fmime; + } else { + $value=~s/\s+$//s; + } + &add_to_env("form.$name",$value); + } + if ($i<$#lines) { + $i++; + $lines[$i]=~ + /Content\-Disposition\:\s*form\-data\;\s*name\=\"([^\"]+)\"/i; + $name=$1; + $value=''; + if ($lines[$i]=~/filename\=\"([^\"]+)\"/i) { + $fname=$1; + if + ($lines[$i+1]=~/Content\-Type\:\s*([\w\-\/]+)/i) { + $fmime=$1; + $i++; + } else { + $fmime=''; + } + } else { + $fname=''; + $fmime=''; + } + $i++; + } + } else { + $value.=$lines[$i]."\n"; + } + } + } + $ENV{'request.method'}=$ENV{'REQUEST_METHOD'}; + $r->method_number(M_GET); + $r->method('GET'); + $r->headers_in->unset('Content-length'); +} + +=pod + +=item * get_unprocessed_cgi($query,$possible_names) + +Modify the %ENV hash to contain unprocessed CGI form parameters held in +$query. The parameters listed in $possible_names (an array reference), +will be set in $ENV{'form.name'} if they do not already exist. + +Typically called with $ENV{'QUERY_STRING'} as the first parameter. +$possible_names is an ref to an array of form element names. As an example: +get_unprocessed_cgi($ENV{'QUERY_STRING'},['uname','udom']); +will result in $ENV{'form.uname'} and $ENV{'form.udom'} being set. + +=cut + sub get_unprocessed_cgi { my ($query,$possible_names)= @_; # $Apache::lonxml::debug=1; @@ -1162,6 +2313,14 @@ sub get_unprocessed_cgi { } } +=pod + +=item * cacheheader() + +returns cache-controlling header code + +=cut + sub cacheheader { unless ($ENV{'request.method'} eq 'GET') { return ''; } my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); @@ -1171,6 +2330,14 @@ sub cacheheader { return $output; } +=pod + +=item * no_cache($r) + +specifies header code to not have cache + +=cut + sub no_cache { my ($r) = @_; unless ($ENV{'request.method'} eq 'GET') { return ''; } @@ -1180,6 +2347,24 @@ sub no_cache { #$r->header_out("Expires" => $date); } +sub content_type { + my ($r,$type,$charset) = @_; + unless ($charset) { + $charset=&Apache::lonlocal::current_encoding; + } + $r->content_type($type.($charset?'; charset='.$charset:'')); +} + +=pod + +=item * add_to_env($name,$value) + +adds $name to the %ENV hash with value +$value, if $name already exists, the entry is converted to an array +reference and $value is added to the array. + +=cut + sub add_to_env { my ($name,$value)=@_; if (defined($ENV{$name})) { @@ -1201,11 +2386,11 @@ sub add_to_env { =back -=head2 CSV Upload/Handling functions +=head1 CSV Upload/Handling functions =over 4 -=item upfile_store($r) +=item * upfile_store($r) Store uploaded file, $r should be the HTTP Request object, needs $ENV{'form.upfile'} @@ -1230,7 +2415,9 @@ sub upfile_store { return $datatoken; } -=item load_tmp_file($r) +=pod + +=item * load_tmp_file($r) Load uploaded file from tmp, $r should be the HTTP Request object, needs $ENV{'form.datatoken'}, @@ -1251,7 +2438,9 @@ sub load_tmp_file { $ENV{'form.upfile'}=join('',@studentdata); } -=item upfile_record_sep() +=pod + +=item * upfile_record_sep() Separate uploaded file into records returns array of records, @@ -1266,7 +2455,9 @@ sub upfile_record_sep { } } -=item record_sep($record) +=pod + +=item * record_sep($record) Separate a record into fields $record should be an item from the upfile_record_sep(), needs $ENV{'form.upfiletype'} @@ -1316,7 +2507,9 @@ sub record_sep { return %components; } -=item upfile_select_html() +=pod + +=item * upfile_select_html() return HTML code to select file and specify its type @@ -1324,7 +2517,7 @@ return HTML code to select file and spec sub upfile_select_html { return (<<'ENDUPFORM'); -<input type="file" name="upfile" size="50"> +<input type="file" name="upfile" size="50" /> <br />Type: <select name="upfiletype"> <option value="csv">CSV (comma separated values, spreadsheet)</option> <option value="space">Space separated</option> @@ -1334,7 +2527,9 @@ sub upfile_select_html { ENDUPFORM } -=item csv_print_samples($r,$records) +=pod + +=item * csv_print_samples($r,$records) Prints a table of sample values from each column uploaded $r is an Apache Request ref, $records is an arrayref from @@ -1364,7 +2559,9 @@ sub csv_print_samples { $r->print('</tr></table><br />'."\n"); } -=item csv_print_select_table($r,$records,$d) +=pod + +=item * csv_print_select_table($r,$records,$d) Prints a table to create associations between values and table columns. $r is an Apache Request ref, @@ -1396,7 +2593,9 @@ sub csv_print_select_table { return $i; } -=item csv_samples_select_table($r,$records,$d) +=pod + +=item * csv_samples_select_table($r,$records,$d) Prints a table of sample values from the upload and can make associate samples to internal names. @@ -1433,104 +2632,196 @@ sub csv_samples_select_table { $i--; return($i); } -1; -__END__; =pod -=back - -=head2 Access .tab File Data - -=over 4 +=item clean_excel_name($name) -=item languageids() - -returns list of all language ids - -=item languagedescription() - -returns description of a specified language id +Returns a replacement for $name which does not contain any illegal characters. -=item copyrightids() +=cut -returns list of all copyrights +sub clean_excel_name { + my ($name) = @_; + $name =~ s/[:\*\?\/\\]//g; + if (length($name) > 31) { + $name = substr($name,0,31); + } + return $name; +} -=item copyrightdescription() +=pod -returns description of a specified copyright id +=item * check_if_partid_hidden($id,$symb,$udom,$uname) -=item filecategories() +Returns either 1 or undef -returns list of all file categories +1 if the part is to be hidden, undef if it is to be shown -=item filecategorytypes() +Arguments are: -returns list of file types belonging to a given file -category +$id the id of the part to be checked +$symb, optional the symb of the resource to check +$udom, optional the domain of the user to check for +$uname, optional the username of the user to check for -=item fileembstyle() +=cut -returns embedding style for a specified file type +sub check_if_partid_hidden { + my ($id,$symb,$udom,$uname) = @_; + my $hiddenparts=&Apache::lonnet::EXT('resource.0.hiddenparts', + $symb,$udom,$uname); + my @hiddenlist=split(/,/,$hiddenparts); + foreach my $checkid (@hiddenlist) { + if ($checkid =~ /^\s*\Q$id\E\s*$/) { return 1; } + } + return undef; +} -=item filedescription() +sub get_cgi_id { + return (time.'_'.int(rand(1000))); +} -returns description for a specified file type +############################################################ +############################################################ -=item filedescriptionex() +=pod -returns description for a specified file type with -extra formatting +=item DrawBarGraph -=back +=cut -=head2 Alternate Problem Views +############################################################ +############################################################ +sub DrawBarGraph { + my ($Title,$xlabel,$ylabel,$Max,$colors,@Values)=@_; + # + if (! defined($colors)) { + $colors = ['#33ff00', + '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933', + '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66', + ]; + } + # + my $identifier = &get_cgi_id(); + my $id = 'cgi.'.$identifier; + if (! @Values || ref($Values[0]) ne 'ARRAY') { + return ''; + } + my $NumBars = scalar(@{$Values[0]}); + my %ValuesHash; + my $NumSets=1; + foreach my $array (@Values) { + next if (! ref($array)); + $ValuesHash{$id.'.data.'.$NumSets++} = + join(',',@$array); + } + # + my ($height,$width,$xskip,$bar_width) = (200,120,1,15); + if ($NumBars < 10) { + $width = 120+$NumBars*15; + $xskip = 1; + $bar_width = 15; + } elsif ($NumBars <= 25) { + $width = 120+$NumBars*11; + $xskip = 5; + $bar_width = 8; + } elsif ($NumBars <= 50) { + $width = 120+$NumBars*8; + $xskip = 5; + $bar_width = 4; + } else { + $width = 120+$NumBars*8; + $xskip = 5; + $bar_width = 4; + } + # + my @Labels; + for (my $i=0;$i<@{$Values[0]};$i++) { + push (@Labels,$i+1); + } + # + $Title = '' if (! defined($Title)); + $xlabel = '' if (! defined($xlabel)); + $ylabel = '' if (! defined($ylabel)); + $ValuesHash{$id.'.title'} = &Apache::lonnet::escape($Title); + $ValuesHash{$id.'.xlabel'} = &Apache::lonnet::escape($xlabel); + $ValuesHash{$id.'.ylabel'} = &Apache::lonnet::escape($ylabel); + $ValuesHash{$id.'.Max'} = $Max; + $ValuesHash{$id.'.NumBars'} = $NumBars; + $ValuesHash{$id.'.NumSets'} = $NumSets; + $ValuesHash{$id.'.PlotType'} = 'bar'; + $ValuesHash{$id.'.Colors'} = join(',',@{$colors}); + $ValuesHash{$id.'.height'} = $height; + $ValuesHash{$id.'.width'} = $width; + $ValuesHash{$id.'.xskip'} = $xskip; + $ValuesHash{$id.'.bar_width'} = $bar_width; + $ValuesHash{$id.'.labels'} = join(',',@Labels); + # + $Max = 1 if ($Max < 1); + if ( int($Max) < $Max ) { + $Max++; + $Max = int($Max); + } + # + &Apache::lonnet::appenv(%ValuesHash); + return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />'; +} -=over 4 +############################################################ +############################################################ -=item get_previous_attempt() +=pod -return string with previous attempt on problem +=item DrawXYGraph -=item get_student_view() +=cut -show a snapshot of what student was looking at +############################################################ +############################################################ +sub DrawXYGraph { + my ($Title,$xlabel,$ylabel,$Max,$Xlabels,$Ydata,%Values)=@_; + # + # Create the identifier for the graph + my $identifier = &get_cgi_id(); + my $id = 'cgi.'.$identifier; + # + $Title = '' if (! defined($Title)); + $xlabel = '' if (! defined($xlabel)); + $ylabel = '' if (! defined($ylabel)); + my %ValuesHash = + ( + $id.'.title' => &Apache::lonnet::escape($Title), + $id.'.xlabel' => &Apache::lonnet::escape($xlabel), + $id.'.ylabel' => &Apache::lonnet::escape($ylabel), + $id.'.Max' => $Max, + $id.'.labels' => join(',',@$Xlabels), + $id.'.PlotType' => 'XY', + $id.'.NumSets' => 2, + ); + # + if (! ref($Ydata) || ref($Ydata) ne 'ARRAY') { + return ''; + } + my $NumSets=1; + foreach my $array ($Ydata){ + next if (! ref($array)); + $ValuesHash{$id.'.data.'.$NumSets++} = join(',',@$array); + } + # + &Apache::lonnet::appenv(%ValuesHash); + return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />'; +} -=item get_student_answers() +############################################################ +############################################################ -show a snapshot of how student was answering problem +=pod =back -=head2 HTTP Helper - -=over 4 - -=item get_unprocessed_cgi($query,$possible_names) - -Modify the %ENV hash to contain unprocessed CGI form parameters held in -$query. The parameters listed in $possible_names (an array reference), -will be set in $ENV{'form.name'} if they do not already exist. - -Typically called with $ENV{'QUERY_STRING'} as the first parameter. -$possible_names is an ref to an array of form element names. As an example: -get_unprocessed_cgi($ENV{'QUERY_STRING'},['uname','udom']); -will result in $ENV{'form.uname'} and $ENV{'form.udom'} being set. - -=item cacheheader() - -returns cache-controlling header code - -=item nocache() - -specifies header code to not have cache - -=item add_to_env($name,$value) - -adds $name to the %ENV hash with value -$value, if $name already exists, the entry is converted to an array -reference and $value is added to the array. +=cut -=back +1; +__END__; -=cut