--- loncom/lonnet/perl/lonnet.pm 2012/05/28 12:28:14 1.1174 +++ loncom/lonnet/perl/lonnet.pm 2016/08/31 21:50:42 1.1319 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1174 2012/05/28 12:28:14 raeburn Exp $ +# $Id: lonnet.pm,v 1.1319 2016/08/31 21:50:42 damieng Exp $ # # Copyright Michigan State University Board of Trustees # @@ -75,6 +75,7 @@ use LWP::UserAgent(); use HTTP::Date; use Image::Magick; + use Encode; use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir @@ -91,7 +92,7 @@ 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; @@ -104,7 +105,7 @@ use LONCAPA::Lond; 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; @@ -112,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); } } @@ -225,6 +229,46 @@ sub get_server_distarch { 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 =~ /^(?:centos|redhat|scientific)(\d+)$/) { + if ($1 < 6) { + $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)) { @@ -352,8 +396,11 @@ sub get_remote_globals { } sub remote_devalidate_cache { - my ($lonhost,$name,$id) = @_; - my $response = &reply('devalidatecache:'.&escape($name).':'.&escape($id),$lonhost); + my ($lonhost,$cachekeys) = @_; + my $items; + return unless (ref($cachekeys) eq 'ARRAY'); + my $cachestr = join('&',@{$cachekeys}); + my $response = &reply('devalidatecache:'.&escape($cachestr),$lonhost); return $response; } @@ -368,7 +415,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. @@ -386,7 +433,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) { @@ -415,8 +462,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)"); @@ -441,7 +488,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"); @@ -462,7 +509,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; @@ -479,7 +526,7 @@ sub critical { close($dfh); } } - sleep 2; + sleep 1; my $wcmd=''; { my $dfh; @@ -599,7 +646,7 @@ sub transfer_profile_to_env { # ---------------------------------------------------- Check for valid session sub check_for_valid_session { - my ($r,$name) = @_; + my ($r,$name,$userhashref) = @_; my %cookies=CGI::Cookie->parse($r->header_in('Cookie')); if ($name eq '') { $name = 'lonID'; @@ -630,6 +677,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; } @@ -663,7 +716,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; } @@ -836,10 +889,8 @@ sub spareserver { if (ref($spareshash) eq 'HASH') { if (ref($spareshash->{'primary'}) eq 'ARRAY') { foreach my $try_server (@{ $spareshash->{'primary'} }) { - if ($uint_dom) { - next unless (&spare_can_host($udom,$uint_dom,$remotesessions, - $try_server)); - } + next unless (&spare_can_host($udom,$uint_dom,$remotesessions, + $try_server)); ($spare_server, $lowest_load) = &compare_server_load($try_server, $spare_server, $lowest_load); } @@ -850,10 +901,8 @@ sub spareserver { if (!$found_server) { if (ref($spareshash->{'default'}) eq 'ARRAY') { foreach my $try_server (@{ $spareshash->{'default'} }) { - if ($uint_dom) { - next unless (&spare_can_host($udom,$uint_dom, - $remotesessions,$try_server)); - } + next unless (&spare_can_host($udom,$uint_dom, + $remotesessions,$try_server)); ($spare_server, $lowest_load) = &compare_server_load($try_server, $spare_server, $lowest_load); } @@ -877,7 +926,17 @@ 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); @@ -938,26 +997,43 @@ sub has_user_session { # --------- determine least loaded server in a user's domain which allows login sub choose_server { - my ($udom,$checkloginvia) = @_; + 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,$portal_path,$isredirect); + 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)) { + 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); + &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); + &compare_server_load($lonhost, $login_host, $lowest_load, $required); if ($login_host eq $lonhost) { $portal_path = ''; $isredirect = ''; @@ -965,7 +1041,7 @@ sub choose_server { } } 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 '') { @@ -1138,19 +1214,27 @@ sub can_host_session { sub spare_can_host { my ($udom,$uint_dom,$remotesessions,$try_server)=@_; my $canhost=1; - my @intdoms; - my $internet_names = &Apache::lonnet::get_internet_names($try_server); - if (ref($internet_names) eq 'ARRAY') { - @intdoms = @{$internet_names}; - } - unless (grep(/^\Q$uint_dom\E$/,@intdoms)) { - my $serverhomeID = &Apache::lonnet::get_server_homeID($try_server); - my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID); - my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom); - my $remoterev = &Apache::lonnet::get_server_loncaparev(undef,$try_server); - $canhost = &can_host_session($udom,$try_server,$remoterev, - $remotesessions, - $defdomdefaults{'hostedsessions'}); + 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; } @@ -1236,14 +1320,15 @@ sub get_lonbalancer_config { sub check_loadbalancing { my ($uname,$udom) = @_; - my ($is_balancer,$dom_in_use,$homeintdom,$rule_in_effect, - $offloadto,$otherserver); + 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)) { @@ -1258,18 +1343,13 @@ sub check_loadbalancing { &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') { - my $currbalancer = $result->{'lonhost'}; - my $currtargets = $result->{'targets'}; - my $currrules = $result->{'rules'}; - if ($currbalancer ne '') { - my @hosts = ¤t_machine_ids(); - if (grep(/^\Q$currbalancer\E$/,@hosts)) { - $is_balancer = 1; - } - } + ($is_balancer,$currtargets,$currrules) = + &check_balancer_result($result,@hosts); if ($is_balancer) { if (ref($currrules) eq 'HASH') { if ($homeintdom) { @@ -1318,21 +1398,20 @@ sub check_loadbalancing { } } } elsif (($homeintdom) && ($udom ne $serverhomedom)) { - my ($result,$cached)=&is_cached_new('loadbalancing',$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',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime); + } else { + $domneedscache = $serverhomedom; } } if (ref($result) eq 'HASH') { - my $currbalancer = $result->{'lonhost'}; - my $currtargets = $result->{'targets'}; - my $currrules = $result->{'rules'}; - - if ($currbalancer eq $lonhost) { - $is_balancer = 1; + ($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'}; @@ -1346,48 +1425,97 @@ sub check_loadbalancing { $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; + } } - 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); + 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); + my $found_server = ($otherserver ne '' && $lowest_load < 100); - if (!$found_server) { - if (ref($offloadto->{'default'}) eq 'ARRAY') { - foreach my $try_server (@{$offloadto->{'default'}}) { + 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); } } } - } 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); + 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 '') { + if ($rule_in_effect eq 'none') { + return [$perlvar{'lonHostID'}]; + } elsif ($rule_in_effect eq '') { $offloadto = $currtargets; } else { if ($rule_in_effect eq 'homeserver') { @@ -1405,7 +1533,7 @@ sub get_loadbalancer_targets { } } } else { - my %servers = &dom_servers($udom); + my %servers = &internet_dom_servers($udom); my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers); if (&hostname($remotebalancer) ne '') { $offloadto = [$remotebalancer]; @@ -1476,17 +1604,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); @@ -1494,10 +1638,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; } @@ -1512,27 +1656,138 @@ 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); + } + } +} + +# ------------- 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; + } + if ($namespace eq '') { + $namespace = 'ids'; + } + 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); } } @@ -1551,6 +1806,7 @@ sub dump_dom { sub get_dom { my ($namespace,$storearr,$udom,$uhome)=@_; + return if ($udom eq 'public'); my $items=''; foreach my $item (@$storearr) { $items.=&escape($item).'&'; @@ -1558,6 +1814,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 { @@ -1661,14 +1918,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); @@ -1684,15 +1940,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'; } @@ -1833,6 +2089,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; @@ -1912,19 +2225,25 @@ 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'],$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'}; @@ -1942,27 +2261,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') { @@ -1972,12 +2326,79 @@ 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'}{'connect'}) eq 'HASH') { + $domdefaults{'connect'} = $domconfig{'ssl'}{'connect'}; + } + } + if (ref($domconfig{'autoenroll'}) eq 'HASH') { + $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'}; } - &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 { @@ -2214,21 +2635,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("Early 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"); } @@ -2238,13 +2661,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)) { @@ -2260,17 +2684,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; @@ -2384,7 +2808,7 @@ 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)); } @@ -2564,14 +2988,21 @@ sub ssi { &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 $content = Encode::decode_utf8($response->content); + my $content = $response->content; + + if (wantarray) { return ($content, $response); } else { @@ -2604,6 +3035,285 @@ 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+/exttools?$}) { + $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+/exttools?$}) && ($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+/exttools?$}) { + $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, @@ -2894,7 +3604,9 @@ sub userfileupload { $codebase,$thumbwidth,$thumbheight, $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,$mimetype); @@ -2909,7 +3621,7 @@ sub userfileupload { } 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'}; } @@ -3059,7 +3771,9 @@ 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; @@ -3147,8 +3861,26 @@ sub extract_embedded_items { } } } + 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); + pop(@state); } } elsif ($t->[0] eq 'E') { my ($tagname) = ($t->[1]); @@ -3513,38 +4245,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)); @@ -3558,6 +4322,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'}); @@ -3570,20 +4338,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) { @@ -3614,8 +4369,7 @@ sub get_my_roles { 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'})) { @@ -3657,7 +4411,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); @@ -3683,28 +4437,15 @@ sub get_my_roles { } } 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; } @@ -3796,7 +4537,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=''; } @@ -3808,18 +4550,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); @@ -3925,7 +4682,7 @@ sub get_domain_roles { } my $rolelist; if (ref($roles) eq 'ARRAY') { - $rolelist = join(':',@{$roles}); + $rolelist = join('&',@{$roles}); } my %personnel = (); @@ -3956,12 +4713,13 @@ my $cachedkey=''; # The cached times for this user my %cachedtimes=(); # When this was last done -my $cachedtime=(); +my $cachedtime=''; sub load_all_first_access { - my ($uname,$udom)=@_; + my ($uname,$udom,$ignorecache)=@_; if (($cachedkey eq $uname.':'.$udom) && - (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) { + (abs($cachedtime-time)<5) && (!$env{'form.markaccess'}) && + (!$ignorecache)) { return; } $cachedtime=time; @@ -3970,7 +4728,7 @@ sub load_all_first_access { } sub get_first_access { - my ($type,$argsymb,$argmap)=@_; + my ($type,$argsymb,$argmap,$ignorecache)=@_; my ($symb,$courseid,$udom,$uname)=&whichuser(); if ($argsymb) { $symb=$argsymb; } my ($map,$id,$res)=&decode_symb($symb); @@ -3982,7 +4740,7 @@ sub get_first_access { } else { $res=$symb; } - &load_all_first_access($uname,$udom); + &load_all_first_access($uname,$udom,$ignorecache); return $cachedtimes{"$courseid\0$res"}; } @@ -4018,6 +4776,7 @@ sub set_first_access { return 'already_set'; } } + # --------------------------------------------- Set Expire Date for Spreadsheet sub expirespread { @@ -4388,7 +5147,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); } @@ -4418,13 +5177,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); } @@ -4455,7 +5214,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 @@ -4467,9 +5226,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'}) { @@ -4604,22 +5366,95 @@ sub update_released_required { # -------------------------------------------------See if a user is privileged sub privileged { - my ($username,$domain)=@_; - - my %rolesdump = &dump("roles", $domain, $username) or return 0; + 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}) { + for my $role (@rolesdump{grep { ! /^rolesdef_/ } keys(%rolesdump)}) { my ($trole, $tend, $tstart) = split(/_/, $role); - if (($trole eq 'dc') || ($trole eq 'su')) { + if (grep(/^\Q$trole\E$/,@{$roles})) { return 1 unless ($tend && $tend < $now) - or ($tstart && $tstart > $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 { @@ -4648,7 +5483,7 @@ sub rolesinit { my %allroles=(); my %allgroups=(); - for my $area (grep { ! /^rolesdef_/ } keys %rolesdump) { + for my $area (grep { ! /^rolesdef_/ } keys(%rolesdump)) { my $role = $rolesdump{$area}; $area =~ s/\_\w\w$//; @@ -4729,16 +5564,18 @@ sub rolesinit { } 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); @@ -4859,11 +5696,11 @@ sub set_userprivs { sub role_status { my ($rolekey,$update,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; - my @pwhere = (); 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'; @@ -4979,15 +5816,19 @@ sub delete_env_groupprivs { sub check_adhoc_privs { my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_; my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum; + my $setprivs; if ($env{$cckey}) { my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend); &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,$caller); + $setprivs = 1; } } else { &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); + $setprivs = 1; } + return $setprivs; } sub set_adhoc_privileges { @@ -4996,7 +5837,7 @@ sub set_adhoc_privileges { my $area = '/'.$dcdom.'/'.$pickedcourse; my $spec = $role.'.'.$area; my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'}, - $env{'user.name'}); + $env{'user.name'},1); my %ccrole = (); &standard_roleprivs(\%ccrole,$role,$dcdom,$spec,$pickedcourse,$area); my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole); @@ -5059,16 +5900,40 @@ 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); @@ -5076,7 +5941,8 @@ sub dump { if (!($rep =~ /^error/ )) { foreach my $item (@pairs) { my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); + $key = unescape($key) unless $escapedkeys; + #$key = &unescape($key); next if ($key =~ /^error: 2 /); $returnhash{$key}=&thaw_unescape($value); } @@ -5089,23 +5955,9 @@ sub dump { 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 @@ -5131,7 +5983,15 @@ 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=(); @@ -5252,7 +6112,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); @@ -5266,6 +6126,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, @@ -5374,12 +6245,111 @@ sub tmpdel { 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') { @@ -5405,7 +6375,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); @@ -5414,7 +6384,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})) { @@ -5438,10 +6408,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) { @@ -5628,17 +6613,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'}; } @@ -5647,25 +6639,32 @@ 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 { if (ref($userenvref) eq 'HASH') { - $toolstatus = $userenvref->{$context.'.'.$tool}; + $toolstatus = $userenvref->{$envkey}; $inststatus = $userenvref->{'inststatus'}; } else { - my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus'); - $toolstatus = $userenv{$context.'.'.$tool}; + my %userenv = &userenvironment($udom,$uname,$envkey,'inststatus'); + $toolstatus = $userenv{$envkey}; $inststatus = $userenv{'inststatus'}; } } @@ -5731,7 +6730,7 @@ sub usertools_access { } } } else { - if ($context eq 'tools') { + if (($context eq 'tools') && ($tool ne 'webdav')) { $access = 1; } else { $access = 0; @@ -5912,7 +6911,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; @@ -6107,11 +7106,16 @@ sub allowed { if ($match) { if ($env{'user.priv.'.$env{'request.role'}.'./'} =~/\Q$priv\E\&([^\:]*)/) { - my @blockers = &has_comm_blocking($priv,$symb,$uri); - if (@blockers > 0) { - $thisallowed = 'B'; + my $value = $1; + if ($noblockcheck) { + $thisallowed.=$value; } else { - $thisallowed.=$1; + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } } } } else { @@ -6123,11 +7127,15 @@ sub allowed { $refuri=&declutter($refuri); my ($match) = &is_on_map($refuri); if ($match) { - my @blockers = &has_comm_blocking($priv,$symb,$refuri); - if (@blockers > 0) { - $thisallowed = 'B'; - } else { + if ($noblockcheck) { $thisallowed='F'; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed='F'; + } } } } @@ -6139,9 +7147,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'; @@ -6182,11 +7190,15 @@ sub allowed { =~/\Q$priv\E\&([^\:]*)/) { my $value = $1; if ($priv eq 'bre') { - my @blockers = &has_comm_blocking($priv,$symb,$uri); - if (@blockers > 0) { - $thisallowed = 'B'; - } else { + if ($noblockcheck) { $thisallowed.=$value; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } } } else { $thisallowed.=$value; @@ -6220,11 +7232,15 @@ sub allowed { =~/\Q$priv\E\&([^\:]*)/) { my $value = $1; if ($priv eq 'bre') { - my @blockers = &has_comm_blocking($priv,$symb,$refuri); - if (@blockers > 0) { - $thisallowed = 'B'; - } else { + if ($noblockcheck) { $thisallowed.=$value; + } else { + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } } } else { $thisallowed.=$value; @@ -6332,7 +7348,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') && ($priv ne 'plc')) { + 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'}); @@ -6342,7 +7358,7 @@ sub allowed { if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.users.denied'} =~/\Q$unamedom\E/) { - if (($priv ne 'pch') && ($priv ne 'plc')) { + 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'}); @@ -6388,6 +7404,108 @@ 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 '') { @@ -6408,27 +7526,21 @@ sub get_comm_blocks { return %commblocks; } -sub has_comm_blocking { - my ($priv,$symb,$uri,$blocks) = @_; - return unless ($env{'request.course.id'}); - return unless ($priv eq 'bre'); - return if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/); +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 unless (keys(%commblocks) > 0); - if (!$symb) { $symb=&symbread($uri,1); } - my ($map,$resid,undef)=&decode_symb($symb); - my %tocheck = ( - maps => $map, - resources => $symb, - ); - my @blockers; - my $now = time; + 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); @@ -6436,17 +7548,13 @@ sub has_comm_blocking { 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 ($commblocks{$block}{'blocks'}{'docs'}{'maps'}{$map}) { - unless (grep(/^\Q$block\E$/,@blockers)) { - push(@blockers,$block); - } + 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 ($commblocks{$block}{'blocks'}{'docs'}{'resources'}{$symb}) { - unless (grep(/^\Q$block\E$/,@blockers)) { - push(@blockers,$block); - } + if (keys(%{$commblocks{$block}{'blocks'}{'docs'}{'resources'}})) { + $blockers{$block}{'resources'} = $commblocks{$block}{'blocks'}{'docs'}{'resources'}; } } } @@ -6457,32 +7565,31 @@ sub has_comm_blocking { my @to_test; if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') { - my $check_interval; - if (&check_docs_block($commblocks{$block}{'blocks'}{'docs'},\%tocheck)) { - my @interval; - my $type = 'map'; - if ($item eq 'course') { - $type = 'course'; - @interval=&EXT("resource.0.interval"); + 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 { - 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,1); - foreach my $res (@to_test) { - my $symb = $res->symb(); - next if ($symb eq $mapsymb); - if ($symb ne '') { - @interval=&EXT("resource.0.interval",$symb); + 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; } } @@ -6490,26 +7597,36 @@ sub has_comm_blocking { } } } - if ($interval[0] =~ /\d+/) { - 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+$interval[0]; - if ($timesup > $now) { - foreach my $res (@to_test) { - if ($res->is_problem()) { - if ($res->completable()) { - unless (grep(/^\Q$block\E$/,@blockers)) { - push(@blockers,$block); - } - 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'}; } } } @@ -6520,33 +7637,66 @@ sub has_comm_blocking { } } } - return @blockers; + return %blockers; } -sub check_docs_block { - my ($docsblock,$tocheck) =@_; - if ((ref($docsblock) ne 'HASH') || (ref($tocheck) ne 'HASH')) { - return; +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 (ref($docsblock->{'maps'}) eq 'HASH') { - if ($tocheck->{'maps'}) { - if ($docsblock->{'maps'}{$tocheck->{'maps'}}) { - return 1; + 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($docsblock->{'resources'}) eq 'HASH') { - if ($tocheck->{'resources'}) { - if ($docsblock->{'resources'}{$tocheck->{'resources'}}) { - return 1; + 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; + return if ($noblock); + return @blockers; +} } +# -------------------------------- Deversion and split uri into path an filename + # -# Removes the versino from a URI and +# 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: @@ -6655,19 +7805,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; } @@ -6724,10 +7878,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); @@ -6745,10 +7901,10 @@ 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/) ) { @@ -6790,11 +7946,17 @@ sub fetch_enrollment_query { } sub get_query_reply { - my $queryid=shift; + 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>); @@ -6913,8 +8075,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 { @@ -7156,17 +8318,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; } @@ -7185,6 +8350,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 { @@ -7332,6 +8627,7 @@ sub plaintext { my %rolenames = ( Course => 'std', Community => 'alt1', + Placement => 'std', ); if ($cid ne '') { if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') { @@ -7452,6 +8748,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. @@ -7501,11 +8832,25 @@ 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')) { + &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); @@ -7695,7 +9040,7 @@ sub modifyuser { 'current user id "'.$uidhash{$uname}.'".'; } } else { - &idput($udom,($uname => $uid)); + &idput($udom,{$uname => $uid},$uhome,'ids'); } } # -------------------------------------------------------------- Add names, etc @@ -7805,7 +9150,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'; @@ -7817,15 +9162,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'}) { @@ -7872,7 +9219,7 @@ sub modify_student_enrollment { my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum); my $reply=cput('classlist', {$user => - join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype) }, + join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) }, $cdom,$cnum); if (($reply eq 'ok') || ($reply eq 'delayed')) { &devalidate_getsection_cache($udom,$uname,$cid); @@ -8101,7 +9448,7 @@ sub is_course { my %courses = &courseiddump($cdom, '.', 1, '.', '.', $cnum, undef, undef, '.'); - return unless exists($courses{$cdom.'_'.$cnum}); + return unless(exists($courses{$cdom.'_'.$cnum})); return wantarray ? ($cdom, $cnum) : $cdom.'_'.$cnum; } @@ -8126,6 +9473,9 @@ sub store_userdata { $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&'; } $namevalue=~s/\&$//; + unless ($namespace eq 'courserequests') { + $datakey = &escape($datakey); + } $result = &reply("store:$udom:$uname:$namespace:$datakey:". $namevalue,$uhome); } @@ -8297,6 +9647,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 { @@ -8393,9 +9827,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') { @@ -8441,49 +9875,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'; } } @@ -8688,7 +10205,24 @@ sub dirlist { foreach my $user (sort(keys(%allusers))) { push(@alluserslist,$user.'&user'); } - return (\@alluserslist); + + 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 username'); } @@ -8761,6 +10295,115 @@ sub stat_file { 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 @@ -8905,10 +10548,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); @@ -8922,7 +10567,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 @@ -8932,7 +10584,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); @@ -8941,17 +10594,80 @@ 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 $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.'); } @@ -8976,7 +10692,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; @@ -9091,36 +10807,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'}; @@ -9137,17 +10877,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); } @@ -9155,15 +10899,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); } @@ -9180,8 +10927,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) { @@ -9189,18 +10937,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); } } @@ -9257,22 +11007,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); @@ -9357,11 +11136,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|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard|exttools?)$})) || ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) { return undef; } - if (($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) + if (($uri =~ /^priv/ || $uri=~m{^home/httpd/html/priv}) && &Apache::lonxml::get_state('target') =~ /^(|meta)$/) { return undef; } @@ -9622,7 +11401,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 } @@ -9788,7 +11567,7 @@ sub get_course_slots { my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); my ($tmp) = keys(%slots); if ($tmp !~ /^(con_lost|error|no_such_host)/i) { - &Apache::lonnet::do_cache_new('allslots',$hashid,\%slots,600); + &do_cache_new('allslots',$hashid,\%slots,600); return %slots; } } @@ -9801,6 +11580,41 @@ sub devalidate_slots_cache { &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 { @@ -9828,7 +11642,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 @@ -9847,28 +11661,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) { 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'}) || ($thisurl eq '/adm/navmaps')) { - $okay=1; + $okay=1; + last; } } } @@ -9943,9 +11772,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'}) { @@ -10007,18 +11842,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=''; } @@ -10026,7 +11889,7 @@ sub symbread { $syval=''; } } - untie(%bighash) + untie(%bighash); } } if ($syval) { @@ -10365,8 +12228,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); } @@ -10757,7 +12624,9 @@ 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/||; @@ -10800,6 +12669,8 @@ sub clutter { # &logthis("Got a blank emb style"); } } + } elsif ($thisfn =~ m{^/adm/$match_domain/$match_courseid/\d+/exttools?$}) { + $thisfn='/adm/wrapper'.$thisfn; } return $thisfn; } @@ -10862,12 +12733,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; } } @@ -10884,7 +12755,7 @@ sub get_dns { $alldns{$host} = $protocol; } while (%alldns) { - my ($dns) = keys(%alldns); + my ($dns) = sort { $b cmp $a } keys(%alldns); my $ua=new LWP::UserAgent; $ua->timeout(30); my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url"); @@ -10892,8 +12763,10 @@ sub get_dns { 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); @@ -10901,9 +12774,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; @@ -10932,8 +12862,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>; @@ -11019,8 +12949,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); @@ -11042,7 +12972,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; } @@ -11164,7 +13095,7 @@ sub get_dns { } sub get_iphost { - my ($ignore_cache) = @_; + my ($ignore_cache,$nocache) = @_; if (!$ignore_cache) { if (%iphost) { @@ -11188,7 +13119,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})) { @@ -11213,9 +13144,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; } @@ -11271,15 +13204,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 @@ -11356,33 +13322,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) { @@ -11392,7 +13339,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; @@ -11581,8 +13536,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. @@ -11665,10 +13620,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 @@ -11677,7 +13634,27 @@ 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 @@ -11718,13 +13695,44 @@ 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 * + +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 * @@ -11744,7 +13752,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, @@ -11758,6 +13766,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 @@ -11799,8 +13842,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 @@ -11850,7 +13893,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 @@ -11858,20 +13903,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 @@ -11895,6 +13940,10 @@ Inputs: =item $context +=item $credits, number of credits student will earn from this class + +=item $instsec, institutional course section code for student + =back @@ -11951,7 +14000,7 @@ If defined, the supplied username is use 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 * @@ -11960,6 +14009,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 @@ -12020,10 +14072,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 @@ -12056,17 +14113,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 * @@ -12119,6 +14191,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 @@ -12127,15 +14227,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 * @@ -12158,10 +14264,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 * @@ -12271,15 +14378,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). + +=over + +=item defaultquota, authorquota + +=back + +=item +tools (availability of aboutme page, blog, webDAV access for authoring spaces, +portfolio for users). + +=over -%domdefaults = &get_auth_defaults($target_domain); +=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 @@ -12504,7 +14687,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 @@ -12674,6 +14858,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 @@ -12691,7 +14877,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