--- loncom/lonnet/perl/lonnet.pm 2010/08/17 22:22:05 1.1081 +++ loncom/lonnet/perl/lonnet.pm 2017/08/27 17:49:49 1.1354 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1081 2010/08/17 22:22:05 raeburn Exp $ +# $Id: lonnet.pm,v 1.1354 2017/08/27 17:49:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -71,12 +71,15 @@ delayed. package Apache::lonnet; use strict; -use LWP::UserAgent(); use HTTP::Date; use Image::Magick; + +use Encode; + use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir - $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease); + $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease + %managerstab); my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash, %userrolehash, $processmarker, $dumpcount, %coursedombuf, @@ -88,16 +91,21 @@ use GDBM_File; use HTML::LCParser; use Fcntl qw(:flock); use Storable qw(thaw nfreeze); -use Time::HiRes qw( gettimeofday tv_interval ); +use Time::HiRes qw( sleep gettimeofday tv_interval ); use Cache::Memcached; use Digest::MD5; use Math::Random; use File::MMagic; use LONCAPA qw(:DEFAULT :match); use LONCAPA::Configuration; +use LONCAPA::lonmetadata; +use LONCAPA::Lond; +use LONCAPA::LWPReq; + +use File::Copy; my $readit; -my $max_connection_retries = 10; # Or some such value. +my $max_connection_retries = 20; # Or some such value. require Exporter; @@ -105,30 +113,33 @@ our @ISA = qw (Exporter); our @EXPORT = qw(%env); -# --------------------------------------------------------------------- Logging +# ------------------------------------ Logging (parameters, docs, slots, roles) { my $logid; - sub instructor_log { - my ($hash_name,$storehash,$delflag,$uname,$udom,$cnum,$cdom)=@_; - if (($cnum eq '') || ($cdom eq '')) { - $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; - $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + sub write_log { + my ($context,$hash_name,$storehash,$delflag,$uname,$udom,$cnum,$cdom)=@_; + if ($context eq 'course') { + if (($cnum eq '') || ($cdom eq '')) { + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } } - $logid++; + $logid ++; my $now = time(); my $id=$now.'00000'.$$.'00000'.$logid; - return &Apache::lonnet::put('nohist_'.$hash_name, - { $id => { - 'exe_uname' => $env{'user.name'}, - 'exe_udom' => $env{'user.domain'}, - 'exe_time' => $now, - 'exe_ip' => $ENV{'REMOTE_ADDR'}, - 'delflag' => $delflag, - 'logentry' => $storehash, - 'uname' => $uname, - 'udom' => $udom, - } - },$cdom,$cnum); + my $logentry = { + $id => { + 'exe_uname' => $env{'user.name'}, + 'exe_udom' => $env{'user.domain'}, + 'exe_time' => $now, + 'exe_ip' => $ENV{'REMOTE_ADDR'}, + 'delflag' => $delflag, + 'logentry' => $storehash, + 'uname' => $uname, + 'udom' => $udom, + } + }; + return &put('nohist_'.$hash_name,$logentry,$cdom,$cnum); } } @@ -195,6 +206,75 @@ sub get_server_timezone { } } +sub get_server_distarch { + my ($lonhost,$ignore_cache) = @_; + if (defined($lonhost)) { + if (!defined(&hostname($lonhost))) { + return; + } + my $cachetime = 12*3600; + if (!$ignore_cache) { + my ($distarch,$cached)=&is_cached_new('serverdistarch',$lonhost); + if (defined($cached)) { + return $distarch; + } + } + my $rep = &reply('serverdistarch',$lonhost); + unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' || + $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' || + $rep eq '') { + return &do_cache_new('serverdistarch',$lonhost,$rep,$cachetime); + } + } + return; +} + +sub get_servercerts_info { + my ($lonhost,$context) = @_; + my ($rep,$uselocal); + if (grep { $_ eq $lonhost } ¤t_machine_ids()) { + $uselocal = 1; + } + if (($context ne 'cgi') && ($uselocal)) { + my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0]; + if ($distro eq '') { + $uselocal = 0; + } elsif ($distro =~ /^(?:centos|redhat|scientific)(\d+)$/) { + if ($1 < 6) { + $uselocal = 0; + } + } elsif ($distro =~ /^(?:sles)(\d+)$/) { + if ($1 < 12) { + $uselocal = 0; + } + } + } + if ($uselocal) { + $rep = LONCAPA::Lond::server_certs(\%perlvar); + } else { + $rep=&reply('servercerts',$lonhost); + } + my ($result,%returnhash); + if (defined($lonhost)) { + if (!defined(&hostname($lonhost))) { + return; + } + } + if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') || + ($rep eq 'unknown_cmd')) { + $result = $rep; + } else { + $result = 'ok'; + my @pairs=split(/\&/,$rep); + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + my $what = &unescape($key); + $returnhash{$what}=&thaw_unescape($value); + } + } + return ($result,\%returnhash); +} + sub get_server_loncaparev { my ($dom,$lonhost,$ignore_cache,$caller) = @_; if (defined($lonhost)) { @@ -229,13 +309,11 @@ sub get_server_loncaparev { $answer = &reply('serverloncaparev',$lonhost); if (($answer eq 'unknown_cmd') || ($answer eq 'con_lost')) { if ($caller eq 'loncron') { - my $ua=new LWP::UserAgent; - $ua->timeout(20); my $protocol = $protocol{$lonhost}; $protocol = 'http' if ($protocol ne 'https'); my $url = $protocol.'://'.&hostname($lonhost).'/adm/about.html'; my $request=new HTTP::Request('GET',$url); - my $response=$ua->request($request); + my $response=&LONCAPA::LWPReq::makerequest($lonhost,$request,'',\%perlvar,4,1); unless ($response->is_error()) { my $content = $response->content; if ($content =~ /

