--- loncom/lonnet/perl/lonnet.pm 2003/05/08 21:50:54 1.370 +++ loncom/lonnet/perl/lonnet.pm 2003/08/06 16:43:36 1.397 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.370 2003/05/08 21:50:54 albertel Exp $ +# $Id: lonnet.pm,v 1.397 2003/08/06 16:43:36 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -243,6 +243,26 @@ sub critical { } return $answer; } + +# ------------------------------------------- Transfer profile into environment + +sub transfer_profile_to_env { + my ($lonidsdir,$handle)=@_; + my @profile; + { + my $idf=Apache::File->new("$lonidsdir/$handle.id"); + flock($idf,LOCK_SH); + @profile=<$idf>; + $idf->close(); + } + my $envi; + for ($envi=0;$envi<=$#profile;$envi++) { + chomp($profile[$envi]); + my ($envname,$envvalue)=split(/=/,$profile[$envi]); + $ENV{$envname} = $envvalue; + } + $ENV{'user.environment'} = "$lonidsdir/$handle.id"; +} # ---------------------------------------------------------- Append Environment @@ -358,15 +378,16 @@ sub userload { while ($filename=readdir(LONIDS)) { if ($filename eq '.' || $filename eq '..') {next;} my ($atime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[8]; - if ($curtime-$atime < 3600) { $num_users++; } + if ($curtime-$atime < 3600) { $numusers++; } } closedir(LONIDS); } my $userloadpercent=0; my $maxuserload=$perlvar{'lonUserLoadLim'}; if ($maxuserload) { - $userloadpercent=100*$num_users/$maxuserload; + $userloadpercent=100*$numusers/$maxuserload; } + $userloadpercent=sprintf("%.2f",$userloadpercent); return $userloadpercent; } @@ -961,9 +982,9 @@ sub repcopy { # ------------------------------------------------ Get server side include body sub ssi_body { - my $filelink=shift; + my ($filelink,%form)=@_; my $output=($filelink=~/^http\:/?&externalssi($filelink): - &ssi($filelink)); + &ssi($filelink,%form)); $output=~s/^.*\]*\>//si; $output=~s/\<\/body\s*\>.*$//si; $output=~ @@ -1259,8 +1280,8 @@ sub get_course_adv_roles { } else { $returnhash{$key}=$username.':'.$domain; } - } - return sort %returnhash; + } + return %returnhash; } # ---------------------------------------------------------- Course ID routines @@ -1404,19 +1425,19 @@ sub devalidate { my ($symb,$uname,$udom)=@_; my $cid=$ENV{'request.course.id'}; if ($cid) { -# delete the stored spreadsheets for -# - the student level sheet of this user in course's homespace -# - the assessment level sheet for this resource -# for this user in user's homespace + # delete the stored spreadsheets for + # - the student level sheet of this user in course's homespace + # - the assessment level sheet for this resource + # for this user in user's homespace my $key=$uname.':'.$udom.':'; my $status= &del('nohist_calculatedsheets', - [$key.'studentcalc'], + [$key.'studentcalc:'], $ENV{'course.'.$cid.'.domain'}, $ENV{'course.'.$cid.'.num'}) .' '. &del('nohist_calculatedsheets_'.$cid, - [$key.'assesscalc:'.$symb]); + [$key.'assesscalc:'.$symb],$udom,$uname); unless ($status eq 'ok ok') { &logthis('Could not devalidate spreadsheet '. $uname.' at '.$udom.' for '. @@ -1622,7 +1643,7 @@ sub tmpreset { my ($symb,$namespace,$domain,$stuname) = @_; if (!$symb) { $symb=&symbread(); - if (!$symb) { $symb= $ENV{'REQUEST_URI'}; } + if (!$symb) { $symb= $ENV{'request.url'}; } } $symb=escape($symb); @@ -1915,14 +1936,14 @@ sub rolesinit { my ($tdummy,$tdomain,$trest)=split(/\//,$area); if ($trole =~ /^cr\//) { my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$trole); - my $homsvr=homeserver($rauthor,$rdomain); + my $homsvr=homeserver($rauthor,$rdomain); if ($hostname{$homsvr} ne '') { - my $roledef= - reply("get:$rdomain:$rauthor:roles:rolesdef_$rrole", - $homsvr); - if (($roledef ne 'con_lost') && ($roledef ne '')) { + my ($rdummy,$roledef)= + &get('roles',["rolesdef_$rrole"],$rdomain,$rauthor); + + if (($rdummy ne 'con_lost') && ($roledef ne '')) { my ($syspriv,$dompriv,$coursepriv)= - split(/\_/,unescape($roledef)); + split(/\_/,$roledef); if (defined($syspriv)) { $allroles{'cm./'}.=':'.$syspriv; $allroles{$spec.'./'}.=':'.$syspriv; @@ -2500,7 +2521,7 @@ sub is_on_map { sub definerole { if (allowed('mcr','/')) { my ($rolename,$sysrole,$domrole,$courole)=@_; - foreach (split('/',$sysrole)) { + foreach (split(':',$sysrole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:s'}!~/$crole/) { return "refused:s:$crole"; } if ($pr{'cr:s'}=~/$crole\&/) { @@ -2509,7 +2530,7 @@ sub definerole { } } } - foreach (split('/',$domrole)) { + foreach (split(':',$domrole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:d'}!~/$crole/) { return "refused:d:$crole"; } if ($pr{'cr:d'}=~/$crole\&/) { @@ -2518,7 +2539,7 @@ sub definerole { } } } - foreach (split('/',$courole)) { + foreach (split(':',$courole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:c'}!~/$crole/) { return "refused:c:$crole"; } if ($pr{'cr:c'}=~/$crole\&/) { @@ -2630,7 +2651,9 @@ sub assignrole { my ($udom,$uname,$url,$role,$end,$start,$deleteflag)=@_; my $mrole; if ($role =~ /^cr\//) { - unless (&allowed('ccr',$url)) { + my $cwosec=$url; + $cwosec=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/; + unless (&allowed('ccr',$cwosec)) { &logthis('Refused custom assignrole: '. $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '. $ENV{'user.name'}.' at '.$ENV{'user.domain'}); @@ -2640,7 +2663,7 @@ sub assignrole { } else { my $cwosec=$url; $cwosec=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/; - unless (&allowed('c'.$role,$cwosec)) { + unless ((&allowed('c'.$role,$cwosec)) || &allowed('c'.$role,$udom)) { &logthis('Refused assignrole: '. $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '. $ENV{'user.name'}.' at '.$ENV{'user.domain'}); @@ -2660,10 +2683,11 @@ sub assignrole { } # actually delete if ($deleteflag) { - if (&allowed('dro',$udom)) { + if ((&allowed('dro',$udom)) || (&allowed('dro',$url))) { # modify command to delete the role $command="encrypt:rolesdel:$ENV{'user.domain'}:$ENV{'user.name'}:". "$udom:$uname:$url".'_'."$mrole"; + &logthis("$ENV{'user.name'} at $ENV{'user.domain'} deletes $mrole in $url for $uname at $udom"); # set start and finish to negative values for userrolelog $start=-1; $end=-1; @@ -2710,7 +2734,7 @@ sub modifyuser { my ($udom, $uname, $uid, $umode, $upass, $first, $middle, $last, $gene, - $forceid, $desiredhome)=@_; + $forceid, $desiredhome, $email)=@_; $udom=~s/\W//g; $uname=~s/\W//g; &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '. @@ -2752,7 +2776,7 @@ sub modifyuser { } $uhome=&homeserver($uname,$udom,'true'); if (($uhome eq '') || ($uhome eq 'no_host') || ($uhome ne $unhome)) { - return 'error: verify home'; + return 'error: unable verify users home machine.'; } } # End of creation of new user # ---------------------------------------------------------------------- Add ID @@ -2762,7 +2786,8 @@ sub modifyuser { if (($uidhash{$uname}) && ($uidhash{$uname}!~/error\:/) && (!$forceid)) { unless ($uid eq $uidhash{$uname}) { - return 'error: mismatch '.$uidhash{$uname}.' versus '.$uid; + return 'error: user id "'.$uid.'" does not match '. + 'current user id "'.$uidhash{$uname}.'".'; } } else { &idput($udom,($uname => $uid)); @@ -2778,10 +2803,17 @@ sub modifyuser { } else { %names = @tmp; } +# +# Make sure to not trash student environment if instructor does not bother +# to supply name and email information +# if ($first) { $names{'firstname'} = $first; } - if ($middle) { $names{'middlename'} = $middle; } + if (defined($middle)) { $names{'middlename'} = $middle; } if ($last) { $names{'lastname'} = $last; } - if ($gene) { $names{'generation'} = $gene; } + if (defined($gene)) { $names{'generation'} = $gene; } + if ($email) { $names{'notification'} = $email; + $names{'critnotification'} = $email; } + my $reply = &put('environment', \%names, $udom,$uname); if ($reply ne 'ok') { return 'error: '.$reply; } &logthis('Success modifying user '.$udom.', '.$uname.', '.$uid.', '. @@ -2795,7 +2827,7 @@ sub modifyuser { sub modifystudent { my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, - $end,$start,$forceid,$desiredhome)=@_; + $end,$start,$forceid,$desiredhome,$email)=@_; my $cid=''; unless ($cid=$ENV{'request.course.id'}) { return 'not_in_class'; @@ -2803,7 +2835,7 @@ sub modifystudent { # --------------------------------------------------------------- Make the user my $reply=&modifyuser ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$forceid, - $desiredhome); + $desiredhome,$email); unless ($reply eq 'ok') { return $reply; } # This will cause &modify_student_enrollment to get the uid from the # students environment @@ -3038,7 +3070,7 @@ sub dirlist { } my $alldomstr=''; foreach (sort keys %alldom) { - $alldomstr.=$perlvar{'lonDocRoot'}.'/res/'.$_.'&domain:'; + $alldomstr.=$perlvar{'lonDocRoot'}.'/res/'.$_.'/&domain:'; } $alldomstr=~s/:$//; return split(/:/,$alldomstr); @@ -3062,12 +3094,12 @@ sub GetFileTimestamp { $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; my $proname="$studentDomain/$subdir/$studentName"; $proname .= '/'.$filename; - my @dir = &Apache::lonnet::dirlist($proname, $studentDomain, $studentName, - $root); - my $fileStat = $dir[0]; + my ($fileStat) = &Apache::lonnet::dirlist($proname, $studentDomain, + $studentName, $root); my @stats = split('&', $fileStat); if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { - return $stats[9]; + # @stats contains first the filename, then the stat output + return $stats[10]; # so this is 10 instead of 9. } else { return -1; } @@ -3163,10 +3195,34 @@ sub courseresdata { return undef; } -# --------------------------------------------------------- Value of a Variable +# +# EXT resource caching routines +# + +sub clear_EXT_cache_status { + &delenv('cache.EXT.'); +} +sub EXT_cache_status { + my ($target_domain,$target_user) = @_; + my $cachename = 'cache.EXT.'.$target_user.'.'.$target_domain; + if (exists($ENV{$cachename}) && ($ENV{$cachename}+600) > time) { + # We know already the user has no data + return 1; + } else { + return 0; + } +} + +sub EXT_cache_set { + my ($target_domain,$target_user) = @_; + my $cachename = 'cache.EXT.'.$target_user.'.'.$target_domain; + &appenv($cachename => time); +} + +# --------------------------------------------------------- Value of a Variable sub EXT { - my ($varname,$symbparm,$udom,$uname,)=@_; + my ($varname,$symbparm,$udom,$uname,$usection,$recurse)=@_; unless ($varname) { return ''; } #get real user name/domain, courseid and symb @@ -3174,7 +3230,7 @@ sub EXT { my $publicuser; if (!($uname && $udom)) { (my $cursymb,$courseid,$udom,$uname,$publicuser)= - &Apache::lonxml::whichuser(); + &Apache::lonxml::whichuser($symbparm); if (!$symbparm) { $symbparm=$cursymb; } } else { $courseid=$ENV{'request.course.id'}; @@ -3251,8 +3307,9 @@ sub EXT { } } elsif ($realm eq 'query') { # ---------------------------------------------- pull stuff out of query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},[$space]); - return $ENV{'form.'.$space}; + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + [$spacequalifierrest]); + return $ENV{'form.'.$spacequalifierrest}; } elsif ($realm eq 'request') { # ------------------------------------------------------------- request.browser if ($space eq 'browser') { @@ -3266,6 +3323,7 @@ sub EXT { return $ENV{'course.'.$courseid.'.'.$spacequalifierrest}; } elsif ($realm eq 'resource') { + my $section; if (defined($courseid) && $courseid eq $ENV{'request.course.id'}) { #print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; @@ -3278,12 +3336,15 @@ sub EXT { my $symbparm=$symbp.'.'.$spacequalifierrest; my $mapparm=$mapp.'___(all).'.$spacequalifierrest; - my $section; if (($ENV{'user.name'} eq $uname) && ($ENV{'user.domain'} eq $udom)) { $section=$ENV{'request.course.sec'}; } else { - $section=&usection($udom,$uname,$courseid); + if (! defined($usection)) { + $section=&usection($udom,$uname,$courseid); + } else { + $section = $usection; + } } my $seclevel=$courseid.'.['.$section.'].'.$spacequalifierrest; @@ -3295,11 +3356,9 @@ sub EXT { my $courselevelm=$courseid.'.'.$mapparm; # ----------------------------------------------------------- first, check user - #most student don't have any data set, check if there is some data + #most student don\'t have any data set, check if there is some data #every thirty minutes - if (! - (exists($ENV{'cache.studentresdata'}) - && (($ENV{'cache.studentresdata'}+1800) > time))) { + if (! &EXT_cache_status($udom,$uname)) { my %resourcedata=&get('resourcedata', [$courselevelr,$courselevelm,$courselevel], $udom,$uname); @@ -3318,9 +3377,7 @@ sub EXT { $uname." at ".$udom.": ". $tmp.""); } elsif ($tmp=~/error:No such file/) { - $ENV{'cache.studentresdata'}=time; - &appenv(('cache.studentresdata'=> - $ENV{'cache.studentresdata'})); + &EXT_cache_set($udom,$uname); } elsif ($tmp =~ /^(con_lost|no_such_host)/) { return $tmp; } @@ -3369,9 +3426,12 @@ sub EXT { my $part=join('_',@parts); if ($part eq '') { $part='0'; } my $partgeneral=&EXT('resource.'.$part.'.'.$qualifierrest, - $symbparm,$udom,$uname); + $symbparm,$udom,$uname,$section,1); if (defined($partgeneral)) { return $partgeneral; } } + if ($recurse) { return undef; } + my $pack_def=&packages_tab_default($filename,$varname); + if (defined($pack_def)) { return $pack_def; } # ---------------------------------------------------- Any other user namespace } elsif ($realm eq 'environment') { @@ -3392,6 +3452,19 @@ sub EXT { return ''; } +sub packages_tab_default { + my ($uri,$varname)=@_; + my (undef,$part,$name)=split(/\./,$varname); + my $packages=&metadata($uri,'packages'); + foreach my $package (split(/,/,$packages)) { + my ($pack_type,$pack_part)=split(/_/,$package,2); + if ($pack_part eq $part) { + return $packagetab{"$pack_type&$name&default"}; + } + } + return undef; +} + sub add_prefix_and_part { my ($prefix,$part)=@_; my $keyroot; @@ -3460,6 +3533,9 @@ sub metadata { foreach (keys %packagetab) { if ($_=~/^$package\&/) { my ($pack,$name,$subp)=split(/\&/,$_); + # ignore package.tab specified default values + # here &package_tab_default() will fetch those + if ($subp eq 'default') { next; } my $value=$packagetab{$_}; my $part=$keyroot; $part=~s/^\_//; @@ -3467,13 +3543,8 @@ sub metadata { $value.=' [Part: '.$part.']'; } my $unikey='parameter'.$keyroot.'_'.$name; - if ($subp eq 'default') { - $unikey='parameter_0_'.$name; - $metacache{$uri.':'.$unikey.'.part'}='0'; - } else { - $metacache{$uri.':'.$unikey.'.part'}=$part; - $metathesekeys{$unikey}=1; - } + $metacache{$uri.':'.$unikey.'.part'}=$part; + $metathesekeys{$unikey}=1; unless (defined($metacache{$uri.':'.$unikey.'.'.$subp})) { $metacache{$uri.':'.$unikey.'.'.$subp}=$value; } @@ -3606,7 +3677,13 @@ sub gettitle { unless ($urlsymb) { $urlsymb=$ENV{'request.filename'}; } return &metadata($urlsymb,'title'); } - if ($titlecache{$symb}) { return $titlecache{$symb}; } + if ($titlecache{$symb}) { + if (time < ($titlecache{$symb}[1] + 600)) { + return $titlecache{$symb}[0]; + } else { + delete($titlecache{$symb}); + } + } my ($map,$resid,$url)=split(/\_\_\_/,$symb); my $title=''; my %bighash; @@ -3618,7 +3695,7 @@ sub gettitle { } $title=~s/\&colon\;/\:/gs; if ($title) { - $titlecache{$symb}=$title; + $titlecache{$symb}=[$title,time]; return $title; } else { return &metadata($urlsymb,'title'); @@ -4039,7 +4116,8 @@ BEGIN { %domain_auth_arg_def = (); if ($fh) { while (<$fh>) { - next if /^\#/; + next if (/^(\#|\s*$)/); +# next if /^\#/; chomp; my ($domain, $domain_description, $def_auth, $def_auth_arg) = split(/:/,$_,4); @@ -4182,45 +4260,125 @@ being set. =back -=head1 INTRODUCTION +=head1 OVERVIEW -This module provides subroutines which interact with the -lonc/lond (TCP) network layer of LON-CAPA. And Can be used to ask about -- classes -- users -- resources +lonnet provides subroutines which interact with the +lonc/lond (TCP) network layer of LON-CAPA. They can be used to ask +about classes, users, and resources. For many of these objects you can also use this to store data about them or modify them in various ways. -This is part of the LearningOnline Network with CAPA project -described at http://www.lon-capa.org. +=head2 Symbs -=head1 RETURN MESSAGES +To identify a specific instance of a resource, LON-CAPA uses symbols +or "symbs"X. These identifiers are built from the URL of the +map, the resource number of the resource in the map, and the URL of +the resource itself. The latter is somewhat redundant, but might help +if maps change. -=over 4 +An example is -=item * + msu/korte/parts/part1.sequence___19___msu/korte/tests/part12.problem -con_lost : unable to contact remote host +The respective map entry is -=item * + + -con_delayed : unable to contact remote host, message will be delivered -when the connection is brought back up +Symbs are used by the random number generator, as well as to store and +restore data specific to a certain instance of for example a problem. -=item * +=head2 Storing And Retrieving Data -con_failed : unable to contact remote host and unable to save message -for later delivery +XXXThree of the most important functions +in C are C<&Apache::lonnet::cstore()>, +C<&Apache::lonnet:restore()>, and C<&Apache::lonnet::store()>, which +is is the non-critical message twin of cstore. These functions are for +handlers to store a perl hash to a user's permanent data space in an +easy manner, and to retrieve it again on another call. It is expected +that a handler would use this once at the beginning to retrieve data, +and then again once at the end to send only the new data back. -=item * +The data is stored in the user's data directory on the user's +homeserver under the ID of the course. -error: : an error a occured, a description of the error follows the : +The hash that is returned by restore will have all of the previous +value for all of the elements of the hash. -=item * +Example: + + #creating a hash + my %hash; + $hash{'foo'}='bar'; + + #storing it + &Apache::lonnet::cstore(\%hash); + + #changing a value + $hash{'foo'}='notbar'; + + #adding a new value + $hash{'bar'}='foo'; + &Apache::lonnet::cstore(\%hash); + + #retrieving the hash + my %history=&Apache::lonnet::restore(); -no_such_host : unable to fund a host associated with the user/domain + #print the hash + foreach my $key (sort(keys(%history))) { + print("\%history{$key} = $history{$key}"); + } + +Will print out: + + %history{1:foo} = bar + %history{1:keys} = foo:timestamp + %history{1:timestamp} = 990455579 + %history{2:bar} = foo + %history{2:foo} = notbar + %history{2:keys} = foo:bar:timestamp + %history{2:timestamp} = 990455580 + %history{bar} = foo + %history{foo} = notbar + %history{timestamp} = 990455580 + %history{version} = 2 + +Note that the special hash entries C, C and +C were added to the hash. C will be equal to the +total number of versions of the data that have been stored. The +C attribute will be the UNIX time the hash was +stored. C is available in every historical section to list which +keys were added or changed at a specific historical revision of a +hash. + +B: do not store the hash that restore returns directly. This +will cause a mess since it will restore the historical keys as if the +were new keys. I.E. 1:foo will become 1:1:foo etc. + +Calling convention: + + my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$uname,$home); + &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$uname,$home); + +For more detailed information, see lonnet specific documentation. + +=head1 RETURN MESSAGES + +=over 4 + +=item * B: unable to contact remote host + +=item * B: unable to contact remote host, message will be delivered +when the connection is brought back up + +=item * B: unable to contact remote host and unable to save message +for later delivery + +=item * B: an error a occured, a description of the error follows the : + +=item * B: unable to fund a host associated with the user/domain that was requested =back @@ -4231,15 +4389,18 @@ that was requested =over 4 -=item * - -appenv(%hash) : the value of %hash is written to the user envirnoment -file, and will be restored for each access this user makes during this -session, also modifies the %ENV for the current process +=item * +X +B: the value of %hash is written to +the user envirnoment file, and will be restored for each access this +user makes during this session, also modifies the %ENV for the current +process =item * - -delenv($regexp) : removes all items from the session environment file that matches the regular expression in $regexp. The values are also delted from the current processes %ENV. +X +B: removes all items from the session +environment file that matches the regular expression in $regexp. The +values are also delted from the current processes %ENV. =back @@ -4248,50 +4409,51 @@ delenv($regexp) : removes all items from =over 4 =item * - -queryauthenticate($uname,$udom) : try to determine user's current +X +B: try to determine user's current authentication scheme =item * - -authenticate($uname,$upass,$udom) : try to authenticate user from domain's lib -servers (first use the current one), $upass should be the users password +X +B: try to +authenticate user from domain's lib servers (first use the current +one). C<$upass> should be the users password. =item * - -homeserver($uname,$udom) : find the server which has the user's -directory and files (there must be only one), this caches the answer, -and also caches if there is a borken connection. +X +B: find the server which has +the user's directory and files (there must be only one), this caches +the answer, and also caches if there is a borken connection. =item * - -idget($udom,@ids) : find the usernames behind a list of IDs (IDs are a -unique resource in a domain, there must be only 1 ID per username, and -only 1 username per ID in a specific domain) (returns hash: -id=>name,id=>name) +X +B: find the usernames behind a list of IDs +(IDs are a unique resource in a domain, there must be only 1 ID per +username, and only 1 username per ID in a specific domain) (returns +hash: id=>name,id=>name) =item * - -idrget($udom,@unames) : find the IDs behind a list of usernames (returns hash: -name=>id,name=>id) +X +B: find the IDs behind a list of +usernames (returns hash: name=>id,name=>id) =item * - -idput($udom,%ids) : store away a list of names and associated IDs +X +B: store away a list of names and associated IDs =item * - -rolesinit($udom,$username,$authhost) : get user privileges +X +B: get user privileges =item * - -usection($udom,$uname,$cname) : finds the section of student in the +X +B: finds the section of student in the course $cname, return section name/number or '' for "not in course" and '-1' for "no section" =item * - -userenvironment($udom,$uname,@what) : gets the values of the keys +X +B: gets the values of the keys passed in @what from the requested user's environment, returns a hash =back