--- loncom/lonnet/perl/lonnet.pm 2010/03/15 05:10:03 1.1055 +++ loncom/lonnet/perl/lonnet.pm 2010/07/06 18:36:56 1.1072 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1055 2010/03/15 05:10:03 raeburn Exp $ +# $Id: lonnet.pm,v 1.1072 2010/07/06 18:36:56 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -665,30 +665,6 @@ sub userload { return $userloadpercent; } -# ------------------------------------------ Fight off request when overloaded - -sub overloaderror { - my ($r,$checkserver)=@_; - unless ($checkserver) { $checkserver=$perlvar{'lonHostID'}; } - my $loadavg; - if ($checkserver eq $perlvar{'lonHostID'}) { - open(my $loadfile,'/proc/loadavg'); - $loadavg=<$loadfile>; - $loadavg =~ s/\s.*//g; - $loadavg = 100*$loadavg/$perlvar{'lonLoadLim'}; - close($loadfile); - } else { - $loadavg=&reply('load',$checkserver); - } - my $overload=$loadavg-100; - if ($overload>0) { - $r->err_headers_out->{'Retry-After'}=$overload; - $r->log_error('Overload of '.$overload.' on '.$checkserver); - return 413; - } - return ''; -} - # ------------------------------ Find server with least workload from spare.tab sub spareserver { @@ -734,7 +710,7 @@ sub compare_server_load { my $userloadans = &reply('userload',$try_server); if ($loadans !~ /\d/ && $userloadans !~ /\d/) { - next; #didn't get a number from the server + return; #didn't get a number from the server } my $load; @@ -3059,7 +3035,7 @@ sub courseiddump { my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter, $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok, $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone, - $cloneonly,$createdbefore,$createdafter,$creationcontext)=@_; + $cloneonly,$createdbefore,$createdafter,$creationcontext,$domcloner)=@_; my $as_hash = 1; my %returnhash; if (!$domfilter) { $domfilter=''; } @@ -3081,7 +3057,8 @@ sub courseiddump { $showhidden.':'.$caller.':'.&escape($cloner).':'. &escape($cc_clone).':'.$cloneonly.':'. &escape($createdbefore).':'.&escape($createdafter).':'. - &escape($creationcontext),$tryserver); + &escape($creationcontext).':'.$domcloner, + $tryserver); my @pairs=split(/\&/,$rep); foreach my $item (@pairs) { my ($key,$value)=split(/\=/,$item,2); @@ -3207,7 +3184,7 @@ sub get_domain_roles { return %personnel; } -# ----------------------------------------------------------- Check out an item +# ----------------------------------------------------------- Interval timing sub get_first_access { my ($type,$argsymb)=@_; @@ -3243,91 +3220,6 @@ sub set_first_access { return 'already_set'; } -sub checkout { - my ($symb,$tuname,$tudom,$tcrsid)=@_; - my $now=time; - my $lonhost=$perlvar{'lonHostID'}; - my $infostr=&escape( - 'CHECKOUTTOKEN&'. - $tuname.'&'. - $tudom.'&'. - $tcrsid.'&'. - $symb.'&'. - $now.'&'.$ENV{'REMOTE_ADDR'}); - my $token=&reply('tmpput:'.$infostr,$lonhost); - if ($token=~/^error\:/) { - &logthis("WARNING: ". - "Checkout tmpput failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - return ''; - } - - $token=~s/^(\d+)\_.*\_(\d+)$/$1\*$2\*$lonhost/; - $token=~tr/a-z/A-Z/; - - my %infohash=('resource.0.outtoken' => $token, - 'resource.0.checkouttime' => $now, - 'resource.0.outremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout cstore failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkout '.$infostr.' - '. - $token)) ne 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout log failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - return $token; -} - -# ------------------------------------------------------------ Check in an item - -sub checkin { - my $token=shift; - my $now=time; - my ($ta,$tb,$lonhost)=split(/\*/,$token); - $lonhost=~tr/A-Z/a-z/; - my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb; - $dtoken=~s/\W/\_/g; - my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= - split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost))); - - unless (($tuname) && ($tudom)) { - &logthis('Check in '.$token.' ('.$dtoken.') failed'); - return ''; - } - - unless (&allowed('mgr',$tcrsid)) { - &logthis('Check in '.$token.' ('.$dtoken.') unauthorized: '. - $env{'user.name'}.' - '.$env{'user.domain'}); - return ''; - } - - my %infohash=('resource.0.intoken' => $token, - 'resource.0.checkintime' => $now, - 'resource.0.inremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkin - '.$token)) ne 'ok') { - return ''; - } - - return ($symb,$tuname,$tudom,$tcrsid); -} - # --------------------------------------------- Set Expire Date for Spreadsheet sub expirespread { @@ -4028,23 +3920,36 @@ sub standard_roleprivs { } sub set_userprivs { - my ($userroles,$allroles,$allgroups) = @_; + my ($userroles,$allroles,$allgroups,$groups_roles) = @_; my $author=0; my $adv=0; my %grouproles = (); if (keys(%{$allgroups}) > 0) { + my @groupkeys; foreach my $role (keys(%{$allroles})) { - my ($trole,$area,$sec,$extendedarea); - if ($role =~ m-^(\w+|cr/$match_domain/$match_username/\w+)\.(/$match_domain/$match_courseid)(/?\w*)\.-) { - $trole = $1; - $area = $2; - $sec = $3; - $extendedarea = $area.$sec; - if (exists($$allgroups{$area})) { - foreach my $group (keys(%{$$allgroups{$area}})) { - my $spec = $trole.'.'.$extendedarea; - $grouproles{$spec.'.'.$area.'/'.$group} = + push(@groupkeys,$role); + } + if (ref($groups_roles) eq 'HASH') { + foreach my $key (keys(%{$groups_roles})) { + unless (grep(/^\Q$key\E$/,@groupkeys)) { + push(@groupkeys,$key); + } + } + } + if (@groupkeys > 0) { + foreach my $role (@groupkeys) { + my ($trole,$area,$sec,$extendedarea); + if ($role =~ m-^(\w+|cr/$match_domain/$match_username/\w+)\.(/$match_domain/$match_courseid)(/?\w*)\.-) { + $trole = $1; + $area = $2; + $sec = $3; + $extendedarea = $area.$sec; + if (exists($$allgroups{$area})) { + foreach my $group (keys(%{$$allgroups{$area}})) { + my $spec = $trole.'.'.$extendedarea; + $grouproles{$spec.'.'.$area.'/'.$group} = $$allgroups{$area}{$group}; + } } } } @@ -4091,26 +3996,58 @@ sub role_status { if ($$tstart<$now) { if ($$tstart && $$tstart>$refresh) { if (($$where ne '') && ($$role ne '')) { - my (%allroles,%allgroups,$group_privs); + my (%allroles,%allgroups,$group_privs, + %groups_roles,@rolecodes); my %userroles = ( 'user.role.'.$$role.'.'.$$where => $$tstart.'.'.$$tend ); + @rolecodes = ('cm'); my $spec=$$role.'.'.$$where; my ($tdummy,$tdomain,$trest)=split(/\//,$$where); if ($$role =~ /^cr\//) { &custom_roleprivs(\%allroles,$$role,$tdomain,$trest,$spec,$$where); + push(@rolecodes,'cr'); } elsif ($$role eq 'gr') { + push(@rolecodes,$$role); my %rolehash = &get('roles',[$$where.'_'.$$role],$env{'user.domain'}, $env{'user.name'}); - my $trole = split('_',$rolehash{$$where.'_'.$$role},1); + my ($trole) = split('_',$rolehash{$$where.'_'.$$role},2); (undef,my $group_privs) = split(/\//,$trole); $group_privs = &unescape($group_privs); &group_roleprivs(\%allgroups,$$where,$group_privs,$$tend,$$tstart); + my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1); + if (keys(%course_roles) > 0) { + my ($tnum) = ($trest =~ /^($match_courseid)/); + if ($tdomain ne '' && $tnum ne '') { + foreach my $key (keys(%course_roles)) { + if ($key =~ /^\Q$tnum\E:\Q$tdomain\E:([^:]+):?([^:]*)/) { + my $crsrole = $1; + my $crssec = $2; + if ($crsrole =~ /^cr/) { + unless (grep(/^cr$/,@rolecodes)) { + push(@rolecodes,'cr'); + } + } else { + unless(grep(/^\Q$crsrole\E$/,@rolecodes)) { + push(@rolecodes,$crsrole); + } + } + my $rolekey = $crsrole.'./'.$tdomain.'/'.$tnum; + if ($crssec ne '') { + $rolekey .= '/'.$crssec; + } + $rolekey .= './'; + $groups_roles{$rolekey} = \@rolecodes; + } + } + } + } } else { + push(@rolecodes,$$role); &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where); } - my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups); - &appenv(\%userroles,[$$role,'cm']); + my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups,\%groups_roles); + &appenv(\%userroles,\@rolecodes); &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); } } @@ -4624,7 +4561,7 @@ sub get_portfolio_access { my (%allgroups,%allroles); my ($start,$end,$role,$sec,$group); foreach my $envkey (%env) { - if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) { + if ($envkey =~ m-^user\.role\.(gr|cc|co|in|ta|ep|ad|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) { my $cid = $2.'_'.$3; if ($1 eq 'gr') { $group = $4; @@ -5812,8 +5749,8 @@ sub auto_validate_instcode { $homeserver = &domain($cdom,'primary'); } } - my $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'. - &escape($instcode).':'.&escape($owner),$homeserver)); + $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'. + &escape($instcode).':'.&escape($owner),$homeserver)); my ($outcome,$description) = map { &unescape($_); } split('&',$response,2); return ($outcome,$description); } @@ -6418,12 +6355,12 @@ sub autoupdate_coowners { my %coursehash = &coursedescription($cdom.'_'.$cnum); my $instcode = $coursehash{'internal.coursecode'}; if ($instcode ne '') { - unless ($coursehash{'internal.courseowner'} eq $uname.':'.$udom) { - my ($result,$desc) = &auto_validate_instcode($cnum,$cdom,$instcode,$uname.':'.$udom); - if ($result eq 'valid') { + if (($start && $start <= $now) && ($end == 0) || ($end > $now)) { + unless ($coursehash{'internal.courseowner'} eq $uname.':'.$udom) { my ($delcoowners,@newcoowners,$putresult,$delresult,$coowners); - if (($end == 0) || ($end > $now)) { - if ($coursehash{'internal.co-owners'}) { + my ($result,$desc) = &auto_validate_instcode($cnum,$cdom,$instcode,$uname.':'.$udom); + if ($result eq 'valid') { + if ($coursehash{'internal.co-owners'}) { foreach my $coowner (split(',',$coursehash{'internal.co-owners'})) { push(@newcoowners,$coowner); } @@ -6524,12 +6461,18 @@ sub modifyuser { my ($udom, $uname, $uid, $umode, $upass, $first, $middle, $last, $gene, - $forceid, $desiredhome, $email, $inststatus)=@_; + $forceid, $desiredhome, $email, $inststatus, $candelete)=@_; $udom= &LONCAPA::clean_domain($udom); $uname=&LONCAPA::clean_username($uname); + my $showcandelete = 'none'; + if (ref($candelete) eq 'ARRAY') { + if (@{$candelete} > 0) { + $showcandelete = join(', ',@{$candelete}); + } + } &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '. $umode.', '.$first.', '.$middle.', '. - $last.', '.$gene.'(forceid: '.$forceid.')'. + $last.', '.$gene.'(forceid: '.$forceid.'; candelete: '.$showcandelete.')'. (defined($desiredhome) ? ' desiredhome = '.$desiredhome : ' desiredhome not specified'). ' by '.$env{'user.name'}.' at '.$env{'user.domain'}. @@ -6594,9 +6537,33 @@ sub modifyuser { %names = @tmp; } # -# Make sure to not trash student environment if instructor does not bother -# to supply name and email information -# +# If name, email and/or uid are blank (e.g., because an uploaded file +# of users did not contain them), do not overwrite existing values +# unless field is in $candelete array ref. +# + + my @fields = ('firstname','middlename','lastname','generation', + 'permanentemail','id'); + my %newvalues; + if (ref($candelete) eq 'ARRAY') { + foreach my $field (@fields) { + if (grep(/^\Q$field\E$/,@{$candelete})) { + if ($field eq 'firstname') { + $names{$field} = $first; + } elsif ($field eq 'middlename') { + $names{$field} = $middle; + } elsif ($field eq 'lastname') { + $names{$field} = $last; + } elsif ($field eq 'generation') { + $names{$field} = $gene; + } elsif ($field eq 'permanentemail') { + $names{$field} = $email; + } elsif ($field eq 'id') { + $names{$field} = $uid; + } + } + } + } if ($first) { $names{'firstname'} = $first; } if (defined($middle)) { $names{'middlename'} = $middle; } if ($last) { $names{'lastname'} = $last; } @@ -6873,8 +6840,13 @@ ENDINITMAP } # ----------------------------------------------------------- Write preferences &writecoursepref($udom.'_'.$uname, - ('description' => $description, - 'url' => $topurl)); + ('description' => $description, + 'url' => $topurl, + 'internal.creator' => $env{'user.name'}.':'. + $env{'user.domain'}, + 'internal.created' => $now, + 'internal.creationcontext' => $context) + ); return '/'.$udom.'/'.$uname; } @@ -8160,6 +8132,7 @@ sub add_prefix_and_part { # ---------------------------------------------------------------- Get metadata my %metaentry; +my %importedpartids; sub metadata { my ($uri,$what,$liburi,$prefix,$depthcount)=@_; $uri=&declutter($uri); @@ -8186,6 +8159,10 @@ sub metadata { if (defined($cached)) { return $result->{':'.$what}; } } { +# Imported parts would go here + my %importedids=(); + my @origfileimportpartids=(); + my $importedparts=0; # # Is this a recursive call for a library? # @@ -8269,27 +8246,55 @@ sub metadata { # This is not a package - some other kind of start tag # my $entry=$token->[1]; - my $unikey; - if ($entry eq 'import') { - $unikey=''; - } else { - $unikey=$entry; - } - $unikey.=&add_prefix_and_part($prefix,$token->[2]->{'part'}); - - if (defined($token->[2]->{'id'})) { - $unikey.='_'.$token->[2]->{'id'}; - } + my $unikey=''; if ($entry eq 'import') { # # Importing a library here # + my $location=$parser->get_text('/import'); + my $dir=$filename; + $dir=~s|[^/]*$||; + $location=&filelocation($dir,$location); + + my $importmode=$token->[2]->{'importmode'}; + if ($importmode eq 'problem') { +# Import as problem/response + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + } elsif ($importmode eq 'part') { +# Import as part(s) + $importedparts=1; +# We need to get the original file and the imported file to get the part order correct +# Good news: we do not need to worry about nested libraries, since parts cannot be nested +# Load and inspect original file + if ($#origfileimportpartids<0) { + undef(%importedpartids); + my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri); + my $origfile=&getfile($origfilelocation); + @origfileimportpartids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + } + +# Load and inspect imported file + my $impfile=&getfile($location); + my @impfilepartids=($impfile=~/]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + if ($#impfilepartids>=0) { +# This problem had parts + $importedpartids{$token->[2]->{'id'}}=join(',',@impfilepartids); + } else { +# Importing by turning a single problem into a problem part +# It gets the import-tags ID as part-ID + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'id'}); + $importedpartids{$token->[2]->{'id'}}=$token->[2]->{'id'}; + } + } else { +# Normal import + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } + } + if ($depthcount<20) { - my $location=$parser->get_text('/import'); - my $dir=$filename; - $dir=~s|[^/]*$||; - $location=&filelocation($dir,$location); my $metadata = &metadata($uri,'keys', $location,$unikey, $depthcount+1); @@ -8297,9 +8302,16 @@ sub metadata { $metaentry{':'.$meta}=$metaentry{':'.$meta}; $metathesekeys{$meta}=1; } - } - } else { + } + } else { +# +# Not importing, some other kind of non-package, non-library start tag +# + $unikey=$entry.&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } if (defined($token->[2]->{'name'})) { $unikey.='_'.$token->[2]->{'name'}; } @@ -8373,6 +8385,22 @@ sub metadata { grep { ! $seen{$_} ++ } (split(',',$metaentry{':packages'})); $metaentry{':packages'} = join(',',@uniq_packages); + if ($importedparts) { +# We had imported parts and need to rebuild partorder + $metaentry{':partorder'}=''; + $metathesekeys{'partorder'}=1; + for (my $index=0;$index<$#origfileimportpartids;$index+=2) { + if ($origfileimportpartids[$index] eq 'part') { +# original part, part of the problem + $metaentry{':partorder'}.=','.$origfileimportpartids[$index+1]; + } else { +# we have imported parts at this position + $metaentry{':partorder'}.=','.$importedpartids{$origfileimportpartids[$index+1]}; + } + } + $metaentry{':partorder'}=~s/^\,//; + } + $metaentry{':keys'} = join(',',keys(%metathesekeys)); &metadata_generate_part0(\%metathesekeys,\%metaentry,$uri); $metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys); @@ -9733,6 +9761,12 @@ sub get_dns { return %libserv; } + sub unique_library { + #2x reverse removes all hostnames that appear more than once + my %unique = reverse &all_library(); + return reverse %unique; + } + sub get_servers { &load_hosts_tab() if (!$loaded); @@ -9756,6 +9790,11 @@ sub get_dns { return %result; } + sub get_unique_servers { + my %unique = reverse &get_servers(@_); + return reverse %unique; + } + sub host_domain { &load_hosts_tab() if (!$loaded); @@ -10318,9 +10357,16 @@ modifyuserauth($udom,$uname,$umode,$upas =item * -modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene, - $forceid,$desiredhome,$email,$inststatus) : -modify user +modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last, $gene, + $forceid,$desiredhome,$email,$inststatus,$candelete) : + +will update user information (firstname,middlename,lastname,generation, +permanentemail), and if forceid is true, student/employee ID also. +A user's institutional affiliation(s) can also be updated. +User information fields will not be overwritten with empty entries +unless the field is included in the $candelete array reference. +This array is included when a single user is modified via "Manage Users", +or when Autoupdate.pl is run by cron in a domain. =item *