VERSION\:\s*([\w.\-]+)<\/p>/) { @@ -281,6 +359,55 @@ sub get_server_homeID { return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime); } +sub get_remote_globals { + my ($lonhost,$whathash,$ignore_cache) = @_; + my ($result,%returnhash,%whatneeded); + if (ref($whathash) eq 'HASH') { + foreach my $what (sort(keys(%{$whathash}))) { + my $hashid = $lonhost.'-'.$what; + my ($response,$cached); + unless ($ignore_cache) { + ($response,$cached)=&is_cached_new('lonnetglobal',$hashid); + } + if (defined($cached)) { + $returnhash{$what} = $response; + } else { + $whatneeded{$what} = 1; + } + } + if (keys(%whatneeded) == 0) { + $result = 'ok'; + } else { + my $requested = &freeze_escape(\%whatneeded); + my $rep=&reply('readlonnetglobal:'.$requested,$lonhost); + if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') || + ($rep eq 'unknown_cmd')) { + $result = $rep; + } else { + $result = 'ok'; + my @pairs=split(/\&/,$rep); + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + my $what = &unescape($key); + my $hashid = $lonhost.'-'.$what; + $returnhash{$what}=&thaw_unescape($value); + &do_cache_new('lonnetglobal',$hashid,$returnhash{$what},600); + } + } + } + } + return ($result,\%returnhash); +} + +sub remote_devalidate_cache { + my ($lonhost,$cachekeys) = @_; + my $items; + return unless (ref($cachekeys) eq 'ARRAY'); + my $cachestr = join('&',@{$cachekeys}); + my $response = &reply('devalidatecache:'.&escape($cachestr),$lonhost); + return $response; +} + # -------------------------------------------------- Non-critical communication sub subreply { my ($cmd,$server)=@_; @@ -292,7 +419,7 @@ sub subreply { my $lockfile=$peerfile.".lock"; while (-e $lockfile) { # Need to wait for the lockfile to disappear. - sleep(1); + sleep(0.1); } # At this point, either a loncnew parent is listening or an old lonc # or loncnew child is listening so we can connect or everything's dead. @@ -310,7 +437,7 @@ sub subreply { } else { &create_connection(&hostname($server),$server); } - sleep(1); # Try again later if failed connection. + sleep(0.1); # Try again later if failed connection. } my $answer; if ($client) { @@ -339,8 +466,8 @@ sub reply { sub reconlonc { my ($lonid) = @_; - my $hostname = &hostname($lonid); if ($lonid) { + my $hostname = &hostname($lonid); my $peerfile="$perlvar{'lonSockDir'}/$hostname"; if ($hostname && -e $peerfile) { &logthis("Trying to reconnect lonc for $lonid ($hostname)"); @@ -365,7 +492,7 @@ sub reconlonc { &logthis("lonc at pid $loncpid responding, sending USR1"); kill USR1 => $loncpid; sleep 1; - } else { + } else { &logthis( "WARNING:". " lonc at pid $loncpid not responding, giving up"); @@ -386,7 +513,7 @@ sub critical { } my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { - &reconlonc("$perlvar{'lonSockDir'}/$server"); + &reconlonc($server); my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { my $now=time; @@ -403,7 +530,7 @@ sub critical { close($dfh); } } - sleep 2; + sleep 1; my $wcmd=''; { my $dfh; @@ -523,13 +650,34 @@ sub transfer_profile_to_env { # ---------------------------------------------------- Check for valid session sub check_for_valid_session { - my ($r) = @_; + my ($r,$name,$userhashref) = @_; my %cookies=CGI::Cookie->parse($r->header_in('Cookie')); - my $lonid=$cookies{'lonID'}; + my ($linkname,$pubname); + if ($name eq '') { + $name = 'lonID'; + $linkname = 'lonLinkID'; + $pubname = 'lonPubID'; + } + my $lonid=$cookies{$name}; + if (!$lonid) { + if (($name eq 'lonID') && ($ENV{'SERVER_PORT'} != 443) && ($linkname)) { + $lonid=$cookies{$linkname}; + } + if (!$lonid) { + if (($name eq 'lonID') && ($pubname)) { + $lonid=$cookies{$pubname}; + } + } + } return undef if (!$lonid); my $handle=&LONCAPA::clean_handle($lonid->value); - my $lonidsdir=$r->dir_config('lonIDsDir'); + my $lonidsdir; + if ($name eq 'lonDAV') { + $lonidsdir=$r->dir_config('lonDAVsessDir'); + } else { + $lonidsdir=$r->dir_config('lonIDsDir'); + } return undef if (!-e "$lonidsdir/$handle.id"); my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id"); @@ -546,6 +694,12 @@ sub check_for_valid_session { || !defined($disk_env{'user.domain'})) { return undef; } + + if (ref($userhashref) eq 'HASH') { + $userhashref->{'name'} = $disk_env{'user.name'}; + $userhashref->{'domain'} = $disk_env{'user.domain'}; + } + return $handle; } @@ -579,7 +733,7 @@ sub appenv { if (($key =~ /^user\.role/) || ($key =~ /^user\.priv/)) { $refused = 1; if (ref($roles) eq 'ARRAY') { - my ($type,$role) = ($key =~ /^user\.(role|priv)\.([^.]+)\./); + my ($type,$role) = ($key =~ m{^user\.(role|priv)\.(.+?)\./}); if (grep(/^\Q$role\E$/,@{$roles})) { $refused = 0; } @@ -611,11 +765,20 @@ sub appenv { # ----------------------------------------------------- Delete from Environment sub delenv { - my ($delthis,$regexp) = @_; - if (($delthis=~/user\.role/) || ($delthis=~/user\.priv/)) { - &logthis("WARNING: ". - "Attempt to delete from environment ".$delthis); - return 'error'; + my ($delthis,$regexp,$roles) = @_; + if (($delthis=~/^user\.role/) || ($delthis=~/^user\.priv/)) { + my $refused = 1; + if (ref($roles) eq 'ARRAY') { + my ($type,$role) = ($delthis =~ /^user\.(role|priv)\.([^.]+)\./); + if (grep(/^\Q$role\E$/,@{$roles})) { + $refused = 0; + } + } + if ($refused) { + &logthis("WARNING: ". + "Attempt to delete from environment ".$delthis); + return 'error'; + } } my $opened = open(my $env_file,'+<',$env{'user.environment'}); if ($opened @@ -727,24 +890,41 @@ sub userload { # ------------------------------ Find server with least workload from spare.tab sub spareserver { - my ($loadpercent,$userloadpercent,$want_server_name) = @_; + my ($loadpercent,$userloadpercent,$want_server_name,$udom) = @_; my $spare_server; if ($userloadpercent !~ /\d/) { $userloadpercent=0; } my $lowest_load=($loadpercent > $userloadpercent) ? $loadpercent : $userloadpercent; - - foreach my $try_server (@{ $spareid{'primary'} }) { - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } - - my $found_server = ($spare_server ne '' && $lowest_load < 100); + my ($uint_dom,$remotesessions); + if (($udom ne '') && (&domain($udom) ne '')) { + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); + my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom); + $remotesessions = $udomdefaults{'remotesessions'}; + } + my $spareshash = &this_host_spares($udom); + if (ref($spareshash) eq 'HASH') { + if (ref($spareshash->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'primary'} }) { + next unless (&spare_can_host($udom,$uint_dom,$remotesessions, + $try_server)); + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } + } - if (!$found_server) { - foreach my $try_server (@{ $spareid{'default'} }) { - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } + my $found_server = ($spare_server ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + next unless (&spare_can_host($udom,$uint_dom, + $remotesessions,$try_server)); + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } + } + } } if (!$want_server_name) { @@ -754,7 +934,7 @@ sub spareserver { } if (defined($spare_server)) { my $hostname = &hostname($spare_server); - if (defined($hostname)) { + if (defined($hostname)) { $spare_server = $protocol.'://'.$hostname; } } @@ -763,13 +943,23 @@ sub spareserver { } sub compare_server_load { - my ($try_server, $spare_server, $lowest_load) = @_; + my ($try_server, $spare_server, $lowest_load, $required) = @_; + + if ($required) { + my ($reqdmajor,$reqdminor) = ($required =~ /^(\d+)\.(\d+)$/); + my $remoterev = &get_server_loncaparev(undef,$try_server); + my ($major,$minor) = ($remoterev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/); + if (($major eq '' && $minor eq '') || + (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)))) { + return ($spare_server,$lowest_load); + } + } my $loadans = &reply('load', $try_server); my $userloadans = &reply('userload',$try_server); if ($loadans !~ /\d/ && $userloadans !~ /\d/) { - return; #didn't get a number from the server + return ($spare_server, $lowest_load); #didn't get a number from the server } my $load; @@ -795,9 +985,18 @@ sub compare_server_load { # --------------------------- ask offload servers if user already has a session sub find_existing_session { my ($udom,$uname) = @_; - foreach my $try_server (@{ $spareid{'primary'} }, - @{ $spareid{'default'} }) { - return $try_server if (&has_user_session($try_server, $udom, $uname)); + my $spareshash = &this_host_spares($udom); + if (ref($spareshash) eq 'HASH') { + if (ref($spareshash->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'primary'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } } return; } @@ -815,22 +1014,57 @@ sub has_user_session { # --------- determine least loaded server in a user's domain which allows login sub choose_server { - my ($udom) = @_; + my ($udom,$checkloginvia,$required,$skiploadbal) = @_; my %domconfhash = &Apache::loncommon::get_domainconf($udom); my %servers = &get_servers($udom); my $lowest_load = 30000; - my ($login_host,$hostname); + my ($login_host,$hostname,$portal_path,$isredirect,$balancers); + if ($skiploadbal) { + ($balancers,my $cached)=&is_cached_new('loadbalancing',$udom); + unless (defined($cached)) { + my $cachetime = 60*60*24; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $balancers = &do_cache_new('loadbalancing',$udom,$domconfig{'loadbalancing'}, + $cachetime); + } + } + } foreach my $lonhost (keys(%servers)) { - my $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost}; - if ($loginvia eq '') { + if ($skiploadbal) { + if (ref($balancers) eq 'HASH') { + next if (exists($balancers->{$lonhost})); + } + } + my $loginvia; + if ($checkloginvia) { + $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost}; + if ($loginvia) { + my ($server,$path) = split(/:/,$loginvia); + ($login_host, $lowest_load) = + &compare_server_load($server, $login_host, $lowest_load, $required); + if ($login_host eq $server) { + $portal_path = $path; + $isredirect = 1; + } + } else { + ($login_host, $lowest_load) = + &compare_server_load($lonhost, $login_host, $lowest_load, $required); + if ($login_host eq $lonhost) { + $portal_path = ''; + $isredirect = ''; + } + } + } else { ($login_host, $lowest_load) = - &compare_server_load($lonhost, $login_host, $lowest_load); + &compare_server_load($lonhost, $login_host, $lowest_load, $required); } } if ($login_host ne '') { - $hostname = $servers{$login_host}; + $hostname = &hostname($login_host); } - return ($login_host,$hostname); + return ($login_host,$hostname,$portal_path,$isredirect,$lowest_load); } # --------------------------------------------- Try to change a user's password @@ -971,15 +1205,19 @@ sub can_host_session { } if ($canhost) { if (ref($hostedsessions) eq 'HASH') { + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); if (ref($hostedsessions->{'excludedomain'}) eq 'ARRAY') { - if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'excludedomain'}})) { + if (($uint_dom ne '') && + (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'excludedomain'}}))) { $canhost = 0; } else { $canhost = 1; } } if (ref($hostedsessions->{'includedomain'}) eq 'ARRAY') { - if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'includedomain'}})) { + if (($uint_dom ne '') && + (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'includedomain'}}))) { $canhost = 1; } else { $canhost = 0; @@ -990,6 +1228,470 @@ sub can_host_session { return $canhost; } +sub spare_can_host { + my ($udom,$uint_dom,$remotesessions,$try_server)=@_; + my $canhost=1; + my $try_server_hostname = &hostname($try_server); + my $serverhomeID = &get_server_homeID($try_server_hostname); + my $serverhomedom = &host_domain($serverhomeID); + my %defdomdefaults = &get_domain_defaults($serverhomedom); + if (ref($defdomdefaults{'offloadnow'}) eq 'HASH') { + if ($defdomdefaults{'offloadnow'}{$try_server}) { + $canhost = 0; + } + } + if (($canhost) && ($uint_dom)) { + my @intdoms; + my $internet_names = &get_internet_names($try_server); + if (ref($internet_names) eq 'ARRAY') { + @intdoms = @{$internet_names}; + } + unless (grep(/^\Q$uint_dom\E$/,@intdoms)) { + my $remoterev = &get_server_loncaparev(undef,$try_server); + $canhost = &can_host_session($udom,$try_server,$remoterev, + $remotesessions, + $defdomdefaults{'hostedsessions'}); + } + } + return $canhost; +} + +sub this_host_spares { + my ($dom) = @_; + my ($dom_in_use,$lonhost_in_use,$result); + my @hosts = ¤t_machine_ids(); + foreach my $lonhost (@hosts) { + if (&host_domain($lonhost) eq $dom) { + $dom_in_use = $dom; + $lonhost_in_use = $lonhost; + last; + } + } + if ($dom_in_use ne '') { + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + } + if (ref($result) ne 'HASH') { + $lonhost_in_use = $perlvar{'lonHostID'}; + $dom_in_use = &host_domain($lonhost_in_use); + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + if (ref($result) ne 'HASH') { + $result = \%spareid; + } + } + return $result; +} + +sub spares_for_offload { + my ($dom_in_use,$lonhost_in_use) = @_; + my ($result,$cached)=&is_cached_new('spares',$dom_in_use); + if (defined($cached)) { + return $result; + } else { + my $cachetime = 60*60*24; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['usersessions'],$dom_in_use); + if (ref($domconfig{'usersessions'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}{$lonhost_in_use}) eq 'HASH') { + return &do_cache_new('spares',$dom_in_use,$domconfig{'usersessions'}{'spares'}{$lonhost_in_use},$cachetime); + } + } + } + } + return; +} + +sub get_lonbalancer_config { + my ($servers) = @_; + my ($currbalancer,$currtargets); + if (ref($servers) eq 'HASH') { + foreach my $server (keys(%{$servers})) { + my %what = ( + spareid => 1, + perlvar => 1, + ); + my ($result,$returnhash) = &get_remote_globals($server,\%what); + if ($result eq 'ok') { + if (ref($returnhash) eq 'HASH') { + if (ref($returnhash->{'perlvar'}) eq 'HASH') { + if ($returnhash->{'perlvar'}->{'lonBalancer'} eq 'yes') { + $currbalancer = $server; + $currtargets = {}; + if (ref($returnhash->{'spareid'}) eq 'HASH') { + if (ref($returnhash->{'spareid'}->{'primary'}) eq 'ARRAY') { + $currtargets->{'primary'} = $returnhash->{'spareid'}->{'primary'}; + } + if (ref($returnhash->{'spareid'}->{'default'}) eq 'ARRAY') { + $currtargets->{'default'} = $returnhash->{'spareid'}->{'default'}; + } + } + last; + } + } + } + } + } + } + return ($currbalancer,$currtargets); +} + +sub check_loadbalancing { + my ($uname,$udom,$caller) = @_; + my ($is_balancer,$currtargets,$currrules,$dom_in_use,$homeintdom, + $rule_in_effect,$offloadto,$otherserver); + my $lonhost = $perlvar{'lonHostID'}; + my @hosts = ¤t_machine_ids(); + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + my $uintdom = &Apache::lonnet::internet_dom($uprimary_id); + my $intdom = &Apache::lonnet::internet_dom($lonhost); + my $serverhomedom = &host_domain($lonhost); + my $domneedscache; + my $cachetime = 60*60*24; + + if (($uintdom ne '') && ($uintdom eq $intdom)) { + $dom_in_use = $udom; + $homeintdom = 1; + } else { + $dom_in_use = $serverhomedom; + } + my ($result,$cached)=&is_cached_new('loadbalancing',$dom_in_use); + unless (defined($cached)) { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + } else { + $domneedscache = $dom_in_use; + } + } + if (ref($result) eq 'HASH') { + ($is_balancer,$currtargets,$currrules) = + &check_balancer_result($result,@hosts); + if ($is_balancer) { + if (ref($currrules) eq 'HASH') { + if ($homeintdom) { + if ($uname ne '') { + if (($currrules->{'_LC_adv'} ne '') || ($currrules->{'_LC_author'} ne '')) { + my ($is_adv,$is_author) = &is_advanced_user($udom,$uname); + if (($currrules->{'_LC_author'} ne '') && ($is_author)) { + $rule_in_effect = $currrules->{'_LC_author'}; + } elsif (($currrules->{'_LC_adv'} ne '') && ($is_adv)) { + $rule_in_effect = $currrules->{'_LC_adv'} + } + } + if ($rule_in_effect eq '') { + my %userenv = &userenvironment($udom,$uname,'inststatus'); + if ($userenv{'inststatus'} ne '') { + my @statuses = map { &unescape($_); } split(/:/,$userenv{'inststatus'}); + my ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($udom); + if (ref($types) eq 'ARRAY') { + foreach my $type (@{$types}) { + if (grep(/^\Q$type\E$/,@statuses)) { + if (exists($currrules->{$type})) { + $rule_in_effect = $currrules->{$type}; + } + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } else { + if ($currrules->{'_LC_external'} ne '') { + $rule_in_effect = $currrules->{'_LC_external'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } + } elsif (($homeintdom) && ($udom ne $serverhomedom)) { + ($result,$cached)=&is_cached_new('loadbalancing',$serverhomedom); + unless (defined($cached)) { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime); + } else { + $domneedscache = $serverhomedom; + } + } + if (ref($result) eq 'HASH') { + ($is_balancer,$currtargets,$currrules) = + &check_balancer_result($result,@hosts); + if ($is_balancer) { + if (ref($currrules) eq 'HASH') { + if ($currrules->{'_LC_internetdom'} ne '') { + $rule_in_effect = $currrules->{'_LC_internetdom'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + unless (defined($cached)) { + $domneedscache = $serverhomedom; + } + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + unless (defined($cached)) { + $domneedscache = $serverhomedom; + } + } + if ($domneedscache) { + &do_cache_new('loadbalancing',$domneedscache,$is_balancer,$cachetime); + } + if ($is_balancer) { + my $lowest_load = 30000; + if (ref($offloadto) eq 'HASH') { + if (ref($offloadto->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'primary'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + my $found_server = ($otherserver ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($offloadto->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'default'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + } elsif (ref($offloadto) eq 'ARRAY') { + if (@{$offloadto} == 1) { + $otherserver = $offloadto->[0]; + } elsif (@{$offloadto} > 1) { + foreach my $try_server (@{$offloadto}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + unless ($caller eq 'login') { + if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) { + $is_balancer = 0; + if ($uname ne '' && $udom ne '') { + if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { + + &appenv({'user.loadbalexempt' => $lonhost, + 'user.loadbalcheck.time' => time}); + } + } + } + } + } + return ($is_balancer,$otherserver); +} + +sub check_balancer_result { + my ($result,@hosts) = @_; + my ($is_balancer,$currtargets,$currrules); + if (ref($result) eq 'HASH') { + if ($result->{'lonhost'} ne '') { + my $currbalancer = $result->{'lonhost'}; + if (grep(/^\Q$currbalancer\E$/,@hosts)) { + $is_balancer = 1; + $currtargets = $result->{'targets'}; + $currrules = $result->{'rules'}; + } + } else { + foreach my $key (keys(%{$result})) { + if (($key ne '') && (grep(/^\Q$key\E$/,@hosts)) && + (ref($result->{$key}) eq 'HASH')) { + $is_balancer = 1; + $currrules = $result->{$key}{'rules'}; + $currtargets = $result->{$key}{'targets'}; + last; + } + } + } + } + return ($is_balancer,$currtargets,$currrules); +} + +sub get_loadbalancer_targets { + my ($rule_in_effect,$currtargets,$uname,$udom) = @_; + my $offloadto; + if ($rule_in_effect eq 'none') { + return [$perlvar{'lonHostID'}]; + } elsif ($rule_in_effect eq '') { + $offloadto = $currtargets; + } else { + if ($rule_in_effect eq 'homeserver') { + my $homeserver = &homeserver($uname,$udom); + if ($homeserver ne 'no_host') { + $offloadto = [$homeserver]; + } + } elsif ($rule_in_effect eq 'externalbalancer') { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + if ($domconfig{'loadbalancing'}{'lonhost'} ne '') { + if (&hostname($domconfig{'loadbalancing'}{'lonhost'}) ne '') { + $offloadto = [$domconfig{'loadbalancing'}{'lonhost'}]; + } + } + } else { + my %servers = &internet_dom_servers($udom); + my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers); + if (&hostname($remotebalancer) ne '') { + $offloadto = [$remotebalancer]; + } + } + } elsif (&hostname($rule_in_effect) ne '') { + $offloadto = [$rule_in_effect]; + } + } + return $offloadto; +} + +sub internet_dom_servers { + my ($dom) = @_; + my (%uniqservers,%servers); + my $primaryserver = &hostname(&domain($dom,'primary')); + my @machinedoms = &machine_domains($primaryserver); + foreach my $mdom (@machinedoms) { + my %currservers = %servers; + my %server = &get_servers($mdom); + %servers = (%currservers,%server); + } + my %by_hostname; + foreach my $id (keys(%servers)) { + push(@{$by_hostname{$servers{$id}}},$id); + } + foreach my $hostname (sort(keys(%by_hostname))) { + if (@{$by_hostname{$hostname}} > 1) { + my $match = 0; + foreach my $id (@{$by_hostname{$hostname}}) { + if (&host_domain($id) eq $dom) { + $uniqservers{$id} = $hostname; + $match = 1; + } + } + unless ($match) { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } else { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } + return %uniqservers; +} + +sub trusted_domains { + my ($cmdtype,$calldom) = @_; + my ($trusted,$untrusted); + if (&domain($calldom) eq '') { + return ($trusted,$untrusted); + } + unless ($cmdtype =~ /^(content|shared|enroll|coaurem|domroles|catalog|reqcrs|msg)$/) { + return ($trusted,$untrusted); + } + my $callprimary = &domain($calldom,'primary'); + my $intcalldom = &Apache::lonnet::internet_dom($callprimary); + if ($intcalldom eq '') { + return ($trusted,$untrusted); + } + + my ($trustconfig,$cached)=&Apache::lonnet::is_cached_new('trust',$calldom); + unless (defined($cached)) { + my %domconfig = &Apache::lonnet::get_dom('configuration',['trust'],$calldom); + &Apache::lonnet::do_cache_new('trust',$calldom,$domconfig{'trust'},3600); + $trustconfig = $domconfig{'trust'}; + } + if (ref($trustconfig)) { + my (%possexc,%possinc,@allexc,@allinc); + if (ref($trustconfig->{$cmdtype}) eq 'HASH') { + if (ref($trustconfig->{$cmdtype}->{'exc'}) eq 'ARRAY') { + map { $possexc{$_} = 1; } @{$trustconfig->{$cmdtype}->{'exc'}}; + } + if (ref($trustconfig->{$cmdtype}->{'inc'}) eq 'ARRAY') { + map { $possinc{$_} = 1; } @{$trustconfig->{$cmdtype}->{'inc'}}; + } + } + if (keys(%possexc)) { + if (keys(%possinc)) { + foreach my $key (sort(keys(%possexc))) { + next if ($key eq $intcalldom); + unless ($possinc{$key}) { + push(@allexc,$key); + } + } + } else { + @allexc = sort(keys(%possexc)); + } + } + if (keys(%possinc)) { + $possinc{$intcalldom} = 1; + @allinc = sort(keys(%possinc)); + } + if ((@allexc > 0) || (@allinc > 0)) { + my %doms_by_intdom; + my %allintdoms = &all_host_intdom(); + my %alldoms = &all_host_domain(); + foreach my $key (%allintdoms) { + if (ref($doms_by_intdom{$allintdoms{$key}}) eq 'ARRAY') { + unless (grep(/^\Q$alldoms{$key}\E$/,@{$doms_by_intdom{$allintdoms{$key}}})) { + push(@{$doms_by_intdom{$allintdoms{$key}}},$alldoms{$key}); + } + } else { + $doms_by_intdom{$allintdoms{$key}} = [$alldoms{$key}]; + } + } + foreach my $exc (@allexc) { + if (ref($doms_by_intdom{$exc}) eq 'ARRAY') { + $untrusted = $doms_by_intdom{$exc}; + } + } + foreach my $inc (@allinc) { + if (ref($doms_by_intdom{$inc}) eq 'ARRAY') { + $trusted = $doms_by_intdom{$inc}; + } + } + } + } + return ($trusted,$untrusted); +} + +sub will_trust { + my ($cmdtype,$domain,$possdom) = @_; + return 1 if ($domain eq $possdom); + my ($trustedref,$untrustedref) = &trusted_domains($cmdtype,$possdom); + my $willtrust; + if ((ref($trustedref) eq 'ARRAY') && (@{$trustedref} > 0)) { + if (grep(/^\Q$domain\E$/,@{$trustedref})) { + $willtrust = 1; + } + } elsif ((ref($untrustedref) eq 'ARRAY') && (@{$untrustedref} > 0)) { + unless (grep(/^\Q$domain\E$/,@{$untrustedref})) { + $willtrust = 1; + } + } else { + $willtrust = 1; + } + return $willtrust; +} + # ---------------------- Find the homebase for a user from domain's lib servers my %homecache; @@ -1015,17 +1717,33 @@ sub homeserver { return 'no_host'; } -# ------------------------------------- Find the usernames behind a list of IDs +# ----- Find the usernames behind a list of student/employee IDs or clicker IDs sub idget { - my ($udom,@ids)=@_; + my ($udom,$idsref,$namespace)=@_; my %returnhash=(); + my @ids=(); + if (ref($idsref) eq 'ARRAY') { + @ids = @{$idsref}; + } else { + return %returnhash; + } + if ($namespace eq '') { + $namespace = 'ids'; + } my %servers = &get_servers($udom,'library'); foreach my $tryserver (keys(%servers)) { - my $idlist=join('&',@ids); - $idlist=~tr/A-Z/a-z/; - my $reply=&reply("idget:$udom:".$idlist,$tryserver); + my $idlist=join('&', map { &escape($_); } @ids); + if ($namespace eq 'ids') { + $idlist=~tr/A-Z/a-z/; + } + my $reply; + if ($namespace eq 'ids') { + $reply=&reply("idget:$udom:".$idlist,$tryserver); + } else { + $reply=&reply("getdom:$udom:$namespace:$idlist",$tryserver); + } my @answer=(); if (($reply ne 'con_lost') && ($reply!~/^error\:/)) { @answer=split(/\&/,$reply); @@ -1033,10 +1751,10 @@ sub idget { my $i; for ($i=0;$i<=$#ids;$i++) { if ($answer[$i]) { - $returnhash{$ids[$i]}=$answer[$i]; - } + $returnhash{$ids[$i]}=&unescape($answer[$i]); + } } - } + } return %returnhash; } @@ -1051,48 +1769,157 @@ sub idrget { return %returnhash; } -# ------------------------------- Store away a list of names and associated IDs +# Store away a list of names and associated student/employee IDs or clicker IDs sub idput { - my ($udom,%ids)=@_; + my ($udom,$idsref,$uhom,$namespace)=@_; my %servers=(); + my %ids=(); + my %byid = (); + if (ref($idsref) eq 'HASH') { + %ids=%{$idsref}; + } + if ($namespace eq '') { + $namespace = 'ids'; + } foreach my $uname (keys(%ids)) { &cput('environment',{'id'=>$ids{$uname}},$udom,$uname); - my $uhom=&homeserver($uname,$udom); + if ($uhom eq '') { + $uhom=&homeserver($uname,$udom); + } if ($uhom ne 'no_host') { - my $id=&escape($ids{$uname}); - $id=~tr/A-Z/a-z/; my $esc_unam=&escape($uname); - if ($servers{$uhom}) { - $servers{$uhom}.='&'.$id.'='.$esc_unam; + if ($namespace eq 'ids') { + my $id=&escape($ids{$uname}); + $id=~tr/A-Z/a-z/; + my $esc_unam=&escape($uname); + $servers{$uhom}.=$id.'='.$esc_unam.'&'; } else { - $servers{$uhom}=$id.'='.$esc_unam; + my @currids = split(/,/,$ids{$uname}); + foreach my $id (@currids) { + $byid{$uhom}{$id} .= $uname.','; + } + } + } + } + if ($namespace eq 'clickers') { + foreach my $server (keys(%byid)) { + if (ref($byid{$server}) eq 'HASH') { + foreach my $id (keys(%{$byid{$server}})) { + $byid{$server} =~ s/,$//; + $servers{$uhom}.=&escape($id).'='.&escape($byid{$server}).'&'; + } } } } foreach my $server (keys(%servers)) { - &critical('idput:'.$udom.':'.$servers{$server},$server); + $servers{$server} =~ s/\&$//; + if ($namespace eq 'ids') { + &critical('idput:'.$udom.':'.$servers{$server},$server); + } else { + &critical('updateclickers:'.$udom.':add:'.$servers{$server},$server); + } } } -# ------------------------------dump from db file owned by domainconfig user -sub dump_dom { - my ($namespace,$udom,$regexp,$range)=@_; - if (!$udom) { - $udom=$env{'user.domain'}; +# ------------- Delete unwanted student/employee IDs or clicker IDs from domain + +sub iddel { + my ($udom,$idshashref,$uhome,$namespace)=@_; + my %result=(); + my %ids=(); + my %byid = (); + if (ref($idshashref) eq 'HASH') { + %ids=%{$idshashref}; + } else { + return %result; } - my %returnhash; - if ($udom) { - my $uname = &get_domainconfiguser($udom); - %returnhash = &dump($namespace,$udom,$uname,$regexp,$range); + if ($namespace eq '') { + $namespace = 'ids'; } - return %returnhash; + my %servers=(); + while (my ($id,$unamestr) = each(%ids)) { + if ($namespace eq 'ids') { + my $uhom = $uhome; + if ($uhom eq '') { + $uhom=&homeserver($unamestr,$udom); + } + if ($uhom ne 'no_host') { + $servers{$uhom}.='&'.&escape($id); + } + } else { + my @curritems = split(/,/,$ids{$id}); + foreach my $uname (@curritems) { + my $uhom = $uhome; + if ($uhom eq '') { + $uhom=&homeserver($uname,$udom); + } + if ($uhom ne 'no_host') { + $byid{$uhom}{$id} .= $uname.','; + } + } + } + } + if ($namespace eq 'clickers') { + foreach my $server (keys(%byid)) { + if (ref($byid{$server}) eq 'HASH') { + foreach my $id (keys(%{$byid{$server}})) { + $byid{$server}{$id} =~ s/,$//; + $servers{$server}.=&escape($id).'='.&escape($byid{$server}{$id}).'&'; + } + } + } + } + foreach my $server (keys(%servers)) { + $servers{$server} =~ s/\&$//; + if ($namespace eq 'ids') { + $result{$server} = &critical('iddel:'.$udom.':'.$servers{$server},$uhome); + } elsif ($namespace eq 'clickers') { + $result{$server} = &critical('updateclickers:'.$udom.':del:'.$servers{$server},$server); + } + } + return %result; +} + +# ----- Update clicker ID-to-username look-ups in clickers.db on library server + +sub updateclickers { + my ($udom,$action,$idshashref,$uhome,$critical) = @_; + my %clickers; + if (ref($idshashref) eq 'HASH') { + %clickers=%{$idshashref}; + } else { + return; + } + my $items=''; + foreach my $item (keys(%clickers)) { + $items.=&escape($item).'='.&escape($clickers{$item}).'&'; + } + $items=~s/\&$//; + my $request = "updateclickers:$udom:$action:$items"; + if ($critical) { + return &critical($request,$uhome); + } else { + return &reply($request,$uhome); + } +} + +# ------------------------------dump from db file owned by domainconfig user +sub dump_dom { + my ($namespace, $udom, $regexp) = @_; + + $udom ||= $env{'user.domain'}; + + return () unless $udom; + + return &dump($namespace, $udom, &get_domainconfiguser($udom), $regexp); } # ------------------------------------------ get items from domain db files sub get_dom { my ($namespace,$storearr,$udom,$uhome)=@_; + return if ($udom eq 'public'); my $items=''; foreach my $item (@$storearr) { $items.=&escape($item).'&'; @@ -1100,6 +1927,7 @@ sub get_dom { $items=~s/\&$//; if (!$udom) { $udom=$env{'user.domain'}; + return if ($udom eq 'public'); if (defined(&domain($udom,'primary'))) { $uhome=&domain($udom,'primary'); } else { @@ -1113,7 +1941,12 @@ sub get_dom { } } if ($udom && $uhome && ($uhome ne 'no_host')) { - my $rep=&reply("getdom:$udom:$namespace:$items",$uhome); + my $rep; + if ($namespace =~ /^enc/) { + $rep=&reply("encrypt:egetdom:$udom:$namespace:$items",$uhome); + } else { + $rep=&reply("getdom:$udom:$namespace:$items",$uhome); + } my %returnhash; if ($rep eq '' || $rep =~ /^error: 2 /) { return %returnhash; @@ -1157,7 +1990,11 @@ sub put_dom { $items.=&escape($item).'='.&freeze_escape($$storehash{$item}).'&'; } $items=~s/\&$//; - return &reply("putdom:$udom:$namespace:$items",$uhome); + if ($namespace =~ /^enc/) { + return &reply("encrypt:putdom:$udom:$namespace:$items",$uhome); + } else { + return &reply("putdom:$udom:$namespace:$items",$uhome); + } } else { &logthis("put_dom failed - no homeserver and/or domain"); } @@ -1203,14 +2040,13 @@ sub retrieve_inst_usertypes { my %domdefs = &Apache::lonnet::get_domain_defaults($udom); if ((ref($domdefs{'inststatustypes'}) eq 'HASH') && (ref($domdefs{'inststatusorder'}) eq 'ARRAY')) { - %returnhash = %{$domdefs{'inststatustypes'}}; - @order = @{$domdefs{'inststatusorder'}}; + return ($domdefs{'inststatustypes'},$domdefs{'inststatusorder'}); } else { if (defined(&domain($udom,'primary'))) { my $uhome=&domain($udom,'primary'); my $rep=&reply("inst_usertypes:$udom",$uhome); if ($rep =~ /^(con_lost|error|no_such_host|refused)/) { - &logthis("get_dom failed - $rep returned from $uhome in domain: $udom"); + &logthis("retrieve_inst_usertypes failed - $rep returned from $uhome in domain: $udom"); return (\%returnhash,\@order); } my ($hashitems,$orderitems) = split(/:/,$rep); @@ -1226,15 +2062,15 @@ sub retrieve_inst_usertypes { push(@order,&unescape($item)); } } else { - &logthis("get_dom failed - no primary domain server for $udom"); + &logthis("retrieve_inst_usertypes failed - no primary domain server for $udom"); } + return (\%returnhash,\@order); } - return (\%returnhash,\@order); } sub is_domainimage { my ($url) = @_; - if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+-) { + if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+[^/]-) { if (&domain($1) ne '') { return '1'; } @@ -1255,7 +2091,7 @@ sub inst_directory_query { &escape($srch->{'srchtype'}),$homeserver); my $host=&hostname($homeserver); if ($queryid !~/^\Q$host\E\_/) { - &logthis('instituional directory search invalid queryid: '.$queryid.' for host: '.$homeserver.'in domain '.$udom); + &logthis('institutional directory search invalid queryid: '.$queryid.' for host: '.$homeserver.' in domain '.$udom); return; } my $response = &get_query_reply($queryid); @@ -1375,6 +2211,63 @@ sub get_instuser { return ($outcome,%userinfo); } +sub get_multiple_instusers { + my ($udom,$users,$caller) = @_; + my ($outcome,$results); + if (ref($users) eq 'HASH') { + my $count = keys(%{$users}); + my $requested = &freeze_escape($users); + my $homeserver = &domain($udom,'primary'); + if ($homeserver ne '') { + my $queryid=&reply('querysend:getmultinstusers:::'.$caller.'='.$requested,$homeserver); + my $host=&hostname($homeserver); + if ($queryid !~/^\Q$host\E\_/) { + &logthis('get_multiple_instusers invalid queryid: '.$queryid. + ' for host: '.$homeserver.'in domain '.$udom); + return ($outcome,$results); + } + my $response = &get_query_reply($queryid); + my $maxtries = 5; + if ($count > 100) { + $maxtries = 1+int($count/20); + } + my $tries = 1; + while (($response=~/^timeout/) && ($tries <= $maxtries)) { + $response = &get_query_reply($queryid); + $tries ++; + } + if ($response eq '') { + $results = {}; + foreach my $key (keys(%{$users})) { + my ($uname,$id); + if ($caller eq 'id') { + $id = $key; + } else { + $uname = $key; + } + my ($resp,%info) = &get_instuser($udom,$uname,$id); + $outcome = $resp; + if ($resp eq 'ok') { + %{$results} = (%{$results}, %info); + } else { + last; + } + } + } elsif(!&error($response) && ($response ne 'refused')) { + if (($response eq 'unavailable') || ($response eq 'invalid') || ($response eq 'timeout')) { + $outcome = $response; + } else { + ($outcome,my $userdata) = split(/=/,$response,2); + if ($outcome eq 'ok') { + $results = &thaw_unescape($userdata); + } + } + } + } + } + return ($outcome,$results); +} + sub inst_rulecheck { my ($udom,$uname,$id,$item,$rules) = @_; my %returnhash; @@ -1454,25 +2347,36 @@ sub inst_userrules { # ------------- Get Authentication, Language and User Tools Defaults for Domain sub get_domain_defaults { - my ($domain) = @_; + my ($domain,$ignore_cache) = @_; + return if (($domain eq '') || ($domain eq 'public')); my $cachetime = 60*60*24; - my ($result,$cached)=&is_cached_new('domdefaults',$domain); - if (defined($cached)) { - if (ref($result) eq 'HASH') { - return %{$result}; + unless ($ignore_cache) { + my ($result,$cached)=&is_cached_new('domdefaults',$domain); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + return %{$result}; + } } } my %domdefaults; my %domconfig = &Apache::lonnet::get_dom('configuration',['defaults','quotas', 'requestcourses','inststatus', - 'coursedefaults','usersessions'],$domain); + 'coursedefaults','usersessions', + 'requestauthor','selfenrollment', + 'coursecategories','ssl','autoenroll', + 'trust','helpsettings'],$domain); + my @coursetypes = ('official','unofficial','community','textbook','placement'); if (ref($domconfig{'defaults'}) eq 'HASH') { $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'}; $domdefaults{'auth_arg_def'} = $domconfig{'defaults'}{'auth_arg_def'}; $domdefaults{'timezone_def'} = $domconfig{'defaults'}{'timezone_def'}; $domdefaults{'datelocale_def'} = $domconfig{'defaults'}{'datelocale_def'}; + $domdefaults{'portal_def'} = $domconfig{'defaults'}{'portal_def'}; + $domdefaults{'intauth_cost'} = $domconfig{'defaults'}{'intauth_cost'}; + $domdefaults{'intauth_switch'} = $domconfig{'defaults'}{'intauth_switch'}; + $domdefaults{'intauth_check'} = $domconfig{'defaults'}{'intauth_check'}; } else { $domdefaults{'lang_def'} = &domain($domain,'lang_def'); $domdefaults{'auth_def'} = &domain($domain,'auth_def'); @@ -1483,27 +2387,62 @@ sub get_domain_defaults { $domdefaults{'defaultquota'} = $domconfig{'quotas'}{'defaultquota'}; } else { $domdefaults{'defaultquota'} = $domconfig{'quotas'}; - } - my @usertools = ('aboutme','blog','portfolio'); + } + my @usertools = ('aboutme','blog','webdav','portfolio'); foreach my $item (@usertools) { if (ref($domconfig{'quotas'}{$item}) eq 'HASH') { $domdefaults{$item} = $domconfig{'quotas'}{$item}; } } + if (ref($domconfig{'quotas'}{'authorquota'}) eq 'HASH') { + $domdefaults{'authorquota'} = $domconfig{'quotas'}{'authorquota'}; + } } if (ref($domconfig{'requestcourses'}) eq 'HASH') { - foreach my $item ('official','unofficial','community') { + foreach my $item ('official','unofficial','community','textbook','placement') { $domdefaults{$item} = $domconfig{'requestcourses'}{$item}; } } + if (ref($domconfig{'requestauthor'}) eq 'HASH') { + $domdefaults{'requestauthor'} = $domconfig{'requestauthor'}; + } if (ref($domconfig{'inststatus'}) eq 'HASH') { - foreach my $item ('inststatustypes','inststatusorder') { + foreach my $item ('inststatustypes','inststatusorder','inststatusguest') { $domdefaults{$item} = $domconfig{'inststatus'}{$item}; } } if (ref($domconfig{'coursedefaults'}) eq 'HASH') { - foreach my $item ('canuse_pdfforms') { - $domdefaults{$item} = $domconfig{'coursedefaults'}{$item}; + $domdefaults{'canuse_pdfforms'} = $domconfig{'coursedefaults'}{'canuse_pdfforms'}; + $domdefaults{'usejsme'} = $domconfig{'coursedefaults'}{'usejsme'}; + $domdefaults{'uselcmath'} = $domconfig{'coursedefaults'}{'uselcmath'}; + if (ref($domconfig{'coursedefaults'}{'postsubmit'}) eq 'HASH') { + $domdefaults{'postsubmit'} = $domconfig{'coursedefaults'}{'postsubmit'}{'client'}; + } + foreach my $type (@coursetypes) { + if (ref($domconfig{'coursedefaults'}{'coursecredits'}) eq 'HASH') { + unless ($type eq 'community') { + $domdefaults{$type.'credits'} = $domconfig{'coursedefaults'}{'coursecredits'}{$type}; + } + } + if (ref($domconfig{'coursedefaults'}{'uploadquota'}) eq 'HASH') { + $domdefaults{$type.'quota'} = $domconfig{'coursedefaults'}{'uploadquota'}{$type}; + } + if ($domdefaults{'postsubmit'} eq 'on') { + if (ref($domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}) eq 'HASH') { + $domdefaults{$type.'postsubtimeout'} = + $domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}{$type}; + } + } + } + if (ref($domconfig{'coursedefaults'}{'canclone'}) eq 'HASH') { + if (ref($domconfig{'coursedefaults'}{'canclone'}{'instcode'}) eq 'ARRAY') { + my @clonecodes = @{$domconfig{'coursedefaults'}{'canclone'}{'instcode'}}; + if (@clonecodes) { + $domdefaults{'canclone'} = join('+',@clonecodes); + } + } + } elsif ($domconfig{'coursedefaults'}{'canclone'}) { + $domdefaults{'canclone'}=$domconfig{'coursedefaults'}{'canclone'}; } } if (ref($domconfig{'usersessions'}) eq 'HASH') { @@ -1513,12 +2452,96 @@ sub get_domain_defaults { if (ref($domconfig{'usersessions'}{'hosted'}) eq 'HASH') { $domdefaults{'hostedsessions'} = $domconfig{'usersessions'}{'hosted'}; } + if (ref($domconfig{'usersessions'}{'offloadnow'}) eq 'HASH') { + $domdefaults{'offloadnow'} = $domconfig{'usersessions'}{'offloadnow'}; + } + } + if (ref($domconfig{'selfenrollment'}) eq 'HASH') { + if (ref($domconfig{'selfenrollment'}{'admin'}) eq 'HASH') { + my @settings = ('types','registered','enroll_dates','access_dates','section', + 'approval','limit'); + foreach my $type (@coursetypes) { + if (ref($domconfig{'selfenrollment'}{'admin'}{$type}) eq 'HASH') { + my @mgrdc = (); + foreach my $item (@settings) { + if ($domconfig{'selfenrollment'}{'admin'}{$type}{$item} eq '0') { + push(@mgrdc,$item); + } + } + if (@mgrdc) { + $domdefaults{$type.'selfenrolladmdc'} = join(',',@mgrdc); + } + } + } + } + if (ref($domconfig{'selfenrollment'}{'default'}) eq 'HASH') { + foreach my $type (@coursetypes) { + if (ref($domconfig{'selfenrollment'}{'default'}{$type}) eq 'HASH') { + foreach my $item (keys(%{$domconfig{'selfenrollment'}{'default'}{$type}})) { + $domdefaults{$type.'selfenroll'.$item} = $domconfig{'selfenrollment'}{'default'}{$type}{$item}; + } + } + } + } + } + if (ref($domconfig{'coursecategories'}) eq 'HASH') { + $domdefaults{'catauth'} = 'std'; + $domdefaults{'catunauth'} = 'std'; + if ($domconfig{'coursecategories'}{'auth'}) { + $domdefaults{'catauth'} = $domconfig{'coursecategories'}{'auth'}; + } + if ($domconfig{'coursecategories'}{'unauth'}) { + $domdefaults{'catunauth'} = $domconfig{'coursecategories'}{'unauth'}; + } + } + if (ref($domconfig{'ssl'}) eq 'HASH') { + if (ref($domconfig{'ssl'}{'replication'}) eq 'HASH') { + $domdefaults{'replication'} = $domconfig{'ssl'}{'replication'}; + } + if (ref($domconfig{'ssl'}{'connto'}) eq 'HASH') { + $domdefaults{'connect'} = $domconfig{'ssl'}{'connto'}; + } + if (ref($domconfig{'ssl'}{'connfrom'}) eq 'HASH') { + $domdefaults{'connect'} = $domconfig{'ssl'}{'connfrom'}; + } + } + if (ref($domconfig{'trust'}) eq 'HASH') { + my @prefixes = qw(content shared enroll othcoau coaurem domroles catalog reqcrs msg); + foreach my $prefix (@prefixes) { + if (ref($domconfig{'trust'}{$prefix}) eq 'HASH') { + $domdefaults{'trust'.$prefix} = $domconfig{'trust'}{$prefix}; + } + } + } + if (ref($domconfig{'autoenroll'}) eq 'HASH') { + $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'}; + } + if (ref($domconfig{'helpsettings'}) eq 'HASH') { + $domdefaults{'submitbugs'} = $domconfig{'helpsettings'}{'submitbugs'}; + if (ref($domconfig{'helpsettings'}{'adhoc'}) eq 'HASH') { + $domdefaults{'adhocroles'} = $domconfig{'helpsettings'}{'adhoc'}; + } } - &Apache::lonnet::do_cache_new('domdefaults',$domain,\%domdefaults, - $cachetime); + &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime); return %domdefaults; } +sub course_portal_url { + my ($cnum,$cdom) = @_; + my $chome = &homeserver($cnum,$cdom); + my $hostname = &hostname($chome); + my $protocol = $protocol{$chome}; + $protocol = 'http' if ($protocol ne 'https'); + my %domdefaults = &get_domain_defaults($cdom); + my $firsturl; + if ($domdefaults{'portal_def'}) { + $firsturl = $domdefaults{'portal_def'}; + } else { + $firsturl = $protocol.'://'.$hostname; + } + return $firsturl; +} + # --------------------------------------------------- Assign a key to a student sub assign_access_key { @@ -1755,21 +2778,23 @@ sub make_key { sub devalidate_cache_new { my ($name,$id,$debug) = @_; if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); } + my $remembered_id=$name.':'.$id; $id=&make_key($name,$id); $memcache->delete($id); - delete($remembered{$id}); - delete($accessed{$id}); + delete($remembered{$remembered_id}); + delete($accessed{$remembered_id}); } sub is_cached_new { my ($name,$id,$debug) = @_; - $id=&make_key($name,$id); - if (exists($remembered{$id})) { - if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); } - $accessed{$id}=[&gettimeofday()]; + my $remembered_id=$name.':'.$id; # this is to avoid make_key (which is slow) whenever possible + if (exists($remembered{$remembered_id})) { + if ($debug) { &Apache::lonnet::logthis("Early return $remembered_id of $remembered{$remembered_id} "); } + $accessed{$remembered_id}=[&gettimeofday()]; $hits++; - return ($remembered{$id},1); + return ($remembered{$remembered_id},1); } + $id=&make_key($name,$id); my $value = $memcache->get($id); if (!(defined($value))) { if ($debug) { &Apache::lonnet::logthis("getting $id is not defined"); } @@ -1779,13 +2804,14 @@ sub is_cached_new { if ($debug) { &Apache::lonnet::logthis("getting $id is __undef__"); } $value=undef; } - &make_room($id,$value,$debug); + &make_room($remembered_id,$value,$debug); if ($debug) { &Apache::lonnet::logthis("getting $id is $value"); } return ($value,1); } sub do_cache_new { my ($name,$id,$value,$time,$debug) = @_; + my $remembered_id=$name.':'.$id; $id=&make_key($name,$id); my $setvalue=$value; if (!defined($setvalue)) { @@ -1801,17 +2827,17 @@ sub do_cache_new { $memcache->disconnect_all(); } # need to make a copy of $value - &make_room($id,$value,$debug); + &make_room($remembered_id,$value,$debug); return $value; } sub make_room { - my ($id,$value,$debug)=@_; + my ($remembered_id,$value,$debug)=@_; - $remembered{$id}= (ref($value)) ? &Storable::dclone($value) + $remembered{$remembered_id}= (ref($value)) ? &Storable::dclone($value) : $value; if ($to_remember<0) { return; } - $accessed{$id}=[&gettimeofday()]; + $accessed{$remembered_id}=[&gettimeofday()]; if (scalar(keys(%remembered)) <= $to_remember) { return; } my $to_kick; my $max_time=0; @@ -1925,26 +2951,35 @@ sub chatsend { sub getversion { my $fname=&clutter(shift); - unless ($fname=~/^\/res\//) { return -1; } + unless ($fname=~m{^(/adm/wrapper|)/res/}) { return -1; } return ¤tversion(&filelocation('',$fname)); } sub currentversion { my $fname=shift; - my ($result,$cached)=&is_cached_new('resversion',$fname); - if (defined($cached)) { return $result; } my $author=$fname; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; my ($udom,$uname)=split(/\//,$author); - my $home=homeserver($uname,$udom); + my $home=&homeserver($uname,$udom); if ($home eq 'no_host') { return -1; } - my $answer=reply("currentversion:$fname",$home); + my $answer=&reply("currentversion:$fname",$home); if (($answer eq 'con_lost') || ($answer eq 'rejected')) { return -1; } - return &do_cache_new('resversion',$fname,$answer,600); + return $answer; +} + +# +# Return special version number of resource if set by override, empty otherwise +# +sub usedversion { + my $fname=shift; + unless ($fname) { $fname=$env{'request.uri'}; } + my ($urlversion)=($fname=~/\.(\d+)\.\w+$/); + if ($urlversion) { return $urlversion; } + return ''; } # ----------------------------- Subscribe to a resource, return URL if possible @@ -1972,10 +3007,11 @@ sub subscribe { sub repcopy { my $filename=shift; $filename=~s/\/+/\//g; - if ($filename=~m|^/home/httpd/html/adm/|) { return 'ok'; } - if ($filename=~m|^/home/httpd/html/lonUsers/|) { return 'ok'; } - if ($filename=~m|^/home/httpd/html/userfiles/| or - $filename=~m -^/*(uploaded|editupload)/-) { + my $londocroot = $perlvar{'lonDocRoot'}; + if ($filename=~m{^\Q$londocroot/adm/\E}) { return 'ok'; } + if ($filename=~m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; } + if ($filename=~m{^\Q$londocroot/userfiles/\E} or + $filename=~m{^/*(uploaded|editupload)/}) { return &repcopy_userfile($filename); } $filename=~s/[\n\r]//g; @@ -2002,7 +3038,7 @@ sub repcopy { unless ($home eq $perlvar{'lonHostID'}) { my @parts=split(/\//,$filename); my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]"; - if ($path ne "$perlvar{'lonDocRoot'}/res") { + if ($path ne "$londocroot/res") { &logthis("Malconfiguration for replication: $filename"); return 'bad_request'; } @@ -2013,9 +3049,13 @@ sub repcopy { mkdir($path,0777); } } - my $ua=new LWP::UserAgent; my $request=new HTTP::Request('GET',"$remoteurl"); - my $response=$ua->request($request,$transname); + my $response; + if ($remoteurl =~ m{/raw/}) { + $response=&LONCAPA::LWPReq::makerequest($home,$request,$transname,\%perlvar,'',0,1); + } else { + $response=&LONCAPA::LWPReq::makerequest($home,$request,$transname,\%perlvar,'',1); + } if ($response->is_error()) { unlink($transname); my $message=$response->status_line; @@ -2025,7 +3065,12 @@ sub repcopy { } else { if ($remoteurl!~/\.meta$/) { my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); - my $mresponse=$ua->request($mrequest,$filename.'.meta'); + my $mresponse; + if ($remoteurl =~ m{/raw/}) { + $mresponse = &LONCAPA::LWPReq::makerequest($home,$mrequest,$filename.'.meta',\%perlvar,'',0,1); + } else { + $mresponse = &LONCAPA::LWPReq::makerequest($home,$mrequest,$filename.'.meta',\%perlvar,'',1); + } if ($mresponse->is_error()) { unlink($filename.'.meta'); &logthis( @@ -2088,20 +3133,25 @@ sub absolute_url { sub ssi { my ($fn,%form)=@_; - my $ua=new LWP::UserAgent; my $request; $form{'no_update_last_known'}=1; &Apache::lonenc::check_encrypt(\$fn); if (%form) { $request=new HTTP::Request('POST',&absolute_url().$fn); - $request->content(join('&',map { &escape($_).'='.&escape($form{$_}) } keys(%form))); + $request->content(join('&',map { + my $name = escape($_); + "$name=" . ( ref($form{$_}) eq 'ARRAY' + ? join("&$name=", map {escape($_) } @{$form{$_}}) + : &escape($form{$_}) ); + } keys(%form))); } else { $request=new HTTP::Request('GET',&absolute_url().$fn); } $request->header(Cookie => $ENV{'HTTP_COOKIE'}); - my $response=$ua->request($request); + my $lonhost = $perlvar{'lonHostID'}; + my $response= &LONCAPA::LWPReq::makerequest($lonhost,$request,'',\%perlvar); if (wantarray) { return ($response->content, $response); @@ -2112,9 +3162,8 @@ sub ssi { sub externalssi { my ($url)=@_; - my $ua=new LWP::UserAgent; my $request=new HTTP::Request('GET',$url); - my $response=$ua->request($request); + my $response = &LONCAPA::LWPReq::makerequest('',$request,'',\%perlvar); if (wantarray) { return ($response->content, $response); } else { @@ -2122,6 +3171,71 @@ sub externalssi { } } + +# If the local copy of a replicated resource is outdated, trigger a +# connection from the homeserver to flush the delayed queue. If no update +# happens, remove local copies of outdated resource (and corresponding +# metadata file). + +sub remove_stale_resfile { + my ($url) = @_; + my $removed; + if ($url=~m{^/res/($match_domain)/($match_username)/}) { + my $audom = $1; + my $auname = $2; + unless (($url =~ /\.\d+\.\w+$/) || ($url =~ m{^/res/lib/templates/})) { + my $homeserver = &homeserver($auname,$audom); + unless (($homeserver eq 'no_host') || + (grep { $_ eq $homeserver } ¤t_machine_ids())) { + my $fname = &filelocation('',$url); + if (-e $fname) { + my $protocol = $protocol{$homeserver}; + $protocol = 'http' if ($protocol ne 'https'); + my $hostname = &hostname($homeserver); + if ($hostname) { + my $uri = &declutter($url); + my $request=new HTTP::Request('HEAD',$protocol.'://'.$hostname.'/raw/'.$uri); + my $response = &LONCAPA::LWPReq::makerequest($homeserver,$request,'',\%perlvar,5,0,1); + if ($response->is_success()) { + my $remmodtime = &HTTP::Date::str2time( $response->header('Last-modified') ); + my $locmodtime = (stat($fname))[9]; + if ($locmodtime < $remmodtime) { + my $stale; + my $answer = &reply('pong',$homeserver); + if ($answer eq $homeserver.':'.$perlvar{'lonHostID'}) { + sleep(0.2); + $locmodtime = (stat($fname))[9]; + if ($locmodtime < $remmodtime) { + my $posstransfer = $fname.'.in.transfer'; + if ((-e $posstransfer) && ($remmodtime < (stat($posstransfer))[9])) { + $removed = 1; + } else { + $stale = 1; + } + } else { + $removed = 1; + } + } else { + $stale = 1; + } + if ($stale) { + unlink($fname); + if ($uri!~/\.meta$/) { + unlink($fname.'.meta'); + } + &reply("unsub:$fname",$homeserver); + $removed = 1; + } + } + } + } + } + } + } + } + return $removed; +} + # -------------------------------- Allow a /uploaded/ URI to be vouched for sub allowuploaded { @@ -2135,11 +3249,292 @@ sub allowuploaded { &Apache::lonnet::appenv(\%httpref); } +# +# Determine if the current user should be able to edit a particular resource, +# when viewing in course context. +# (a) When viewing resource used to determine if "Edit" item is included in +# Functions. +# (b) When displaying folder contents in course editor, used to determine if +# "Edit" link will be displayed alongside resource. +# +# input: six args -- filename (decluttered), course number, course domain, +# url, symb (if registered) and group (if this is a group +# item -- e.g., bulletin board, group page etc.). +# output: array of five scalars -- +# $cfile -- url for file editing if editable on current server +# $home -- homeserver of resource (i.e., for author if published, +# or course if uploaded.). +# $switchserver -- 1 if server switch will be needed. +# $forceedit -- 1 if icon/link should be to go to edit mode +# $forceview -- 1 if icon/link should be to go to view mode +# + +sub can_edit_resource { + my ($file,$cnum,$cdom,$resurl,$symb,$group) = @_; + my ($cfile,$home,$switchserver,$forceedit,$forceview,$uploaded,$incourse); +# +# For aboutme pages user can only edit his/her own. +# + if ($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) { + my ($sdom,$sname) = ($1,$2); + if (($sdom eq $env{'user.domain'}) && ($sname eq $env{'user.name'})) { + $home = $env{'user.home'}; + $cfile = $resurl; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + return ($cfile,$home,$switchserver,$forceedit,$forceview); + } else { + return; + } + } + + if ($env{'request.course.id'}) { + my $crsedit = &Apache::lonnet::allowed('mdc',$env{'request.course.id'}); + if ($group ne '') { +# if this is a group homepage or group bulletin board, check group privs + my $allowed = 0; + if ($resurl =~ m{^/?adm/$cdom/$cnum/$group/smppg$}) { + if ((&allowed('mdg',$env{'request.course.id'}. + ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) || + (&allowed('mgh',$env{'request.course.id'}.'/'.$group)) || $crsedit) { + $allowed = 1; + } + } elsif ($resurl =~ m{^/?adm/$cdom/$cnum/\d+/bulletinboard$}) { + if ((&allowed('mdg',$env{'request.course.id'}.($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) || + (&allowed('cgb',$env{'request.course.id'}.'/'.$group)) || $crsedit) { + $allowed = 1; + } + } + if ($allowed) { + $home=&homeserver($cnum,$cdom); + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } else { + return; + } + } else { + if ($resurl =~ m{^/?adm/viewclasslist$}) { + unless (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) { + return; + } + } elsif (!$crsedit) { +# +# No edit allowed where CC has switched to student role. +# + return; + } + } + } + + if ($file ne '') { + if (($cnum =~ /$match_courseid/) && ($cdom =~ /$match_domain/)) { + if (&is_course_upload($file,$cnum,$cdom)) { + $uploaded = 1; + $incourse = 1; + if ($file =~/\.(htm|html|css|js|txt)$/) { + $cfile = &hreflocation('',$file); + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + } + } elsif ($resurl =~ m{^/public/$cdom/$cnum/syllabus}) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif (($resurl ne '') && (&is_on_map($resurl))) { + if ($resurl =~ m{^/adm/$match_domain/$match_username/\d+/smppg|bulletinboard$}) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif ($resurl eq '/res/lib/templates/simpleproblem.problem') { + $incourse = 1; + $cfile = $resurl.'/smpedit'; + } elsif ($resurl =~ m{^/adm/wrapper/ext/}) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif ($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif ($resurl =~ m{^/?adm/viewclasslist$}) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = ($resurl =~ m{^/} ? $resurl : "/$resurl"); + } + } elsif ($resurl eq '/res/lib/templates/simpleproblem.problem/smpedit') { + my $template = '/res/lib/templates/simpleproblem.problem'; + if (&is_on_map($template)) { + $incourse = 1; + $forceview = 1; + $cfile = $template; + } + } elsif (($resurl =~ m{^/adm/wrapper/ext/}) && ($env{'form.folderpath'} =~ /^supplemental/)) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif (($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) && ($env{'form.folderpath'} =~ /^supplemental/)) { + $incourse = 1; + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = $resurl; + } elsif (($resurl eq '/adm/extresedit') && ($symb || $env{'form.folderpath'})) { + $incourse = 1; + $forceview = 1; + if ($symb) { + my ($map,$id,$res)=&decode_symb($symb); + $env{'request.symb'} = $symb; + $cfile = &clutter($res); + } else { + $cfile = $env{'form.suppurl'}; + my $escfile = &unescape($cfile); + if ($escfile =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) { + $cfile = '/adm/wrapper'.$escfile; + } else { + $escfile =~ s{^http://}{}; + $cfile = &escape("/adm/wrapper/ext/$escfile"); + } + } + } elsif ($resurl =~ m{^/?adm/viewclasslist$}) { + if ($env{'form.forceedit'}) { + $forceview = 1; + } else { + $forceedit = 1; + } + $cfile = ($resurl =~ m{^/} ? $resurl : "/$resurl"); + } + } + if ($uploaded || $incourse) { + $home=&homeserver($cnum,$cdom); + } elsif ($file !~ m{/$}) { + $file=~s{^(priv/$match_domain/$match_username)}{/$1}; + $file=~s{^($match_domain/$match_username)}{/priv/$1}; + # Check that the user has permission to edit this resource + my $setpriv = 1; + my ($cfuname,$cfudom)=&constructaccess($file,$setpriv); + if (defined($cfudom)) { + $home=&homeserver($cfuname,$cfudom); + $cfile=$file; + } + } + if (($cfile ne '') && (!$incourse || $uploaded) && + (($home ne '') && ($home ne 'no_host'))) { + my @ids=¤t_machine_ids(); + unless (grep(/^\Q$home\E$/,@ids)) { + $switchserver=1; + } + } + } + return ($cfile,$home,$switchserver,$forceedit,$forceview); +} + +sub is_course_upload { + my ($file,$cnum,$cdom) = @_; + my $uploadpath = &LONCAPA::propath($cdom,$cnum); + $uploadpath =~ s{^\/}{}; + if (($file =~ m{^\Q$uploadpath\E/userfiles/(docs|supplemental)/}) || + ($file =~ m{^userfiles/\Q$cdom\E/\Q$cnum\E/(docs|supplemental)/})) { + return 1; + } + return; +} + +sub in_course { + my ($udom,$uname,$cdom,$cnum,$type,$hideprivileged) = @_; + if ($hideprivileged) { + my $skipuser; + my %coursehash = &coursedescription($cdom.'_'.$cnum); + my @possdoms = ($cdom); + if ($coursehash{'checkforpriv'}) { + push(@possdoms,split(/,/,$coursehash{'checkforpriv'})); + } + if (&privileged($uname,$udom,\@possdoms)) { + $skipuser = 1; + if ($coursehash{'nothideprivileged'}) { + foreach my $item (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) { + my $user; + if ($item =~ /:/) { + $user = $item; + } else { + $user = join(':',split(/[\@]/,$item)); + } + if ($user eq $uname.':'.$udom) { + undef($skipuser); + last; + } + } + } + if ($skipuser) { + return 0; + } + } + } + $type ||= 'any'; + if (!defined($cdom) || !defined($cnum)) { + my $cid = $env{'request.course.id'}; + $cdom = $env{'course.'.$cid.'.domain'}; + $cnum = $env{'course.'.$cid.'.num'}; + } + my $typesref; + if (($type eq 'any') || ($type eq 'all')) { + $typesref = ['active','previous','future']; + } elsif ($type eq 'previous' || $type eq 'future') { + $typesref = [$type]; + } + my %roles = &get_my_roles($uname,$udom,'userroles', + $typesref,undef,[$cdom]); + my ($tmp) = keys(%roles); + return 0 if ($tmp =~ /^(con_lost|error|no_such_host)/i); + my @course_roles = grep(/^\Q$cnum\E:\Q$cdom\E:/, keys(%roles)); + if (@course_roles > 0) { + return 1; + } + return 0; +} + # --------- File operations in /home/httpd/html/userfiles/$domain/1/2/3/$course # input: action, courseID, current domain, intended # path to file, source of file, instruction to parse file for objects, # ref to hash for embedded objects, # ref to hash for codebase of java objects. +# reference to scalar to accommodate mime type determined +# from File::MMagic if $parser = parse. # # output: url to file (if action was uploaddoc), # ok if successful, or diagnostic message otherwise (if action was propagate or copy) @@ -2166,7 +3561,8 @@ sub allowuploaded { # sub process_coursefile { - my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase)=@_; + my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase, + $mimetype)=@_; my $fetchresult; my $home=&homeserver($docuname,$docudom); if ($action eq 'propagate') { @@ -2194,13 +3590,16 @@ sub process_coursefile { close($fh); if ($parser eq 'parse') { my $mm = new File::MMagic; - my $mime_type = $mm->checktype_filename($filepath.'/'.$fname); - if ($mime_type eq 'text/html') { + my $type = $mm->checktype_filename($filepath.'/'.$fname); + if ($type eq 'text/html') { my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase); unless ($parse_result eq 'ok') { &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result); } } + if (ref($mimetype)) { + $$mimetype = $type; + } } $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file, $home); @@ -2316,9 +3715,13 @@ sub resizeImage { # --------------- Take an uploaded file and put it into the userfiles directory # input: $formname - the contents of the file are in $env{"form.$formname"} -# the desired filenam is in $env{"form.$formname.filename"} -# $coursedoc - if true up to the current course -# if false +# the desired filename is in $env{"form.$formname.filename"} +# $context - possible values: coursedoc, existingfile, overwrite, +# canceloverwrite, or ''. +# if 'coursedoc': upload to the current course +# if 'existingfile': write file to tmp/overwrites directory +# if 'canceloverwrite': delete file written to tmp/overwrites directory +# $context is passed as argument to &finishuserfileupload # $subdir - directory in userfile to store the file into # $parser - instruction to parse file for objects ($parser = parse) # $allfiles - reference to hash for embedded objects @@ -2329,37 +3732,60 @@ sub resizeImage { # $thumbheight - height (pixels) of thumbnail to make for uploaded image # $resizewidth - width (pixels) to which to resize uploaded image # $resizeheight - height (pixels) to which to resize uploaded image +# $mimetype - reference to scalar to accommodate mime type determined +# from File::MMagic. # # output: url of file in userspace, or error: # or /adm/notfound.html if failure to upload occurse sub userfileupload { - my ($formname,$coursedoc,$subdir,$parser,$allfiles,$codebase,$destuname, - $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight)=@_; + my ($formname,$context,$subdir,$parser,$allfiles,$codebase,$destuname, + $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight,$mimetype)=@_; if (!defined($subdir)) { $subdir='unknown'; } my $fname=$env{'form.'.$formname.'.filename'}; $fname=&clean_filename($fname); -# See if there is anything left + # See if there is anything left unless ($fname) { return 'error: no uploaded file'; } - chop($env{'form.'.$formname}); - if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) { #files uploaded to help request form are handled differently + # Files uploaded to help request form, or uploaded to "create course" page are handled differently + if ((($formname eq 'screenshot') && ($subdir eq 'helprequests')) || + (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) || + ($context eq 'existingfile') || ($context eq 'canceloverwrite')) { my $now = time; - my $filepath = 'tmp/helprequests/'.$now; - my @parts=split(/\//,$filepath); - my $fullpath = $perlvar{'lonDaemons'}; - for (my $i=0;$i<@parts;$i++) { - $fullpath .= '/'.$parts[$i]; - if ((-e $fullpath)!=1) { - mkdir($fullpath,0777); + my $filepath; + if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) { + $filepath = 'tmp/helprequests/'.$now; + } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) { + $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}. + '_'.$env{'user.domain'}.'/pending'; + } elsif (($context eq 'existingfile') || ($context eq 'canceloverwrite')) { + my ($docuname,$docudom); + if ($destudom =~ /^$match_domain$/) { + $docudom = $destudom; + } else { + $docudom = $env{'user.domain'}; + } + if ($destuname =~ /^$match_username$/) { + $docuname = $destuname; + } else { + $docuname = $env{'user.name'}; + } + if (exists($env{'form.group'})) { + $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; + $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; + } + $filepath = 'tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$subdir; + if ($context eq 'canceloverwrite') { + my $tempfile = $perlvar{'lonDaemons'}.'/'.$filepath.'/'.$fname; + if (-e $tempfile) { + my @info = stat($tempfile); + if ($info[9] eq $env{'form.timestamp'}) { + unlink($tempfile); + } + } + return; } } - open(my $fh,'>'.$fullpath.'/'.$fname); - print $fh $env{'form.'.$formname}; - close($fh); - return $fullpath.'/'.$fname; - } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) { #files uploaded to create course page are handled differently - my $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}. - '_'.$env{'user.domain'}.'/pending'; + # Create the directory if not present my @parts=split(/\//,$filepath); my $fullpath = $perlvar{'lonDaemons'}; for (my $i=0;$i<@parts;$i++) { @@ -2371,27 +3797,33 @@ sub userfileupload { open(my $fh,'>'.$fullpath.'/'.$fname); print $fh $env{'form.'.$formname}; close($fh); - return $fullpath.'/'.$fname; + if ($context eq 'existingfile') { + my @info = stat($fullpath.'/'.$fname); + return ($fullpath.'/'.$fname,$info[9]); + } else { + return $fullpath.'/'.$fname; + } } if ($subdir eq 'scantron') { $fname = 'scantron_orig_'.$fname; - } else { -# Create the directory if not present + } else { $fname="$subdir/$fname"; } - if ($coursedoc) { + if ($context eq 'coursedoc') { my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; if ($env{'form.folder'} =~ m/^(default|supplemental)/) { return &finishuserfileupload($docuname,$docudom, $formname,$fname,$parser,$allfiles, $codebase,$thumbwidth,$thumbheight, - $resizewidth,$resizeheight); + $resizewidth,$resizeheight,$context,$mimetype); } else { - $fname=$env{'form.folder'}.'/'.$fname; + if ($env{'form.folder'}) { + $fname=$env{'form.folder'}.'/'.$fname; + } return &process_coursefile('uploaddoc',$docuname,$docudom, $fname,$formname,$parser, - $allfiles,$codebase); + $allfiles,$codebase,$mimetype); } } elsif (defined($destuname)) { my $docuname=$destuname; @@ -2399,25 +3831,24 @@ sub userfileupload { return &finishuserfileupload($docuname,$docudom,$formname,$fname, $parser,$allfiles,$codebase, $thumbwidth,$thumbheight, - $resizewidth,$resizeheight); - + $resizewidth,$resizeheight,$context,$mimetype); } else { my $docuname=$env{'user.name'}; my $docudom=$env{'user.domain'}; - if (exists($env{'form.group'})) { + if ((exists($env{'form.group'})) || ($context eq 'syllabus')) { $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; } return &finishuserfileupload($docuname,$docudom,$formname,$fname, $parser,$allfiles,$codebase, $thumbwidth,$thumbheight, - $resizewidth,$resizeheight); + $resizewidth,$resizeheight,$context,$mimetype); } } sub finishuserfileupload { my ($docuname,$docudom,$formname,$fname,$parser,$allfiles,$codebase, - $thumbwidth,$thumbheight,$resizewidth,$resizeheight) = @_; + $thumbwidth,$thumbheight,$resizewidth,$resizeheight,$context,$mimetype) = @_; my $path=$docudom.'/'.$docuname.'/'; my $filepath=$perlvar{'lonDocRoot'}; @@ -2443,7 +3874,23 @@ sub finishuserfileupload { print STDERR ('Failed to create '.$filepath.'/'.$file."\n"); return '/adm/notfound.html'; } - if (!print FH ($env{'form.'.$formname})) { + if ($context eq 'overwrite') { + my $source = LONCAPA::tempdir().'/overwrites/'.$docudom.'/'.$docuname.'/'.$fname; + my $target = $filepath.'/'.$file; + if (-e $source) { + my @info = stat($source); + if ($info[9] eq $env{'form.timestamp'}) { + unless (&File::Copy::move($source,$target)) { + &logthis('Failed to overwrite '.$filepath.'/'.$file); + return "Moving from $source failed"; + } + } else { + return "Temporary file: $source had unexpected date/time for last modification"; + } + } else { + return "Temporary file: $source missing"; + } + } elsif (!print FH ($env{'form.'.$formname})) { &logthis('Failed to write to '.$filepath.'/'.$file); print STDERR ('Failed to write to '.$filepath.'/'.$file."\n"); return '/adm/notfound.html'; @@ -2457,10 +3904,17 @@ sub finishuserfileupload { } } } + if (($context eq 'coursedoc') || ($parser eq 'parse')) { + if (ref($mimetype)) { + if ($$mimetype eq '') { + my $mm = new File::MMagic; + my $type = $mm->checktype_filename($filepath.'/'.$file); + $$mimetype = $type; + } + } + } if ($parser eq 'parse') { - my $mm = new File::MMagic; - my $mime_type = $mm->checktype_filename($filepath.'/'.$file); - if ($mime_type eq 'text/html') { + if ((ref($mimetype)) && ($$mimetype eq 'text/html')) { my $parse_result = &extract_embedded_items($filepath.'/'.$file, $allfiles,$codebase); unless ($parse_result eq 'ok') { @@ -2504,6 +3958,7 @@ sub finishuserfileupload { sub extract_embedded_items { my ($fullpath,$allfiles,$codebase,$content) = @_; my @state = (); + my (%lastids,%related,%shockwave,%flashvars); my %javafiles = ( codebase => '', code => '', @@ -2530,13 +3985,35 @@ sub extract_embedded_items { &add_filetype($allfiles,$attr->{'src'},'src'); } if (lc($tagname) eq 'a') { - &add_filetype($allfiles,$attr->{'href'},'href'); + unless (($attr->{'href'} =~ /^#/) || ($attr->{'href'} eq '')) { + &add_filetype($allfiles,$attr->{'href'},'href'); + } } if (lc($tagname) eq 'script') { + my $src; if ($attr->{'archive'} =~ /\.jar$/i) { &add_filetype($allfiles,$attr->{'archive'},'archive'); } else { - &add_filetype($allfiles,$attr->{'src'},'src'); + if ($attr->{'src'} ne '') { + $src = $attr->{'src'}; + &add_filetype($allfiles,$src,'src'); + } + } + my $text = $p->get_trimmed_text(); + if ($text =~ /\Qswfobject.registerObject(\E([^\)]+)\)/) { + my @swfargs = split(/,/,$1); + foreach my $item (@swfargs) { + $item =~ s/["']//g; + $item =~ s/^\s+//; + $item =~ s/\s+$//; + } + if (($swfargs[0] ne'') && ($swfargs[2] ne '')) { + if (ref($related{$swfargs[0]}) eq 'ARRAY') { + push(@{$related{$swfargs[0]}},$swfargs[2]); + } else { + $related{$swfargs[0]} = [$swfargs[2]]; + } + } } } if (lc($tagname) eq 'link') { @@ -2549,6 +4026,9 @@ sub extract_embedded_items { foreach my $item (keys(%javafiles)) { $javafiles{$item} = ''; } + if ((lc($tagname) eq 'object') && (lc($state[-2]) ne 'object')) { + $lastids{lc($tagname)} = $attr->{'id'}; + } } if (lc($state[-2]) eq 'object' && lc($tagname) eq 'param') { my $name = lc($attr->{'name'}); @@ -2558,12 +4038,22 @@ sub extract_embedded_items { last; } } + my $pathfrom; foreach my $item (keys(%mediafiles)) { if ($name eq $item) { - &add_filetype($allfiles, $attr->{'value'}, 'value'); + $pathfrom = $attr->{'value'}; + $shockwave{$lastids{lc($state[-2])}} = $pathfrom; + &add_filetype($allfiles,$pathfrom,$name); last; } } + if ($name eq 'flashvars') { + $flashvars{$lastids{lc($state[-2])}} = $attr->{'value'}; + } + if ($pathfrom ne '') { + &embedded_dependency($allfiles,\%related,$lastids{lc($state[-2])}, + $pathfrom); + } } if (lc($tagname) eq 'embed' || lc($tagname) eq 'applet') { foreach my $item (keys(%javafiles)) { @@ -2578,7 +4068,34 @@ sub extract_embedded_items { last; } } + if (lc($tagname) eq 'embed') { + if (($attr->{'name'} ne '') && ($attr->{'src'} ne '')) { + &embedded_dependency($allfiles,\%related,$attr->{'name'}, + $attr->{'src'}); + } + } } + if (lc($tagname) eq 'iframe') { + my $src = $attr->{'src'} ; + if (($src ne '') && ($src !~ m{^(/|https?://)})) { + &add_filetype($allfiles,$src,'src'); + } elsif ($src =~ m{^/}) { + if ($env{'request.course.id'}) { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $url = &hreflocation('',$fullpath); + if ($url =~ m{^/uploaded/$cdom/$cnum/docs/(\w+/\d+)/}) { + my $relpath = $1; + if ($src =~ m{^/uploaded/$cdom/$cnum/docs/\Q$relpath\E/(.+)$}) { + &add_filetype($allfiles,$1,'src'); + } + } + } + } + } + if ($t->[4] =~ m{/>$}) { + pop(@state); + } } elsif ($t->[0] eq 'E') { my ($tagname) = ($t->[1]); if ($javafiles{'codebase'} ne '') { @@ -2598,6 +4115,23 @@ sub extract_embedded_items { pop @state; } } + foreach my $id (sort(keys(%flashvars))) { + if ($shockwave{$id} ne '') { + my @pairs = split(/\&/,$flashvars{$id}); + foreach my $pair (@pairs) { + my ($key,$value) = split(/\=/,$pair); + if ($key eq 'thumb') { + &add_filetype($allfiles,$value,$key); + } elsif ($key eq 'content') { + my ($path) = ($shockwave{$id} =~ m{^(.+/)[^/]+$}); + my ($ext) = ($value =~ /\.([^.]+)$/); + if ($ext ne '') { + &add_filetype($allfiles,$path.$value,$ext); + } + } + } + } + } return 'ok'; } @@ -2612,6 +4146,21 @@ sub add_filetype { } } +sub embedded_dependency { + my ($allfiles,$related,$identifier,$pathfrom) = @_; + if ((ref($allfiles) eq 'HASH') && (ref($related) eq 'HASH')) { + if (($identifier ne '') && + (ref($related->{$identifier}) eq 'ARRAY') && + ($pathfrom ne '')) { + my ($path) = ($pathfrom =~ m{^(.+/)[^/]+$}); + foreach my $dep (@{$related->{$identifier}}) { + &add_filetype($allfiles,$path.$dep,'object'); + } + } + } + return; +} + sub removeuploadedurl { my ($url)=@_; my (undef,undef,$udom,$uname,$fname)=split('/',$url,5); @@ -2738,15 +4287,10 @@ sub flushcourselogs { my $result = &inc('nohist_accesscount',\%temphash,$dom,$name); if ($result eq 'ok') { delete $accesshash{$entry}; - } elsif ($result eq 'unknown_cmd') { - # Target server has old code running on it. - my %temphash=($entry => $value); - if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') { - delete $accesshash{$entry}; - } } } else { my ($dom,$name) = ($entry=~m{___($match_domain)/($match_name)/(.*)___(\w+)$}); + if (($dom eq 'uploaded') || ($dom eq 'adm')) { next; } my %temphash=($entry => $accesshash{$entry}); if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') { delete $accesshash{$entry}; @@ -2767,7 +4311,7 @@ sub flushcourselogs { } } # -# Reverse lookup of domain roles (dc, ad, li, sc, au) +# Reverse lookup of domain roles (dc, ad, li, sc, dh, da, au) # my %domrolebuffer = (); foreach my $entry (keys(%domainrolehash)) { @@ -2782,10 +4326,19 @@ sub flushcourselogs { delete $domainrolehash{$entry}; } foreach my $dom (keys(%domrolebuffer)) { - my %servers = &get_servers($dom,'library'); + my %servers; + if (defined(&domain($dom,'primary'))) { + my $primary=&domain($dom,'primary'); + my $hostname=&hostname($primary); + $servers{$primary} = $hostname; + } else { + %servers = &get_servers($dom,'library'); + } foreach my $tryserver (keys(%servers)) { - unless (&reply('domroleput:'.$dom.':'. - $domrolebuffer{$dom},$tryserver) eq 'ok') { + if (&reply('domroleput:'.$dom.':'. + $domrolebuffer{$dom},$tryserver) eq 'ok') { + last; + } else { &logthis('Put of domain roles failed for '.$dom.' and '.$tryserver); } } @@ -2825,7 +4378,7 @@ sub courseacclog { my $fnsymb=shift; unless ($env{'request.course.id'}) { return ''; } my $what=$fnsymb.':'.$env{'user.name'}.':'.$env{'user.domain'}; - if ($fnsymb=~/(problem|exam|quiz|assess|survey|form|task|page)$/) { + if ($fnsymb=~/$LONCAPA::assess_re/) { $what.=':POST'; # FIXME: Probably ought to escape things.... foreach my $key (keys(%env)) { @@ -2857,7 +4410,13 @@ sub countacc { my $url=&declutter(shift); return if (! defined($url) || $url eq ''); unless ($env{'request.course.id'}) { return ''; } +# +# Mark that this url was used in this course +# $accesshash{$env{'request.course.id'}.'___'.$url.'___course'}=1; +# +# Increase the access count for this resource in this child process +# my $key=$$.$processmarker.'_'.$dumpcount.'___'.$url.'___count'; $accesshash{$key}++; } @@ -2869,31 +4428,37 @@ sub linklog { $accesshash{$from.'___'.$to.'___comefrom'}=1; $accesshash{$to.'___'.$from.'___goto'}=1; } + +sub statslog { + my ($symb,$part,$users,$av_attempts,$degdiff)=@_; + if ($users<2) { return; } + my %dynstore=&LONCAPA::lonmetadata::dynamic_metadata_storage({ + 'course' => $env{'request.course.id'}, + 'sections' => '"all"', + 'num_students' => $users, + 'part' => $part, + 'symb' => $symb, + 'mean_tries' => $av_attempts, + 'deg_of_diff' => $degdiff}); + foreach my $key (keys(%dynstore)) { + $accesshash{$key}=$dynstore{$key}; + } +} sub userrolelog { my ($trole,$username,$domain,$area,$tstart,$tend)=@_; - if (($trole=~/^ca/) || ($trole=~/^aa/) || - ($trole=~/^in/) || ($trole=~/^cc/) || - ($trole=~/^ep/) || ($trole=~/^cr/) || - ($trole=~/^ta/) || ($trole=~/^co/)) { + if ( $trole =~ /^(ca|aa|in|cc|ep|cr|ta|co)/ ) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $userrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} =$tend.':'.$tstart; } - if (($env{'request.role'} =~ /dc\./) && - (($trole=~/^au/) || ($trole=~/^in/) || - ($trole=~/^cc/) || ($trole=~/^ep/) || - ($trole=~/^cr/) || ($trole=~/^ta/) || - ($trole=~/^co/))) { + if ($env{'request.role'} =~ /dc\./ && $trole =~ /^(au|in|cc|ep|cr|ta|co)/) { $userrolehash {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'} =$tend.':'.$tstart; } - if (($trole=~/^dc/) || ($trole=~/^ad/) || - ($trole=~/^li/) || ($trole=~/^li/) || - ($trole=~/^au/) || ($trole=~/^dg/) || - ($trole=~/^sc/)) { + if ($trole =~ /^(dc|ad|li|au|dg|sc|dh|da)/ ) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $domainrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} @@ -2903,38 +4468,70 @@ sub userrolelog { sub courserolelog { my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$selfenroll,$context)=@_; - if (($trole eq 'cc') || ($trole eq 'in') || - ($trole eq 'ep') || ($trole eq 'ad') || - ($trole eq 'ta') || ($trole eq 'st') || - ($trole=~/^cr/) || ($trole eq 'gr') || - ($trole eq 'co')) { - if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) { - my $cdom = $1; - my $cnum = $2; - my $sec = $3; - my $namespace = 'rolelog'; - my %storehash = ( - role => $trole, - start => $tstart, - end => $tend, - selfenroll => $selfenroll, - context => $context, - ); - if ($trole eq 'gr') { - $namespace = 'groupslog'; - $storehash{'group'} = $sec; - } else { - $storehash{'section'} = $sec; - } - &instructor_log($namespace,\%storehash,$delflag,$username,$domain,$cnum,$cdom); - if (($trole ne 'st') || ($sec ne '')) { - &devalidate_cache_new('getcourseroles',$cdom.'_'.$cnum); - } + if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) { + my $cdom = $1; + my $cnum = $2; + my $sec = $3; + my $namespace = 'rolelog'; + my %storehash = ( + role => $trole, + start => $tstart, + end => $tend, + selfenroll => $selfenroll, + context => $context, + ); + if ($trole eq 'gr') { + $namespace = 'groupslog'; + $storehash{'group'} = $sec; + } else { + $storehash{'section'} = $sec; + } + &write_log('course',$namespace,\%storehash,$delflag,$username, + $domain,$cnum,$cdom); + if (($trole ne 'st') || ($sec ne '')) { + &devalidate_cache_new('getcourseroles',$cdom.'_'.$cnum); } } return; } +sub domainrolelog { + my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$context)=@_; + if ($area =~ m{^/($match_domain)/$}) { + my $cdom = $1; + my $domconfiguser = &Apache::lonnet::get_domainconfiguser($cdom); + my $namespace = 'rolelog'; + my %storehash = ( + role => $trole, + start => $tstart, + end => $tend, + context => $context, + ); + &write_log('domain',$namespace,\%storehash,$delflag,$username, + $domain,$domconfiguser,$cdom); + } + return; + +} + +sub coauthorrolelog { + my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$context)=@_; + if ($area =~ m{^/($match_domain)/($match_username)$}) { + my $audom = $1; + my $auname = $2; + my $namespace = 'rolelog'; + my %storehash = ( + role => $trole, + start => $tstart, + end => $tend, + context => $context, + ); + &write_log('author',$namespace,\%storehash,$delflag,$username, + $domain,$auname,$audom); + } + return; +} + sub get_course_adv_roles { my ($cid,$codes) = @_; $cid=$env{'request.course.id'} unless (defined($cid)); @@ -2948,6 +4545,10 @@ sub get_course_adv_roles { $nothide{$user}=1; } } + my @possdoms = ($coursehash{'domain'}); + if ($coursehash{'checkforpriv'}) { + push(@possdoms,split(/,/,$coursehash{'checkforpriv'})); + } my %returnhash=(); my %dumphash= &dump('nohist_userroles',$coursehash{'domain'},$coursehash{'num'}); @@ -2960,20 +4561,7 @@ sub get_course_adv_roles { if (($tstart) && ($now<$tstart)) { next; } my ($role,$username,$domain,$section)=split(/\:/,$entry); if ($username eq '' || $domain eq '') { next; } - unless (ref($privileged{$domain}) eq 'HASH') { - my %dompersonnel = - &Apache::lonnet::get_domain_roles($domain,['dc'],$now,$now); - $privileged{$domain} = {}; - foreach my $server (keys(%dompersonnel)) { - if (ref($dompersonnel{$server}) eq 'HASH') { - foreach my $user (keys(%{$dompersonnel{$server}})) { - my ($trole,$uname,$udom) = split(/:/,$user); - $privileged{$udom}{$uname} = 1; - } - } - } - } - if ((exists($privileged{$domain}{$username})) && + if ((&privileged($username,$domain,\@possdoms)) && (!$nothide{$username.':'.$domain})) { next; } if ($role eq 'cr') { next; } if ($codes) { @@ -3001,11 +4589,10 @@ sub get_my_roles { unless (defined($uname)) { $uname=$env{'user.name'}; } unless (defined($udom)) { $udom=$env{'user.domain'}; } my (%dumphash,%nothide); - if ($context eq 'userroles') { + if ($context eq 'userroles') { %dumphash = &dump('roles',$udom,$uname); } else { - %dumphash= - &dump('nohist_userroles',$udom,$uname); + %dumphash = &dump('nohist_userroles',$udom,$uname); if ($hidepriv) { my %coursehash=&coursedescription($udom.'_'.$uname); foreach my $user (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) { @@ -3023,6 +4610,7 @@ sub get_my_roles { foreach my $entry (keys(%dumphash)) { my ($role,$tend,$tstart); if ($context eq 'userroles') { + next if ($entry =~ /^rolesdef/); ($role,$tend,$tstart)=split(/_/,$dumphash{$entry}); } else { ($tend,$tstart)=split(/\:/,$dumphash{$entry}); @@ -3046,7 +4634,7 @@ sub get_my_roles { } my ($rolecode,$username,$domain,$section,$area); if ($context eq 'userroles') { - ($area,$rolecode) = split(/_/,$entry); + ($area,$rolecode) = ($entry =~ /^(.+)_([^_]+)$/); (undef,$domain,$username,$section) = split(/\//,$area); } else { ($role,$username,$domain,$section) = split(/\:/,$entry); @@ -3062,34 +4650,25 @@ sub get_my_roles { if (!grep(/^cr$/,@{$roles})) { next; } + } elsif ($role =~ /^gr\//) { + if (!grep(/^gr$/,@{$roles})) { + next; + } } else { next; } } } if ($hidepriv) { + my @privroles = ('dc','su'); if ($context eq 'userroles') { - if ((&privileged($username,$domain)) && - (!$nothide{$username.':'.$domain})) { - next; - } + next if (grep(/^\Q$role\E$/,@privroles)); } else { - unless (ref($privileged{$domain}) eq 'HASH') { - my %dompersonnel = - &Apache::lonnet::get_domain_roles($domain,['dc'],$now,$now); - $privileged{$domain} = {}; - if (keys(%dompersonnel)) { - foreach my $server (keys(%dompersonnel)) { - if (ref($dompersonnel{$server}) eq 'HASH') { - foreach my $user (keys(%{$dompersonnel{$server}})) { - my ($trole,$uname,$udom) = split(/:/,$user); - $privileged{$udom}{$uname} = $trole; - } - } - } - } + my $possdoms = [$domain]; + if (ref($roledoms) eq 'ARRAY') { + push(@{$possdoms},@{$roledoms}); } - if (exists($privileged{$domain}{$username})) { + if (&privileged($username,$domain,$possdoms,\@privroles)) { if (!$nothide{$username.':'.$domain}) { next; } @@ -3106,6 +4685,195 @@ sub get_my_roles { return %returnhash; } +sub get_all_adhocroles { + my ($dom) = @_; + my @roles_by_num = (); + my %domdefaults = &get_domain_defaults($dom); + my (%description,%access_in_dom,%access_info); + if (ref($domdefaults{'adhocroles'}) eq 'HASH') { + my $count = 0; + my %domcurrent = %{$domdefaults{'adhocroles'}}; + my %ordered; + foreach my $role (sort(keys(%domcurrent))) { + my ($order,$desc,$access_in_dom); + if (ref($domcurrent{$role}) eq 'HASH') { + $order = $domcurrent{$role}{'order'}; + $desc = $domcurrent{$role}{'desc'}; + $access_in_dom{$role} = $domcurrent{$role}{'access'}; + $access_info{$role} = $domcurrent{$role}{$access_in_dom{$role}}; + } + if ($order eq '') { + $order = $count; + } + $ordered{$order} = $role; + if ($desc ne '') { + $description{$role} = $desc; + } else { + $description{$role}= $role; + } + $count++; + } + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@roles_by_num,$ordered{$item}); + } + } + return (\@roles_by_num,\%description,\%access_in_dom,\%access_info); +} + +sub get_my_adhocroles { + my ($cid,$checkreg) = @_; + my ($cdom,$cnum,%info,@possroles,$description,$roles_by_num); + if ($env{'request.course.id'} eq $cid) { + $cdom = $env{'course.'.$cid.'.domain'}; + $cnum = $env{'course.'.$cid.'.num'}; + $info{'internal.coursecode'} = $env{'course.'.$cid.'.internal.coursecode'}; + } elsif ($cid =~ /^($match_domain)_($match_courseid)$/) { + $cdom = $1; + $cnum = $2; + %info = &Apache::lonnet::get('environment',['internal.coursecode'], + $cdom,$cnum); + } + if (($info{'internal.coursecode'} ne '') && ($checkreg)) { + my $user = $env{'user.name'}.':'.$env{'user.domain'}; + my %rosterhash = &get('classlist',[$user],$cdom,$cnum); + if ($rosterhash{$user} ne '') { + my $type = (split(/:/,$rosterhash{$user}))[5]; + return ([],{}) if ($type eq 'auto'); + } + } + if (($cdom ne '') && ($cnum ne '')) { + if (($env{"user.role.dh./$cdom/"}) || ($env{"user.role.da./$cdom/"})) { + my $then=$env{'user.login.time'}; + my $update=$env{'user.update.time'}; + if (!$update) { + $update = $then; + } + my @liveroles; + foreach my $role ('dh','da') { + if ($env{"user.role.$role./$cdom/"}) { + my ($tstart,$tend)=split(/\./,$env{"user.role.$role./$cdom/"}); + my $limit = $update; + if ($env{'request.role'} eq "$role./$cdom/") { + $limit = $then; + } + my $activerole = 1; + if ($tstart && $tstart>$limit) { $activerole = 0; } + if ($tend && $tend <$limit) { $activerole = 0; } + if ($activerole) { + push(@liveroles,$role); + } + } + } + if (@liveroles) { + if (&homeserver($cnum,$cdom) ne 'no_host') { + my ($accessref,$accessinfo,%access_in_dom); + ($roles_by_num,$description,$accessref,$accessinfo) = &get_all_adhocroles($cdom); + if (ref($roles_by_num) eq 'ARRAY') { + if (@{$roles_by_num}) { + my %settings; + if ($env{'request.course.id'} eq $cid) { + foreach my $envkey (keys(%env)) { + if ($envkey =~ /^\Qcourse.$cid.\E(internal\.adhoc.+)$/) { + $settings{$1} = $env{$envkey}; + } + } + } else { + %settings = &dump('environment',$cdom,$cnum,'internal\.adhoc'); + } + my %setincrs; + if ($settings{'internal.adhocaccess'}) { + map { $setincrs{$_} = 1; } split(/,/,$settings{'internal.adhocaccess'}); + } + my @statuses; + if ($env{'environment.inststatus'}) { + @statuses = split(/,/,$env{'environment.inststatus'}); + } + my $user = $env{'user.name'}.':'.$env{'user.domain'}; + if (ref($accessref) eq 'HASH') { + %access_in_dom = %{$accessref}; + } + foreach my $role (@{$roles_by_num}) { + my ($curraccess,@okstatus,@personnel); + if ($setincrs{$role}) { + ($curraccess,my $rest) = split(/=/,$settings{'internal.adhoc.'.$role}); + if ($curraccess eq 'status') { + @okstatus = split(/\&/,$rest); + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + @personnel = split(/\&/,$rest); + } + } else { + $curraccess = $access_in_dom{$role}; + if (ref($accessinfo) eq 'HASH') { + if ($curraccess eq 'status') { + if (ref($accessinfo->{$role}) eq 'ARRAY') { + @okstatus = @{$accessinfo->{$role}}; + } + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + if (ref($accessinfo->{$role}) eq 'ARRAY') { + @personnel = @{$accessinfo->{$role}}; + } + } + } + } + if ($curraccess eq 'none') { + next; + } elsif ($curraccess eq 'all') { + push(@possroles,$role); + } elsif ($curraccess eq 'dh') { + if (grep(/^dh$/,@liveroles)) { + push(@possroles,$role); + } else { + next; + } + } elsif ($curraccess eq 'da') { + if (grep(/^da$/,@liveroles)) { + push(@possroles,$role); + } else { + next; + } + } elsif ($curraccess eq 'status') { + if (@okstatus) { + if (!@statuses) { + if (grep(/^default$/,@okstatus)) { + push(@possroles,$role); + } + } else { + foreach my $status (@okstatus) { + if (grep(/^\Q$status\E$/,@statuses)) { + push(@possroles,$role); + last; + } + } + } + } + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + if (grep(/^\Q$user\E$/,@personnel)) { + if ($curraccess eq 'exc') { + push(@possroles,$role); + } + } elsif ($curraccess eq 'inc') { + push(@possroles,$role); + } + } + } + } + } + } + } + } + } + unless (ref($description) eq 'HASH') { + if (ref($roles_by_num) eq 'ARRAY') { + my %desc; + map { $desc{$_} = $_; } (@{$roles_by_num}); + $description = \%desc; + } else { + $description = {}; + } + } + return (\@possroles,$description); +} + # ----------------------------------------------------- Frontpage Announcements # # @@ -3181,7 +4949,8 @@ 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,$domcloner)=@_; + $cloneonly,$createdbefore,$createdafter,$creationcontext,$domcloner, + $hasuniquecode,$reqcrsdom,$reqinstcode)=@_; my $as_hash = 1; my %returnhash; if (!$domfilter) { $domfilter=''; } @@ -3193,18 +4962,33 @@ sub courseiddump { if (($domfilter eq '') || (&host_domain($tryserver) eq $domfilter)) { - my $rep = - &reply('courseiddump:'.&host_domain($tryserver).':'. - $sincefilter.':'.&escape($descfilter).':'. - &escape($instcodefilter).':'.&escape($ownerfilter). - ':'.&escape($coursefilter).':'.&escape($typefilter). - ':'.&escape($regexp_ok).':'.$as_hash.':'. - &escape($selfenrollonly).':'.&escape($catfilter).':'. - $showhidden.':'.$caller.':'.&escape($cloner).':'. - &escape($cc_clone).':'.$cloneonly.':'. - &escape($createdbefore).':'.&escape($createdafter).':'. - &escape($creationcontext).':'.$domcloner, - $tryserver); + my $rep; + if (grep { $_ eq $tryserver } current_machine_ids()) { + $rep = LONCAPA::Lond::dump_course_id_handler( + join(":", (&host_domain($tryserver), $sincefilter, + &escape($descfilter), &escape($instcodefilter), + &escape($ownerfilter), &escape($coursefilter), + &escape($typefilter), &escape($regexp_ok), + $as_hash, &escape($selfenrollonly), + &escape($catfilter), $showhidden, $caller, + &escape($cloner), &escape($cc_clone), $cloneonly, + &escape($createdbefore), &escape($createdafter), + &escape($creationcontext),$domcloner,$hasuniquecode, + $reqcrsdom,&escape($reqinstcode)))); + } else { + $rep = &reply('courseiddump:'.&host_domain($tryserver).':'. + $sincefilter.':'.&escape($descfilter).':'. + &escape($instcodefilter).':'.&escape($ownerfilter). + ':'.&escape($coursefilter).':'.&escape($typefilter). + ':'.&escape($regexp_ok).':'.$as_hash.':'. + &escape($selfenrollonly).':'.&escape($catfilter).':'. + $showhidden.':'.$caller.':'.&escape($cloner).':'. + &escape($cc_clone).':'.$cloneonly.':'. + &escape($createdbefore).':'.&escape($createdafter).':'. + &escape($creationcontext).':'.$domcloner.':'.$hasuniquecode. + ':'.$reqcrsdom.':'.&escape($reqinstcode),$tryserver); + } + my @pairs=split(/\&/,$rep); foreach my $item (@pairs) { my ($key,$value)=split(/\=/,$item,2); @@ -3310,7 +5094,7 @@ sub get_domain_roles { } my $rolelist; if (ref($roles) eq 'ARRAY') { - $rolelist = join(':',@{$roles}); + $rolelist = join('&',@{$roles}); } my %personnel = (); @@ -3330,13 +5114,52 @@ sub get_domain_roles { return %personnel; } +sub get_active_domroles { + my ($dom,$roles) = @_; + return () unless (ref($roles) eq 'ARRAY'); + my $now = time; + my %dompersonnel = &get_domain_roles($dom,$roles,$now,$now); + my %domroles; + foreach my $server (keys(%dompersonnel)) { + foreach my $user (sort(keys(%{$dompersonnel{$server}}))) { + my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,$user); + $domroles{$uname.':'.$udom} = $dompersonnel{$server}{$user}; + } + } + return %domroles; +} + # ----------------------------------------------------------- Interval timing +{ +# Caches needed for speedup of navmaps +# We don't want to cache this for very long at all (5 seconds at most) +# +# The user for whom we cache +my $cachedkey=''; +# The cached times for this user +my %cachedtimes=(); +# When this was last done +my $cachedtime=''; + +sub load_all_first_access { + my ($uname,$udom,$ignorecache)=@_; + if (($cachedkey eq $uname.':'.$udom) && + (abs($cachedtime-time)<5) && (!$env{'form.markaccess'}) && + (!$ignorecache)) { + return; + } + $cachedtime=time; + $cachedkey=$uname.':'.$udom; + %cachedtimes=&dump('firstaccesstimes',$udom,$uname); +} + sub get_first_access { - my ($type,$argsymb)=@_; + my ($type,$argsymb,$argmap,$ignorecache)=@_; my ($symb,$courseid,$udom,$uname)=&whichuser(); if ($argsymb) { $symb=$argsymb; } my ($map,$id,$res)=&decode_symb($symb); + if ($argmap) { $map = $argmap; } if ($type eq 'course') { $res='course'; } elsif ($type eq 'map') { @@ -3344,12 +5167,12 @@ sub get_first_access { } else { $res=$symb; } - my %times=&get('firstaccesstimes',["$courseid\0$res"],$udom,$uname); - return $times{"$courseid\0$res"}; + &load_all_first_access($uname,$udom,$ignorecache); + return $cachedtimes{"$courseid\0$res"}; } sub set_first_access { - my ($type)=@_; + my ($type,$interval)=@_; my ($symb,$courseid,$udom,$uname)=&whichuser(); my ($map,$id,$res)=&decode_symb($symb); if ($type eq 'course') { @@ -3359,12 +5182,27 @@ sub set_first_access { } else { $res=$symb; } - my $firstaccess=&get_first_access($type,$symb); + $cachedkey=''; + my $firstaccess=&get_first_access($type,$symb,$map); if (!$firstaccess) { - return &put('firstaccesstimes',{"$courseid\0$res"=>time},$udom,$uname); + my $start = time; + my $putres = &put('firstaccesstimes',{"$courseid\0$res"=>$start}, + $udom,$uname); + if ($putres eq 'ok') { + &put('timerinterval',{"$courseid\0$res"=>$interval}, + $udom,$uname); + &appenv( + { + 'course.'.$courseid.'.firstaccess.'.$res => $start, + 'course.'.$courseid.'.timerinterval.'.$res => $interval, + } + ); + } + return $putres; } return 'already_set'; } +} # --------------------------------------------- Set Expire Date for Spreadsheet @@ -3470,7 +5308,7 @@ sub hashref2str { $result.='='; #print("Got a ref of ".(ref($key))." skipping."); } else { - if ($key) {$result.=&escape($key).'=';} else { last; } + if (defined($key)) {$result.=&escape($key).'=';} else { last; } } if(ref($hashref->{$key}) eq 'ARRAY') { @@ -3622,7 +5460,7 @@ sub tmpreset { if ($domain eq 'public' && $stuname eq 'public') { $stuname=$ENV{'REMOTE_ADDR'}; } - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); my %hash; if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', @@ -3661,7 +5499,7 @@ sub tmpstore { } my $now=time; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_WRCREAT(),0640)) { @@ -3707,7 +5545,7 @@ sub tmprestore { $namespace=~s/\//\_/g; $namespace=~s/\W//g; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_READER(),0640)) { @@ -3736,7 +5574,7 @@ sub tmprestore { # ----------------------------------------------------------------------- Store sub store { - my ($storehash,$symb,$namespace,$domain,$stuname) = @_; + my ($storehash,$symb,$namespace,$domain,$stuname,$laststore) = @_; my $home=''; if ($stuname) { $home=&homeserver($stuname,$domain); } @@ -3766,13 +5604,13 @@ sub store { } $namevalue=~s/\&$//; &courselog($symb.':'.$stuname.':'.$domain.':STORE:'.$namevalue); - return reply("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); + return reply("store:$domain:$stuname:$namespace:$symb:$namevalue:$laststore","$home"); } # -------------------------------------------------------------- Critical Store sub cstore { - my ($storehash,$symb,$namespace,$domain,$stuname) = @_; + my ($storehash,$symb,$namespace,$domain,$stuname,$laststore) = @_; my $home=''; if ($stuname) { $home=&homeserver($stuname,$domain); } @@ -3803,7 +5641,7 @@ sub cstore { $namevalue=~s/\&$//; &courselog($symb.':'.$stuname.':'.$domain.':CSTORE:'.$namevalue); return critical - ("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); + ("store:$domain:$stuname:$namespace:$symb:$namevalue:$laststore","$home"); } # --------------------------------------------------------------------- Restore @@ -3815,9 +5653,12 @@ sub restore { if ($stuname) { $home=&homeserver($stuname,$domain); } if (!$symb) { - unless ($symb=escape(&symbread())) { return ''; } + return if ($namespace eq 'courserequests'); + unless ($symb=escape(&symbread())) { return ''; } } else { - $symb=&escape(&symbclean($symb)); + unless ($namespace eq 'courserequests') { + $symb=&escape(&symbclean($symb)); + } } if (!$namespace) { unless ($namespace=$env{'request.course.id'}) { @@ -3844,6 +5685,8 @@ sub restore { } # ---------------------------------------------------------- Course Description +# +# sub coursedescription { my ($courseid,$args)=@_; @@ -3873,7 +5716,8 @@ sub coursedescription { return %returnhash; } - # get the data agin + # get the data again + if (!$args->{'one_time'}) { $envhash{'course.'.$normalid.'.last_cache'}=time; } @@ -3881,6 +5725,10 @@ sub coursedescription { if ($chome ne 'no_host') { %returnhash=&dump('environment',$cdomain,$cnum); if (!exists($returnhash{'con_lost'})) { + my $username = $env{'user.name'}; # Defult username + if(defined $args->{'user'}) { + $username = $args->{'user'}; + } $returnhash{'home'}= $chome; $returnhash{'domain'} = $cdomain; $returnhash{'num'} = $cnum; @@ -3891,8 +5739,8 @@ sub coursedescription { $envhash{'course.'.$normalid.'.'.$name}=$value; } $returnhash{'url'}=&clutter($returnhash{'url'}); - $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'. - $env{'user.name'}.'_'.$cdomain.'_'.$cnum; + $returnhash{'fn'}=LONCAPA::tempdir() . + $username.'_'.$cdomain.'_'.$cnum; $envhash{'course.'.$normalid.'.home'}=$chome; $envhash{'course.'.$normalid.'.domain'}=$cdomain; $envhash{'course.'.$normalid.'.num'}=$cnum; @@ -3945,109 +5793,217 @@ sub update_released_required { # -------------------------------------------------See if a user is privileged sub privileged { - my ($username,$domain)=@_; - my $rolesdump=&reply("dump:$domain:$username:roles", - &homeserver($username,$domain)); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || - ($rolesdump =~ /^error:/)) { - return 0; - } - my $now=time; - if ($rolesdump ne '') { - foreach my $entry (split(/&/,$rolesdump)) { - if ($entry!~/^rolesdef_/) { - my ($area,$role)=split(/=/,$entry); - $area=~s/\_\w\w$//; - my ($trole,$tend,$tstart)=split(/_/,$role); - if (($trole eq 'dc') || ($trole eq 'su')) { - my $active=1; - if ($tend) { - if ($tend<$now) { $active=0; } - } - if ($tstart) { - if ($tstart>$now) { $active=0; } - } - if ($active) { return 1; } - } - } - } + my ($username,$domain,$possdomains,$possroles)=@_; + my $now = time; + my $roles; + if (ref($possroles) eq 'ARRAY') { + $roles = $possroles; + } else { + $roles = ['dc','su']; + } + if (ref($possdomains) eq 'ARRAY') { + my %privileged = &privileged_by_domain($possdomains,$roles); + foreach my $dom (@{$possdomains}) { + if (($username =~ /^$match_username$/) && ($domain =~ /^$match_domain$/) && + (ref($privileged{$dom}) eq 'HASH')) { + foreach my $role (@{$roles}) { + if (ref($privileged{$dom}{$role}) eq 'HASH') { + if (exists($privileged{$dom}{$role}{$username.':'.$domain})) { + my ($end,$start) = split(/:/,$privileged{$dom}{$role}{$username.':'.$domain}); + return 1 unless (($end && $end < $now) || + ($start && $start > $now)); + } + } + } + } + } + } else { + my %rolesdump = &dump("roles", $domain, $username) or return 0; + my $now = time; + + for my $role (@rolesdump{grep { ! /^rolesdef_/ } keys(%rolesdump)}) { + my ($trole, $tend, $tstart) = split(/_/, $role); + if (grep(/^\Q$trole\E$/,@{$roles})) { + return 1 unless ($tend && $tend < $now) + or ($tstart && $tstart > $now); + } + } } return 0; } +sub privileged_by_domain { + my ($domains,$roles) = @_; + my %privileged = (); + my $cachetime = 60*60*24; + my $now = time; + unless ((ref($domains) eq 'ARRAY') && (ref($roles) eq 'ARRAY')) { + return %privileged; + } + foreach my $dom (@{$domains}) { + next if (ref($privileged{$dom}) eq 'HASH'); + my $needroles; + foreach my $role (@{$roles}) { + my ($result,$cached)=&is_cached_new('priv_'.$role,$dom); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + $privileged{$dom}{$role} = $result; + } + } else { + $needroles = 1; + } + } + if ($needroles) { + my %dompersonnel = &get_domain_roles($dom,$roles); + $privileged{$dom} = {}; + foreach my $server (keys(%dompersonnel)) { + if (ref($dompersonnel{$server}) eq 'HASH') { + foreach my $item (keys(%{$dompersonnel{$server}})) { + my ($trole,$uname,$udom,$rest) = split(/:/,$item,4); + my ($end,$start) = split(/:/,$dompersonnel{$server}{$item}); + next if ($end && $end < $now); + $privileged{$dom}{$trole}{$uname.':'.$udom} = + $dompersonnel{$server}{$item}; + } + } + } + if (ref($privileged{$dom}) eq 'HASH') { + foreach my $role (@{$roles}) { + if (ref($privileged{$dom}{$role}) eq 'HASH') { + &do_cache_new('priv_'.$role,$dom,$privileged{$dom}{$role},$cachetime); + } else { + my %hash = (); + &do_cache_new('priv_'.$role,$dom,\%hash,$cachetime); + } + } + } + } + } + return %privileged; +} + # -------------------------------------------------------- Get user privileges sub rolesinit { - my ($domain,$username,$authhost)=@_; - my $now=time; - my %userroles = ('user.login.time' => $now); - my $extra = &freeze_escape({'clientcheckrole' => 1}); - my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || - ($rolesdump =~ /^error:/)) { - return \%userroles; + my ($domain, $username) = @_; + my %userroles = ('user.login.time' => time); + my %rolesdump = &dump("roles", $domain, $username) or return \%userroles; + + # firstaccess and timerinterval are related to timed maps/resources. + # also, blocking can be triggered by an activating timer + # it's saved in the user's %env. + my %firstaccess = &dump('firstaccesstimes', $domain, $username); + my %timerinterval = &dump('timerinterval', $domain, $username); + my (%coursetimerstarts, %firstaccchk, %firstaccenv, %coursetimerintervals, + %timerintchk, %timerintenv); + + foreach my $key (keys(%firstaccess)) { + my ($cid, $rest) = split(/\0/, $key); + $coursetimerstarts{$cid}{$rest} = $firstaccess{$key}; + } + + foreach my $key (keys(%timerinterval)) { + my ($cid,$rest) = split(/\0/,$key); + $coursetimerintervals{$cid}{$rest} = $timerinterval{$key}; } + my %allroles=(); - my %allgroups=(); - my $group_privs; + my %allgroups=(); - if ($rolesdump ne '') { - foreach my $entry (split(/&/,$rolesdump)) { - if ($entry!~/^rolesdef_/) { - my ($area,$role)=split(/=/,$entry); - $area=~s/\_\w\w$//; - my ($trole,$tend,$tstart,$group_privs); - if ($role=~/^cr/) { - if ($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) { - ($trole,my $trest)=($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|); - ($tend,$tstart)=split('_',$trest); - } else { - $trole=$role; - } - } elsif ($role =~ m|^gr/|) { - ($trole,$tend,$tstart) = split(/_/,$role); - ($trole,$group_privs) = split(/\//,$trole); - $group_privs = &unescape($group_privs); - } else { - ($trole,$tend,$tstart)=split(/_/,$role); - } - my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain, - $username); - @userroles{keys(%new_role)} = @new_role{keys(%new_role)}; - if (($tend!=0) && ($tend<$now)) { $trole=''; } - if (($tstart!=0) && ($tstart>$now)) { $trole=''; } - if (($area ne '') && ($trole ne '')) { - my $spec=$trole.'.'.$area; - my ($tdummy,$tdomain,$trest)=split(/\//,$area); - if ($trole =~ /^cr\//) { - &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area); - } elsif ($trole eq 'gr') { - &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart); - } else { - &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); - } + for my $area (grep { ! /^rolesdef_/ } keys(%rolesdump)) { + my $role = $rolesdump{$area}; + $area =~ s/\_\w\w$//; + + my ($trole, $tend, $tstart, $group_privs); + + if ($role =~ /^cr/) { + # Custom role, defined by a user + # e.g., user.role.cr/msu/smith/mynewrole + if ($role =~ m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) { + $trole = $1; + ($tend, $tstart) = split('_', $2); + } else { + $trole = $role; } - } + } elsif ($role =~ m|^gr/|) { + # Role of member in a group, defined within a course/community + # e.g., user.role.gr/msu/04935610a19ee4a5fmsul1/leopards + ($trole, $tend, $tstart) = split(/_/, $role); + next if $tstart eq '-1'; + ($trole, $group_privs) = split(/\//, $trole); + $group_privs = &unescape($group_privs); + } else { + # Just a normal role, defined in roles.tab + ($trole, $tend, $tstart) = split(/_/,$role); + } + + my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain, + $username); + @userroles{keys(%new_role)} = @new_role{keys(%new_role)}; + + # role expired or not available yet? + $trole = '' if ($tend != 0 && $tend < $userroles{'user.login.time'}) or + ($tstart != 0 && $tstart > $userroles{'user.login.time'}); + + next if $area eq '' or $trole eq ''; + + my $spec = "$trole.$area"; + my ($tdummy, $tdomain, $trest) = split(/\//, $area); + + if ($trole =~ /^cr\//) { + # Custom role, defined by a user + &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area); + } elsif ($trole eq 'gr') { + # Role of a member in a group, defined within a course/community + &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart); + next; + } else { + # Normal role, defined in roles.tab + &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); + } + + my $cid = $tdomain.'_'.$trest; + unless ($firstaccchk{$cid}) { + if (ref($coursetimerstarts{$cid}) eq 'HASH') { + foreach my $item (keys(%{$coursetimerstarts{$cid}})) { + $firstaccenv{'course.'.$cid.'.firstaccess.'.$item} = + $coursetimerstarts{$cid}{$item}; + } + } + $firstaccchk{$cid} = 1; + } + unless ($timerintchk{$cid}) { + if (ref($coursetimerintervals{$cid}) eq 'HASH') { + foreach my $item (keys(%{$coursetimerintervals{$cid}})) { + $timerintenv{'course.'.$cid.'.timerinterval.'.$item} = + $coursetimerintervals{$cid}{$item}; + } + } + $timerintchk{$cid} = 1; } - my ($author,$adv) = &set_userprivs(\%userroles,\%allroles,\%allgroups); - $userroles{'user.adv'} = $adv; - $userroles{'user.author'} = $author; - $env{'user.adv'}=$adv; } - return \%userroles; + + @userroles{'user.author','user.adv','user.rar'} = &set_userprivs(\%userroles, + \%allroles, \%allgroups); + $env{'user.adv'} = $userroles{'user.adv'}; + $env{'user.rar'} = $userroles{'user.rar'}; + + return (\%userroles,\%firstaccenv,\%timerintenv); } sub set_arearole { - my ($trole,$area,$tstart,$tend,$domain,$username) = @_; + my ($trole,$area,$tstart,$tend,$domain,$username,$nolog) = @_; + unless ($nolog) { # log the associated role with the area - &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + } return ('user.role.'.$trole.'.'.$area => $tstart.'.'.$tend); } sub custom_roleprivs { my ($allroles,$trole,$tdomain,$trest,$spec,$area) = @_; my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$trole); - my $homsvr=homeserver($rauthor,$rdomain); + my $homsvr = &homeserver($rauthor,$rdomain); if (&hostname($homsvr) ne '') { my ($rdummy,$roledef)= &get('roles',["rolesdef_$rrole"],$rdomain,$rauthor); @@ -4066,6 +6022,10 @@ sub custom_roleprivs { $$allroles{$spec.'./'.$tdomain.'/'}.=':'.$dompriv; } if (($trest ne '') && (defined($coursepriv))) { + if ($trole =~ m{^cr/$tdomain/$tdomain\Q-domainconfig\E/([^/]+)$}) { + my $rolename = $1; + $coursepriv = &course_adhocrole_privs($rolename,$tdomain,$trest,$coursepriv); + } $$allroles{'cm.'.$area}.=':'.$coursepriv; $$allroles{$spec.'.'.$area}.=':'.$coursepriv; } @@ -4074,6 +6034,48 @@ sub custom_roleprivs { } } +sub course_adhocrole_privs { + my ($rolename,$cdom,$cnum,$coursepriv) = @_; + my %overrides = &get('environment',["internal.adhocpriv.$rolename"],$cdom,$cnum); + if ($overrides{"internal.adhocpriv.$rolename"}) { + my (%currprivs,%storeprivs); + foreach my $item (split(/:/,$coursepriv)) { + my ($priv,$restrict) = split(/\&/,$item); + $currprivs{$priv} = $restrict; + } + my (%possadd,%possremove,%full); + foreach my $item (split(/\:/,$Apache::lonnet::pr{'cr:c'})) { + my ($priv,$restrict)=split(/\&/,$item); + $full{$priv} = $restrict; + } + foreach my $item (split(/,/,$overrides{"internal.adhocpriv.$rolename"})) { + next if ($item eq ''); + my ($rule,$rest) = split(/=/,$item); + next unless (($rule eq 'off') || ($rule eq 'on')); + foreach my $priv (split(/:/,$rest)) { + if ($priv ne '') { + if ($rule eq 'off') { + $possremove{$priv} = 1; + } else { + $possadd{$priv} = 1; + } + } + } + } + foreach my $priv (sort(keys(%full))) { + if (exists($currprivs{$priv})) { + unless (exists($possremove{$priv})) { + $storeprivs{$priv} = $currprivs{$priv}; + } + } elsif (exists($possadd{$priv})) { + $storeprivs{$priv} = $full{$priv}; + } + } + $coursepriv = ':'.join(':',map { $_.'&'.$storeprivs{$_}; } sort(keys(%storeprivs))); + } + return $coursepriv; +} + sub group_roleprivs { my ($allgroups,$area,$group_privs,$tend,$tstart) = @_; my $access = 1; @@ -4108,6 +6110,7 @@ sub set_userprivs { my ($userroles,$allroles,$allgroups,$groups_roles) = @_; my $author=0; my $adv=0; + my $rar=0; my %grouproles = (); if (keys(%{$allgroups}) > 0) { my @groupkeys; @@ -4155,28 +6158,29 @@ sub set_userprivs { $thesepriv{$privilege}.=$restrictions; } if ($thesepriv{'adv'} eq 'F') { $adv=1; } + if ($thesepriv{'rar'} eq 'F') { $rar=1; } } } my $thesestr=''; - foreach my $priv (keys(%thesepriv)) { + foreach my $priv (sort(keys(%thesepriv))) { $thesestr.=':'.$priv.'&'.$thesepriv{$priv}; } $userroles->{'user.priv.'.$role} = $thesestr; } - return ($author,$adv); + return ($author,$adv,$rar); } sub role_status { - my ($rolekey,$then,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; - my @pwhere = (); + my ($rolekey,$update,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; if (exists($env{$rolekey}) && $env{$rolekey} ne '') { - (undef,undef,$$role,@pwhere)=split(/\./,$rolekey); + my ($one,$two) = split(m{\./},$rolekey,2); + (undef,undef,$$role) = split(/\./,$one,3); unless (!defined($$role) || $$role eq '') { - $$where=join('.',@pwhere); + $$where = '/'.$two; $$trolecode=$$role.'.'.$$where; ($$tstart,$$tend)=split(/\./,$env{$rolekey}); $$tstatus='is'; - if ($$tstart && $$tstart>$then) { + if ($$tstart && $$tstart>$update) { $$tstatus='future'; if ($$tstart<$now) { if ($$tstart && $$tstart>$refresh) { @@ -4201,46 +6205,24 @@ sub role_status { $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; - } - } - } - } + &get_groups_roles($tdomain,$trest, + \%course_roles,\@rolecodes, + \%groups_roles); } else { push(@rolecodes,$$role); &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where); } - my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups,\%groups_roles); + my ($author,$adv,$rar)= &set_userprivs(\%userroles,\%allroles,\%allgroups, + \%groups_roles); &appenv(\%userroles,\@rolecodes); - &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); + &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$spec); } } $$tstatus = 'is'; } } if ($$tend) { - if ($$tend<$then) { + if ($$tend<$update) { $$tstatus='expired'; } elsif ($$tend<$now) { $$tstatus='will_not'; @@ -4250,40 +6232,121 @@ sub role_status { } } +sub get_groups_roles { + my ($cdom,$rest,$cdom_courseroles,$rolecodes,$groups_roles) = @_; + return unless((ref($cdom_courseroles) eq 'HASH') && + (ref($rolecodes) eq 'ARRAY') && + (ref($groups_roles) eq 'HASH')); + if (keys(%{$cdom_courseroles}) > 0) { + my ($cnum) = ($rest =~ /^($match_courseid)/); + if ($cdom ne '' && $cnum ne '') { + foreach my $key (keys(%{$cdom_courseroles})) { + if ($key =~ /^\Q$cnum\E:\Q$cdom\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./$cdom/$cnum"; + if ($crssec ne '') { + $rolekey .= "/$crssec"; + } + $rolekey .= './'; + $groups_roles->{$rolekey} = $rolecodes; + } + } + } + } + return; +} + +sub delete_env_groupprivs { + my ($where,$courseroles,$possroles) = @_; + return unless((ref($courseroles) eq 'HASH') && (ref($possroles) eq 'ARRAY')); + my ($dummy,$udom,$uname,$group) = split(/\//,$where); + unless (ref($courseroles->{$udom}) eq 'HASH') { + %{$courseroles->{$udom}} = + &get_my_roles('','','userroles',['active'], + $possroles,[$udom],1); + } + if (ref($courseroles->{$udom}) eq 'HASH') { + foreach my $item (keys(%{$courseroles->{$udom}})) { + my ($cnum,$cdom,$crsrole,$crssec) = split(/:/,$item); + my $area = '/'.$cdom.'/'.$cnum; + my $privkey = "user.priv.$crsrole.$area"; + if ($crssec ne '') { + $privkey .= '/'.$crssec; + } + $privkey .= ".$area/$group"; + &Apache::lonnet::delenv($privkey,undef,[$crsrole]); + } + } + return; +} + sub check_adhoc_privs { - my ($cdom,$cnum,$then,$refresh,$now,$checkrole) = @_; + my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller,$sec) = @_; my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum; + if ($sec) { + $cckey .= '/'.$sec; + } + my $setprivs; if ($env{$cckey}) { my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend); - &role_status($cckey,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); + &role_status($cckey,$update,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) { - &set_adhoc_privileges($cdom,$cnum,$checkrole); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller,$sec); + $setprivs = 1; } } else { - &set_adhoc_privileges($cdom,$cnum,$checkrole); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller,$sec); + $setprivs = 1; } + return $setprivs; } sub set_adhoc_privileges { -# role can be cc or ca - my ($dcdom,$pickedcourse,$role) = @_; +# role can be cc, ca, or cr//-domainconfig/role + my ($dcdom,$pickedcourse,$role,$caller,$sec) = @_; my $area = '/'.$dcdom.'/'.$pickedcourse; + if ($sec ne '') { + $area .= '/'.$sec; + } my $spec = $role.'.'.$area; my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'}, - $env{'user.name'}); - my %ccrole = (); - &standard_roleprivs(\%ccrole,$role,$dcdom,$spec,$pickedcourse,$area); - my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole); + $env{'user.name'},1); + my %rolehash = (); + if ($role =~ m{^\Qcr/$dcdom/$dcdom\E\-domainconfig/(\w+)$}) { + my $rolename = $1; + &custom_roleprivs(\%rolehash,$role,$dcdom,$pickedcourse,$spec,$area); + my %domdef = &get_domain_defaults($dcdom); + if (ref($domdef{'adhocroles'}) eq 'HASH') { + if (ref($domdef{'adhocroles'}{$rolename}) eq 'HASH') { + &appenv({'request.role.desc' => $domdef{'adhocroles'}{$rolename}{'desc'},}); + } + } + } else { + &standard_roleprivs(\%rolehash,$role,$dcdom,$spec,$pickedcourse,$area); + } + my ($author,$adv,$rar)= &set_userprivs(\%userroles,\%rolehash); &appenv(\%userroles,[$role,'cm']); - &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); - &appenv( {'request.role' => $spec, - 'request.role.domain' => $dcdom, - 'request.course.sec' => '' - } - ); - my $tadv=0; - if (&allowed('adv') eq 'F') { $tadv=1; } - &appenv({'request.role.adv' => $tadv}); + &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$spec); + unless ($caller eq 'constructaccess' && $env{'request.course.id'}) { + &appenv( {'request.role' => $spec, + 'request.role.domain' => $dcdom, + 'request.course.sec' => $sec, + } + ); + my $tadv=0; + if (&allowed('adv') eq 'F') { $tadv=1; } + &appenv({'request.role.adv' => $tadv}); + } } # --------------------------------------------------------------- get interface @@ -4331,49 +6394,64 @@ sub del { # -------------------------------------------------------------- dump interface +sub unserialize { + my ($rep, $escapedkeys) = @_; + + return {} if $rep =~ /^error/; + + my %returnhash=(); + foreach my $item (split(/\&/,$rep)) { + my ($key, $value) = split(/=/, $item, 2); + $key = unescape($key) unless $escapedkeys; + next if $key =~ /^error: 2 /; + $returnhash{$key} = &thaw_unescape($value); + } + #return %returnhash; + return \%returnhash; +} + +# see Lond::dump_with_regexp +# if $escapedkeys hash keys won't get unescaped. sub dump { - my ($namespace,$udomain,$uname,$regexp,$range)=@_; + my ($namespace,$udomain,$uname,$regexp,$range,$escapedkeys)=@_; if (!$udomain) { $udomain=$env{'user.domain'}; } if (!$uname) { $uname=$env{'user.name'}; } my $uhome=&homeserver($uname,$udomain); + if ($regexp) { - $regexp=&escape($regexp); + $regexp=&escape($regexp); } else { - $regexp='.'; + $regexp='.'; + } + if (grep { $_ eq $uhome } current_machine_ids()) { + # user is hosted on this machine + my $reply = LONCAPA::Lond::dump_with_regexp(join(":", ($udomain, + $uname, $namespace, $regexp, $range)), $perlvar{'lonVersion'}); + return %{unserialize($reply, $escapedkeys)}; } my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); + if (!($rep =~ /^error/ )) { + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + $key = unescape($key) unless $escapedkeys; + #$key = &unescape($key); + next if ($key =~ /^error: 2 /); + $returnhash{$key}=&thaw_unescape($value); + } } return %returnhash; } + # --------------------------------------------------------- dumpstore interface sub dumpstore { my ($namespace,$udomain,$uname,$regexp,$range)=@_; - if (!$udomain) { $udomain=$env{'user.domain'}; } - if (!$uname) { $uname=$env{'user.name'}; } - my $uhome=&homeserver($uname,$udomain); - if ($regexp) { - $regexp=&escape($regexp); - } else { - $regexp='.'; - } - my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); - my @pairs=split(/\&/,$rep); - my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); - } - return %returnhash; + # same as dump but keys must be escaped. They may contain colon separated + # lists of values that may themself contain colons (e.g. symbs). + return &dump($namespace, $udomain, $uname, $regexp, $range, 1); } # -------------------------------------------------------------- keys interface @@ -4399,12 +6477,20 @@ sub currentdump { $sdom = $env{'user.domain'} if (! defined($sdom)); $sname = $env{'user.name'} if (! defined($sname)); my $uhome = &homeserver($sname,$sdom); - my $rep=reply('currentdump:'.$sdom.':'.$sname.':'.$courseid,$uhome); + my $rep; + + if (grep { $_ eq $uhome } current_machine_ids()) { + $rep = LONCAPA::Lond::dump_profile_database(join(":", ($sdom, $sname, + $courseid))); + } else { + $rep = reply('currentdump:'.$sdom.':'.$sname.':'.$courseid,$uhome); + } + return if ($rep =~ /^(error:|no_such_host)/); # my %returnhash=(); # - if ($rep eq "unknown_cmd") { + if ($rep eq 'unknown_cmd') { # an old lond will not know currentdump # Do a dump and make it look like a currentdump my @tmp = &dumpstore($courseid,$sdom,$sname,'.'); @@ -4520,7 +6606,7 @@ sub newput { # --------------------------------------------------------- putstore interface sub putstore { - my ($namespace,$symb,$version,$storehash,$udomain,$uname)=@_; + my ($namespace,$symb,$version,$storehash,$udomain,$uname,$tolog)=@_; if (!$udomain) { $udomain=$env{'user.domain'}; } if (!$uname) { $uname=$env{'user.name'}; } my $uhome=&homeserver($uname,$udomain); @@ -4534,6 +6620,17 @@ sub putstore { my $reply = &reply("putstore:$udomain:$uname:$namespace:$esc_symb:$esc_v:$items", $uhome); + if (($tolog) && ($reply eq 'ok')) { + my $namevalue=''; + foreach my $key (keys(%{$storehash})) { + $namevalue.=&escape($key).'='.&freeze_escape($storehash->{$key}).'&'; + } + $namevalue .= 'ip='.&escape($ENV{'REMOTE_ADDR'}). + '&host='.&escape($perlvar{'lonHostID'}). + '&version='.$esc_v. + '&by='.&escape($env{'user.name'}.':'.$env{'user.domain'}); + &Apache::lonnet::courselog($symb.':'.$uname.':'.$udomain.':PUTSTORE:'.$namevalue); + } if ($reply eq 'unknown_cmd') { # gfall back to way things use to be done return &old_putstore($namespace,$symb,$version,$storehash,$udomain, @@ -4627,27 +6724,128 @@ sub tmpget { if (!defined($server)) { $server = $perlvar{'lonHostID'}; } my $rep=&reply("tmpget:$token",$server); my %returnhash; + if ($rep =~ /^(con_lost|error|no_such_host)/i) { + return %returnhash; + } foreach my $item (split(/\&/,$rep)) { my ($key,$value)=split(/=/,$item); - next if ($key =~ /^error: 2 /); $returnhash{&unescape($key)}=&thaw_unescape($value); } return %returnhash; } -# ------------------------------------------------------------ tmpget interface +# ------------------------------------------------------------ tmpdel interface sub tmpdel { my ($token,$server)=@_; if (!defined($server)) { $server = $perlvar{'lonHostID'}; } return &reply("tmpdel:$token",$server); } +# ------------------------------------------------------------ get_timebased_id + +sub get_timebased_id { + my ($prefix,$keyid,$namespace,$cdom,$cnum,$idtype,$who,$locktries, + $maxtries) = @_; + my ($newid,$error,$dellock); + unless (($prefix =~ /^\w+$/) && ($keyid =~ /^\w+$/) && ($namespace ne '')) { + return ('','ok','invalid call to get suffix'); + } + +# set defaults for any optional args for which values were not supplied + if ($who eq '') { + $who = $env{'user.name'}.':'.$env{'user.domain'}; + } + if (!$locktries) { + $locktries = 3; + } + if (!$maxtries) { + $maxtries = 10; + } + + if (($cdom eq '') || ($cnum eq '')) { + if ($env{'request.course.id'}) { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } + if (($cdom eq '') || ($cnum eq '')) { + return ('','ok','call to get suffix not in course context'); + } + } + +# construct locking item + my $lockhash = { + $prefix."\0".'locked_'.$keyid => $who, + }; + my $tries = 0; + +# attempt to get lock on nohist_$namespace file + my $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + while (($gotlock ne 'ok') && $tries <$locktries) { + $tries ++; + sleep 1; + $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + } + +# attempt to get unique identifier, based on current timestamp + if ($gotlock eq 'ok') { + my %inuse = &Apache::lonnet::dump('nohist_'.$namespace,$cdom,$cnum,$prefix); + my $id = time; + $newid = $id; + if ($idtype eq 'addcode') { + $newid .= &sixnum_code(); + } + my $idtries = 0; + while (exists($inuse{$prefix."\0".$newid}) && $idtries < $maxtries) { + if ($idtype eq 'concat') { + $newid = $id.$idtries; + } elsif ($idtype eq 'addcode') { + $newid = $newid.&sixnum_code(); + } else { + $newid ++; + } + $idtries ++; + } + if (!exists($inuse{$prefix."\0".$newid})) { + my %new_item = ( + $prefix."\0".$newid => $who, + ); + my $putresult = &Apache::lonnet::put('nohist_'.$namespace,\%new_item, + $cdom,$cnum); + if ($putresult ne 'ok') { + undef($newid); + $error = 'error saving new item: '.$putresult; + } + } else { + undef($newid); + $error = ('error: no unique suffix available for the new item '); + } +# remove lock + my @del_lock = ($prefix."\0".'locked_'.$keyid); + $dellock = &Apache::lonnet::del('nohist_'.$namespace,\@del_lock,$cdom,$cnum); + } else { + $error = "error: could not obtain lockfile\n"; + $dellock = 'ok'; + if (($prefix eq 'paste') && ($namespace eq 'courseeditor') && ($keyid eq 'num')) { + $dellock = 'nolock'; + } + } + return ($newid,$dellock,$error); +} + +sub sixnum_code { + my $code; + for (0..6) { + $code .= int( rand(9) ); + } + return $code; +} + # -------------------------------------------------- portfolio access checking sub portfolio_access { - my ($requrl) = @_; + my ($requrl,$clientip) = @_; my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl); - my $result = &get_portfolio_access($udom,$unum,$file_name,$group); + my $result = &get_portfolio_access($udom,$unum,$file_name,$group,$clientip); if ($result) { my %setters; if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') { @@ -4673,7 +6871,7 @@ sub portfolio_access { } sub get_portfolio_access { - my ($udom,$unum,$file_name,$group,$access_hash) = @_; + my ($udom,$unum,$file_name,$group,$clientip,$access_hash) = @_; if (!ref($access_hash)) { my $current_perms = &get_portfile_permissions($udom,$unum); @@ -4682,7 +6880,7 @@ sub get_portfolio_access { $access_hash = $access_controls{$file_name}; } - my ($public,$guest,@domains,@users,@courses,@groups); + my ($public,$guest,@domains,@users,@courses,@groups,@ips); my $now = time; if (ref($access_hash) eq 'HASH') { foreach my $key (keys(%{$access_hash})) { @@ -4706,10 +6904,25 @@ sub get_portfolio_access { push(@courses,$key); } elsif ($scope eq 'group') { push(@groups,$key); + } elsif ($scope eq 'ip') { + push(@ips,$key); } } if ($public) { return 'ok'; + } elsif (@ips > 0) { + my $allowed; + foreach my $ipkey (@ips) { + if (ref($access_hash->{$ipkey}{'ip'}) eq 'ARRAY') { + if (&Apache::loncommon::check_ip_acc(join(',',@{$access_hash->{$ipkey}{'ip'}}),$clientip)) { + $allowed = 1; + last; + } + } + } + if ($allowed) { + return 'ok'; + } } if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') { if ($guest) { @@ -4886,7 +7099,7 @@ sub is_portfolio_file { } sub usertools_access { - my ($uname,$udom,$tool,$action,$context) = @_; + my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_; my ($access,%tools); if ($context eq '') { $context = 'tools'; @@ -4896,17 +7109,24 @@ sub usertools_access { official => 1, unofficial => 1, community => 1, + textbook => 1, + placement => 1, + ); + } elsif ($context eq 'requestauthor') { + %tools = ( + requestauthor => 1, ); } else { %tools = ( aboutme => 1, blog => 1, + webdav => 1, portfolio => 1, ); } return if (!defined($tools{$tool})); - if ((!defined($udom)) || (!defined($uname))) { + if (($udom eq '') || ($uname eq '')) { $udom = $env{'user.domain'}; $uname = $env{'user.name'}; } @@ -4915,22 +7135,34 @@ sub usertools_access { if ($action ne 'reload') { if ($context eq 'requestcourses') { return $env{'environment.canrequest.'.$tool}; + } elsif ($context eq 'requestauthor') { + return $env{'environment.canrequest.author'}; } else { return $env{'environment.availabletools.'.$tool}; } } } - my ($toolstatus,$inststatus); + my ($toolstatus,$inststatus,$envkey); + if ($context eq 'requestauthor') { + $envkey = $context; + } else { + $envkey = $context.'.'.$tool; + } if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'}) && ($action ne 'reload')) { - $toolstatus = $env{'environment.'.$context.'.'.$tool}; + $toolstatus = $env{'environment.'.$envkey}; $inststatus = $env{'environment.inststatus'}; } else { - my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus'); - $toolstatus = $userenv{$context.'.'.$tool}; - $inststatus = $userenv{'inststatus'}; + if (ref($userenvref) eq 'HASH') { + $toolstatus = $userenvref->{$envkey}; + $inststatus = $userenvref->{'inststatus'}; + } else { + my %userenv = &userenvironment($udom,$uname,$envkey,'inststatus'); + $toolstatus = $userenv{$envkey}; + $inststatus = $userenv{'inststatus'}; + } } if ($toolstatus ne '') { @@ -4942,8 +7174,17 @@ sub usertools_access { return $access; } - my $is_adv = &is_advanced_user($udom,$uname); - my %domdef = &get_domain_defaults($udom); + my ($is_adv,%domdef); + if (ref($is_advref) eq 'HASH') { + $is_adv = $is_advref->{'is_adv'}; + } else { + $is_adv = &is_advanced_user($udom,$uname); + } + if (ref($domdefref) eq 'HASH') { + %domdef = %{$domdefref}; + } else { + %domdef = &get_domain_defaults($udom); + } if (ref($domdef{$tool}) eq 'HASH') { if ($is_adv) { if ($domdef{$tool}{'_LC_adv'} ne '') { @@ -4985,7 +7226,7 @@ sub usertools_access { } } } else { - if ($context eq 'tools') { + if (($context eq 'tools') && ($tool ne 'webdav')) { $access = 1; } else { $access = 0; @@ -5017,9 +7258,18 @@ sub is_course_owner { sub is_advanced_user { my ($udom,$uname) = @_; + if ($udom ne '' && $uname ne '') { + if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) { + if (wantarray) { + return ($env{'user.adv'},$env{'user.author'}); + } else { + return $env{'user.adv'}; + } + } + } my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1); my %allroles; - my $is_adv; + my ($is_adv,$is_author); foreach my $role (keys(%roleshash)) { my ($trest,$tdomain,$trole,$sec) = split(/:/,$role); my $area = '/'.$tdomain.'/'.$trest; @@ -5033,6 +7283,9 @@ sub is_advanced_user { } elsif ($trole ne 'gr') { &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); } + if ($trole eq 'au') { + $is_author = 1; + } } } foreach my $role (keys(%allroles)) { @@ -5047,6 +7300,9 @@ sub is_advanced_user { } } } + if (wantarray) { + return ($is_adv,$is_author); + } return $is_adv; } @@ -5151,7 +7407,7 @@ sub customaccess { # ------------------------------------------------- Check for a user privilege sub allowed { - my ($priv,$uri,$symb,$role)=@_; + my ($priv,$uri,$symb,$role,$clientip,$noblockcheck)=@_; my $ver_orguri=$uri; $uri=&deversion($uri); my $orguri=$uri; @@ -5168,7 +7424,7 @@ sub allowed { if (defined($env{'allowed.'.$priv})) { return $env{'allowed.'.$priv}; } # Free bre access to adm and meta resources - if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard)$})) + if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard|ext\.tool)$})) || (($uri=~/\.meta$/) && ($uri!~m|^uploaded/|) )) && ($priv eq 'bre')) { return 'F'; @@ -5317,6 +7573,15 @@ sub allowed { } } +# User who is not author or co-author might still be able to edit +# resource of an author in the domain (e.g., if Domain Coordinator). + if (($priv eq 'eco') && ($thisallowed eq '') && ($env{'request.course.id'}) && + (&allowed('mdc',$env{'request.course.id'}))) { + if ($env{"user.priv.cm./$uri/"}=~/\Q$priv\E\&([^\:]*)/) { + $thisallowed.=$1; + } + } + # Course: uri itself is a course my $courseuri=$uri; $courseuri=~s/\_(\d)/\/$1/; @@ -5337,7 +7602,17 @@ sub allowed { if ($match) { if ($env{'user.priv.'.$env{'request.role'}.'./'} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my $value = $1; + if ($noblockcheck) { + $thisallowed.=$value; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } + } } } else { my $refuri = $env{'httpref.'.$orguri} || $env{'httpref.'.$ver_orguri}; @@ -5348,7 +7623,16 @@ sub allowed { $refuri=&declutter($refuri); my ($match) = &is_on_map($refuri); if ($match) { - $thisallowed='F'; + if ($noblockcheck) { + $thisallowed='F'; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed='F'; + } + } } } } @@ -5359,9 +7643,9 @@ sub allowed { && $thisallowed ne 'F' && $thisallowed ne '2' && &is_portfolio_url($uri)) { - $thisallowed = &portfolio_access($uri); + $thisallowed = &portfolio_access($uri,$clientip); } - + # Full access at system, domain or course-wide level? Exit. if ($thisallowed=~/F/) { return 'F'; @@ -5400,7 +7684,21 @@ sub allowed { $statecond=$cond; if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my $value = $1; + if ($priv eq 'bre') { + if ($noblockcheck) { + $thisallowed.=$value; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } + } + } else { + $thisallowed.=$value; + } $checkreferer=0; } } @@ -5428,7 +7726,21 @@ sub allowed { my $refstatecond=$cond; if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my $value = $1; + if ($priv eq 'bre') { + if ($noblockcheck) { + $thisallowed.=$value; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } + } + } else { + $thisallowed.=$value; + } $uri=$refuri; $statecond=$refstatecond; } @@ -5532,7 +7844,7 @@ sub allowed { my $unamedom=$env{'user.name'}.':'.$env{'user.domain'}; if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.roles.denied'} =~/\Q$rolecode\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc') && ($priv ne 'pac')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. $env{'request.course.id'}); @@ -5542,7 +7854,7 @@ sub allowed { if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.users.denied'} =~/\Q$unamedom\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc') && ($priv ne 'pac')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}. 'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '. $env{'request.course.id'}); @@ -5556,7 +7868,7 @@ sub allowed { if ($thisallowed=~/R/) { my $rolecode=(split(/\./,$env{'request.role'}))[0]; if (&metadata($uri,'roledeny')=~/\Q$rolecode\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode); } @@ -5588,6 +7900,318 @@ sub allowed { return 'F'; } +# ------------------------------------------- Check construction space access + +sub constructaccess { + my ($url,$setpriv)=@_; + +# We do not allow editing of previous versions of files + if ($url=~/\.(\d+)\.(\w+)$/) { return ''; } + +# Get username and domain from URL + my ($ownername,$ownerdomain,$ownerhome); + + ($ownerdomain,$ownername) = + ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)(?:/daxepage|/daxeopen)?/priv/($match_domain)/($match_username)(?:/|$)}); + +# The URL does not really point to any authorspace, forget it + unless (($ownername) && ($ownerdomain)) { return ''; } + +# Now we need to see if the user has access to the authorspace of +# $ownername at $ownerdomain + + if (($ownername eq $env{'user.name'}) && ($ownerdomain eq $env{'user.domain'})) { +# Real author for this? + $ownerhome = $env{'user.home'}; + if (exists($env{'user.priv.au./'.$ownerdomain.'/./'})) { + return ($ownername,$ownerdomain,$ownerhome); + } + } else { +# Co-author for this? + if (exists($env{'user.priv.ca./'.$ownerdomain.'/'.$ownername.'./'}) || + exists($env{'user.priv.aa./'.$ownerdomain.'/'.$ownername.'./'}) ) { + $ownerhome = &homeserver($ownername,$ownerdomain); + return ($ownername,$ownerdomain,$ownerhome); + } + if ($env{'request.course.id'}) { + if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) && + ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) { + if (&allowed('mdc',$env{'request.course.id'})) { + $ownerhome = $env{'course.'.$env{'request.course.id'}.'.home'}; + return ($ownername,$ownerdomain,$ownerhome); + } + } + } + } + +# We don't have any access right now. If we are not possibly going to do anything about this, +# we might as well leave + unless ($setpriv) { return ''; } + +# Backdoor access? + my $allowed=&allowed('eco',$ownerdomain); +# Nope + unless ($allowed) { return ''; } +# Looks like we may have access, but could be locked by the owner of the construction space + if ($allowed eq 'U') { + my %blocked=&get('environment',['domcoord.author'], + $ownerdomain,$ownername); +# Is blocked by owner + if ($blocked{'domcoord.author'} eq 'blocked') { return ''; } + } + if (($allowed eq 'F') || ($allowed eq 'U')) { +# Grant temporary access + my $then=$env{'user.login.time'}; + my $update=$env{'user.update.time'}; + if (!$update) { $update = $then; } + my $refresh=$env{'user.refresh.time'}; + if (!$refresh) { $refresh = $update; } + my $now = time; + &check_adhoc_privs($ownerdomain,$ownername,$update,$refresh, + $now,'ca','constructaccess'); + $ownerhome = &homeserver($ownername,$ownerdomain); + return($ownername,$ownerdomain,$ownerhome); + } +# No business here + return ''; +} + +# ----------------------------------------------------------- Content Blocking + +{ +# Caches for faster Course Contents display where content blocking +# is in operation (i.e., interval param set) for timed quiz. +# +# User for whom data are being temporarily cached. +my $cacheduser=''; +# Cached blockers for this user (a hash of blocking items). +my %cachedblockers=(); +# When the data were last cached. +my $cachedlast=''; + +sub load_all_blockers { + my ($uname,$udom,$blocks)=@_; + if (($uname ne '') && ($udom ne '')) { + if (($cacheduser eq $uname.':'.$udom) && + (abs($cachedlast-time)<5)) { + return; + } + } + $cachedlast=time; + $cacheduser=$uname.':'.$udom; + %cachedblockers = &get_commblock_resources($blocks); +} + +sub get_comm_blocks { + my ($cdom,$cnum) = @_; + if ($cdom eq '' || $cnum eq '') { + return unless ($env{'request.course.id'}); + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } + my %commblocks; + my $hashid=$cdom.'_'.$cnum; + my ($blocksref,$cached)=&is_cached_new('comm_block',$hashid); + if ((defined($cached)) && (ref($blocksref) eq 'HASH')) { + %commblocks = %{$blocksref}; + } else { + %commblocks = &Apache::lonnet::dump('comm_block',$cdom,$cnum); + my $cachetime = 600; + &do_cache_new('comm_block',$hashid,\%commblocks,$cachetime); + } + return %commblocks; +} + +sub get_commblock_resources { + my ($blocks) = @_; + my %blockers = (); + return %blockers unless ($env{'request.course.id'}); + return %blockers if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/); + my %commblocks; + if (ref($blocks) eq 'HASH') { + %commblocks = %{$blocks}; + } else { + %commblocks = &get_comm_blocks(); + } + return %blockers unless (keys(%commblocks) > 0); + my $navmap = Apache::lonnavmaps::navmap->new(); + return %blockers unless (ref($navmap)); + my $now = time; + foreach my $block (keys(%commblocks)) { + if ($block =~ /^(\d+)____(\d+)$/) { + my ($start,$end) = ($1,$2); + if ($start <= $now && $end >= $now) { + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}{'maps'}) eq 'HASH') { + if (keys(%{$commblocks{$block}{'blocks'}{'docs'}{'maps'}})) { + $blockers{$block}{maps} = $commblocks{$block}{'blocks'}{'docs'}{'maps'}; + } + } + if (ref($commblocks{$block}{'blocks'}{'docs'}{'resources'}) eq 'HASH') { + if (keys(%{$commblocks{$block}{'blocks'}{'docs'}{'resources'}})) { + $blockers{$block}{'resources'} = $commblocks{$block}{'blocks'}{'docs'}{'resources'}; + } + } + } + } + } + } elsif ($block =~ /^firstaccess____(.+)$/) { + my $item = $1; + my @to_test; + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') { + my @interval; + my $type = 'map'; + if ($item eq 'course') { + $type = 'course'; + @interval=&EXT("resource.0.interval"); + } else { + if ($item =~ /___\d+___/) { + $type = 'resource'; + @interval=&EXT("resource.0.interval",$item); + if (ref($navmap)) { + my $res = $navmap->getBySymb($item); + push(@to_test,$res); + } + } else { + my $mapsymb = &symbread($item,1); + if ($mapsymb) { + if (ref($navmap)) { + my $mapres = $navmap->getBySymb($mapsymb); + @to_test = $mapres->retrieveResources($mapres,undef,0,0,0,1); + foreach my $res (@to_test) { + my $symb = $res->symb(); + next if ($symb eq $mapsymb); + if ($symb ne '') { + @interval=&EXT("resource.0.interval",$symb); + if ($interval[1] eq 'map') { + last; + } + } + } + } + } + } + } + if ($interval[0] =~ /^(\d+)/) { + my $timelimit = $1; + my $first_access; + if ($type eq 'resource') { + $first_access=&get_first_access($interval[1],$item); + } elsif ($type eq 'map') { + $first_access=&get_first_access($interval[1],undef,$item); + } else { + $first_access=&get_first_access($interval[1]); + } + if ($first_access) { + my $timesup = $first_access+$timelimit; + if ($timesup > $now) { + my $activeblock; + foreach my $res (@to_test) { + if ($res->answerable()) { + $activeblock = 1; + last; + } + } + if ($activeblock) { + if (ref($commblocks{$block}{'blocks'}{'docs'}{'maps'}) eq 'HASH') { + if (keys(%{$commblocks{$block}{'blocks'}{'docs'}{'maps'}})) { + $blockers{$block}{'maps'} = $commblocks{$block}{'blocks'}{'docs'}{'maps'}; + } + } + if (ref($commblocks{$block}{'blocks'}{'docs'}{'resources'}) eq 'HASH') { + if (keys(%{$commblocks{$block}{'blocks'}{'docs'}{'resources'}})) { + $blockers{$block}{'resources'} = $commblocks{$block}{'blocks'}{'docs'}{'resources'}; + } + } + } + } + } + } + } + } + } + } + return %blockers; +} + +sub has_comm_blocking { + my ($priv,$symb,$uri,$blocks) = @_; + my @blockers; + return unless ($env{'request.course.id'}); + return unless ($priv eq 'bre'); + return if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/); + return if ($env{'request.state'} eq 'construct'); + &load_all_blockers($env{'user.name'},$env{'user.domain'},$blocks); + return unless (keys(%cachedblockers) > 0); + my (%possibles,@symbs); + if (!$symb) { + $symb = &symbread($uri,1,1,1,\%possibles); + } + if ($symb) { + @symbs = ($symb); + } elsif (keys(%possibles)) { + @symbs = keys(%possibles); + } + my $noblock; + foreach my $symb (@symbs) { + last if ($noblock); + my ($map,$resid,$resurl)=&decode_symb($symb); + foreach my $block (keys(%cachedblockers)) { + if ($block =~ /^firstaccess____(.+)$/) { + my $item = $1; + if (($item eq $map) || ($item eq $symb)) { + $noblock = 1; + last; + } + } + if (ref($cachedblockers{$block}) eq 'HASH') { + if (ref($cachedblockers{$block}{'resources'}) eq 'HASH') { + if ($cachedblockers{$block}{'resources'}{$symb}) { + unless (grep(/^\Q$block\E$/,@blockers)) { + push(@blockers,$block); + } + } + } + } + if (ref($cachedblockers{$block}{'maps'}) eq 'HASH') { + if ($cachedblockers{$block}{'maps'}{$map}) { + unless (grep(/^\Q$block\E$/,@blockers)) { + push(@blockers,$block); + } + } + } + } + } + return if ($noblock); + return @blockers; +} +} + +# -------------------------------- Deversion and split uri into path an filename + +# +# Removes the version from a URI and +# splits it in to its filename and path to the filename. +# Seems like File::Basename could have done this more clearly. +# Parameters: +# $uri - input URI +# Returns: +# Two element list consisting of +# $pathname - the URI up to and excluding the trailing / +# $filename - The part of the URI following the last / +# NOTE: +# Another realization of this is simply: +# use File::Basename; +# ... +# $uri = shift; +# $filename = basename($uri); +# $path = dirname($uri); +# return ($filename, $path); +# +# The implementation below is probably faster however. +# sub split_uri_for_cond { my $uri=&deversion(&declutter(shift)); my @uriparts=split(/\//,$uri); @@ -5636,7 +8260,7 @@ sub get_symb_from_alias { sub definerole { if (allowed('mcr','/')) { - my ($rolename,$sysrole,$domrole,$courole)=@_; + my ($rolename,$sysrole,$domrole,$courole,$uname,$udom)=@_; foreach my $role (split(':',$sysrole)) { my ($crole,$cqual)=split(/\&/,$role); if ($pr{'cr:s'}!~/\Q$crole\E/) { return "refused:s:$crole"; } @@ -5664,11 +8288,19 @@ sub definerole { } } } + my $uhome; + if (($uname ne '') && ($udom ne '')) { + $uhome = &homeserver($uname,$udom); + return $uhome if ($uhome eq 'no_host'); + } else { + $uname = $env{'user.name'}; + $udom = $env{'user.domain'}; + $uhome = $env{'user.home'}; + } my $command="encrypt:rolesput:$env{'user.domain'}:$env{'user.name'}:". - "$env{'user.domain'}:$env{'user.name'}:". - "rolesdef_$rolename=". + "$udom:$uname:rolesdef_$rolename=". escape($sysrole.'_'.$domrole.'_'.$courole); - return reply($command,$env{'user.home'}); + return reply($command,$uhome); } else { return 'refused'; } @@ -5677,19 +8309,23 @@ sub definerole { # ---------------- Make a metadata query against the network of library servers sub metadata_query { - my ($query,$custom,$customshow,$server_array)=@_; + my ($query,$custom,$customshow,$server_array,$domains_hash)=@_; my %rhash; my %libserv = &all_library(); my @server_list = (defined($server_array) ? @$server_array : keys(%libserv) ); for my $server (@server_list) { + my $domains = ''; + if (ref($domains_hash) eq 'HASH') { + $domains = $domains_hash->{$server}; + } unless ($custom or $customshow) { - my $reply=&reply("querysend:".&escape($query),$server); + my $reply=&reply("querysend:".&escape($query).':::'.&escape($domains),$server); $rhash{$server}=$reply; } else { my $reply=&reply("querysend:".&escape($query).':'. - &escape($custom).':'.&escape($customshow), + &escape($custom).':'.&escape($customshow).':'.&escape($domains), $server); $rhash{$server}=$reply; } @@ -5746,10 +8382,12 @@ sub update_allusers_table { sub fetch_enrollment_query { my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_; - my $homeserver; + my ($homeserver,$sleep,$loopmax); my $maxtries = 1; if ($context eq 'automated') { $homeserver = $perlvar{'lonHostID'}; + $sleep = 2; + $loopmax = 100; $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout } else { $homeserver = &homeserver($cnum,$dom); @@ -5767,23 +8405,23 @@ sub fetch_enrollment_query { &logthis('fetch_enrollment_query: invalid queryid: '.$queryid.' for host: '.$host.' and homeserver: '.$homeserver.' context: '.$context.' '.$cnum); return 'error: '.$queryid; } - my $reply = &get_query_reply($queryid); + my $reply = &get_query_reply($queryid,$sleep,$loopmax); my $tries = 1; while (($reply=~/^timeout/) && ($tries < $maxtries)) { - $reply = &get_query_reply($queryid); + $reply = &get_query_reply($queryid,$sleep,$loopmax); $tries ++; } if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) { &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$env{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries); } else { my @responses = split(/:/,$reply); - if ($homeserver eq $perlvar{'lonHostID'}) { + if (grep { $_ eq $homeserver } ¤t_machine_ids()) { foreach my $line (@responses) { my ($key,$value) = split(/=/,$line,2); $$replyref{$key} = $value; } } else { - my $pathname = $perlvar{'lonDaemons'}.'/tmp'; + my $pathname = LONCAPA::tempdir(); foreach my $line (@responses) { my ($key,$value) = split(/=/,$line); $$replyref{$key} = $value; @@ -5812,11 +8450,17 @@ sub fetch_enrollment_query { } sub get_query_reply { - my $queryid=shift; - my $replyfile=$perlvar{'lonDaemons'}.'/tmp/'.$queryid; + my ($queryid,$sleep,$loopmax) = @_;; + if (($sleep eq '') || ($sleep !~ /^\d+\.?\d*$/)) { + $sleep = 0.2; + } + if (($loopmax eq '') || ($loopmax =~ /\D/)) { + $loopmax = 100; + } + my $replyfile=LONCAPA::tempdir().$queryid; my $reply=''; - for (1..100) { - sleep 2; + for (1..$loopmax) { + sleep($sleep); if (-e $replyfile.'.end') { if (open(my $fh,$replyfile)) { $reply = join('',<$fh>); @@ -5909,9 +8553,9 @@ sub auto_get_sections { } sub auto_new_course { - my ($cnum,$cdom,$inst_course_id,$owner) = @_; + my ($cnum,$cdom,$inst_course_id,$owner,$coowners) = @_; my $homeserver = &homeserver($cnum,$cdom); - my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.$owner.':'.$cdom,$homeserver)); + my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.&escape($owner).':'.$cdom.':'.&escape($coowners),$homeserver)); return $response; } @@ -5935,8 +8579,8 @@ sub auto_validate_instcode { } $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'. &escape($instcode).':'.&escape($owner),$homeserver)); - my ($outcome,$description) = map { &unescape($_); } split('&',$response,2); - return ($outcome,$description); + my ($outcome,$description,$defaultcredits) = map { &unescape($_); } split('&',$response,3); + return ($outcome,$description,$defaultcredits); } sub auto_create_password { @@ -6178,17 +8822,20 @@ sub auto_courserequest_checks { } sub auto_courserequest_validation { - my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = @_; + my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist,$custominfo) = @_; my ($homeserver,$response); if ($dom =~ /^$match_domain$/) { $homeserver = &domain($dom,'primary'); } - unless ($homeserver eq 'no_host') { - + unless ($homeserver eq 'no_host') { + my $customdata; + if (ref($custominfo) eq 'HASH') { + $customdata = &freeze_escape($custominfo); + } $response=&unescape(&reply('autocrsreqvalidation:'.$dom.':'.&escape($owner). ':'.&escape($crstype).':'.&escape($inststatuslist). - ':'.&escape($instcode).':'.&escape($instseclist), - $homeserver)); + ':'.&escape($instcode).':'.&escape($instseclist).':'. + $customdata,$homeserver)); } return $response; } @@ -6207,6 +8854,136 @@ sub auto_validate_class_sec { return $response; } +sub auto_crsreq_update { + my ($cdom,$cnum,$crstype,$action,$ownername,$ownerdomain,$fullname,$title, + $code,$accessstart,$accessend,$inbound) = @_; + my ($homeserver,%crsreqresponse); + if ($cdom =~ /^$match_domain$/) { + $homeserver = &domain($cdom,'primary'); + } + unless (($homeserver eq 'no_host') || ($homeserver eq '')) { + my $info; + if (ref($inbound) eq 'HASH') { + $info = &freeze_escape($inbound); + } + my $response=&reply('autocrsrequpdate:'.$cdom.':'.$cnum.':'.&escape($crstype). + ':'.&escape($action).':'.&escape($ownername).':'. + &escape($ownerdomain).':'.&escape($fullname).':'. + &escape($title).':'.&escape($code).':'. + &escape($accessstart).':'.&escape($accessend).':'.$info, + $homeserver); + unless ($response =~ /(con_lost|error|no_such_host|refused)/) { + my @items = split(/&/,$response); + foreach my $item (@items) { + my ($key,$value) = split('=',$item); + $crsreqresponse{&unescape($key)} = &thaw_unescape($value); + } + } + } + return \%crsreqresponse; +} + +sub auto_export_grades { + my ($cdom,$cnum,$inforef,$gradesref) = @_; + my ($homeserver,%exportresponse); + if ($cdom =~ /^$match_domain$/) { + $homeserver = &domain($cdom,'primary'); + } + unless (($homeserver eq 'no_host') || ($homeserver eq '')) { + my $info; + if (ref($inforef) eq 'HASH') { + $info = &freeze_escape($inforef); + } + if (ref($gradesref) eq 'HASH') { + my $grades = &freeze_escape($gradesref); + my $response=&reply('encrypt:autoexportgrades:'.$cdom.':'.$cnum.':'. + $info.':'.$grades,$homeserver); + unless ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/) { + my @items = split(/&/,$response); + foreach my $item (@items) { + my ($key,$value) = split('=',$item); + $exportresponse{&unescape($key)} = &thaw_unescape($value); + } + } + } + } + return \%exportresponse; +} + +sub check_instcode_cloning { + my ($codedefaults,$code_order,$cloner,$clonefromcode,$clonetocode) = @_; + unless ((ref($codedefaults) eq 'HASH') && (ref($code_order) eq 'ARRAY')) { + return; + } + my $canclone; + if (@{$code_order} > 0) { + my $instcoderegexp ='^'; + my @clonecodes = split(/\&/,$cloner); + foreach my $item (@{$code_order}) { + if (grep(/^\Q$item\E=/,@clonecodes)) { + foreach my $pair (@clonecodes) { + my ($key,$val) = split(/\=/,$pair,2); + $val = &unescape($val); + if ($key eq $item) { + $instcoderegexp .= '('.$val.')'; + last; + } + } + } else { + $instcoderegexp .= $codedefaults->{$item}; + } + } + $instcoderegexp .= '$'; + my (@from,@to); + eval { + (@from) = ($clonefromcode =~ /$instcoderegexp/); + (@to) = ($clonetocode =~ /$instcoderegexp/); + }; + if ((@from > 0) && (@to > 0)) { + my @diffs = &Apache::loncommon::compare_arrays(\@from,\@to); + if (!@diffs) { + $canclone = 1; + } + } + } + return $canclone; +} + +sub default_instcode_cloning { + my ($clonedom,$domdefclone,$clonefromcode,$clonetocode,$codedefaultsref,$codeorderref) = @_; + my (%codedefaults,@code_order,$canclone); + if ((ref($codedefaultsref) eq 'HASH') && (ref($codeorderref) eq 'ARRAY')) { + %codedefaults = %{$codedefaultsref}; + @code_order = @{$codeorderref}; + } elsif ($clonedom) { + &auto_instcode_defaults($clonedom,\%codedefaults,\@code_order); + } + if (($domdefclone) && (@code_order)) { + my @clonecodes = split(/\+/,$domdefclone); + my $instcoderegexp ='^'; + foreach my $item (@code_order) { + if (grep(/^\Q$item\E$/,@clonecodes)) { + $instcoderegexp .= '('.$codedefaults{$item}.')'; + } else { + $instcoderegexp .= $codedefaults{$item}; + } + } + $instcoderegexp .= '$'; + my (@from,@to); + eval { + (@from) = ($clonefromcode =~ /$instcoderegexp/); + (@to) = ($clonetocode =~ /$instcoderegexp/); + }; + if ((@from > 0) && (@to > 0)) { + my @diffs = &Apache::loncommon::compare_arrays(\@from,\@to); + if (!@diffs) { + $canclone = 1; + } + } + } + return $canclone; +} + # ------------------------------------------------------- Course Group routines sub get_coursegroups { @@ -6354,6 +9131,7 @@ sub plaintext { my %rolenames = ( Course => 'std', Community => 'alt1', + Placement => 'std', ); if ($cid ne '') { if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') { @@ -6474,6 +9252,41 @@ sub assignrole { } } } + } elsif ($context eq 'requestauthor') { + if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'}) && + ($url eq '/'.$udom.'/') && ($role eq 'au')) { + if ($env{'environment.requestauthor'} eq 'automatic') { + $refused = ''; + } else { + my %domdefaults = &get_domain_defaults($udom); + if (ref($domdefaults{'requestauthor'}) eq 'HASH') { + my $checkbystatus; + if ($env{'user.adv'}) { + my $disposition = $domdefaults{'requestauthor'}{'_LC_adv'}; + if ($disposition eq 'automatic') { + $refused = ''; + } elsif ($disposition eq '') { + $checkbystatus = 1; + } + } else { + $checkbystatus = 1; + } + if ($checkbystatus) { + if ($env{'environment.inststatus'}) { + my @inststatuses = split(/,/,$env{'environment.inststatus'}); + foreach my $type (@inststatuses) { + if (($type ne '') && + ($domdefaults{'requestauthor'}{$type} eq 'automatic')) { + $refused = ''; + } + } + } elsif ($domdefaults{'requestauthor'}{'default'} eq 'automatic') { + $refused = ''; + } + } + } + } + } } if ($refused) { &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url. @@ -6482,6 +9295,13 @@ sub assignrole { return 'refused'; } } + } elsif ($role eq 'au') { + if ($url ne '/'.$udom.'/') { + &logthis('Attempt by '.$env{'user.name'}.':'.$env{'user.domain'}. + ' to assign author role for '.$uname.':'.$udom. + ' in domain: '.$url.' refused (wrong domain).'); + return 'refused'; + } } $mrole=$role; } @@ -6516,11 +9336,26 @@ sub assignrole { # log new user role if status is ok if ($answer eq 'ok') { &userrolelog($role,$uname,$udom,$url,$start,$end); + if (($role eq 'cc') || ($role eq 'in') || + ($role eq 'ep') || ($role eq 'ad') || + ($role eq 'ta') || ($role eq 'st') || + ($role=~/^cr/) || ($role eq 'gr') || + ($role eq 'co')) { # for course roles, perform group memberships changes triggered by role change. - &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,$selfenroll,$context); - unless ($role =~ /^gr/) { - &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend, - $origstart,$selfenroll,$context); + unless ($role =~ /^gr/) { + &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend, + $origstart,$selfenroll,$context); + } + &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag, + $selfenroll,$context); + } elsif (($role eq 'li') || ($role eq 'dg') || ($role eq 'sc') || + ($role eq 'au') || ($role eq 'dc') || ($role eq 'dh') || + ($role eq 'da')) { + &domainrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag, + $context); + } elsif (($role eq 'ca') || ($role eq 'aa')) { + &coauthorrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag, + $context); } if ($role eq 'cc') { &autoupdate_coowners($url,$end,$start,$uname,$udom); @@ -6710,7 +9545,7 @@ sub modifyuser { 'current user id "'.$uidhash{$uname}.'".'; } } else { - &idput($udom,($uname => $uid)); + &idput($udom,{$uname => $uid},$uhome,'ids'); } } # -------------------------------------------------------------- Add names, etc @@ -6805,6 +9640,9 @@ sub modifyuser { if ($reply ne 'ok') { return 'error: '.$reply; } + if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) { + &Apache::lonnet::devalidate_cache_new('emailscache',$uname.':'.$udom); + } my $sqlresult = &update_allusers_table($uname,$udom,\%names); &devalidate_cache_new('namescache',$uname.':'.$udom); $logmsg = 'Success modifying user '.$logmsg; @@ -6817,7 +9655,7 @@ sub modifyuser { sub modifystudent { my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid, - $selfenroll,$context,$inststatus)=@_; + $selfenroll,$context,$inststatus,$credits,$instsec)=@_; if (!$cid) { unless ($cid=$env{'request.course.id'}) { return 'not_in_class'; @@ -6829,15 +9667,17 @@ sub modifystudent { $desiredhome,$email,$inststatus); unless ($reply eq 'ok') { return $reply; } # This will cause &modify_student_enrollment to get the uid from the - # students environment + # student's environment $uid = undef if (!$forceid); $reply = &modify_student_enrollment($udom,$uname,$uid,$first,$middle,$last, - $gene,$usec,$end,$start,$type,$locktype,$cid,$selfenroll,$context); + $gene,$usec,$end,$start,$type,$locktype, + $cid,$selfenroll,$context,$credits,$instsec); return $reply; } sub modify_student_enrollment { - my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,$locktype,$cid,$selfenroll,$context) = @_; + my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type, + $locktype,$cid,$selfenroll,$context,$credits,$instsec) = @_; my ($cdom,$cnum,$chome); if (!$cid) { unless ($cid=$env{'request.course.id'}) { @@ -6880,14 +9720,16 @@ sub modify_student_enrollment { $uid = $tmp{'id'} if (!defined($uid) || $uid eq ''); } my $fullname = &format_name($first,$middle,$last,$gene,'lastname'); + my $user = "$uname:$udom"; + my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum); my $reply=cput('classlist', - {"$uname:$udom" => - join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype) }, + {$user => + join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) }, $cdom,$cnum); - unless (($reply eq 'ok') || ($reply eq 'delayed')) { + if (($reply eq 'ok') || ($reply eq 'delayed')) { + &devalidate_getsection_cache($udom,$uname,$cid); + } else { return 'error: '.$reply; - } else { - &devalidate_getsection_cache($udom,$uname,$cid); } # Add student role to user my $uurl='/'.$cid; @@ -6895,7 +9737,16 @@ sub modify_student_enrollment { if ($usec) { $uurl.='/'.$usec; } - return &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,$selfenroll,$context); + my $result = &assignrole($udom,$uname,$uurl,'st',$end,$start,undef, + $selfenroll,$context); + if ($result ne 'ok') { + if ($old_entry{$user} ne '') { + $reply = &cput('classlist',\%old_entry,$cdom,$cnum); + } else { + $reply = &del('classlist',[$user],$cdom,$cnum); + } + } + return $result; } sub format_name { @@ -7094,13 +9945,16 @@ sub generate_coursenum { } sub is_course { - my ($cdom,$cnum) = @_; - my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef, - undef,'.'); - if (exists($courses{$cdom.'_'.$cnum})) { - return 1; - } - return 0; + my ($cdom, $cnum) = scalar(@_) == 1 ? + ($_[0] =~ /^($match_domain)_($match_courseid)$/) : @_; + + return unless $cdom and $cnum; + + my %courses = &courseiddump($cdom, '.', 1, '.', '.', $cnum, undef, undef, + '.'); + + return unless(exists($courses{$cdom.'_'.$cnum})); + return wantarray ? ($cdom, $cnum) : $cdom.'_'.$cnum; } sub store_userdata { @@ -7124,8 +9978,11 @@ sub store_userdata { $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&'; } $namevalue=~s/\&$//; - $result = &reply("store:$env{'user.domain'}:$env{'user.name'}:". - "$namespace:$datakey:$namevalue",$uhome); + unless ($namespace eq 'courserequests') { + $datakey = &escape($datakey); + } + $result = &reply("store:$udom:$uname:$namespace:$datakey:". + $namevalue,$uhome); } } else { $result = 'error: data to store was not a hash reference'; @@ -7178,10 +10035,10 @@ sub diskusage { } sub is_locked { - my ($file_name, $domain, $user) = @_; + my ($file_name, $domain, $user, $which) = @_; my @check; my $is_locked; - push @check, $file_name; + push (@check,$file_name); my %locked = &get('file_permissions',\@check, $env{'user.domain'},$env{'user.name'}); my ($tmp)=keys(%locked); @@ -7190,14 +10047,19 @@ sub is_locked { if (ref($locked{$file_name}) eq 'ARRAY') { $is_locked = 'false'; foreach my $entry (@{$locked{$file_name}}) { - if (ref($entry) eq 'ARRAY') { + if (ref($entry) eq 'ARRAY') { $is_locked = 'true'; - last; + if (ref($which) eq 'ARRAY') { + push(@{$which},$entry); + } else { + last; + } } } } else { $is_locked = 'false'; } + return $is_locked; } sub declutter_portfile { @@ -7241,7 +10103,7 @@ sub save_selected_files { sub clear_selected_files { my ($user) = @_; my $filename = $user."savedfiles"; - open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (OUT, '>'.LONCAPA::tempdir().$filename); print (OUT undef); close (OUT); return ("ok"); @@ -7251,7 +10113,7 @@ sub files_in_path { my ($user, $path) = @_; my $filename = $user."savedfiles"; my %return_files; - open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (IN, '<'.LONCAPA::tempdir().$filename); while (my $line_in = ) { chomp ($line_in); my @paths_and_file = split (m!/!, $line_in); @@ -7273,7 +10135,7 @@ sub files_not_in_path { my $filename = $user."savedfiles"; my @return_files; my $path_part; - open(IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open(IN, '<'.LONCAPA::.$filename); while (my $line = ) { #ok, I know it's clunky, but I want it to work my @paths_and_file = split(m|/|, $line); @@ -7290,6 +10152,90 @@ sub files_not_in_path { return (@return_files); } +#------------------------------Submitted/Handedback Portfolio Files Versioning + +sub portfiles_versioning { + my ($symb,$domain,$stu_name,$portfiles,$versioned_portfiles) = @_; + my $portfolio_root = '/userfiles/portfolio'; + return unless ((ref($portfiles) eq 'ARRAY') && (ref($versioned_portfiles) eq 'ARRAY')); + foreach my $file (@{$portfiles}) { + &unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file); + my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/); + my ($answer_name,$answer_ver,$answer_ext) = &file_name_version_ext($answer_file); + my $getpropath = 1; + my ($dir_list,$listerror) = &dirlist($portfolio_root.$directory,$domain, + $stu_name,$getpropath); + my $version = &get_next_version($answer_name,$answer_ext,$dir_list); + my $new_answer = + &version_selected_portfile($domain,$stu_name,$directory,$answer_file,$version); + if ($new_answer ne 'problem getting file') { + push(@{$versioned_portfiles}, $directory.$new_answer); + &mark_as_readonly($domain,$stu_name,[$directory.$new_answer], + [$symb,$env{'request.course.id'},'graded']); + } + } +} + +sub get_next_version { + my ($answer_name, $answer_ext, $dir_list) = @_; + my $version; + if (ref($dir_list) eq 'ARRAY') { + foreach my $row (@{$dir_list}) { + my ($file) = split(/\&/,$row,2); + my ($file_name,$file_version,$file_ext) = + &file_name_version_ext($file); + if (($file_name eq $answer_name) && + ($file_ext eq $answer_ext)) { + # gets here if filename and extension match, + # regardless of version + if ($file_version ne '') { + # a versioned file is found so save it for later + if ($file_version > $version) { + $version = $file_version; + } + } + } + } + } + $version ++; + return($version); +} + +sub version_selected_portfile { + my ($domain,$stu_name,$directory,$file_name,$version) = @_; + my ($answer_name,$answer_ver,$answer_ext) = + &file_name_version_ext($file_name); + my $new_answer; + $env{'form.copy'} = + &getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name"); + if($env{'form.copy'} eq '-1') { + $new_answer = 'problem getting file'; + } else { + $new_answer = $answer_name.'.'.$version.'.'.$answer_ext; + my $copy_result = + &finishuserfileupload($stu_name,$domain,'copy', + '/portfolio'.$directory.$new_answer); + } + undef($env{'form.copy'}); + return ($new_answer); +} + +sub file_name_version_ext { + my ($file)=@_; + my @file_parts = split(/\./, $file); + my ($name,$version,$ext); + if (@file_parts > 1) { + $ext=pop(@file_parts); + if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) { + $version=pop(@file_parts); + } + $name=join('.',@file_parts); + } else { + $name=join('.',@file_parts); + } + return($name,$version,$ext); +} + #----------------------------------------------Get portfolio file permissions sub get_portfile_permissions { @@ -7386,9 +10332,9 @@ sub modify_access_controls { my $tries = 0; my $gotlock = &newput('file_permissions',$lockhash,$domain,$user); - while (($gotlock ne 'ok') && $tries <3) { + while (($gotlock ne 'ok') && $tries < 10) { $tries ++; - sleep 1; + sleep(0.1); $gotlock = &newput('file_permissions',$lockhash,$domain,$user); } if ($gotlock eq 'ok') { @@ -7434,49 +10380,132 @@ sub modify_access_controls { } sub make_public_indefinitely { - my ($requrl) = @_; + my (@requrl) = @_; + return &automated_portfile_access('public',\@requrl); +} + +sub automated_portfile_access { + my ($accesstype,$addsref,$delsref,$info) = @_; + unless (($accesstype eq 'public') || ($accesstype eq 'ip')) { + return 'invalid'; + } + my %urls; + if (ref($addsref) eq 'ARRAY') { + foreach my $requrl (@{$addsref}) { + if (&is_portfolio_url($requrl)) { + unless (exists($urls{$requrl})) { + $urls{$requrl} = 'add'; + } + } + } + } + if (ref($delsref) eq 'ARRAY') { + foreach my $requrl (@{$delsref}) { + if (&is_portfolio_url($requrl)) { + unless (exists($urls{$requrl})) { + $urls{$requrl} = 'delete'; + } + } + } + } + unless (keys(%urls)) { + return 'invalid'; + } + my $ip; + if ($accesstype eq 'ip') { + if (ref($info) eq 'HASH') { + if ($info->{'ip'} ne '') { + $ip = $info->{'ip'}; + } + } + if ($ip eq '') { + return 'invalid'; + } + } + my $errors; my $now = time; - my $action = 'activate'; - my $aclnum = 0; - if (&is_portfolio_url($requrl)) { + my %current_perms; + foreach my $requrl (sort(keys(%urls))) { + my $action; + if ($urls{$requrl} eq 'add') { + $action = 'activate'; + } else { + $action = 'none'; + } + my $aclnum = 0; my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl); - my $current_perms = &get_portfile_permissions($udom,$unum); - my %access_controls = &get_access_controls($current_perms, + unless (exists($current_perms{$unum.':'.$udom})) { + $current_perms{$unum.':'.$udom} = &get_portfile_permissions($udom,$unum); + } + my %access_controls = &get_access_controls($current_perms{$unum.':'.$udom}, $group,$file_name); foreach my $key (keys(%{$access_controls{$file_name}})) { my ($num,$scope,$end,$start) = ($key =~ /^([^:]+):([a-z]+)_(\d*)_?(\d*)$/); - if ($scope eq 'public') { - if ($start <= $now && $end == 0) { - $action = 'none'; - } else { + if ($scope eq $accesstype) { + if (($start <= $now) && ($end == 0)) { + if ($accesstype eq 'ip') { + if (ref($access_controls{$file_name}{$key}) eq 'HASH') { + if (ref($access_controls{$file_name}{$key}{'ip'}) eq 'ARRAY') { + if (grep(/^\Q$ip\E$/,@{$access_controls{$file_name}{$key}{'ip'}})) { + if ($urls{$requrl} eq 'add') { + $action = 'none'; + last; + } else { + $action = 'delete'; + $aclnum = $num; + last; + } + } + } + } + } elsif ($accesstype eq 'public') { + if ($urls{$requrl} eq 'add') { + $action = 'none'; + last; + } else { + $action = 'delete'; + $aclnum = $num; + last; + } + } + } elsif ($accesstype eq 'public') { $action = 'update'; $aclnum = $num; + last; } - last; } } if ($action eq 'none') { - return 'ok'; + next; } else { my %changes; my $newend = 0; my $newstart = $now; - my $newkey = $aclnum.':public_'.$newend.'_'.$newstart; + my $newkey = $aclnum.':'.$accesstype.'_'.$newend.'_'.$newstart; $changes{$action}{$newkey} = { - type => 'public', + type => $accesstype, time => { start => $newstart, end => $newend, }, }; + if ($accesstype eq 'ip') { + $changes{$action}{$newkey}{'ip'} = [$ip]; + } my ($outcome,$deloutcome,$new_values,$translation) = &modify_access_controls($file_name,\%changes,$udom,$unum); - return $outcome; + unless ($outcome eq 'ok') { + $errors .= $outcome.' '; + } } + } + if ($errors) { + $errors =~ s/\s$//; + return $errors; } else { - return 'invalid'; + return 'ok'; } } @@ -7621,26 +10650,33 @@ sub dirlist { if($udom) { if($uname) { + my $uhome = &homeserver($uname,$udom); + if ($uhome eq 'no_host') { + return ([],'no_host'); + } $listing = &reply('ls3:'.&escape('/'.$uri).':'.$getpropath.':' .$getuserdir.':'.&escape($dirRoot) - .':'.&escape($uname).':'.&escape($udom), - &homeserver($uname,$udom)); + .':'.&escape($uname).':'.&escape($udom),$uhome); if ($listing eq 'unknown_cmd') { - $listing = &reply('ls2:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls2:'.$dirRoot.'/'.$uri,$uhome); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } if ($listing eq 'unknown_cmd') { - $listing = &reply('ls:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls:'.$dirRoot.'/'.$uri,$uhome); @listing_results = split(/:/,$listing); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } - return @listing_results; + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + return ([],$listing); + } else { + return (\@listing_results); + } } elsif(!$alternateRoot) { - my %allusers; + my (%allusers,%listerror); my %servers = &get_servers($udom,'library'); foreach my $tryserver (keys(%servers)) { $listing = &reply('ls3:'.&escape("/res/$udom").':::::'. @@ -7659,32 +10695,48 @@ sub dirlist { @listing_results = map { &unescape($_); } split(/:/,$listing); } - if ($listing_results[0] ne 'no_such_dir' && - $listing_results[0] ne 'empty' && - $listing_results[0] ne 'con_lost') { + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + $listerror{$tryserver} = $listing; + } else { foreach my $line (@listing_results) { my ($entry) = split(/&/,$line,2); $allusers{$entry} = 1; } } } - my $alluserstr=''; + my @alluserslist=(); foreach my $user (sort(keys(%allusers))) { - $alluserstr.=$user.'&user:'; + push(@alluserslist,$user.'&user'); } - $alluserstr=~s/:$//; - return split(/:/,$alluserstr); + + if (!%listerror) { + # no errors + return (\@alluserslist); + } elsif (scalar(keys(%servers)) == 1) { + # one library server, one error + my ($key) = keys(%listerror); + return (\@alluserslist, $listerror{$key}); + } elsif ( grep { $_ eq 'con_lost' } values(%listerror) ) { + # con_lost indicates that we might miss data from at least one + # library server + return (\@alluserslist, 'con_lost'); + } else { + # multiple library servers and no con_lost -> data should be + # complete. + return (\@alluserslist); + } + } else { - return ('missing user name'); + return ([],'missing username'); } } elsif(!defined($getpropath)) { - my @all_domains = sort(&all_domains()); - foreach my $domain (@all_domains) { - $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain'; - } - return @all_domains; + my $path = $perlvar{'lonDocRoot'}.'/res/'; + my @all_domains = map { $path.$_.'/&domain'; } (sort(&all_domains())); + return (\@all_domains); } else { - return ('missing domain'); + return ([],'missing domain'); } } @@ -7697,11 +10749,13 @@ sub GetFileTimestamp { my ($studentDomain,$studentName,$filename,$getuserdir)=@_; $studentDomain = &LONCAPA::clean_domain($studentDomain); $studentName = &LONCAPA::clean_username($studentName); - my ($fileStat) = - &Apache::lonnet::dirlist($filename,$studentDomain,$studentName, - undef,$getuserdir); - my @stats = split('&', $fileStat); - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { + my ($fileref,$error) = &dirlist($filename,$studentDomain,$studentName, + undef,$getuserdir); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return -1; + } + if (ref($fileref) eq 'ARRAY') { + my @stats = split('&',$fileref->[0]); # @stats contains first the filename, then the stat output return $stats[10]; # so this is 10 instead of 9. } else { @@ -7733,16 +10787,128 @@ sub stat_file { if ($file =~ /^userfiles\//) { $getpropath = 1; } - my ($result) = &dirlist($file,$udom,$uname,$getpropath); - my @stats = split('&', $result); - - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { - shift(@stats); #filename is first - return @stats; + my ($listref,$error) = &dirlist($file,$udom,$uname,$getpropath); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return (); + } else { + if (ref($listref) eq 'ARRAY') { + my @stats = split('&',$listref->[0]); + shift(@stats); #filename is first + return @stats; + } } return (); } +# --------------------------------------------------------- recursedirs +# Recursive function to traverse either a specific user's Authoring Space +# or corresponding Published Resource Space, and populate the hash ref: +# $dirhashref with URLs of all directories, and if $filehashref hash +# ref arg is provided, the URLs of any files, excluding versioned, .meta, +# or .rights files in resource space, and .meta, .save, .log, and .bak +# files in Authoring Space. +# +# Inputs: +# +# $is_home - true if current server is home server for user's space +# $context - either: priv, or res respectively for Authoring or Resource Space. +# $docroot - Document root (i.e., /home/httpd/html +# $toppath - Top level directory (i.e., /res/$dom/$uname or /priv/$dom/$uname +# $relpath - Current path (relative to top level). +# $dirhashref - reference to hash to populate with URLs of directories (Required) +# $filehashref - reference to hash to populate with URLs of files (Optional) +# +# Returns: nothing +# +# Side Effects: populates $dirhashref, and $filehashref (if provided). +# +# Currently used by interface/londocs.pm to create linked select boxes for +# directory and filename to import a Course "Author" resource into a course, and +# also to create linked select boxes for Authoring Space and Directory to choose +# save location for creation of a new "standard" problem from the Course Editor. +# + +sub recursedirs { + my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_; + return unless (ref($dirhashref) eq 'HASH'); + my $currpath = $docroot.$toppath; + if ($relpath) { + $currpath .= "/$relpath"; + } + my $savefile; + if (ref($filehashref)) { + $savefile = 1; + } + if ($is_home) { + if (opendir(my $dirh,$currpath)) { + foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) { + next if ($item eq ''); + if (-d "$currpath/$item") { + my $newpath; + if ($relpath) { + $newpath = "$relpath/$item"; + } else { + $newpath = $item; + } + $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1; + &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref); + } elsif ($savefile) { + if ($context eq 'priv') { + unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) { + $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1; + } + } else { + unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/) || ($item =~ /\.rights$/)) { + $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1; + } + } + } + } + closedir($dirh); + } + } else { + my ($dirlistref,$listerror) = + &dirlist($toppath.$relpath); + my @dir_lines; + my $dirptr=16384; + if (ref($dirlistref) eq 'ARRAY') { + foreach my $dir_line (sort + { + my ($afile)=split('&',$a,2); + my ($bfile)=split('&',$b,2); + return (lc($afile) cmp lc($bfile)); + } (@{$dirlistref})) { + my ($item,$dom,undef,$testdir,undef,undef,undef,undef,$size,undef,$mtime,undef,undef,undef,$obs,undef) = + split(/\&/,$dir_line,16); + $item =~ s/\s+$//; + next if (($item =~ /^\.\.?$/) || ($obs)); + if ($dirptr&$testdir) { + my $newpath; + if ($relpath) { + $newpath = "$relpath/$item"; + } else { + $relpath = '/'; + $newpath = $item; + } + $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1; + &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref); + } elsif ($savefile) { + if ($context eq 'priv') { + unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) { + $filehashref->{$relpath}{$item} = 1; + } + } else { + unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/)) { + $filehashref->{$relpath}{$item} = 1; + } + } + } + } + } + } + return; +} + # -------------------------------------------------------- Value of a Condition # gets the value of a specific preevaluated condition @@ -7887,10 +11053,12 @@ sub get_userresdata { } #error 2 occurs when the .db doesn't exist if ($tmp!~/error: 2 /) { - &logthis("WARNING:". - " Trying to get resource data for ". - $uname." at ".$udom.": ". - $tmp.""); + if ((!defined($cached)) || ($tmp ne 'con_lost')) { + &logthis("WARNING:". + " Trying to get resource data for ". + $uname." at ".$udom.": ". + $tmp.""); + } } elsif ($tmp=~/error: 2 /) { #&EXT_cache_set($udom,$uname); &do_cache_new('userres',$hashid,undef,600); @@ -7904,7 +11072,14 @@ sub get_userresdata { # Parameters: # $name - Course/user name. # $domain - Name of the domain the user/course is registered on. -# $type - Type of thing $name is (must be 'course' or 'user' +# $type - Type of thing $name is (must be 'course' or 'user') +# $mapp - decluttered URL of enclosing map +# $recursed - Ref to scalar -- set to 1, if nested maps have been recursed. +# $recurseup - Ref to array of map URLs, starting with map containing +# $mapp up through hierarchy of nested maps to top level map. +# $courseid - CourseID (first part of param identifier). +# $modifier - Middle part of param identifier. +# $what - Last part of param identifier. # @which - Array of names of resources desired. # Returns: # The value of the first reasource in @which that is found in the @@ -7914,7 +11089,8 @@ sub get_userresdata { # 'user', an undefined reference is returned. # If none of the resources are found, an undef is returned sub resdata { - my ($name,$domain,$type,@which)=@_; + my ($name,$domain,$type,$mapp,$recursed,$recurseup,$courseid, + $modifier,$what,@which)=@_; my $result; if ($type eq 'course') { $result=&get_courseresdata($name,$domain); @@ -7923,17 +11099,90 @@ sub resdata { } if (!ref($result)) { return $result; } foreach my $item (@which) { - if (defined($result->{$item->[0]})) { + if ($item->[1] eq 'course') { + if ((ref($recurseup) eq 'ARRAY') && (ref($recursed) eq 'SCALAR')) { + unless ($$recursed) { + @{$recurseup} = &get_map_hierarchy($mapp,$courseid); + $$recursed = 1; + } + foreach my $item (@${recurseup}) { + my $norecursechk=$courseid.$modifier.$item.'___(all).'.$what; + last if (defined($result->{$norecursechk})); + my $recursechk=$courseid.$modifier.$item.'___(rec).'.$what; + if (defined($result->{$recursechk})) { return [$result->{$recursechk},'map']; } + } + } + } + if (defined($result->{$item->[0]})) { return [$result->{$item->[0]},$item->[1]]; } } return undef; } +sub get_domain_ltitools { + my ($cdom) = @_; + my %ltitools; + my ($result,$cached)=&is_cached_new('ltitools',$cdom); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + %ltitools = %{$result}; + } + } else { + my %domconfig = &get_dom('configuration',['ltitools'],$cdom); + if (ref($domconfig{'ltitools'}) eq 'HASH') { + %ltitools = %{$domconfig{'ltitools'}}; + my %encdomconfig = &get_dom('encconfig',['ltitools'],$cdom); + if (ref($encdomconfig{'ltitools'}) eq 'HASH') { + foreach my $id (keys(%ltitools)) { + if (ref($encdomconfig{'ltitools'}{$id}) eq 'HASH') { + foreach my $item ('key','secret') { + $ltitools{$id}{$item} = $encdomconfig{'ltitools'}{$id}{$item}; + } + } + } + } + } + my $cachetime = 24*60*60; + &do_cache_new('ltitools',$cdom,\%ltitools,$cachetime); + } + return %ltitools; +} + +sub get_numsuppfiles { + my ($cnum,$cdom,$ignorecache)=@_; + my $hashid=$cnum.':'.$cdom; + my ($suppcount,$cached); + unless ($ignorecache) { + ($suppcount,$cached) = &is_cached_new('suppcount',$hashid); + } + unless (defined($cached)) { + my $chome=&homeserver($cnum,$cdom); + unless ($chome eq 'no_host') { + ($suppcount,my $errors) = (0,0); + my $suppmap = 'supplemental.sequence'; + ($suppcount,$errors) = + &Apache::loncommon::recurse_supplemental($cnum,$cdom,$suppmap,$suppcount,$errors); + } + &do_cache_new('suppcount',$hashid,$suppcount,600); + } + return $suppcount; +} + # # EXT resource caching routines # +{ +# Cache (5 seconds) of map hierarchy for speedup of navmaps display +# +# The course for which we cache +my $cachedmapkey=''; +# The cached recursive maps for this course +my %cachedmaps=(); +# When this was last done +my $cachedmaptime=''; + sub clear_EXT_cache_status { &delenv('cache.EXT.'); } @@ -7958,7 +11207,7 @@ sub EXT_cache_set { # --------------------------------------------------------- Value of a Variable sub EXT { - my ($varname,$symbparm,$udom,$uname,$usection,$recurse)=@_; + my ($varname,$symbparm,$udom,$uname,$usection,$recurse,$cid)=@_; unless ($varname) { return ''; } #get real user name/domain, courseid and symb my $courseid; @@ -8059,15 +11308,7 @@ sub EXT { } elsif ($realm eq 'request') { # ------------------------------------------------------------- request.browser if ($space eq 'browser') { - if ($qualifier eq 'textremote') { - if (&Apache::lonlocal::mt('textual_remote_display') eq 'on') { - return 1; - } else { - return 0; - } - } else { - return $env{'browser.'.$qualifier}; - } + return $env{'browser.'.$qualifier}; # ------------------------------------------------------------ request.filename } else { return $env{'request.'.$spacequalifierrest}; @@ -8081,36 +11322,60 @@ sub EXT { if (!$symbparm) { $symbparm=&symbread(); } } - if ($space eq 'title') { - if (!$symbparm) { $symbparm = $env{'request.filename'}; } - return &gettitle($symbparm); - } + if ($qualifier eq '') { + if ($space eq 'title') { + if (!$symbparm) { $symbparm = $env{'request.filename'}; } + return &gettitle($symbparm); + } - if ($space eq 'map') { - my ($map) = &decode_symb($symbparm); - return &symbread($map); - } - if ($space eq 'filename') { - if ($symbparm) { - return &clutter((&decode_symb($symbparm))[2]); + if ($space eq 'map') { + my ($map) = &decode_symb($symbparm); + return &symbread($map); + } + if ($space eq 'maptitle') { + my ($map) = &decode_symb($symbparm); + return &gettitle($map); + } + if ($space eq 'filename') { + if ($symbparm) { + return &clutter((&decode_symb($symbparm))[2]); + } + return &hreflocation('',$env{'request.filename'}); } - return &hreflocation('',$env{'request.filename'}); - } - my ($section, $group, @groups); - my ($courselevelm,$courselevel); - if ($symbparm && defined($courseid) && - $courseid eq $env{'request.course.id'}) { + if ((defined($courseid)) && ($courseid eq $env{'request.course.id'}) && $symbparm) { + if ($space eq 'visibleparts') { + my $navmap = Apache::lonnavmaps::navmap->new(); + my $item; + if (ref($navmap)) { + my $res = $navmap->getBySymb($symbparm); + my $parts = $res->parts(); + if (ref($parts) eq 'ARRAY') { + $item = join(',',@{$parts}); + } + undef($navmap); + } + return $item; + } + } + } + + my ($section, $group, @groups, @recurseup, $recursed); + my ($courselevelm,$courseleveli,$courselevel,$mapp); + if (($courseid eq '') && ($cid)) { + $courseid = $cid; + } + if (($symbparm && $courseid) && + (($courseid eq $env{'request.course.id'}) || ($courseid eq $cid))) { #print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; # ----------------------------------------------------- Cascading lookup scheme my $symbp=$symbparm; - my $mapp=&deversion((&decode_symb($symbp))[0]); - + $mapp=&deversion((&decode_symb($symbp))[0]); my $symbparm=$symbp.'.'.$spacequalifierrest; + my $recurseparm=$mapp.'___(rec).'.$spacequalifierrest; my $mapparm=$mapp.'___(all).'.$spacequalifierrest; - if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { $section=$env{'request.course.sec'}; @@ -8127,17 +11392,21 @@ sub EXT { my $seclevel=$courseid.'.['.$section.'].'.$spacequalifierrest; my $seclevelr=$courseid.'.['.$section.'].'.$symbparm; + my $secleveli=$courseid.'.['.$section.'].'.$recurseparm; my $seclevelm=$courseid.'.['.$section.'].'.$mapparm; $courselevel=$courseid.'.'.$spacequalifierrest; my $courselevelr=$courseid.'.'.$symbparm; + $courseleveli=$courseid.'.'.$recurseparm; $courselevelm=$courseid.'.'.$mapparm; # ----------------------------------------------------------- first, check user - my $userreply=&resdata($uname,$udom,'user', + my $userreply=&resdata($uname,$udom,'user',$mapp,\$recursed, + \@recurseup,$courseid,'.',$spacequalifierrest, ([$courselevelr,'resource'], [$courselevelm,'map' ], + [$courseleveli,'map' ], [$courselevel, 'course' ])); if (defined($userreply)) { return &get_reply($userreply); } @@ -8145,15 +11414,18 @@ sub EXT { my $coursereply; if (@groups > 0) { $coursereply = &check_group_parms($courseid,\@groups,$symbparm, - $mapparm,$spacequalifierrest); - if (defined($coursereply)) { return &get_reply($coursereply); } + $recurseparm,$mapparm,$spacequalifierrest, + $mapp,\$recursed,\@recurseup); + if (defined($coursereply)) { return &get_reply($coursereply); } } $coursereply=&resdata($env{'course.'.$courseid.'.num'}, $env{'course.'.$courseid.'.domain'}, - 'course', + 'course',$mapp,\$recursed,\@recurseup, + $courseid,'.['.$section.'].',$spacequalifierrest, ([$seclevelr, 'resource'], [$seclevelm, 'map' ], + [$secleveli, 'map' ], [$seclevel, 'course' ], [$courselevelr,'resource'])); if (defined($coursereply)) { return &get_reply($coursereply); } @@ -8170,8 +11442,9 @@ sub EXT { if ($thisparm) { return &get_reply([$thisparm,'resource']); } } # ------------------------------------------ fourth, look in resource metadata - - $spacequalifierrest=~s/\./\_/; + + my $what = $spacequalifierrest; + $what=~s/\./\_/; my $filename; if (!$symbparm) { $symbparm=&symbread(); } if ($symbparm) { @@ -8179,18 +11452,20 @@ sub EXT { } else { $filename=$env{'request.filename'}; } - my $metadata=&metadata($filename,$spacequalifierrest); + my $metadata=&metadata($filename,$what); if (defined($metadata)) { return &get_reply([$metadata,'resource']); } - $metadata=&metadata($filename,'parameter_'.$spacequalifierrest); + $metadata=&metadata($filename,'parameter_'.$what); if (defined($metadata)) { return &get_reply([$metadata,'resource']); } -# ---------------------------------------------- fourth, look in rest of course +# ----------------------------------------------- fifth, look in rest of course if ($symbparm && defined($courseid) && $courseid eq $env{'request.course.id'}) { my $coursereply=&resdata($env{'course.'.$courseid.'.num'}, $env{'course.'.$courseid.'.domain'}, - 'course', + 'course',$mapp,\$recursed,\@recurseup, + $courseid,'.',$spacequalifierrest, ([$courselevelm,'map' ], + [$courseleveli,'map' ], [$courselevel, 'course'])); if (defined($coursereply)) { return &get_reply($coursereply); } } @@ -8247,22 +11522,51 @@ sub get_reply { } sub check_group_parms { - my ($courseid,$groups,$symbparm,$mapparm,$what) = @_; - my @groupitems = (); - my $resultitem; - my @levels = ([$symbparm,'resource'],[$mapparm,'map'],[$what,'course']); + my ($courseid,$groups,$symbparm,$recurseparm,$mapparm,$what,$mapp, + $recursed,$recurseupref) = @_; + my @levels = ([$symbparm,'resource'],[$mapparm,'map'],[$recurseparm,'map'], + [$what,'course']); + my $coursereply; foreach my $group (@{$groups}) { + my @groupitems = (); foreach my $level (@levels) { my $item = $courseid.'.['.$group.'].'.$level->[0]; push(@groupitems,[$item,$level->[1]]); } + my $coursereply = &resdata($env{'course.'.$courseid.'.num'}, + $env{'course.'.$courseid.'.domain'}, + 'course',$mapp,$recursed,$recurseupref, + $courseid,'.['.$group.'].',$what, + @groupitems); + last if (defined($coursereply)); } - my $coursereply = &resdata($env{'course.'.$courseid.'.num'}, - $env{'course.'.$courseid.'.domain'}, - 'course',@groupitems); return $coursereply; } +sub get_map_hierarchy { + my ($mapname,$courseid) = @_; + my @recurseup = (); + if ($mapname) { + if (($cachedmapkey eq $courseid) && + (abs($cachedmaptime-time)<5)) { + if (ref($cachedmaps{$mapname}) eq 'ARRAY') { + return @{$cachedmaps{$mapname}}; + } + } + my $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + @recurseup = $navmap->recurseup_maps($mapname); + undef($navmap); + $cachedmaps{$mapname} = \@recurseup; + $cachedmaptime=time; + $cachedmapkey=$courseid; + } + } + return @recurseup; +} + +} + sub sort_course_groups { # Sort groups based on defined rankings. Default is sort(). my ($courseid,@groups) = @_; @groups = sort(@groups); @@ -8347,11 +11651,11 @@ sub metadata { # if it is a non metadata possible uri return quickly if (($uri eq '') || (($uri =~ m|^/*adm/|) && - ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) || - ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) ) { + ($uri !~ m|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard|ext\.tool)$})) || + ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) { return undef; } - if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) + if (($uri =~ /^priv/ || $uri=~m{^home/httpd/html/priv}) && &Apache::lonxml::get_state('target') =~ /^(|meta)$/) { return undef; } @@ -8388,13 +11692,14 @@ sub metadata { my %metathesekeys=(); unless ($filename=~/\.meta$/) { $filename.='.meta'; } my $metastring; - if ($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) { + if ($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) { my $which = &hreflocation('','/'.($liburi || $uri)); $metastring = &Apache::lonnet::ssi_body($which, ('grade_target' => 'meta')); $cachetime = 1; # only want this cached in the child not long term - } elsif ($uri !~ m -^(editupload)/-) { + } elsif (($uri !~ m -^(editupload)/-) && + ($uri !~ m{^/*uploaded/$match_domain/$match_courseid/docs/})) { my $file=&filelocation('',&clutter($filename)); #push(@{$metaentry{$uri.'.file'}},$file); $metastring=&getfile($file); @@ -8611,7 +11916,7 @@ sub metadata { $metaentry{':keys'} = join(',',keys(%metathesekeys)); &metadata_generate_part0(\%metathesekeys,\%metaentry,$uri); - $metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys); + $metaentry{':allpossiblekeys'}=join(',',keys(%metathesekeys)); &do_cache_new('meta',$uri,\%metaentry,$cachetime); # this is the end of "was not already recently cached } @@ -8711,6 +12016,10 @@ sub gettitle { } $title=~s/\&colon\;/\:/gs; if ($title) { +# Remember both $symb and $title for dynamic metadata + $accesshash{$symb.'___crstitle'}=$title; + $accesshash{&declutter($map).'___'.&declutter($url).'___usage'}=time; +# Cache this title and then return it return &do_cache_new('title',$key,$title,600); } $urlsymb=$url; @@ -8743,6 +12052,84 @@ sub get_slot { } return $slotinfo{$which}; } + +sub get_reservable_slots { + my ($cnum,$cdom,$uname,$udom) = @_; + my $now = time; + my $reservable_info; + my $key=join("\0",'reservableslots',$cdom,$cnum,$uname,$udom); + if (exists($remembered{$key})) { + $reservable_info = $remembered{$key}; + } else { + my %resv; + ($resv{'now_order'},$resv{'now'},$resv{'future_order'},$resv{'future'}) = + &Apache::loncommon::get_future_slots($cnum,$cdom,$now); + $reservable_info = \%resv; + $remembered{$key} = $reservable_info; + } + return $reservable_info; +} + +sub get_course_slots { + my ($cnum,$cdom) = @_; + my $hashid=$cnum.':'.$cdom; + my ($result,$cached) = &Apache::lonnet::is_cached_new('allslots',$hashid); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + return %{$result}; + } + } else { + my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); + my ($tmp) = keys(%slots); + if ($tmp !~ /^(con_lost|error|no_such_host)/i) { + &do_cache_new('allslots',$hashid,\%slots,600); + return %slots; + } + } + return; +} + +sub devalidate_slots_cache { + my ($cnum,$cdom)=@_; + my $hashid=$cnum.':'.$cdom; + &devalidate_cache_new('allslots',$hashid); +} + +sub get_coursechange { + my ($cdom,$cnum) = @_; + if ($cdom eq '' || $cnum eq '') { + return unless ($env{'request.course.id'}); + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } + my $hashid=$cdom.'_'.$cnum; + my ($change,$cached)=&is_cached_new('crschange',$hashid); + if ((defined($cached)) && ($change ne '')) { + return $change; + } else { + my %crshash; + %crshash = &get('environment',['internal.contentchange'],$cdom,$cnum); + if ($crshash{'internal.contentchange'} eq '') { + $change = $env{'course.'.$cdom.'_'.$cnum.'.internal.created'}; + if ($change eq '') { + %crshash = &get('environment',['internal.created'],$cdom,$cnum); + $change = $crshash{'internal.created'}; + } + } else { + $change = $crshash{'internal.contentchange'}; + } + my $cachetime = 600; + &do_cache_new('crschange',$hashid,$change,$cachetime); + } + return $change; +} + +sub devalidate_coursechange_cache { + my ($cnum,$cdom)=@_; + my $hashid=$cnum.':'.$cdom; + &devalidate_cache_new('crschange',$hashid); +} + # ------------------------------------------------- Update symbolic store links sub symblist { @@ -8770,7 +12157,7 @@ sub symblist { # --------------------------------------------------------------- Verify a symb sub symbverify { - my ($symb,$thisurl)=@_; + my ($symb,$thisurl,$encstate)=@_; my $thisfn=$thisurl; $thisfn=&declutter($thisfn); # direct jump to resource in page or to a sequence - will construct own symbs @@ -8789,26 +12176,43 @@ sub symbverify { if (tie(%bighash,'GDBM_File',$env{'request.course.fn'}.'.db', &GDBM_READER(),0640)) { + my $noclutter; if (($thisurl =~ m{^/adm/wrapper/ext/}) || ($thisurl =~ m{^ext/})) { $thisurl =~ s/\?.+$//; + if ($map =~ m{^uploaded/.+\.page$}) { + $thisurl =~ s{^(/adm/wrapper|)/ext/}{http://}; + $thisurl =~ s{^\Qhttp://https://\E}{https://}; + $noclutter = 1; + } + } + my $ids; + if ($noclutter) { + $ids=$bighash{'ids_'.$thisurl}; + } else { + $ids=$bighash{'ids_'.&clutter($thisurl)}; } - my $ids=$bighash{'ids_'.&clutter($thisurl)}; - unless ($ids) { - $ids=$bighash{'ids_/'.$thisurl}; + unless ($ids) { + my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl; + $ids=$bighash{$idkey}; } if ($ids) { # ------------------------------------------------------------------- Has ID(s) + if ($thisfn =~ m{^/adm/wrapper/ext/}) { + $symb =~ s/\?.+$//; + } foreach my $id (split(/\,/,$ids)) { my ($mapid,$resid)=split(/\./,$id); - if ($thisfn =~ m{^/adm/wrapper/ext/}) { - $symb =~ s/\?.+$//; - } if ( &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn) - eq $symb) { + eq $symb) { + if (ref($encstate)) { + $$encstate = $bighash{'encrypted_'.$id}; + } if (($env{'request.role.adv'}) || - $bighash{'encrypted_'.$id} eq $env{'request.enc'}) { - $okay=1; + ($bighash{'encrypted_'.$id} eq $env{'request.enc'}) || + ($thisurl eq '/adm/navmaps')) { + $okay=1; + last; } } } @@ -8883,9 +12287,15 @@ sub deversion { # ------------------------------------------------------ Return symb list entry sub symbread { - my ($thisfn,$donotrecurse)=@_; + my ($thisfn,$donotrecurse,$ignorecachednull,$checkforblock,$possibles)=@_; my $cache_str='request.symbread.cached.'.$thisfn; - if (defined($env{$cache_str})) { return $env{$cache_str}; } + if (defined($env{$cache_str})) { + if ($ignorecachednull) { + return $env{$cache_str} unless ($env{$cache_str} eq ''); + } else { + return $env{$cache_str}; + } + } # no filename provided? try from environment unless ($thisfn) { if ($env{'request.symb'}) { @@ -8947,18 +12357,46 @@ sub symbread { my ($mapid,$resid)=split(/\./,$ids); $syval=&encode_symb($bighash{'map_id_'.$mapid}, $resid,$thisfn); - } elsif (!$donotrecurse) { + if (ref($possibles) eq 'HASH') { + $possibles->{$syval} = 1; + } + if ($checkforblock) { + my @blockers = &has_comm_blocking('bre',$syval,$bighash{'src_'.$ids}); + if (@blockers) { + $syval = ''; + return; + } + } + } elsif ((!$donotrecurse) || ($checkforblock) || (ref($possibles) eq 'HASH')) { # ------------------------------------------ There is more than one possibility my $realpossible=0; foreach my $id (@possibilities) { my $file=$bighash{'src_'.$id}; - if (&allowed('bre',$file)) { - my ($mapid,$resid)=split(/\./,$id); - if ($bighash{'map_type_'.$mapid} ne 'page') { - $realpossible++; - $syval=&encode_symb($bighash{'map_id_'.$mapid}, - $resid,$thisfn); - } + my $canaccess; + if (($donotrecurse) || ($checkforblock) || (ref($possibles) eq 'HASH')) { + $canaccess = 1; + } else { + $canaccess = &allowed('bre',$file); + } + if ($canaccess) { + my ($mapid,$resid)=split(/\./,$id); + if ($bighash{'map_type_'.$mapid} ne 'page') { + my $poss_syval=&encode_symb($bighash{'map_id_'.$mapid}, + $resid,$thisfn); + if (ref($possibles) eq 'HASH') { + $possibles->{$syval} = 1; + } + if ($checkforblock) { + my @blockers = &has_comm_blocking('bre',$poss_syval,$file); + unless (@blockers > 0) { + $syval = $poss_syval; + $realpossible++; + } + } else { + $syval = $poss_syval; + $realpossible++; + } + } } } if ($realpossible!=1) { $syval=''; } @@ -8966,7 +12404,7 @@ sub symbread { $syval=''; } } - untie(%bighash) + untie(%bighash); } } if ($syval) { @@ -9082,19 +12520,44 @@ sub getCODE { } return undef; } - +# +# Determines the random seed for a specific context: +# +# parameters: +# symb - in course context the symb for the seed. +# course_id - The course id of the form domain_coursenum. +# domain - Domain for the user. +# course - Course for the user. +# cenv - environment of the course. +# +# NOTE: +# All parameters are picked out of the environment if missing +# or not defined. +# If a symb cannot be determined the current time is used instead. +# +# For a given well defined symb, courside, domain, username, +# and course environment, the seed is reproducible. +# sub rndseed { - my ($symb,$courseid,$domain,$username)=@_; + my ($symb,$courseid,$domain,$username, $cenv)=@_; my ($wsymb,$wcourseid,$wdomain,$wusername)=&whichuser(); if (!defined($symb)) { unless ($symb=$wsymb) { return time; } } - if (!$courseid) { $courseid=$wcourseid; } - if (!$domain) { $domain=$wdomain; } - if (!$username) { $username=$wusername } - my $which=&get_rand_alg(); + if (!defined $courseid) { + $courseid=$wcourseid; + } + if (!defined $domain) { $domain=$wdomain; } + if (!defined $username) { $username=$wusername } + my $which; + if (defined($cenv->{'rndseed'})) { + $which = $cenv->{'rndseed'}; + } else { + $which =&get_rand_alg($courseid); + } if (defined(&getCODE())) { + if ($which eq '64bit5') { return &rndseed_CODE_64bit5($symb,$courseid,$domain,$username); } elsif ($which eq '64bit4') { @@ -9280,8 +12743,12 @@ sub rndseed_CODE_64bit5 { sub setup_random_from_rndseed { my ($rndseed)=@_; if ($rndseed =~/([,:])/) { - my ($num1,$num2)=split(/[,:]/,$rndseed); - &Math::Random::random_set_seed(abs($num1),abs($num2)); + my ($num1,$num2) = map { abs($_); } (split(/[,:]/,$rndseed)); + if ((!$num1) || (!$num2) || ($num1 > 2147483562) || ($num2 > 2147483398)) { + &Math::Random::random_set_seed_from_phrase($rndseed); + } else { + &Math::Random::random_set_seed($num1,$num2); + } } else { &Math::Random::random_set_seed_from_phrase($rndseed); } @@ -9418,8 +12885,9 @@ sub getfile { sub repcopy_userfile { my ($file)=@_; - if ($file =~ m -^/*(uploaded|editupload)/-) { $file=&filelocation("",$file); } - if ($file =~ m|^/home/httpd/html/lonUsers/|) { return 'ok'; } + my $londocroot = $perlvar{'lonDocRoot'}; + if ($file =~ m{^/*(uploaded|editupload)/}) { $file=&filelocation("",$file); } + if ($file =~ m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; } my ($cdom,$cnum,$filename) = ($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+($match_domain)/+($match_name)/+(.*)|); my $uri="/uploaded/$cdom/$cnum/$filename"; @@ -9459,7 +12927,6 @@ sub repcopy_userfile { } # now the path exists for sure # get a user agent - my $ua=new LWP::UserAgent; my $transferfile=$file.'.in.transfer'; # FIXME: this should flock if (-e $transferfile) { return 'ok'; } @@ -9469,7 +12936,7 @@ sub repcopy_userfile { my $protocol = $protocol{$homeserver}; $protocol = 'http' if ($protocol ne 'https'); $request=new HTTP::Request('GET',$protocol.'://'.&hostname($homeserver).'/raw/'.$uri); - my $response=$ua->request($request,$transferfile); + my $response = &LONCAPA::LWPReq::makerequest($homeserver,$request,$transferfile,\%perlvar,'',0,1); # did it work? if ($response->is_error()) { unlink($transferfile); @@ -9513,9 +12980,8 @@ sub getuploaded { my $protocol = $protocol{$homeserver}; $protocol = 'http' if ($protocol ne 'https'); $uri = $protocol.'://'.&hostname($homeserver).'/raw/'.$uri; - my $ua=new LWP::UserAgent; my $request=new HTTP::Request($reqtype,$uri); - my $response=$ua->request($request); + my $response=&LONCAPA::LWPReq::makerequest($homeserver,$request,'',\%perlvar,'',0,1); $$rtncode = $response->code; if (! $response->is_success()) { return 'failed'; @@ -9548,13 +13014,7 @@ sub filelocation { $file=~s-^/adm/coursedocs/showdoc/-/-; } - if ($file=~m:^/~:) { # is a contruction space reference - $location = $file; - $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:; - } elsif ($file=~m{^/home/$match_username/public_html/}) { - # is a correct contruction space reference - $location = $file; - } elsif ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) { + if ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) { $location = $file; } elsif ($file=~/^\/*(uploaded|editupload)/) { # is an uploaded file my ($udom,$uname,$filename)= @@ -9564,7 +13024,7 @@ sub filelocation { my @ids=¤t_machine_ids(); foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } } if ($is_me) { - $location=&propath($udom,$uname).'/userfiles/'.$filename; + $location=propath($udom,$uname).'/userfiles/'.$filename; } else { $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'. $udom.'/'.$uname.'/'.$filename; @@ -9573,11 +13033,12 @@ sub filelocation { $location = $perlvar{'lonDocRoot'}.'/'.$file; } else { $file=~s/^\Q$perlvar{'lonDocRoot'}\E//; - $file=~s:^/res/:/:; + $file=~s:^/(res|priv)/:/:; + my $space=$1; if ( !( $file =~ m:^/:) ) { $location = $dir. '/'.$file; } else { - $location = '/home/httpd/html/res'.$file; + $location = $perlvar{'lonDocRoot'}.'/'.$space.$file; } } $location=~s://+:/:g; # remove duplicate / @@ -9602,11 +13063,9 @@ sub hreflocation { } if ($file=~m-^\Q$perlvar{'lonDocRoot'}\E-) { $file=~s-^\Q$perlvar{'lonDocRoot'}\E--; - } elsif ($file=~m-/home/($match_username)/public_html/-) { - $file=~s-^/home/($match_username)/public_html/-/~$1/-; } elsif ($file=~m-^\Q$perlvar{'lonUsersDir'}\E-) { - $file=~s-^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/ - -/uploaded/$1/$2/-x; + $file=~s{^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/} + {/uploaded/$1/$2/}x; } if ($file=~ m{^/userfiles/}) { $file =~ s{^/userfiles/}{/uploaded/}; @@ -9614,6 +13073,10 @@ sub hreflocation { return $file; } + + + + sub current_machine_domains { return &machine_domains(&hostname($perlvar{'lonHostID'})); } @@ -9674,11 +13137,14 @@ sub default_login_domain { sub declutter { my $thisfn=shift; if ($thisfn=~m|^/enc/|) { $thisfn=&Apache::lonenc::unencrypted($thisfn); } - $thisfn=~s/^\Q$perlvar{'lonDocRoot'}\E//; + unless ($thisfn=~m{^/home/httpd/html/priv/}) { + $thisfn=~s{^/home/httpd/html}{}; + } $thisfn=~s/^\///; $thisfn=~s|^adm/wrapper/||; $thisfn=~s|^adm/coursedocs/showdoc/||; $thisfn=~s/^res\///; + $thisfn=~s/^priv\///; unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) { $thisfn=~s/\?.+$//; } @@ -9716,6 +13182,8 @@ sub clutter { # &logthis("Got a blank emb style"); } } + } elsif ($thisfn =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) { + $thisfn='/adm/wrapper'.$thisfn; } return $thisfn; } @@ -9778,12 +13246,12 @@ sub goodbye { } sub get_dns { - my ($url,$func,$ignore_cache) = @_; + my ($url,$func,$ignore_cache,$nocache,$hashref) = @_; if (!$ignore_cache) { my ($content,$cached)= &Apache::lonnet::is_cached_new('dns',$url); if ($cached) { - &$func($content); + &$func($content,$hashref); return; } } @@ -9800,15 +13268,16 @@ sub get_dns { $alldns{$host} = $protocol; } while (%alldns) { - my ($dns) = keys(%alldns); - my $ua=new LWP::UserAgent; + my ($dns) = sort { $b cmp $a } keys(%alldns); my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url"); - my $response=$ua->request($request); + my $response = &LONCAPA::LWPReq::makerequest('',$request,'',\%perlvar,30,0); delete($alldns{$dns}); next if ($response->is_error()); my @content = split("\n",$response->content); - &Apache::lonnet::do_cache_new('dns',$url,\@content,30*24*60*60); - &$func(\@content); + unless ($nocache) { + &do_cache_new('dns',$url,\@content,30*24*60*60); + } + &$func(\@content,$hashref); return; } close($config); @@ -9816,9 +13285,66 @@ sub get_dns { &logthis("unable to contact DNS defaulting to on disk file dns_$which.tab\n"); open($config,"<$perlvar{'lonTabDir'}/dns_$which.tab"); my @content = <$config>; - &$func(\@content); + &$func(\@content,$hashref); return; } + +# ------------------------------------------------------Get DNS checksums file +sub parse_dns_checksums_tab { + my ($lines,$hashref) = @_; + my $lonhost = $perlvar{'lonHostID'}; + my $machine_dom = &Apache::lonnet::host_domain($lonhost); + my $loncaparev = &get_server_loncaparev($machine_dom); + my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0]; + my $webconfdir = '/etc/httpd/conf'; + if ($distro =~ /^(ubuntu|debian)(\d+)$/) { + $webconfdir = '/etc/apache2'; + } elsif ($distro =~ /^sles(\d+)$/) { + if ($1 >= 10) { + $webconfdir = '/etc/apache2'; + } + } elsif ($distro =~ /^suse(\d+\.\d+)$/) { + if ($1 >= 10.0) { + $webconfdir = '/etc/apache2'; + } + } + my ($release,$timestamp) = split(/\-/,$loncaparev); + my (%chksum,%revnum); + if (ref($lines) eq 'ARRAY') { + chomp(@{$lines}); + my $version = shift(@{$lines}); + if ($version eq $release) { + foreach my $line (@{$lines}) { + my ($file,$version,$shasum) = split(/,/,$line); + if ($file =~ m{^/etc/httpd/conf}) { + if ($webconfdir eq '/etc/apache2') { + $file =~ s{^\Q/etc/httpd/conf/\E}{$webconfdir/}; + } + } + $chksum{$file} = $shasum; + $revnum{$file} = $version; + } + if (ref($hashref) eq 'HASH') { + %{$hashref} = ( + sums => \%chksum, + versions => \%revnum, + ); + } + } + } + return; +} + +sub fetch_dns_checksums { + my %checksums; + my $machine_dom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); + my $loncaparev = &get_server_loncaparev($machine_dom,$perlvar{'lonHostID'}); + my ($release,$timestamp) = split(/\-/,$loncaparev); + &get_dns("/adm/dns/checksums/$release",\&parse_dns_checksums_tab,1,1, + \%checksums); + return \%checksums; +} + # ------------------------------------------------------------ Read domain file { my $loaded; @@ -9847,8 +13373,8 @@ sub get_dns { } sub load_domain_tab { - my ($ignore_cache) = @_; - &get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache); + my ($ignore_cache,$nocache) = @_; + &get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache,$nocache); my $fh; if (open($fh,"<".$perlvar{'lonTabDir'}.'/domain.tab')) { my @lines = <$fh>; @@ -9886,18 +13412,39 @@ sub get_dns { my $loaded; my %name_to_host; my %internetdom; + my %LC_dns_serv; sub parse_hosts_tab { my ($file) = @_; foreach my $configline (@$file) { next if ($configline =~ /^(\#|\s*$ )/x); - next if ($configline =~ /^\^/); - chomp($configline); + chomp($configline); + if ($configline =~ /^\^/) { + if ($configline =~ /^\^([\w.\-]+)/) { + $LC_dns_serv{$1} = 1; + } + next; + } my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline); $name=~s/\s//g; if ($id && $domain && $role && $name) { + if ((exists($hostname{$id})) && ($hostname{$id} ne '')) { + my $curr = $hostname{$id}; + my $skip; + if (ref($name_to_host{$curr}) eq 'ARRAY') { + if (($curr eq $name) && (@{$name_to_host{$curr}} == 1)) { + $skip = 1; + } else { + @{$name_to_host{$curr}} = grep { $_ ne $id } @{$name_to_host{$curr}}; + } + } + unless ($skip) { + push(@{$name_to_host{$name}},$id); + } + } else { + push(@{$name_to_host{$name}},$id); + } $hostname{$id}=$name; - push(@{$name_to_host{$name}}, $id); $hostdom{$id}=$domain; if ($role eq 'library') { $libserv{$id}=$name; } if (defined($protocol)) { @@ -9920,6 +13467,7 @@ sub get_dns { &purge_remembered(); &reset_domain_info(); &reset_hosts_ip_info(); + undef(%internetdom); undef(%name_to_host); undef(%hostname); undef(%hostdom); @@ -9928,8 +13476,8 @@ sub get_dns { } sub load_hosts_tab { - my ($ignore_cache) = @_; - &get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache); + my ($ignore_cache,$nocache) = @_; + &get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache,$nocache); open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab"); my @config = <$config>; &parse_hosts_tab(\@config); @@ -9951,7 +13499,8 @@ sub get_dns { } sub all_names { - &load_hosts_tab() if (!$loaded); + my ($ignore_cache,$nocache) = @_; + &load_hosts_tab($ignore_cache,$nocache) if (!$loaded); return %name_to_host; } @@ -9961,6 +13510,11 @@ sub get_dns { return %hostdom; } + sub all_host_intdom { + &load_hosts_tab() if (!$loaded); + return %internetdom; + } + sub is_library { &load_hosts_tab() if (!$loaded); @@ -10028,6 +13582,14 @@ sub get_dns { my ($lonid) = @_; return $internetdom{$lonid}; } + + sub is_LC_dns { + &load_hosts_tab() if (!$loaded); + + my ($hostname) = @_; + return exists($LC_dns_serv{$hostname}); + } + } { @@ -10065,7 +13627,7 @@ sub get_dns { } sub get_iphost { - my ($ignore_cache) = @_; + my ($ignore_cache,$nocache) = @_; if (!$ignore_cache) { if (%iphost) { @@ -10089,7 +13651,7 @@ sub get_dns { %old_name_to_ip = %{$ip_info->[1]}; } - my %name_to_host = &all_names(); + my %name_to_host = &all_names($ignore_cache,$nocache); foreach my $name (keys(%name_to_host)) { my $ip; if (!exists($name_to_ip{$name})) { @@ -10114,9 +13676,11 @@ sub get_dns { } push(@{$iphost{$ip}},@{$name_to_host{$name}}); } - &Apache::lonnet::do_cache_new('iphost','iphost', - [\%iphost,\%name_to_ip,\%lonid_to_ip], - 48*60*60); + unless ($nocache) { + &do_cache_new('iphost','iphost', + [\%iphost,\%name_to_ip,\%lonid_to_ip], + 48*60*60); + } return %iphost; } @@ -10172,15 +13736,48 @@ sub get_dns { } $seen{$prim_ip} = 1; } - return &Apache::lonnet::do_cache_new('internetnames',$lonid,\@idns,12*60*60); + return &do_cache_new('internetnames',$lonid,\@idns,12*60*60); } } sub all_loncaparevs { - return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10); + return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11); } +# ---------------------------------------------------------- Read loncaparev table +{ + sub load_loncaparevs { + if (-e "$perlvar{'lonTabDir'}/loncaparevs.tab") { + if (open(my $config,"<$perlvar{'lonTabDir'}/loncaparevs.tab")) { + while (my $configline=<$config>) { + chomp($configline); + my ($hostid,$loncaparev)=split(/:/,$configline); + $loncaparevs{$hostid}=$loncaparev; + } + close($config); + } + } + } +} + +# ---------------------------------------------------------- Read serverhostID table +{ + sub load_serverhomeIDs { + if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") { + if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) { + while (my $configline=<$config>) { + chomp($configline); + my ($name,$id)=split(/:/,$configline); + $serverhomeIDs{$name}=$id; + } + close($config); + } + } + } +} + + BEGIN { # ----------------------------------- Read loncapa.conf and loncapa_apache.conf @@ -10257,33 +13854,14 @@ BEGIN { } # ---------------------------------------------------------- Read loncaparev table -{ - if (-e "$perlvar{'lonTabDir'}/loncaparevs.tab") { - if (open(my $config,"<$perlvar{'lonTabDir'}/loncaparevs.tab")) { - while (my $configline=<$config>) { - chomp($configline); - my ($hostid,$loncaparev)=split(/:/,$configline); - $loncaparevs{$hostid}=$loncaparev; - } - close($config); - } - } -} + +&load_loncaparevs(); # ---------------------------------------------------------- Read serverhostID table -{ - if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") { - if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) { - while (my $configline=<$config>) { - chomp($configline); - my ($name,$id)=split(/:/,$configline); - $serverhomeIDs{$name}=$id; - } - close($config); - } - } -} +&load_serverhomeIDs(); + +# ---------------------------------------------------------- Read releaseslist XML { my $file = $Apache::lonnet::perlvar{'lonTabDir'}.'/releaseslist.xml'; if (-e $file) { @@ -10293,7 +13871,15 @@ BEGIN { my $item = $token->[1]; my $name = $token->[2]{'name'}; my $value = $token->[2]{'value'}; - if ($item ne '' && $name ne '' && $value ne '') { + my $valuematch = $token->[2]{'valuematch'}; + my $namematch = $token->[2]{'namematch'}; + if ($item eq 'parameter') { + if (($namematch ne '') || (($name ne '') && ($value ne '' || $valuematch ne ''))) { + my $release = $parser->get_text(); + $release =~ s/(^\s*|\s*$ )//gx; + $needsrelease{$item.':'.$name.':'.$value.':'.$valuematch.':'.$namematch} = $release; + } + } elsif ($item ne '' && $name ne '') { my $release = $parser->get_text(); $release =~ s/(^\s*|\s*$ )//gx; $needsrelease{$item.':'.$name.':'.$value} = $release; @@ -10303,9 +13889,25 @@ BEGIN { } } +# ---------------------------------------------------------- Read managers table +{ + if (-e "$perlvar{'lonTabDir'}/managers.tab") { + if (open(my $config,"<$perlvar{'lonTabDir'}/managers.tab")) { + while (my $configline=<$config>) { + chomp($configline); + next if ($configline =~ /^\#/); + if (($configline =~ /^[\w\-]+$/) || ($configline =~ /^[\w\-]+\:[\w\-]+$/)) { + $managerstab{$configline} = 1; + } + } + close($config); + } + } +} + # ------------- set up temporary directory { - $tmpdir = $perlvar{'lonDaemons'}.'/tmp/'; + $tmpdir = LONCAPA::tempdir(); } @@ -10466,8 +14068,8 @@ were new keys. I.E. 1:foo will become 1: Calling convention: - my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$uname,$home); - &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$uname,$home); + my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$uname); + &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$uname,$laststore); For more detailed information, see lonnet specific documentation. @@ -10550,10 +14152,12 @@ the answer, and also caches if there is =item * 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) +B: find the usernames behind either +a list of student/employee IDs or clicker IDs +(student/employee 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). +clickerIDs are not necessarily unique, as students might share clickers. +(returns hash: id=>name,id=>name) =item * X @@ -10562,11 +14166,37 @@ usernames (returns hash: name=>id,name=> =item * X -B: store away a list of names and associated IDs +B: store away a list of +names and associated student/employee IDs or clicker IDs. + +=item * +X +B: delete unwanted +student/employee ID or clicker ID username look-ups from domain. +The homeserver ($uhome) and namespace ($namespace) are optional. +If no $uhome is provided, it will be determined usig &homeserver() +for each user. If no $namespace is provided, the default is ids. + +=item * +X +B: update +clicker ID-to-username look-ups in clickers.db on library server. +Permitted actions are add or del (i.e., add or delete). The +clickers.db contains clickerID as keys (escaped), and each corresponding +value is an escaped comma-separated list of usernames (for whom the +library server is the homeserver), who registered that particular ID. +If $critical is true, the update will be sent via &critical, otherwise +&reply() will be used. =item * X -B: get user privileges +B: get user privileges. +returns user role, first access and timer interval hashes + +=item * +X +B: returns a true if user has a +privileged and active role (i.e. su or dc), false otherwise. =item * X @@ -10597,19 +14227,51 @@ escaped strings of the action recorded i =item * -allowed($priv,$uri,$symb,$role) : check for a user privilege; returns codes for allowed actions +allowed($priv,$uri,$symb,$role,$clientip,$noblockcheck) : check for a user privilege; +returns codes for allowed actions. + +The first argument is required, all others are optional. + +$priv is the privilege being checked. +$uri contains additional information about what is being checked for access (e.g., +URL, course ID etc.). +$symb is the unique resource instance identifier in a course; if needed, +but not provided, it will be retrieved via a call to &symbread(). +$role is the role for which a priv is being checked (only used if priv is evb). +$clientip is the user's IP address (only used when checking for access to portfolio +files). +$noblockcheck, if true, skips calls to &has_comm_blocking() for the bre priv. This +prevents recursive calls to &allowed. + F: full access U,I,K: authentication modes (cxx only) '': forbidden 1: user needs to choose course 2: browse allowed A: passphrase authentication needed + B: access temporarily blocked because of a blocking event in a course. =item * -definerole($rolename,$sysrole,$domrole,$courole) : define role; define a custom -role rolename set privileges in format of lonTabs/roles.tab for system, domain, -and course level +constructaccess($url,$setpriv) : check for access to construction space URL + +See if the owner domain and name in the URL match those in the +expected environment. If so, return three element list +($ownername,$ownerdomain,$ownerhome). + +Otherwise return the null string. + +If second argument 'setpriv' is true, it assigns the privileges, +and returns the same three element list, unless the owner has +blocked "ad hoc" Domain Coordinator access to the Author Space, +in which case the null string is returned. + +=item * + +definerole($rolename,$sysrole,$domrole,$courole,$uname,$udom) : define role; +define a custom role rolename set privileges in format of lonTabs/roles.tab +for system, domain, and course level. $uname and $udom are optional (current +user's username and domain will be used when either of $uname or $udom are absent. =item * @@ -10623,7 +14285,7 @@ environment). If no custom name is defi =item * -get_my_roles($uname,$udom,$context,$types,$roles,$roledoms,$withsec) : +get_my_roles($uname,$udom,$context,$types,$roles,$roledoms,$withsec,$hidepriv) : All arguments are optional. Returns a hash of a roles, either for co-author/assistant author roles for a user's Construction Space (default), or if $context is 'userroles', roles for the user himself, @@ -10637,6 +14299,41 @@ of role statuses (active, future or prev to restrict the list of roles reported. If no array ref is provided for types, will default to return only active roles. +=item * + +in_course($udom,$uname,$cdom,$cnum,$type,$hideprivileged) : determine if +user: $uname:$udom has a role in the course: $cdom_$cnum. + +Additional optional arguments are: $type (if role checking is to be restricted +to certain user status types -- previous (expired roles), active (currently +available roles) or future (roles available in the future), and +$hideprivileged -- if true will not report course roles for users who +have active Domain Coordinator role in course's domain or in additional +domains (specified in 'Domains to check for privileged users' in course +environment -- set via: Course Settings -> Classlists and staff listing). + +=item * + +privileged($username,$domain,$possdomains,$possroles) : returns 1 if user +$username:$domain is a privileged user (e.g., Domain Coordinator or Super User) +$possdomains and $possroles are optional array refs -- to domains to check and +roles to check. If $possdomains is not specified, a dump will be done of the +users' roles.db to check for a dc or su role in any domain. This can be +time consuming if &privileged is called repeatedly (e.g., when displaying a +classlist), so in such cases, supplying a $possdomains array is preferred, as +this then allows &privileged_by_domain() to be used, which caches the identity +of privileged users, eliminating the need for repeated calls to &dump(). + +=item * + +privileged_by_domain($possdomains,$roles) : returns a hash of a hash of a hash, +where the outer hash keys are domains specified in the $possdomains array ref, +next inner hash keys are privileged roles specified in the $roles array ref, +and the innermost hash contains key = value pairs for username:domain = end:start +for active or future "privileged" users with that role in that domain. To avoid +repeated dumps of domain roles -- via &get_domain_roles() -- contents of the +innerhash are cached using priv_$role and $dom as the identifiers. + =back =head2 User Modification @@ -10678,8 +14375,8 @@ or when Autoupdate.pl is run by cron in modifystudent modify a student's enrollment and identification information. -The course id is resolved based on the current users environment. -This means the envoking user must be a course coordinator or otherwise +The course id is resolved based on the current user's environment. +This means the invoking user must be a course coordinator or otherwise associated with a course. This call is essentially a wrapper for lonnet::modifyuser and @@ -10729,7 +14426,9 @@ Inputs: =item B<$context> role change context (shown in User Management Logs display in a course) -=item B<$inststatus> institutional status of user - : separated string of escaped status types +=item B<$inststatus> institutional status of user - : separated string of escaped status types + +=item B<$credits> Number of credits student will earn from this class - only needs to be supplied if value needs to be different from default credits for class. =back @@ -10737,20 +14436,20 @@ Inputs: modify_student_enrollment -Change a students enrollment status in a class. The environment variable +Change a student's enrollment status in a class. The environment variable 'role.request.course' must be defined for this function to proceed. Inputs: =over 4 -=item $udom, students domain +=item $udom, student's domain -=item $uname, students name +=item $uname, student's name -=item $uid, students user id +=item $uid, student's user id -=item $first, students first name +=item $first, student's first name =item $middle @@ -10774,6 +14473,10 @@ Inputs: =item $context +=item $credits, number of credits student will earn from this class + +=item $instsec, institutional course section code for student + =back @@ -10799,17 +14502,38 @@ revokecustomrole($udom,$uname,$url,$role =item * -coursedescription($courseid) : returns a hash of information about the +coursedescription($courseid,$options) : returns a hash of information about the specified course id, including all environment settings for the course, the description of the course will be in the hash under the key 'description' +$options is an optional parameter that if supplied is a hash reference that controls +what how this function works. It has the following key/values: + +=over 4 + +=item freshen_cache + +If defined, and the environment cache for the course is valid, it is +returned in the returned hash. + +=item one_time + +If defined, the last cache time is set to _now_ + +=item user + +If defined, the supplied username is used instead of the current user. + + +=back + =item * resdata($name,$domain,$type,@which) : request for current parameter setting for a specific $type, where $type is either 'course' or 'user', @what should be a list of parameters to ask about. This routine caches -answers for 5 minutes. +answers for 10 minutes. =item * @@ -10818,6 +14542,9 @@ data base, returning a hash that is keye values that are the resource value. I believe that the timestamps and versions are also returned. +get_numsuppfiles($cnum,$cdom) : retrieve number of files in a course's +supplemental content area. This routine caches the number of files for +10 minutes. =back @@ -10838,6 +14565,19 @@ createcourse($udom,$description,$url,$co generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community). +=item * + +is_course($courseid), is_course($cdom, $cnum) + +Accepts either a combined $courseid (in the form of domain_courseid) or the +two component version $cdom, $cnum. It checks if the specified course exists. + +Returns: + undef if the course doesn't exist, otherwise + in scalar context the combined courseid. + in list context the two components of the course identifier, domain and + courseid. + =back =head2 Resource Subroutines @@ -10865,10 +14605,15 @@ resource. Expects the local filesystem p =item * -EXT($varname,$symb,$udom,$uname) : evaluates and returns the value of -a vairety of different possible values, $varname should be a request -string, and the other parameters can be used to specify who and what -one is asking about. +EXT($varname,$symb,$udom,$uname,$usection,$recurse,$cid) : evaluates +and returns the value of a variety of different possible values, +$varname should be a request string, and the other parameters can be +used to specify who and what one is asking about. Ordinarily, $cid +does not need to be specified, as it is retrived from +$env{'request.course.id'}, but &Apache::lonnet::EXT() is called +within lonuserstate::loadmap() when initializing a course, before +$env{'request.course.id'} has been set, so it needs to be provided +in that one case. Possible values for $varname are environment.lastname (or other item from the envirnment hash), user.name (or someother aspect about the @@ -10901,17 +14646,32 @@ will be stored for query =item * -symbread($filename) : return symbolic list entry (filename argument optional); +symbread($filename,$donotrecurse,$ignorecachednull,$checkforblock,$possibles) : +return symbolic list entry (all arguments optional). + +Args: filename is the filename (including path) for the file for which a symb +is required; donotrecurse, if true will prevent calls to allowed() being made +to check access status if more than one resource was found in the bighash +(see rev. 1.249) to avoid an infinite loop if an ambiguous resource is part of +a randompick); ignorecachednull, if true will prevent a symb of '' being +returned if $env{$cache_str} is defined as ''; checkforblock if true will +cause possible symbs to be checked to determine if they are subject to content +blocking, if so they will not be included as possible symbs; possibles is a +ref to a hash, which, as a side effect, will be populated with all possible +symbs (content blocking not tested). + returns the data handle =item * -symbverify($symb,$thisfn) : verifies that $symb actually exists and is -a possible symb for the URL in $thisfn, and if is an encryypted +symbverify($symb,$thisfn,$encstate) : verifies that $symb actually exists +and is a possible symb for the URL in $thisfn, and if is an encrypted resource that the user accessed using /enc/ returns a 1 on success, 0 -on failure, user must be in a course, as it assumes the existance of -the course initial hash, and uses $env('request.course.id'} - +on failure, user must be in a course, as it assumes the existence of +the course initial hash, and uses $env('request.course.id'}. The third +arg is an optional reference to a scalar. If this arg is passed in the +call to symbverify, it will be set to 1 if the symb has been set to be +encrypted; otherwise it will be null. =item * @@ -10964,6 +14724,34 @@ expirespread($uname,$udom,$stype,$usymb) devalidate($symb) : devalidate temporary spreadsheet calculations, forcing spreadsheet to reevaluate the resource scores next time. +=item * + +can_edit_resource($file,$cnum,$cdom,$resurl,$symb,$group) : determine if current user can edit a particular resource, +when viewing in course context. + + input: six args -- filename (decluttered), course number, course domain, + url, symb (if registered) and group (if this is a + group item -- e.g., bulletin board, group page etc.). + + output: array of five scalars -- + $cfile -- url for file editing if editable on current server + $home -- homeserver of resource (i.e., for author if published, + or course if uploaded.). + $switchserver -- 1 if server switch will be needed. + $forceedit -- 1 if icon/link should be to go to edit mode + $forceview -- 1 if icon/link should be to go to view mode + +=item * + +is_course_upload($file,$cnum,$cdom) + +Used in course context to determine if current file was uploaded to +the course (i.e., would be found in /userfiles/docs on the course's +homeserver. + + input: 3 args -- filename (decluttered), course number and course domain. + output: boolean -- 1 if file was uploaded. + =back =head2 Storing/Retreiving Data @@ -10972,15 +14760,21 @@ forcing spreadsheet to reevaluate the re =item * -store($storehash,$symb,$namespace,$udom,$uname) : stores hash permanently -for this url; hashref needs to be given and should be a \%hashname; the -remaining args aren't required and if they aren't passed or are '' they will -be derived from the env +store($storehash,$symb,$namespace,$udom,$uname,$laststore) : stores hash +permanently for this url; hashref needs to be given and should be a \%hashname; +the remaining args aren't required and if they aren't passed or are '' they will +be derived from the env (with the exception of $laststore, which is an +optional arg used when a user's submission is stored in grading). +$laststore is $version=$timestamp, where $version is the most recent version +number retrieved for the corresponding $symb in the $namespace db file, and +$timestamp is the timestamp for that transaction (UNIX time). +$laststore is currently only passed when cstore() is called by +structuretags::finalize_storage(). =item * -cstore($storehash,$symb,$namespace,$udom,$uname) : same as store but -uses critical subroutine +cstore($storehash,$symb,$namespace,$udom,$uname,$laststore) : same as store +but uses critical subroutine =item * @@ -11003,10 +14797,11 @@ $range should be either an integer '100' =item * -putstore($namespace,$symb,$version,$storehash,$udomain,$uname) : +putstore($namespace,$symb,$version,$storehash,$udomain,$uname,$tolog) : replaces a &store() version of data with a replacement set of data for a particular resource in a namespace passed in the $storehash hash -reference +reference. If $tolog is true, the transaction is logged in the courselog +with an action=PUTSTORE. =item * @@ -11116,15 +14911,91 @@ server ($udom and $uhome are optional) =item * -get_domain_defaults($target_domain) : returns hash with defaults for -authentication and language in the domain. Keys are: auth_def, auth_arg_def, -lang_def; corresponsing values are authentication type (internal, krb4, krb5, -or localauth), initial password or a kerberos realm, language (e.g., en-us). -Values are retrieved from cache (if current), or from domain's configuration.db -(if available), or lastly from values in lonTabs/dns_domain,tab, -or lonTabs/domain.tab. +get_domain_defaults($target_domain,$ignore_cache) : returns hash with defaults +for: authentication, language, quotas, timezone, date locale, and portal URL in +the target domain. + +May also include additional key => value pairs for the following groups: + +=over + +=item +disk quotas (MB allocated by default to portfolios and authoring spaces). -%domdefaults = &get_auth_defaults($target_domain); +=over + +=item defaultquota, authorquota + +=back + +=item +tools (availability of aboutme page, blog, webDAV access for authoring spaces, +portfolio for users). + +=over + +=item +aboutme, blog, webdav, portfolio + +=back + +=item +requestcourses: ability to request courses, and how requests are processed. + +=over + +=item +official, unofficial, community, textbook, placement + +=back + +=item +inststatus: types of institutional affiliation, and order in which they are displayed. + +=over + +=item +inststatustypes, inststatusorder, inststatusguest + +=back + +=item +coursedefaults: can PDF forms can be created, default credits for courses, default quotas (MB) +for course's uploaded content. + +=over + +=item +canuse_pdfforms, officialcredits, unofficialcredits, textbookcredits, officialquota, unofficialquota, +communityquota, textbookquota, placementquota + +=back + +=item +usersessions: set options for hosting of your users in other domains, and hosting of users from other domains +on your servers. + +=over + +=item +remotesessions, hostedsessions + +=back + +=back + +In cases where a domain coordinator has never used the "Set Domain Configuration" +utility to create a configuration.db file on a domain's primary library server +only the following domain defaults: auth_def, auth_arg_def, lang_def +-- corresponding values are authentication type (internal, krb4, krb5, +or localauth), initial password or a kerberos realm, language (e.g., en-us) -- +will be available. Values are retrieved from cache (if current), unless the +optional $ignore_cache arg is true, or from domain's configuration.db (if available), +or lastly from values in lonTabs/dns_domain,tab, or lonTabs/domain.tab. + +Typical usage: + +%domdefaults = &get_domain_defaults($target_domain); =back @@ -11134,7 +15005,82 @@ or lonTabs/domain.tab. =item * -dirlist($uri) : return directory list based on URI +dirlist() : return directory list based on URI (first arg). + +Inputs: 1 required, 5 optional. + +=over + +=item +$uri - path to file in filesystem (starts: /res or /userfiles/). Required. + +=item +$userdomain - domain of user/course to be listed. Extracted from $uri if absent. + +=item +$username - username of user/course to be listed. Extracted from $uri if absent. + +=item +$getpropath - boolean: 1 if prepend path using &propath(). + +=item +$getuserdir - boolean: 1 if prepend path for "userfiles". + +=item +$alternateRoot - path to prepend in place of path from $uri. + +=back + +Returns: Array of up to two items. + +=over + +a reference to an array of files/subdirectories + +=over + +Each element in the array of files/subdirectories is a & separated list of +item name and the result of running stat on the item. If dirlist was requested +for a file instead of a directory, the item name will be ''. For a directory +listing, if the item is a metadata file, the element will end &N&M +(where N amd M are either 0 or 1, corresponding to obsolete set (1), or +default copyright set (1). + +=back + +a scalar containing error condition (if encountered). + +=over + +=item +no_host (no homeserver identified for $username:$domain). + +=item +no_such_host (server contacted for listing not identified as valid host). + +=item +con_lost (connection to remote server failed). + +=item +refused (invalid $username:$domain received on lond side). + +=item +no_such_dir (directory at specified path on lond side does not exist). + +=item +empty (directory at specified path on lond side is empty). + +=over + +This is currently not encountered because the &ls3, &ls2, +&ls (_handler) routines on the lond side do not filter out +. and .. from a directory listing. + +=back + +=back + +=back =item * @@ -11196,11 +15142,12 @@ splitting on '&', supports elements that =head2 Logging Routines -=over 4 These routines allow one to make log messages in the lonnet.log and lonnet.perm logfiles. +=over 4 + =item * logtouch() : make sure the logfile, lonnet.log, exists @@ -11216,6 +15163,7 @@ logperm() : append a permanent message t file never gets deleted by any automated portion of the system, only messages of critical importance should go in here. + =back =head2 General File Helper Routines @@ -11272,7 +15220,8 @@ filelocation except for hrefs =item * -declutter() : declutters URLs (remove docroot, beginning slashes, 'res' etc) +declutter() : declutters URLs -- remove beginning slashes, 'res' etc. +also removes beginning /home/httpd/html unless /priv/ follows it. =back @@ -11289,8 +15238,10 @@ userfileupload(): main rotine for puttin filename, and the contents of the file to create/modifed exist the filename is in $env{'form.'.$formname.'.filename'} and the contents of the file is located in $env{'form.'.$formname} - coursedoc - if true, store the file in the course of the active role - of the current user + context - if coursedoc, store the file in the course of the active role + of the current user; + if 'existingfile': store in 'overwrites' in /home/httpd/perl/tmp + if 'canceloverwrite': delete file in tmp/overwrites directory subdir - required - subdirectory to put the file in under ../userfiles/ if undefined, it will be placed in "unknown" @@ -11312,16 +15263,29 @@ returns: the new clean filename =item * -finishuserfileupload(): routine that creaes and sends the file to +finishuserfileupload(): routine that creates and sends the file to userspace, probably shouldn't be called directly docuname: username or courseid of destination for the file docudom: domain of user/course of destination for the file formname: same as for userfileupload() - fname: filename (inculding subdirectories) for the file + fname: filename (including subdirectories) for the file + parser: if 'parse', will parse (html) file to extract references to objects, links etc. + allfiles: reference to hash used to store objects found by parser + codebase: reference to hash used for codebases of java objects found by parser + thumbwidth: width (pixels) of thumbnail to be created for uploaded image + thumbheight: height (pixels) of thumbnail to be created for uploaded image + resizewidth: width to be used to resize image using resizeImage from ImageMagick + resizeheight: height to be used to resize image using resizeImage from ImageMagick + context: if 'overwrite', will move the uploaded file from its temporary location to + userfiles to facilitate overwriting a previously uploaded file with same name. + mimetype: reference to scalar to accommodate mime type determined + from File::MMagic if $parser = parse. returns either the url of the uploaded file (/uploaded/....) if successful - and /adm/notfound.html if unsuccessful + and /adm/notfound.html if unsuccessful (or an error message if context + was 'overwrite'). + =item * @@ -11427,6 +15391,8 @@ Internal notes: Locks on files (resulting from submission of portfolio file to a homework problem stored in array of arrays. +=item * + modify_access_controls(): Modifies access controls for a portfolio file @@ -11444,7 +15410,51 @@ Returns: 3. reference to hash of any new or updated access controls. 4. reference to hash used to map incoming IDs to uniqueIDs assigned to control. key = integer (inbound ID) - value = uniqueID + value = uniqueID + +=item * + +get_timebased_id(): + +Attempts to get a unique timestamp-based suffix for use with items added to a +course via the Course Editor (e.g., folders, composite pages, +group bulletin boards). + +Args: (first three required; six others optional) + +1. prefix (alphanumeric): of keys in hash, e.g., suppsequence, docspage, + docssequence, or name of group + +2. keyid (alphanumeric): name of temporary locking key in hash, + e.g., num, boardids + +3. namespace: name of gdbm file used to store suffixes already assigned; + file will be named nohist_namespace.db + +4. cdom: domain of course; default is current course domain from %env + +5. cnum: course number; default is current course number from %env + +6. idtype: set to concat if an additional digit is to be appended to the + unix timestamp to form the suffix, if the plain timestamp is already + in use. Default is to not do this, but simply increment the unix + timestamp by 1 until a unique key is obtained. + +7. who: holder of locking key; defaults to user:domain for user. + +8. locktries: number of attempts to obtain a lock (sleep of 1s before + retrying); default is 3. + +9. maxtries: number of attempts to obtain a unique suffix; default is 20. + +Returns: + +1. suffix obtained (numeric) + +2. result of deleting locking key (ok if deleted, or lock never obtained) + +3. error: contains (localized) error message if an error occurred. + =back