--- loncom/lonnet/perl/lonnet.pm 2013/05/27 14:22:55 1.1056.4.37 +++ loncom/lonnet/perl/lonnet.pm 2013/03/04 01:46:31 1.1217 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1056.4.37 2013/05/27 14:22:55 raeburn Exp $ +# $Id: lonnet.pm,v 1.1217 2013/03/04 01:46:31 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -75,7 +75,10 @@ use LWP::UserAgent(); use HTTP::Date; use Image::Magick; -use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir + +use Encode; + +use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir $apache $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease %managerstab); @@ -96,6 +99,9 @@ use Math::Random; use File::MMagic; use LONCAPA qw(:DEFAULT :match); use LONCAPA::Configuration; +use LONCAPA::lonmetadata; +use LONCAPA::Lond; + use File::Copy; my $readit; @@ -107,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); } } @@ -288,7 +297,7 @@ sub get_server_homeID { } my $cachetime = 12*3600; my $serverhomeID; - if ($caller eq 'loncron') { + if ($caller eq 'loncron') { my @machine_ids = &machine_ids($hostname); foreach my $id (@machine_ids) { my $response = &reply('serverhomeID',$id); @@ -306,6 +315,52 @@ sub get_server_homeID { return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime); } +sub get_remote_globals { + my ($lonhost,$whathash,$ignore_cache) = @_; + my ($result,%returnhash,%whatneeded); + if (ref($whathash) eq 'HASH') { + foreach my $what (sort(keys(%{$whathash}))) { + my $hashid = $lonhost.'-'.$what; + my ($response,$cached); + unless ($ignore_cache) { + ($response,$cached)=&is_cached_new('lonnetglobal',$hashid); + } + if (defined($cached)) { + $returnhash{$what} = $response; + } else { + $whatneeded{$what} = 1; + } + } + if (keys(%whatneeded) == 0) { + $result = 'ok'; + } else { + my $requested = &freeze_escape(\%whatneeded); + my $rep=&reply('readlonnetglobal:'.$requested,$lonhost); + if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') || + ($rep eq 'unknown_cmd')) { + $result = $rep; + } else { + $result = 'ok'; + my @pairs=split(/\&/,$rep); + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + my $what = &unescape($key); + my $hashid = $lonhost.'-'.$what; + $returnhash{$what}=&thaw_unescape($value); + &do_cache_new('lonnetglobal',$hashid,$returnhash{$what},600); + } + } + } + } + return ($result,\%returnhash); +} + +sub remote_devalidate_cache { + my ($lonhost,$name,$id) = @_; + my $response = &reply('devalidatecache:'.&escape($name).':'.&escape($id),$lonhost); + return $response; +} + # -------------------------------------------------- Non-critical communication sub subreply { my ($cmd,$server)=@_; @@ -548,13 +603,21 @@ sub transfer_profile_to_env { # ---------------------------------------------------- Check for valid session sub check_for_valid_session { - my ($r) = @_; + my ($r,$name) = @_; my %cookies=CGI::Cookie->parse($r->header_in('Cookie')); - my $lonid=$cookies{'lonID'}; + if ($name eq '') { + $name = 'lonID'; + } + my $lonid=$cookies{$name}; return undef if (!$lonid); my $handle=&LONCAPA::clean_handle($lonid->value); - my $lonidsdir=$r->dir_config('lonIDsDir'); + my $lonidsdir; + if ($name eq 'lonDAV') { + $lonidsdir=$r->dir_config('lonDAVsessDir'); + } else { + $lonidsdir=$r->dir_config('lonIDsDir'); + } return undef if (!-e "$lonidsdir/$handle.id"); my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id"); @@ -571,6 +634,13 @@ sub check_for_valid_session { || !defined($disk_env{'user.domain'})) { return undef; } + if (($r->user() eq '') && ($apache >= 2.4)) { + if ($disk_env{'user.domain'} eq $r->dir_config('lonDefDomain')) { + $r->user($disk_env{'user.name'}); + } else { + $r->user($disk_env{'user.name'}.':'.$disk_env{'user.domain'}); + } + } return $handle; } @@ -636,11 +706,20 @@ sub appenv { # ----------------------------------------------------- Delete from Environment sub delenv { - my ($delthis,$regexp) = @_; - if (($delthis=~/user\.role/) || ($delthis=~/user\.priv/)) { - &logthis("WARNING: ". - "Attempt to delete from environment ".$delthis); - return 'error'; + my ($delthis,$regexp,$roles) = @_; + if (($delthis=~/^user\.role/) || ($delthis=~/^user\.priv/)) { + my $refused = 1; + if (ref($roles) eq 'ARRAY') { + my ($type,$role) = ($delthis =~ /^user\.(role|priv)\.([^.]+)\./); + if (grep(/^\Q$role\E$/,@{$roles})) { + $refused = 0; + } + } + if ($refused) { + &logthis("WARNING: ". + "Attempt to delete from environment ".$delthis); + return 'error'; + } } my $opened = open(my $env_file,'+<',$env{'user.environment'}); if ($opened @@ -749,30 +828,6 @@ sub userload { return $userloadpercent; } -# ------------------------------------------ Fight off request when overloaded - -sub overloaderror { - my ($r,$checkserver)=@_; - unless ($checkserver) { $checkserver=$perlvar{'lonHostID'}; } - my $loadavg; - if ($checkserver eq $perlvar{'lonHostID'}) { - open(my $loadfile,'/proc/loadavg'); - $loadavg=<$loadfile>; - $loadavg =~ s/\s.*//g; - $loadavg = 100*$loadavg/$perlvar{'lonLoadLim'}; - close($loadfile); - } else { - $loadavg=&reply('load',$checkserver); - } - my $overload=$loadavg-100; - if ($overload>0) { - $r->err_headers_out->{'Retry-After'}=$overload; - $r->log_error('Overload of '.$overload.' on '.$checkserver); - return 413; - } - return ''; -} - # ------------------------------ Find server with least workload from spare.tab sub spareserver { @@ -788,26 +843,33 @@ sub spareserver { my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom); $remotesessions = $udomdefaults{'remotesessions'}; } - foreach my $try_server (@{ $spareid{'primary'} }) { - if ($uint_dom) { - next unless (&spare_can_host($udom,$uint_dom,$remotesessions, - $try_server)); + my $spareshash = &this_host_spares($udom); + 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)); + } + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } } - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } - - my $found_server = ($spare_server ne '' && $lowest_load < 100); - if (!$found_server) { - foreach my $try_server (@{ $spareid{'default'} }) { - if ($uint_dom) { - next unless (&spare_can_host($udom,$uint_dom,$remotesessions, - $try_server)); - } - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } + my $found_server = ($spare_server ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + if ($uint_dom) { + next unless (&spare_can_host($udom,$uint_dom, + $remotesessions,$try_server)); + } + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } + } + } } if (!$want_server_name) { @@ -832,7 +894,7 @@ sub compare_server_load { my $userloadans = &reply('userload',$try_server); if ($loadans !~ /\d/ && $userloadans !~ /\d/) { - return ($spare_server, $lowest_load); #didn't get a number from the server + return ($spare_server, $lowest_load); #didn't get a number from the server } my $load; @@ -858,9 +920,18 @@ sub compare_server_load { # --------------------------- ask offload servers if user already has a session sub find_existing_session { my ($udom,$uname) = @_; - foreach my $try_server (@{ $spareid{'primary'} }, - @{ $spareid{'default'} }) { - return $try_server if (&has_user_session($try_server, $udom, $uname)); + my $spareshash = &this_host_spares($udom); + if (ref($spareshash) eq 'HASH') { + if (ref($spareshash->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'primary'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } } return; } @@ -890,7 +961,7 @@ sub choose_server { if ($loginvia) { my ($server,$path) = split(/:/,$loginvia); ($login_host, $lowest_load) = - &compare_server_load($lonhost, $login_host, $lowest_load); + &compare_server_load($server, $login_host, $lowest_load); if ($login_host eq $server) { $portal_path = $path; $isredirect = 1; @@ -900,7 +971,7 @@ sub choose_server { &compare_server_load($lonhost, $login_host, $lowest_load); if ($login_host eq $lonhost) { $portal_path = ''; - $isredirect = ''; + $isredirect = ''; } } } else { @@ -1095,6 +1166,333 @@ sub spare_can_host { return $canhost; } +sub this_host_spares { + my ($dom) = @_; + my ($dom_in_use,$lonhost_in_use,$result); + my @hosts = ¤t_machine_ids(); + foreach my $lonhost (@hosts) { + if (&host_domain($lonhost) eq $dom) { + $dom_in_use = $dom; + $lonhost_in_use = $lonhost; + last; + } + } + if ($dom_in_use ne '') { + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + } + if (ref($result) ne 'HASH') { + $lonhost_in_use = $perlvar{'lonHostID'}; + $dom_in_use = &host_domain($lonhost_in_use); + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + if (ref($result) ne 'HASH') { + $result = \%spareid; + } + } + return $result; +} + +sub spares_for_offload { + my ($dom_in_use,$lonhost_in_use) = @_; + my ($result,$cached)=&is_cached_new('spares',$dom_in_use); + if (defined($cached)) { + return $result; + } else { + my $cachetime = 60*60*24; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['usersessions'],$dom_in_use); + if (ref($domconfig{'usersessions'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}{$lonhost_in_use}) eq 'HASH') { + return &do_cache_new('spares',$dom_in_use,$domconfig{'usersessions'}{'spares'}{$lonhost_in_use},$cachetime); + } + } + } + } + return; +} + +sub get_lonbalancer_config { + my ($servers) = @_; + my ($currbalancer,$currtargets); + if (ref($servers) eq 'HASH') { + foreach my $server (keys(%{$servers})) { + my %what = ( + spareid => 1, + perlvar => 1, + ); + my ($result,$returnhash) = &get_remote_globals($server,\%what); + if ($result eq 'ok') { + if (ref($returnhash) eq 'HASH') { + if (ref($returnhash->{'perlvar'}) eq 'HASH') { + if ($returnhash->{'perlvar'}->{'lonBalancer'} eq 'yes') { + $currbalancer = $server; + $currtargets = {}; + if (ref($returnhash->{'spareid'}) eq 'HASH') { + if (ref($returnhash->{'spareid'}->{'primary'}) eq 'ARRAY') { + $currtargets->{'primary'} = $returnhash->{'spareid'}->{'primary'}; + } + if (ref($returnhash->{'spareid'}->{'default'}) eq 'ARRAY') { + $currtargets->{'default'} = $returnhash->{'spareid'}->{'default'}; + } + } + last; + } + } + } + } + } + } + return ($currbalancer,$currtargets); +} + +sub check_loadbalancing { + my ($uname,$udom) = @_; + 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 $cachetime = 60*60*24; + + if (($uintdom ne '') && ($uintdom eq $intdom)) { + $dom_in_use = $udom; + $homeintdom = 1; + } else { + $dom_in_use = $serverhomedom; + } + my ($result,$cached)=&is_cached_new('loadbalancing',$dom_in_use); + unless (defined($cached)) { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + } + } + if (ref($result) eq 'HASH') { + ($is_balancer,$currtargets,$currrules) = + &check_balancer_result($result,@hosts); + if ($is_balancer) { + if (ref($currrules) eq 'HASH') { + if ($homeintdom) { + if ($uname ne '') { + if (($currrules->{'_LC_adv'} ne '') || ($currrules->{'_LC_author'} ne '')) { + my ($is_adv,$is_author) = &is_advanced_user($udom,$uname); + if (($currrules->{'_LC_author'} ne '') && ($is_author)) { + $rule_in_effect = $currrules->{'_LC_author'}; + } elsif (($currrules->{'_LC_adv'} ne '') && ($is_adv)) { + $rule_in_effect = $currrules->{'_LC_adv'} + } + } + if ($rule_in_effect eq '') { + my %userenv = &userenvironment($udom,$uname,'inststatus'); + if ($userenv{'inststatus'} ne '') { + my @statuses = map { &unescape($_); } split(/:/,$userenv{'inststatus'}); + my ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($udom); + if (ref($types) eq 'ARRAY') { + foreach my $type (@{$types}) { + if (grep(/^\Q$type\E$/,@statuses)) { + if (exists($currrules->{$type})) { + $rule_in_effect = $currrules->{$type}; + } + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } else { + if ($currrules->{'_LC_external'} ne '') { + $rule_in_effect = $currrules->{'_LC_external'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } + } elsif (($homeintdom) && ($udom ne $serverhomedom)) { + my ($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); + } + } + if (ref($result) eq 'HASH') { + ($is_balancer,$currtargets,$currrules) = + &check_balancer_result($result,@hosts); + if ($is_balancer) { + if (ref($currrules) eq 'HASH') { + if ($currrules->{'_LC_internetdom'} ne '') { + $rule_in_effect = $currrules->{'_LC_internetdom'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + } + if ($is_balancer) { + my $lowest_load = 30000; + if (ref($offloadto) eq 'HASH') { + if (ref($offloadto->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'primary'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + my $found_server = ($otherserver ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($offloadto->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'default'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + } elsif (ref($offloadto) eq 'ARRAY') { + if (@{$offloadto} == 1) { + $otherserver = $offloadto->[0]; + } elsif (@{$offloadto} > 1) { + foreach my $try_server (@{$offloadto}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) { + $is_balancer = 0; + if ($uname ne '' && $udom ne '') { + if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { + + &appenv({'user.loadbalexempt' => $lonhost, + 'user.loadbalcheck.time' => time}); + } + } + } + } + return ($is_balancer,$otherserver); +} + +sub check_balancer_result { + my ($result,@hosts) = @_; + my ($is_balancer,$currtargets,$currrules); + if (ref($result) eq 'HASH') { + if ($result->{'lonhost'} ne '') { + my $currbalancer = $result->{'lonhost'}; + if (grep(/^\Q$currbalancer\E$/,@hosts)) { + $is_balancer = 1; + $currtargets = $result->{'targets'}; + $currrules = $result->{'rules'}; + } + } else { + foreach my $key (keys(%{$result})) { + if (($key ne '') && (grep(/^\Q$key\E$/,@hosts)) && + (ref($result->{$key}) eq 'HASH')) { + $is_balancer = 1; + $currrules = $result->{$key}{'rules'}; + $currtargets = $result->{$key}{'targets'}; + last; + } + } + } + } + return ($is_balancer,$currtargets,$currrules); +} + +sub get_loadbalancer_targets { + my ($rule_in_effect,$currtargets,$uname,$udom) = @_; + my $offloadto; + if ($rule_in_effect eq 'none') { + return [$perlvar{'lonHostID'}]; + } elsif ($rule_in_effect eq '') { + $offloadto = $currtargets; + } else { + if ($rule_in_effect eq 'homeserver') { + my $homeserver = &homeserver($uname,$udom); + if ($homeserver ne 'no_host') { + $offloadto = [$homeserver]; + } + } elsif ($rule_in_effect eq 'externalbalancer') { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + if ($domconfig{'loadbalancing'}{'lonhost'} ne '') { + if (&hostname($domconfig{'loadbalancing'}{'lonhost'}) ne '') { + $offloadto = [$domconfig{'loadbalancing'}{'lonhost'}]; + } + } + } else { + my %servers = &internet_dom_servers($udom); + my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers); + if (&hostname($remotebalancer) ne '') { + $offloadto = [$remotebalancer]; + } + } + } elsif (&hostname($rule_in_effect) ne '') { + $offloadto = [$rule_in_effect]; + } + } + return $offloadto; +} + +sub internet_dom_servers { + my ($dom) = @_; + my (%uniqservers,%servers); + my $primaryserver = &hostname(&domain($dom,'primary')); + my @machinedoms = &machine_domains($primaryserver); + foreach my $mdom (@machinedoms) { + my %currservers = %servers; + my %server = &get_servers($mdom); + %servers = (%currservers,%server); + } + my %by_hostname; + foreach my $id (keys(%servers)) { + push(@{$by_hostname{$servers{$id}}},$id); + } + foreach my $hostname (sort(keys(%by_hostname))) { + if (@{$by_hostname{$hostname}} > 1) { + my $match = 0; + foreach my $id (@{$by_hostname{$hostname}}) { + if (&host_domain($id) eq $dom) { + $uniqservers{$id} = $hostname; + $match = 1; + } + } + unless ($match) { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } else { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } + return %uniqservers; +} + # ---------------------- Find the homebase for a user from domain's lib servers my %homecache; @@ -1182,16 +1580,13 @@ sub idput { # ------------------------------dump from db file owned by domainconfig user sub dump_dom { - my ($namespace,$udom,$regexp,$range)=@_; - if (!$udom) { - $udom=$env{'user.domain'}; - } - my %returnhash; - if ($udom) { - my $uname = &get_domainconfiguser($udom); - %returnhash = &dump($namespace,$udom,$uname,$regexp,$range); - } - return %returnhash; + my ($namespace, $udom, $regexp) = @_; + + $udom ||= $env{'user.domain'}; + + return () unless $udom; + + return &dump($namespace, $udom, &get_domainconfiguser($udom), $regexp); } # ------------------------------------------ get items from domain db files @@ -1571,7 +1966,8 @@ sub get_domain_defaults { my %domconfig = &Apache::lonnet::get_dom('configuration',['defaults','quotas', 'requestcourses','inststatus', - 'coursedefaults','usersessions'],$domain); + 'coursedefaults','usersessions', + 'requestauthor'],$domain); if (ref($domconfig{'defaults'}) eq 'HASH') { $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'}; @@ -1590,7 +1986,7 @@ sub get_domain_defaults { } 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}; @@ -1602,6 +1998,9 @@ sub get_domain_defaults { $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') { $domdefaults{$item} = $domconfig{'inststatus'}{$item}; @@ -1611,6 +2010,10 @@ sub get_domain_defaults { foreach my $item ('canuse_pdfforms') { $domdefaults{$item} = $domconfig{'coursedefaults'}{$item}; } + if (ref($domconfig{'coursedefaults'}{'coursecredits'}) eq 'HASH') { + $domdefaults{'officialcredits'} = $domconfig{'coursedefaults'}{'coursecredits'}{'official'}; + $domdefaults{'unofficialcredits'} = $domconfig{'coursedefaults'}{'coursecredits'}{'unofficial'}; + } } if (ref($domconfig{'usersessions'}) eq 'HASH') { if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') { @@ -1805,8 +2208,7 @@ sub getsection { # If there is a role which has expired, return it. # $courseid = &courseid_to_courseurl($courseid); - my $extra = &freeze_escape({'skipcheck' => 1}); - my %roleshash = &dump('roles',$udom,$unam,$courseid,undef,$extra); + my %roleshash = &dump('roles',$udom,$unam,$courseid); foreach my $key (keys(%roleshash)) { next if ($key !~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/); my $section=$1; @@ -1872,7 +2274,7 @@ sub is_cached_new { my ($name,$id,$debug) = @_; $id=&make_key($name,$id); if (exists($remembered{$id})) { - if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); } + if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); } $accessed{$id}=[&gettimeofday()]; $hits++; return ($remembered{$id},1); @@ -2032,26 +2434,35 @@ sub chatsend { sub getversion { my $fname=&clutter(shift); - unless ($fname=~/^\/res\//) { return -1; } + unless ($fname=~m{^(/adm/wrapper|)/res/}) { return -1; } return ¤tversion(&filelocation('',$fname)); } sub currentversion { my $fname=shift; - my ($result,$cached)=&is_cached_new('resversion',$fname); - if (defined($cached)) { return $result; } my $author=$fname; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; my ($udom,$uname)=split(/\//,$author); - my $home=homeserver($uname,$udom); + my $home=&homeserver($uname,$udom); if ($home eq 'no_host') { return -1; } - my $answer=reply("currentversion:$fname",$home); + my $answer=&reply("currentversion:$fname",$home); if (($answer eq 'con_lost') || ($answer eq 'rejected')) { return -1; } - return &do_cache_new('resversion',$fname,$answer,600); + return $answer; +} + +# +# Return special version number of resource if set by override, empty otherwise +# +sub usedversion { + my $fname=shift; + unless ($fname) { $fname=$env{'request.uri'}; } + my ($urlversion)=($fname=~/\.(\d+)\.\w+$/); + if ($urlversion) { return $urlversion; } + return ''; } # ----------------------------- Subscribe to a resource, return URL if possible @@ -2079,10 +2490,11 @@ sub subscribe { sub repcopy { my $filename=shift; $filename=~s/\/+/\//g; - if ($filename=~m|^/home/httpd/html/adm/|) { return 'ok'; } - if ($filename=~m|^/home/httpd/html/lonUsers/|) { return 'ok'; } - if ($filename=~m|^/home/httpd/html/userfiles/| or - $filename=~m -^/*(uploaded|editupload)/-) { + my $londocroot = $perlvar{'lonDocRoot'}; + if ($filename=~m{^\Q$londocroot/adm/\E}) { return 'ok'; } + if ($filename=~m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; } + if ($filename=~m{^\Q$londocroot/userfiles/\E} or + $filename=~m{^/*(uploaded|editupload)/}) { return &repcopy_userfile($filename); } $filename=~s/[\n\r]//g; @@ -2109,7 +2521,7 @@ sub repcopy { unless ($home eq $perlvar{'lonHostID'}) { my @parts=split(/\//,$filename); my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]"; - if ($path ne "$perlvar{'lonDocRoot'}/res") { + if ($path ne "$londocroot/res") { &logthis("Malconfiguration for replication: $filename"); return 'bad_request'; } @@ -2208,12 +2620,14 @@ sub ssi { } $request->header(Cookie => $ENV{'HTTP_COOKIE'}); - my $response=$ua->request($request); + my $response= $ua->request($request); + my $content = $response->content; + if (wantarray) { - return ($response->content, $response); + return ($content, $response); } else { - return $response->content; + return $content; } } @@ -2242,6 +2656,253 @@ 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/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 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'}; + $cfile =~ s{^http://}{}; + $cfile = '/adm/wrapper/ext/'.$cfile; + } + } + } + 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; + if (&privileged($uname,$udom)) { + $skipuser = 1; + my %coursehash = &coursedescription($cdom.'_'.$cnum); + 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, @@ -2313,7 +2974,7 @@ sub process_coursefile { } if (ref($mimetype)) { $$mimetype = $type; - } + } } $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file, $home); @@ -2431,11 +3092,11 @@ sub resizeImage { # input: $formname - the contents of the file are in $env{"form.$formname"} # the desired filename is in $env{"form.$formname.filename"} # $context - possible values: coursedoc, existingfile, overwrite, -# canceloverwrite, or ''. +# canceloverwrite, or ''. # if 'coursedoc': upload to the current course -# if 'existingfile': write file to tmp/overwrites directory +# if 'existingfile': write file to tmp/overwrites directory # if 'canceloverwrite': delete file written to tmp/overwrites directory -# $context is passed as argument to &finishuserfileupload +# $context is passed as argument to &finishuserfileupload # $subdir - directory in userfile to store the file into # $parser - instruction to parse file for objects ($parser = parse) # $allfiles - reference to hash for embedded objects @@ -2447,7 +3108,7 @@ sub resizeImage { # $resizewidth - width (pixels) to which to resize uploaded image # $resizeheight - height (pixels) to which to resize uploaded image # $mimetype - reference to scalar to accommodate mime type determined -# from File::MMagic if $parser = parse. +# from File::MMagic. # # output: url of file in userspace, or error: # or /adm/notfound.html if failure to upload occurse @@ -2587,11 +3248,11 @@ sub finishuserfileupload { return '/adm/notfound.html'; } if ($context eq 'overwrite') { - my $source = $perlvar{'lonDaemons'}.'/tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$fname; + my $source = LONCAPA::tempdir().'/overwrites/'.$docudom.'/'.$docuname.'/'.$fname; my $target = $filepath.'/'.$file; if (-e $source) { my @info = stat($source); - if ($info[9] eq $env{'form.timestamp'}) { + if ($info[9] eq $env{'form.timestamp'}) { unless (&File::Copy::move($source,$target)) { &logthis('Failed to overwrite '.$filepath.'/'.$file); return "Moving from $source failed"; @@ -2602,7 +3263,7 @@ sub finishuserfileupload { } else { return "Temporary file: $source missing"; } - } elsif (!print FH ($env{'form.'.$formname})) { + } elsif (!print FH ($env{'form.'.$formname})) { &logthis('Failed to write to '.$filepath.'/'.$file); print STDERR ('Failed to write to '.$filepath.'/'.$file."\n"); return '/adm/notfound.html'; @@ -2616,10 +3277,17 @@ sub finishuserfileupload { } } } + if (($context eq 'coursedoc') || ($parser eq 'parse')) { + if (ref($mimetype)) { + if ($$mimetype eq '') { + my $mm = new File::MMagic; + my $type = $mm->checktype_filename($filepath.'/'.$file); + $$mimetype = $type; + } + } + } if ($parser eq 'parse') { - my $mm = new File::MMagic; - my $type = $mm->checktype_filename($filepath.'/'.$file); - if ($type eq 'text/html') { + if ((ref($mimetype)) && ($$mimetype eq 'text/html')) { my $parse_result = &extract_embedded_items($filepath.'/'.$file, $allfiles,$codebase); unless ($parse_result eq 'ok') { @@ -2627,9 +3295,6 @@ sub finishuserfileupload { ' for embedded media: '.$parse_result); } } - if (ref($mimetype)) { - $$mimetype = $type; - } } if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) { my $input = $filepath.'/'.$file; @@ -2666,6 +3331,7 @@ sub finishuserfileupload { sub extract_embedded_items { my ($fullpath,$allfiles,$codebase,$content) = @_; my @state = (); + my (%lastids,%related,%shockwave,%flashvars); my %javafiles = ( codebase => '', code => '', @@ -2695,10 +3361,30 @@ sub extract_embedded_items { &add_filetype($allfiles,$attr->{'href'},'href'); } if (lc($tagname) eq 'script') { + my $src; if ($attr->{'archive'} =~ /\.jar$/i) { &add_filetype($allfiles,$attr->{'archive'},'archive'); } else { - &add_filetype($allfiles,$attr->{'src'},'src'); + if ($attr->{'src'} ne '') { + $src = $attr->{'src'}; + &add_filetype($allfiles,$src,'src'); + } + } + my $text = $p->get_trimmed_text(); + if ($text =~ /\Qswfobject.registerObject(\E([^\)]+)\)/) { + my @swfargs = split(/,/,$1); + foreach my $item (@swfargs) { + $item =~ s/["']//g; + $item =~ s/^\s+//; + $item =~ s/\s+$//; + } + if (($swfargs[0] ne'') && ($swfargs[2] ne '')) { + if (ref($related{$swfargs[0]}) eq 'ARRAY') { + push(@{$related{$swfargs[0]}},$swfargs[2]); + } else { + $related{$swfargs[0]} = [$swfargs[2]]; + } + } } } if (lc($tagname) eq 'link') { @@ -2711,6 +3397,9 @@ sub extract_embedded_items { foreach my $item (keys(%javafiles)) { $javafiles{$item} = ''; } + if ((lc($tagname) eq 'object') && (lc($state[-2]) ne 'object')) { + $lastids{lc($tagname)} = $attr->{'id'}; + } } if (lc($state[-2]) eq 'object' && lc($tagname) eq 'param') { my $name = lc($attr->{'name'}); @@ -2720,12 +3409,22 @@ sub extract_embedded_items { last; } } + my $pathfrom; foreach my $item (keys(%mediafiles)) { if ($name eq $item) { - &add_filetype($allfiles, $attr->{'value'}, 'value'); + $pathfrom = $attr->{'value'}; + $shockwave{$lastids{lc($state[-2])}} = $pathfrom; + &add_filetype($allfiles,$pathfrom,$name); last; } } + if ($name eq 'flashvars') { + $flashvars{$lastids{lc($state[-2])}} = $attr->{'value'}; + } + if ($pathfrom ne '') { + &embedded_dependency($allfiles,\%related,$lastids{lc($state[-2])}, + $pathfrom); + } } if (lc($tagname) eq 'embed' || lc($tagname) eq 'applet') { foreach my $item (keys(%javafiles)) { @@ -2740,7 +3439,16 @@ sub extract_embedded_items { last; } } + if (lc($tagname) eq 'embed') { + if (($attr->{'name'} ne '') && ($attr->{'src'} ne '')) { + &embedded_dependency($allfiles,\%related,$attr->{'name'}, + $attr->{'src'}); + } + } } + if ($t->[4] =~ m{/>$}) { + pop(@state); + } } elsif ($t->[0] eq 'E') { my ($tagname) = ($t->[1]); if ($javafiles{'codebase'} ne '') { @@ -2760,6 +3468,23 @@ sub extract_embedded_items { pop @state; } } + foreach my $id (sort(keys(%flashvars))) { + if ($shockwave{$id} ne '') { + my @pairs = split(/\&/,$flashvars{$id}); + foreach my $pair (@pairs) { + my ($key,$value) = split(/\=/,$pair); + if ($key eq 'thumb') { + &add_filetype($allfiles,$value,$key); + } elsif ($key eq 'content') { + my ($path) = ($shockwave{$id} =~ m{^(.+/)[^/]+$}); + my ($ext) = ($value =~ /\.([^.]+)$/); + if ($ext ne '') { + &add_filetype($allfiles,$path.$value,$ext); + } + } + } + } + } return 'ok'; } @@ -2774,6 +3499,21 @@ sub add_filetype { } } +sub embedded_dependency { + my ($allfiles,$related,$identifier,$pathfrom) = @_; + if ((ref($allfiles) eq 'HASH') && (ref($related) eq 'HASH')) { + if (($identifier ne '') && + (ref($related->{$identifier}) eq 'ARRAY') && + ($pathfrom ne '')) { + my ($path) = ($pathfrom =~ m{^(.+/)[^/]+$}); + foreach my $dep (@{$related->{$identifier}}) { + &add_filetype($allfiles,$path.$dep,'object'); + } + } + } + return; +} + sub removeuploadedurl { my ($url)=@_; my (undef,undef,$udom,$uname,$fname)=split('/',$url,5); @@ -2900,15 +3640,10 @@ sub flushcourselogs { my $result = &inc('nohist_accesscount',\%temphash,$dom,$name); if ($result eq 'ok') { delete $accesshash{$entry}; - } elsif ($result eq 'unknown_cmd') { - # Target server has old code running on it. - my %temphash=($entry => $value); - if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') { - delete $accesshash{$entry}; - } } } else { my ($dom,$name) = ($entry=~m{___($match_domain)/($match_name)/(.*)___(\w+)$}); + if (($dom eq 'uploaded') || ($dom eq 'adm')) { next; } my %temphash=($entry => $accesshash{$entry}); if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') { delete $accesshash{$entry}; @@ -2987,7 +3722,7 @@ sub courseacclog { my $fnsymb=shift; unless ($env{'request.course.id'}) { return ''; } my $what=$fnsymb.':'.$env{'user.name'}.':'.$env{'user.domain'}; - if ($fnsymb=~/(problem|exam|quiz|assess|survey|form|task|page)$/) { + if ($fnsymb=~/$LONCAPA::assess_re/) { $what.=':POST'; # FIXME: Probably ought to escape things.... foreach my $key (keys(%env)) { @@ -3019,7 +3754,13 @@ sub countacc { my $url=&declutter(shift); return if (! defined($url) || $url eq ''); unless ($env{'request.course.id'}) { return ''; } +# +# Mark that this url was used in this course +# $accesshash{$env{'request.course.id'}.'___'.$url.'___course'}=1; +# +# Increase the access count for this resource in this child process +# my $key=$$.$processmarker.'_'.$dumpcount.'___'.$url.'___count'; $accesshash{$key}++; } @@ -3031,31 +3772,37 @@ sub linklog { $accesshash{$from.'___'.$to.'___comefrom'}=1; $accesshash{$to.'___'.$from.'___goto'}=1; } + +sub statslog { + my ($symb,$part,$users,$av_attempts,$degdiff)=@_; + if ($users<2) { return; } + my %dynstore=&LONCAPA::lonmetadata::dynamic_metadata_storage({ + 'course' => $env{'request.course.id'}, + 'sections' => '"all"', + 'num_students' => $users, + 'part' => $part, + 'symb' => $symb, + 'mean_tries' => $av_attempts, + 'deg_of_diff' => $degdiff}); + foreach my $key (keys(%dynstore)) { + $accesshash{$key}=$dynstore{$key}; + } +} sub userrolelog { my ($trole,$username,$domain,$area,$tstart,$tend)=@_; - if (($trole=~/^ca/) || ($trole=~/^aa/) || - ($trole=~/^in/) || ($trole=~/^cc/) || - ($trole=~/^ep/) || ($trole=~/^cr/) || - ($trole=~/^ta/) || ($trole=~/^co/)) { + if ( $trole =~ /^(ca|aa|in|cc|ep|cr|ta|co)/ ) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $userrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} =$tend.':'.$tstart; } - if (($env{'request.role'} =~ /dc\./) && - (($trole=~/^au/) || ($trole=~/^in/) || - ($trole=~/^cc/) || ($trole=~/^ep/) || - ($trole=~/^cr/) || ($trole=~/^ta/) || - ($trole=~/^co/))) { + if ($env{'request.role'} =~ /dc\./ && $trole =~ /^(au|in|cc|ep|cr|ta|co)/) { $userrolehash {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'} =$tend.':'.$tstart; } - if (($trole=~/^dc/) || ($trole=~/^ad/) || - ($trole=~/^li/) || ($trole=~/^li/) || - ($trole=~/^au/) || ($trole=~/^dg/) || - ($trole=~/^sc/)) { + if ($trole =~ /^(dc|ad|li|au|dg|sc)/ ) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $domainrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} @@ -3065,38 +3812,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)); @@ -3163,9 +3942,8 @@ sub get_my_roles { unless (defined($uname)) { $uname=$env{'user.name'}; } unless (defined($udom)) { $udom=$env{'user.domain'}; } my (%dumphash,%nothide); - if ($context eq 'userroles') { - my $extra = &freeze_escape({'skipcheck' => 1}); - %dumphash = &dump('roles',$udom,$uname,'.',undef,$extra); + if ($context eq 'userroles') { + %dumphash = &dump('roles',$udom,$uname); } else { %dumphash= &dump('nohist_userroles',$udom,$uname); @@ -3186,6 +3964,7 @@ sub get_my_roles { foreach my $entry (keys(%dumphash)) { my ($role,$tend,$tstart); if ($context eq 'userroles') { + next if ($entry =~ /^rolesdef/); ($role,$tend,$tstart)=split(/_/,$dumphash{$entry}); } else { ($tend,$tstart)=split(/\:/,$dumphash{$entry}); @@ -3209,7 +3988,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); @@ -3225,6 +4004,10 @@ sub get_my_roles { if (!grep(/^cr$/,@{$roles})) { next; } + } elsif ($role =~ /^gr\//) { + if (!grep(/^gr$/,@{$roles})) { + next; + } } else { next; } @@ -3356,18 +4139,32 @@ 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))); + } 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, + $tryserver); + } + my @pairs=split(/\&/,$rep); foreach my $item (@pairs) { my ($key,$value)=split(/\=/,$item,2); @@ -3493,13 +4290,36 @@ sub get_domain_roles { return %personnel; } -# ----------------------------------------------------------- Check out an item +# ----------------------------------------------------------- Interval timing + +{ +# Caches needed for speedup of navmaps +# We don't want to cache this for very long at all (5 seconds at most) +# +# The user for whom we cache +my $cachedkey=''; +# The cached times for this user +my %cachedtimes=(); +# When this was last done +my $cachedtime=(); + +sub load_all_first_access { + my ($uname,$udom)=@_; + if (($cachedkey eq $uname.':'.$udom) && + (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) { + return; + } + $cachedtime=time; + $cachedkey=$uname.':'.$udom; + %cachedtimes=&dump('firstaccesstimes',$udom,$uname); +} sub get_first_access { - my ($type,$argsymb)=@_; + my ($type,$argsymb,$argmap)=@_; my ($symb,$courseid,$udom,$uname)=&whichuser(); if ($argsymb) { $symb=$argsymb; } my ($map,$id,$res)=&decode_symb($symb); + if ($argmap) { $map = $argmap; } if ($type eq 'course') { $res='course'; } elsif ($type eq 'map') { @@ -3507,12 +4327,12 @@ sub get_first_access { } else { $res=$symb; } - my %times=&get('firstaccesstimes',["$courseid\0$res"],$udom,$uname); - return $times{"$courseid\0$res"}; + &load_all_first_access($uname,$udom); + return $cachedtimes{"$courseid\0$res"}; } sub set_first_access { - my ($type)=@_; + my ($type,$interval)=@_; my ($symb,$courseid,$udom,$uname)=&whichuser(); my ($map,$id,$res)=&decode_symb($symb); if ($type eq 'course') { @@ -3522,98 +4342,27 @@ sub set_first_access { } else { $res=$symb; } - my $firstaccess=&get_first_access($type,$symb); + $cachedkey=''; + my $firstaccess=&get_first_access($type,$symb,$map); if (!$firstaccess) { - return &put('firstaccesstimes',{"$courseid\0$res"=>time},$udom,$uname); + my $start = time; + my $putres = &put('firstaccesstimes',{"$courseid\0$res"=>$start}, + $udom,$uname); + if ($putres eq 'ok') { + &put('timerinterval',{"$courseid\0$res"=>$interval}, + $udom,$uname); + &appenv( + { + 'course.'.$courseid.'.firstaccess.'.$res => $start, + 'course.'.$courseid.'.timerinterval.'.$res => $interval, + } + ); + } + return $putres; } return 'already_set'; } - -sub checkout { - my ($symb,$tuname,$tudom,$tcrsid)=@_; - my $now=time; - my $lonhost=$perlvar{'lonHostID'}; - my $infostr=&escape( - 'CHECKOUTTOKEN&'. - $tuname.'&'. - $tudom.'&'. - $tcrsid.'&'. - $symb.'&'. - $now.'&'.$ENV{'REMOTE_ADDR'}); - my $token=&reply('tmpput:'.$infostr,$lonhost); - if ($token=~/^error\:/) { - &logthis("WARNING: ". - "Checkout tmpput failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - return ''; - } - - $token=~s/^(\d+)\_.*\_(\d+)$/$1\*$2\*$lonhost/; - $token=~tr/a-z/A-Z/; - - my %infohash=('resource.0.outtoken' => $token, - 'resource.0.checkouttime' => $now, - 'resource.0.outremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout cstore failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkout '.$infostr.' - '. - $token)) ne 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout log failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - return $token; } - -# ------------------------------------------------------------ Check in an item - -sub checkin { - my $token=shift; - my $now=time; - my ($ta,$tb,$lonhost)=split(/\*/,$token); - $lonhost=~tr/A-Z/a-z/; - my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb; - $dtoken=~s/\W/\_/g; - my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= - split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost))); - - unless (($tuname) && ($tudom)) { - &logthis('Check in '.$token.' ('.$dtoken.') failed'); - return ''; - } - - unless (&allowed('mgr',$tcrsid)) { - &logthis('Check in '.$token.' ('.$dtoken.') unauthorized: '. - $env{'user.name'}.' - '.$env{'user.domain'}); - return ''; - } - - my %infohash=('resource.0.intoken' => $token, - 'resource.0.checkintime' => $now, - 'resource.0.inremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkin - '.$token)) ne 'ok') { - return ''; - } - - return ($symb,$tuname,$tudom,$tcrsid); -} - # --------------------------------------------- Set Expire Date for Spreadsheet sub expirespread { @@ -3718,7 +4467,7 @@ sub hashref2str { $result.='='; #print("Got a ref of ".(ref($key))." skipping."); } else { - if (defined($key)) {$result.=&escape($key).'=';} else { last; } + if (defined($key)) {$result.=&escape($key).'=';} else { last; } } if(ref($hashref->{$key}) eq 'ARRAY') { @@ -3870,7 +4619,7 @@ sub tmpreset { if ($domain eq 'public' && $stuname eq 'public') { $stuname=$ENV{'REMOTE_ADDR'}; } - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); my %hash; if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', @@ -3909,7 +4658,7 @@ sub tmpstore { } my $now=time; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_WRCREAT(),0640)) { @@ -3955,7 +4704,7 @@ sub tmprestore { $namespace=~s/\//\_/g; $namespace=~s/\W//g; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_READER(),0640)) { @@ -4063,12 +4812,9 @@ sub restore { if ($stuname) { $home=&homeserver($stuname,$domain); } if (!$symb) { - return if ($namespace eq 'courserequests'); - unless ($symb=escape(&symbread())) { return ''; } + unless ($symb=escape(&symbread())) { return ''; } } else { - unless ($namespace eq 'courserequests') { - $symb=&escape(&symbclean($symb)); - } + $symb=&escape(&symbclean($symb)); } if (!$namespace) { unless ($namespace=$env{'request.course.id'}) { @@ -4095,6 +4841,8 @@ sub restore { } # ---------------------------------------------------------- Course Description +# +# sub coursedescription { my ($courseid,$args)=@_; @@ -4124,7 +4872,8 @@ sub coursedescription { return %returnhash; } - # get the data agin + # get the data again + if (!$args->{'one_time'}) { $envhash{'course.'.$normalid.'.last_cache'}=time; } @@ -4132,6 +4881,10 @@ sub coursedescription { if ($chome ne 'no_host') { %returnhash=&dump('environment',$cdomain,$cnum); if (!exists($returnhash{'con_lost'})) { + my $username = $env{'user.name'}; # Defult username + if(defined $args->{'user'}) { + $username = $args->{'user'}; + } $returnhash{'home'}= $chome; $returnhash{'domain'} = $cdomain; $returnhash{'num'} = $cnum; @@ -4142,8 +4895,8 @@ sub coursedescription { $envhash{'course.'.$normalid.'.'.$name}=$value; } $returnhash{'url'}=&clutter($returnhash{'url'}); - $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'. - $env{'user.name'}.'_'.$cdomain.'_'.$cnum; + $returnhash{'fn'}=LONCAPA::tempdir() . + $username.'_'.$cdomain.'_'.$cnum; $envhash{'course.'.$normalid.'.home'}=$chome; $envhash{'course.'.$normalid.'.domain'}=$cdomain; $envhash{'course.'.$normalid.'.num'}=$cnum; @@ -4197,101 +4950,135 @@ sub update_released_required { sub privileged { my ($username,$domain)=@_; - my $rolesdump=&reply("dump:$domain:$username:roles", - &homeserver($username,$domain)); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || - ($rolesdump =~ /^error:/)) { - return 0; - } - my $now=time; - if ($rolesdump ne '') { - foreach my $entry (split(/&/,$rolesdump)) { - if ($entry!~/^rolesdef_/) { - my ($area,$role)=split(/=/,$entry); - $area=~s/\_\w\w$//; - my ($trole,$tend,$tstart)=split(/_/,$role); - if (($trole eq 'dc') || ($trole eq 'su')) { - my $active=1; - if ($tend) { - if ($tend<$now) { $active=0; } - } - if ($tstart) { - if ($tstart>$now) { $active=0; } - } - if ($active) { return 1; } - } - } + + my %rolesdump = &dump("roles", $domain, $username) or return 0; + my $now = time; + + for my $role (@rolesdump{grep { ! /^rolesdef_/ } keys %rolesdump}) { + my ($trole, $tend, $tstart) = split(/_/, $role); + if (($trole eq 'dc') || ($trole eq 'su')) { + return 1 unless ($tend && $tend < $now) + or ($tstart && $tstart > $now); + } } - } + return 0; } # -------------------------------------------------------- Get user privileges sub rolesinit { - my ($domain,$username,$authhost)=@_; - my $now=time; - my %userroles = ('user.login.time' => $now); - my $extra = &freeze_escape({'skipcheck' => 1}); - my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || - ($rolesdump =~ /^error:/)) { - return \%userroles; + my ($domain, $username) = @_; + my %userroles = ('user.login.time' => time); + my %rolesdump = &dump("roles", $domain, $username) or return \%userroles; + + # firstaccess and timerinterval are related to timed maps/resources. + # also, blocking can be triggered by an activating timer + # it's saved in the user's %env. + my %firstaccess = &dump('firstaccesstimes', $domain, $username); + my %timerinterval = &dump('timerinterval', $domain, $username); + my (%coursetimerstarts, %firstaccchk, %firstaccenv, %coursetimerintervals, + %timerintchk, %timerintenv); + + foreach my $key (keys(%firstaccess)) { + my ($cid, $rest) = split(/\0/, $key); + $coursetimerstarts{$cid}{$rest} = $firstaccess{$key}; + } + + foreach my $key (keys(%timerinterval)) { + my ($cid,$rest) = split(/\0/,$key); + $coursetimerintervals{$cid}{$rest} = $timerinterval{$key}; } + my %allroles=(); - my %allgroups=(); - my $group_privs; + my %allgroups=(); - if ($rolesdump ne '') { - foreach my $entry (split(/&/,$rolesdump)) { - if ($entry!~/^rolesdef_/) { - my ($area,$role)=split(/=/,$entry); - $area=~s/\_\w\w$//; - my ($trole,$tend,$tstart,$group_privs); - if ($role=~/^cr/) { - if ($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) { - ($trole,my $trest)=($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|); - ($tend,$tstart)=split('_',$trest); - } else { - $trole=$role; - } - } elsif ($role =~ m|^gr/|) { - ($trole,$tend,$tstart) = split(/_/,$role); - ($trole,$group_privs) = split(/\//,$trole); - $group_privs = &unescape($group_privs); - } else { - ($trole,$tend,$tstart)=split(/_/,$role); - } - my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain, - $username); - @userroles{keys(%new_role)} = @new_role{keys(%new_role)}; - if (($tend!=0) && ($tend<$now)) { $trole=''; } - if (($tstart!=0) && ($tstart>$now)) { $trole=''; } - if (($area ne '') && ($trole ne '')) { - my $spec=$trole.'.'.$area; - my ($tdummy,$tdomain,$trest)=split(/\//,$area); - if ($trole =~ /^cr\//) { - &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area); - } elsif ($trole eq 'gr') { - &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart); - } else { - &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); - } + for my $area (grep { ! /^rolesdef_/ } keys %rolesdump) { + my $role = $rolesdump{$area}; + $area =~ s/\_\w\w$//; + + my ($trole, $tend, $tstart, $group_privs); + + if ($role =~ /^cr/) { + # Custom role, defined by a user + # e.g., user.role.cr/msu/smith/mynewrole + if ($role =~ m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) { + $trole = $1; + ($tend, $tstart) = split('_', $2); + } else { + $trole = $role; } - } + } elsif ($role =~ m|^gr/|) { + # Role of member in a group, defined within a course/community + # e.g., user.role.gr/msu/04935610a19ee4a5fmsul1/leopards + ($trole, $tend, $tstart) = split(/_/, $role); + next if $tstart eq '-1'; + ($trole, $group_privs) = split(/\//, $trole); + $group_privs = &unescape($group_privs); + } else { + # Just a normal role, defined in roles.tab + ($trole, $tend, $tstart) = split(/_/,$role); + } + + my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain, + $username); + @userroles{keys(%new_role)} = @new_role{keys(%new_role)}; + + # role expired or not available yet? + $trole = '' if ($tend != 0 && $tend < $userroles{'user.login.time'}) or + ($tstart != 0 && $tstart > $userroles{'user.login.time'}); + + next if $area eq '' or $trole eq ''; + + my $spec = "$trole.$area"; + my ($tdummy, $tdomain, $trest) = split(/\//, $area); + + if ($trole =~ /^cr\//) { + # Custom role, defined by a user + &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area); + } elsif ($trole eq 'gr') { + # Role of a member in a group, defined within a course/community + &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart); + next; + } else { + # Normal role, defined in roles.tab + &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); + } + + my $cid = $tdomain.'_'.$trest; + unless ($firstaccchk{$cid}) { + if (ref($coursetimerstarts{$cid}) eq 'HASH') { + foreach my $item (keys(%{$coursetimerstarts{$cid}})) { + $firstaccenv{'course.'.$cid.'.firstaccess.'.$item} = + $coursetimerstarts{$cid}{$item}; + } + } + $firstaccchk{$cid} = 1; + } + unless ($timerintchk{$cid}) { + if (ref($coursetimerintervals{$cid}) eq 'HASH') { + foreach my $item (keys(%{$coursetimerintervals{$cid}})) { + $timerintenv{'course.'.$cid.'.timerinterval.'.$item} = + $coursetimerintervals{$cid}{$item}; + } + } + $timerintchk{$cid} = 1; } - my ($author,$adv) = &set_userprivs(\%userroles,\%allroles,\%allgroups); - $userroles{'user.adv'} = $adv; - $userroles{'user.author'} = $author; - $env{'user.adv'}=$adv; } - return \%userroles; + + @userroles{'user.author', 'user.adv'} = &set_userprivs(\%userroles, + \%allroles, \%allgroups); + $env{'user.adv'} = $userroles{'user.adv'}; + + return (\%userroles,\%firstaccenv,\%timerintenv); } sub set_arearole { - my ($trole,$area,$tstart,$tend,$domain,$username) = @_; + my ($trole,$area,$tstart,$tend,$domain,$username,$nolog) = @_; + unless ($nolog) { # log the associated role with the area - &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + } return ('user.role.'.$trole.'.'.$area => $tstart.'.'.$tend); } @@ -4361,7 +5148,7 @@ sub set_userprivs { my $adv=0; my %grouproles = (); if (keys(%{$allgroups}) > 0) { - my @groupkeys; + my @groupkeys; foreach my $role (keys(%{$allroles})) { push(@groupkeys,$role); } @@ -4409,7 +5196,7 @@ sub set_userprivs { } } my $thesestr=''; - foreach my $priv (keys(%thesepriv)) { + foreach my $priv (sort(keys(%thesepriv))) { $thesestr.=':'.$priv.'&'.$thesepriv{$priv}; } $userroles->{'user.priv.'.$role} = $thesestr; @@ -4418,7 +5205,7 @@ sub set_userprivs { } sub role_status { - my ($rolekey,$then,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; + 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); @@ -4427,7 +5214,7 @@ sub role_status { $$trolecode=$$role.'.'.$$where; ($$tstart,$$tend)=split(/\./,$env{$rolekey}); $$tstatus='is'; - if ($$tstart && $$tstart>$then) { + if ($$tstart && $$tstart>$update) { $$tstatus='future'; if ($$tstart<$now) { if ($$tstart && $$tstart>$refresh) { @@ -4437,7 +5224,7 @@ sub role_status { my %userroles = ( 'user.role.'.$$role.'.'.$$where => $$tstart.'.'.$$tend ); - @rolecodes = ('cm'); + @rolecodes = ('cm'); my $spec=$$role.'.'.$$where; my ($tdummy,$tdomain,$trest)=split(/\//,$$where); if ($$role =~ /^cr\//) { @@ -4452,32 +5239,9 @@ sub role_status { $group_privs = &unescape($group_privs); &group_roleprivs(\%allgroups,$$where,$group_privs,$$tend,$$tstart); my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1); - if (keys(%course_roles) > 0) { - my ($tnum) = ($trest =~ /^($match_courseid)/); - if ($tdomain ne '' && $tnum ne '') { - foreach my $key (keys(%course_roles)) { - if ($key =~ /^\Q$tnum\E:\Q$tdomain\E:([^:]+):?([^:]*)/) { - my $crsrole = $1; - my $crssec = $2; - if ($crsrole =~ /^cr/) { - unless (grep(/^cr$/,@rolecodes)) { - push(@rolecodes,'cr'); - } - } else { - unless(grep(/^\Q$crsrole\E$/,@rolecodes)) { - push(@rolecodes,$crsrole); - } - } - my $rolekey = $crsrole.'./'.$tdomain.'/'.$tnum; - if ($crssec ne '') { - $rolekey .= '/'.$crssec; - } - $rolekey .= './'; - $groups_roles{$rolekey} = \@rolecodes; - } - } - } - } + &get_groups_roles($tdomain,$trest, + \%course_roles,\@rolecodes, + \%groups_roles); } else { push(@rolecodes,$$role); &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where); @@ -4491,7 +5255,7 @@ sub role_status { } } if ($$tend) { - if ($$tend<$then) { + if ($$tend<$update) { $$tstatus='expired'; } elsif ($$tend<$now) { $$tstatus='will_not'; @@ -4501,18 +5265,80 @@ sub role_status { } } +sub get_groups_roles { + my ($cdom,$rest,$cdom_courseroles,$rolecodes,$groups_roles) = @_; + return unless((ref($cdom_courseroles) eq 'HASH') && + (ref($rolecodes) eq 'ARRAY') && + (ref($groups_roles) eq 'HASH')); + if (keys(%{$cdom_courseroles}) > 0) { + my ($cnum) = ($rest =~ /^($match_courseid)/); + if ($cdom ne '' && $cnum ne '') { + foreach my $key (keys(%{$cdom_courseroles})) { + if ($key =~ /^\Q$cnum\E:\Q$cdom\E:([^:]+):?([^:]*)/) { + my $crsrole = $1; + my $crssec = $2; + if ($crsrole =~ /^cr/) { + unless (grep(/^cr$/,@{$rolecodes})) { + push(@{$rolecodes},'cr'); + } + } else { + unless(grep(/^\Q$crsrole\E$/,@{$rolecodes})) { + push(@{$rolecodes},$crsrole); + } + } + my $rolekey = "$crsrole./$cdom/$cnum"; + if ($crssec ne '') { + $rolekey .= "/$crssec"; + } + $rolekey .= './'; + $groups_roles->{$rolekey} = $rolecodes; + } + } + } + } + return; +} + +sub delete_env_groupprivs { + my ($where,$courseroles,$possroles) = @_; + return unless((ref($courseroles) eq 'HASH') && (ref($possroles) eq 'ARRAY')); + my ($dummy,$udom,$uname,$group) = split(/\//,$where); + unless (ref($courseroles->{$udom}) eq 'HASH') { + %{$courseroles->{$udom}} = + &get_my_roles('','','userroles',['active'], + $possroles,[$udom],1); + } + if (ref($courseroles->{$udom}) eq 'HASH') { + foreach my $item (keys(%{$courseroles->{$udom}})) { + my ($cnum,$cdom,$crsrole,$crssec) = split(/:/,$item); + my $area = '/'.$cdom.'/'.$cnum; + my $privkey = "user.priv.$crsrole.$area"; + if ($crssec ne '') { + $privkey .= '/'.$crssec; + } + $privkey .= ".$area/$group"; + &Apache::lonnet::delenv($privkey,undef,[$crsrole]); + } + } + return; +} + sub check_adhoc_privs { - my ($cdom,$cnum,$then,$refresh,$now,$checkrole,$caller) = @_; + 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,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); + &role_status($cckey,$update,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) { &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); + $setprivs = 1; } } else { &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); + $setprivs = 1; } + return $setprivs; } sub set_adhoc_privileges { @@ -4521,7 +5347,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); @@ -4584,49 +5410,65 @@ 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} = Apache::lonnet::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,$extra)=@_; + my ($namespace,$udomain,$uname,$regexp,$range,$escapedkeys)=@_; if (!$udomain) { $udomain=$env{'user.domain'}; } if (!$uname) { $uname=$env{'user.name'}; } my $uhome=&homeserver($uname,$udomain); + + my $reply; + if (grep { $_ eq $uhome } current_machine_ids()) { + # user is hosted on this machine + $reply = LONCAPA::Lond::dump_with_regexp(join(":", ($udomain, + $uname, $namespace, $regexp, $range)), $loncaparevs{$uhome}); + return %{unserialize($reply, $escapedkeys)}; + } if ($regexp) { $regexp=&escape($regexp); } else { $regexp='.'; } - my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range:$extra",$uhome); + my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); + if (!($rep =~ /^error/ )) { + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + $key = unescape($key) unless $escapedkeys; + #$key = &unescape($key); + next if ($key =~ /^error: 2 /); + $returnhash{$key}=&thaw_unescape($value); + } } return %returnhash; } + # --------------------------------------------------------- dumpstore interface sub dumpstore { my ($namespace,$udomain,$uname,$regexp,$range)=@_; - if (!$udomain) { $udomain=$env{'user.domain'}; } - if (!$uname) { $uname=$env{'user.name'}; } - my $uhome=&homeserver($uname,$udomain); - if ($regexp) { - $regexp=&escape($regexp); - } else { - $regexp='.'; - } - my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); - my @pairs=split(/\&/,$rep); - my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); - } - return %returnhash; + # same as dump but keys must be escaped. They may contain colon separated + # lists of values that may themself contain colons (e.g. symbs). + return &dump($namespace, $udomain, $uname, $regexp, $range, 1); } # -------------------------------------------------------------- keys interface @@ -4652,7 +5494,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=(); @@ -4888,13 +5738,95 @@ sub tmpget { return %returnhash; } -# ------------------------------------------------------------ tmpget interface +# ------------------------------------------------------------ tmpdel interface sub tmpdel { my ($token,$server)=@_; if (!defined($server)) { $server = $perlvar{'lonHostID'}; } return &reply("tmpdel:$token",$server); } +# ------------------------------------------------------------ get_timebased_id + +sub get_timebased_id { + my ($prefix,$keyid,$namespace,$cdom,$cnum,$idtype,$who,$locktries, + $maxtries) = @_; + my ($newid,$error,$dellock); + unless (($prefix =~ /^\w+$/) && ($keyid =~ /^\w+$/) && ($namespace ne '')) { + return ('','ok','invalid call to get suffix'); + } + +# set defaults for any optional args for which values were not supplied + if ($who eq '') { + $who = $env{'user.name'}.':'.$env{'user.domain'}; + } + if (!$locktries) { + $locktries = 3; + } + if (!$maxtries) { + $maxtries = 10; + } + + if (($cdom eq '') || ($cnum eq '')) { + if ($env{'request.course.id'}) { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } + if (($cdom eq '') || ($cnum eq '')) { + return ('','ok','call to get suffix not in course context'); + } + } + +# construct locking item + my $lockhash = { + $prefix."\0".'locked_'.$keyid => $who, + }; + my $tries = 0; + +# attempt to get lock on nohist_$namespace file + my $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + while (($gotlock ne 'ok') && $tries <$locktries) { + $tries ++; + sleep 1; + $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + } + +# attempt to get unique identifier, based on current timestamp + if ($gotlock eq 'ok') { + my %inuse = &Apache::lonnet::dump('nohist_'.$namespace,$cdom,$cnum,$prefix); + my $id = time; + $newid = $id; + my $idtries = 0; + while (exists($inuse{$prefix."\0".$newid}) && $idtries < $maxtries) { + if ($idtype eq 'concat') { + $newid = $id.$idtries; + } 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 { + $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'; + } + return ($newid,$dellock,$error); +} + # -------------------------------------------------- portfolio access checking sub portfolio_access { @@ -5139,7 +6071,7 @@ sub is_portfolio_file { } sub usertools_access { - my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref) = @_; + my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_; my ($access,%tools); if ($context eq '') { $context = 'tools'; @@ -5150,10 +6082,15 @@ sub usertools_access { unofficial => 1, community => 1, ); + } elsif ($context eq 'requestauthor') { + %tools = ( + requestauthor => 1, + ); } else { %tools = ( aboutme => 1, blog => 1, + webdav => 1, portfolio => 1, ); } @@ -5168,25 +6105,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'}; } } @@ -5252,7 +6196,7 @@ sub usertools_access { } } } else { - if ($context eq 'tools') { + if (($context eq 'tools') && ($tool ne 'webdav')) { $access = 1; } else { $access = 0; @@ -5286,12 +6230,16 @@ sub is_advanced_user { my ($udom,$uname) = @_; if ($udom ne '' && $uname ne '') { if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) { - return $env{'user.adv'}; + if (wantarray) { + return ($env{'user.adv'},$env{'user.author'}); + } else { + return $env{'user.adv'}; + } } } my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1); my %allroles; - my $is_adv; + my ($is_adv,$is_author); foreach my $role (keys(%roleshash)) { my ($trest,$tdomain,$trole,$sec) = split(/:/,$role); my $area = '/'.$tdomain.'/'.$trest; @@ -5305,6 +6253,9 @@ sub is_advanced_user { } elsif ($trole ne 'gr') { &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); } + if ($trole eq 'au') { + $is_author = 1; + } } } foreach my $role (keys(%allroles)) { @@ -5319,6 +6270,9 @@ sub is_advanced_user { } } } + if (wantarray) { + return ($is_adv,$is_author); + } return $is_adv; } @@ -5589,6 +6543,15 @@ sub allowed { } } +# User who is not author or co-author might still be able to edit +# resource of an author in the domain (e.g., if Domain Coordinator). + if (($priv eq 'eco') && ($thisallowed eq '') && ($env{'request.course.id'}) && + (&allowed('mdc',$env{'request.course.id'}))) { + if ($env{"user.priv.cm./$uri/"}=~/\Q$priv\E\&([^\:]*)/) { + $thisallowed.=$1; + } + } + # Course: uri itself is a course my $courseuri=$uri; $courseuri=~s/\_(\d)/\/$1/; @@ -5609,7 +6572,12 @@ sub allowed { if ($match) { if ($env{'user.priv.'.$env{'request.role'}.'./'} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$1; + } } } else { my $refuri = $env{'httpref.'.$orguri} || $env{'httpref.'.$ver_orguri}; @@ -5620,7 +6588,12 @@ sub allowed { $refuri=&declutter($refuri); my ($match) = &is_on_map($refuri); if ($match) { - $thisallowed='F'; + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed='F'; + } } } } @@ -5672,7 +6645,17 @@ sub allowed { $statecond=$cond; if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my $value = $1; + if ($priv eq 'bre') { + my @blockers = &has_comm_blocking($priv,$symb,$uri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } + } else { + $thisallowed.=$value; + } $checkreferer=0; } } @@ -5700,7 +6683,17 @@ sub allowed { my $refstatecond=$cond; if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + my $value = $1; + if ($priv eq 'bre') { + my @blockers = &has_comm_blocking($priv,$symb,$refuri); + if (@blockers > 0) { + $thisallowed = 'B'; + } else { + $thisallowed.=$value; + } + } else { + $thisallowed.=$value; + } $uri=$refuri; $statecond=$refstatecond; } @@ -5804,7 +6797,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')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. $env{'request.course.id'}); @@ -5814,7 +6807,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')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}. 'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '. $env{'request.course.id'}); @@ -5828,7 +6821,7 @@ sub allowed { if ($thisallowed=~/R/) { my $rolecode=(split(/\./,$env{'request.role'}))[0]; if (&metadata($uri,'roledeny')=~/\Q$rolecode\E/) { - if (($priv ne 'pch') && ($priv ne 'plc')) { + if (($priv ne 'pch') && ($priv ne 'plc')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode); } @@ -5860,6 +6853,251 @@ 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|)/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); + } + } + +# 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 ''; +} + +sub get_comm_blocks { + my ($cdom,$cnum) = @_; + if ($cdom eq '' || $cnum eq '') { + return unless ($env{'request.course.id'}); + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } + my %commblocks; + my $hashid=$cdom.'_'.$cnum; + my ($blocksref,$cached)=&is_cached_new('comm_block',$hashid); + if ((defined($cached)) && (ref($blocksref) eq 'HASH')) { + %commblocks = %{$blocksref}; + } else { + %commblocks = &Apache::lonnet::dump('comm_block',$cdom,$cnum); + my $cachetime = 600; + &do_cache_new('comm_block',$hashid,\%commblocks,$cachetime); + } + return %commblocks; +} + +sub 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\&([^\:]*)/); + 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; + my $navmap = Apache::lonnavmaps::navmap->new(); + foreach my $block (keys(%commblocks)) { + if ($block =~ /^(\d+)____(\d+)$/) { + my ($start,$end) = ($1,$2); + if ($start <= $now && $end >= $now) { + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}{'maps'}) eq 'HASH') { + if ($commblocks{$block}{'blocks'}{'docs'}{'maps'}{$map}) { + unless (grep(/^\Q$block\E$/,@blockers)) { + push(@blockers,$block); + } + } + } + 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); + } + } + } + } + } + } + } elsif ($block =~ /^firstaccess____(.+)$/) { + my $item = $1; + my @to_test; + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') { + my $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"); + } 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); + last; + } + } + } + } + } + } + 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; + } + } + } + } + } + } + } + } + } + } + } + return @blockers; +} + +sub check_docs_block { + my ($docsblock,$tocheck) =@_; + if ((ref($docsblock) ne 'HASH') || (ref($tocheck) ne 'HASH')) { + return; + } + if (ref($docsblock->{'maps'}) eq 'HASH') { + if ($tocheck->{'maps'}) { + if ($docsblock->{'maps'}{$tocheck->{'maps'}}) { + return 1; + } + } + } + if (ref($docsblock->{'resources'}) eq 'HASH') { + if ($tocheck->{'resources'}) { + if ($docsblock->{'resources'}{$tocheck->{'resources'}}) { + return 1; + } + } + } + return; +} + +# +# Removes the versino from a URI and +# splits it in to its filename and path to the filename. +# Seems like File::Basename could have done this more clearly. +# Parameters: +# $uri - input URI +# Returns: +# Two element list consisting of +# $pathname - the URI up to and excluding the trailing / +# $filename - The part of the URI following the last / +# NOTE: +# Another realization of this is simply: +# use File::Basename; +# ... +# $uri = shift; +# $filename = basename($uri); +# $path = dirname($uri); +# return ($filename, $path); +# +# The implementation below is probably faster however. +# sub split_uri_for_cond { my $uri=&deversion(&declutter(shift)); my @uriparts=split(/\//,$uri); @@ -6055,7 +7293,7 @@ sub fetch_enrollment_query { $$replyref{$key} = $value; } } else { - my $pathname = $perlvar{'lonDaemons'}.'/tmp'; + my $pathname = LONCAPA::tempdir(); foreach my $line (@responses) { my ($key,$value) = split(/=/,$line); $$replyref{$key} = $value; @@ -6085,7 +7323,7 @@ sub fetch_enrollment_query { sub get_query_reply { my $queryid=shift; - my $replyfile=$perlvar{'lonDaemons'}.'/tmp/'.$queryid; + my $replyfile=LONCAPA::tempdir().$queryid; my $reply=''; for (1..100) { sleep 2; @@ -6207,8 +7445,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 { @@ -6574,8 +7812,7 @@ sub get_users_groups { } else { $grouplist = ''; my $courseurl = &courseid_to_courseurl($courseid); - my $extra = &freeze_escape({'skipcheck' => 1}); - my %roleshash = &dump('roles',$udom,$uname,$courseurl,undef,$extra); + my %roleshash = &dump('roles',$udom,$uname,$courseurl); my $access_end = $env{'course.'.$courseid. '.default_enrollment_end_date'}; my $now = time; @@ -6747,6 +7984,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. @@ -6796,11 +8068,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); @@ -6936,7 +8222,7 @@ sub modifyuser { } &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '. $umode.', '.$first.', '.$middle.', '. - $last.', '.$gene.'(forceid: '.$forceid.'; candelete: '.$showcandelete.')'. + $last.', '.$gene.'(forceid: '.$forceid.'; candelete: '.$showcandelete.')'. (defined($desiredhome) ? ' desiredhome = '.$desiredhome : ' desiredhome not specified'). ' by '.$env{'user.name'}.' at '.$env{'user.domain'}. @@ -7082,7 +8368,7 @@ sub modifyuser { return 'ok'; } my $reply = &put('environment', \%names, $udom,$uname); - if ($reply ne 'ok') { + if ($reply ne 'ok') { return 'error: '.$reply; } if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) { @@ -7100,7 +8386,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)=@_; if (!$cid) { unless ($cid=$env{'request.course.id'}) { return 'not_in_class'; @@ -7115,12 +8401,14 @@ sub modifystudent { # students 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); 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) = @_; my ($cdom,$cnum,$chome); if (!$cid) { unless ($cid=$env{'request.course.id'}) { @@ -7163,14 +8451,16 @@ sub modify_student_enrollment { $uid = $tmp{'id'} if (!defined($uid) || $uid eq ''); } my $fullname = &format_name($first,$middle,$last,$gene,'lastname'); + my $user = "$uname:$udom"; + my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum); my $reply=cput('classlist', - {"$uname:$udom" => - join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype) }, + {$user => + join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits) }, $cdom,$cnum); - unless (($reply eq 'ok') || ($reply eq 'delayed')) { + if (($reply eq 'ok') || ($reply eq 'delayed')) { + &devalidate_getsection_cache($udom,$uname,$cid); + } else { return 'error: '.$reply; - } else { - &devalidate_getsection_cache($udom,$uname,$cid); } # Add student role to user my $uurl='/'.$cid; @@ -7178,7 +8468,16 @@ sub modify_student_enrollment { if ($usec) { $uurl.='/'.$usec; } - return &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,$selfenroll,$context); + my $result = &assignrole($udom,$uname,$uurl,'st',$end,$start,undef, + $selfenroll,$context); + if ($result ne 'ok') { + if ($old_entry{$user} ne '') { + $reply = &cput('classlist',\%old_entry,$cdom,$cnum); + } else { + $reply = &del('classlist',[$user],$cdom,$cnum); + } + } + return $result; } sub format_name { @@ -7377,13 +8676,16 @@ sub generate_coursenum { } sub is_course { - my ($cdom,$cnum) = @_; - my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef, - undef,'.'); - if (exists($courses{$cdom.'_'.$cnum})) { - return 1; - } - return 0; + my ($cdom, $cnum) = scalar(@_) == 1 ? + ($_[0] =~ /^($match_domain)_($match_courseid)$/) : @_; + + return unless $cdom and $cnum; + + my %courses = &courseiddump($cdom, '.', 1, '.', '.', $cnum, undef, undef, + '.'); + + return unless exists($courses{$cdom.'_'.$cnum}); + return wantarray ? ($cdom, $cnum) : $cdom.'_'.$cnum; } sub store_userdata { @@ -7407,9 +8709,6 @@ 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); } @@ -7467,7 +8766,7 @@ sub is_locked { my ($file_name, $domain, $user, $which) = @_; my @check; my $is_locked; - push(@check,$file_name); + push (@check,$file_name); my %locked = &get('file_permissions',\@check, $env{'user.domain'},$env{'user.name'}); my ($tmp)=keys(%locked); @@ -7532,7 +8831,7 @@ sub save_selected_files { sub clear_selected_files { my ($user) = @_; my $filename = $user."savedfiles"; - open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (OUT, '>'.LONCAPA::tempdir().$filename); print (OUT undef); close (OUT); return ("ok"); @@ -7542,7 +8841,7 @@ sub files_in_path { my ($user, $path) = @_; my $filename = $user."savedfiles"; my %return_files; - open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (IN, '<'.LONCAPA::tempdir().$filename); while (my $line_in = ) { chomp ($line_in); my @paths_and_file = split (m!/!, $line_in); @@ -7564,7 +8863,7 @@ sub files_not_in_path { my $filename = $user."savedfiles"; my @return_files; my $path_part; - open(IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open(IN, '<'.LONCAPA::.$filename); while (my $line = ) { #ok, I know it's clunky, but I want it to work my @paths_and_file = split(m|/|, $line); @@ -7912,26 +9211,33 @@ sub dirlist { if($udom) { if($uname) { + my $uhome = &homeserver($uname,$udom); + if ($uhome eq 'no_host') { + return ([],'no_host'); + } $listing = &reply('ls3:'.&escape('/'.$uri).':'.$getpropath.':' .$getuserdir.':'.&escape($dirRoot) - .':'.&escape($uname).':'.&escape($udom), - &homeserver($uname,$udom)); + .':'.&escape($uname).':'.&escape($udom),$uhome); if ($listing eq 'unknown_cmd') { - $listing = &reply('ls2:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls2:'.$dirRoot.'/'.$uri,$uhome); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } if ($listing eq 'unknown_cmd') { - $listing = &reply('ls:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls:'.$dirRoot.'/'.$uri,$uhome); @listing_results = split(/:/,$listing); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } - return @listing_results; + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + return ([],$listing); + } else { + return (\@listing_results); + } } elsif(!$alternateRoot) { - my %allusers; + my (%allusers,%listerror); my %servers = &get_servers($udom,'library'); foreach my $tryserver (keys(%servers)) { $listing = &reply('ls3:'.&escape("/res/$udom").':::::'. @@ -7950,32 +9256,31 @@ sub dirlist { @listing_results = map { &unescape($_); } split(/:/,$listing); } - if ($listing_results[0] ne 'no_such_dir' && - $listing_results[0] ne 'empty' && - $listing_results[0] ne 'con_lost') { + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + $listerror{$tryserver} = $listing; + } else { foreach my $line (@listing_results) { my ($entry) = split(/&/,$line,2); $allusers{$entry} = 1; } } } - my $alluserstr=''; + my @alluserslist=(); foreach my $user (sort(keys(%allusers))) { - $alluserstr.=$user.'&user:'; + push(@alluserslist,$user.'&user'); } - $alluserstr=~s/:$//; - return split(/:/,$alluserstr); + return (\@alluserslist); } else { - return ('missing user name'); + return ([],'missing username'); } } elsif(!defined($getpropath)) { - my @all_domains = sort(&all_domains()); - foreach my $domain (@all_domains) { - $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain'; - } - return @all_domains; + my $path = $perlvar{'lonDocRoot'}.'/res/'; + my @all_domains = map { $path.$_.'/&domain'; } (sort(&all_domains())); + return (\@all_domains); } else { - return ('missing domain'); + return ([],'missing domain'); } } @@ -7988,11 +9293,13 @@ sub GetFileTimestamp { my ($studentDomain,$studentName,$filename,$getuserdir)=@_; $studentDomain = &LONCAPA::clean_domain($studentDomain); $studentName = &LONCAPA::clean_username($studentName); - my ($fileStat) = - &Apache::lonnet::dirlist($filename,$studentDomain,$studentName, - undef,$getuserdir); - my @stats = split('&', $fileStat); - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { + my ($fileref,$error) = &dirlist($filename,$studentDomain,$studentName, + undef,$getuserdir); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return -1; + } + if (ref($fileref) eq 'ARRAY') { + my @stats = split('&',$fileref->[0]); # @stats contains first the filename, then the stat output return $stats[10]; # so this is 10 instead of 9. } else { @@ -8024,12 +9331,15 @@ sub stat_file { if ($file =~ /^userfiles\//) { $getpropath = 1; } - my ($result) = &dirlist($file,$udom,$uname,$getpropath); - my @stats = split('&', $result); - - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { - shift(@stats); #filename is first - return @stats; + my ($listref,$error) = &dirlist($file,$udom,$uname,$getpropath); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return (); + } else { + if (ref($listref) eq 'ARRAY') { + my @stats = split('&',$listref->[0]); + shift(@stats); #filename is first + return @stats; + } } return (); } @@ -8350,15 +9660,7 @@ sub EXT { } elsif ($realm eq 'request') { # ------------------------------------------------------------- request.browser if ($space eq 'browser') { - if ($qualifier eq 'textremote') { - if (&Apache::lonlocal::mt('textual_remote_display') eq 'on') { - return 1; - } else { - return 0; - } - } else { - return $env{'browser.'.$qualifier}; - } + return $env{'browser.'.$qualifier}; # ------------------------------------------------------------ request.filename } else { return $env{'request.'.$spacequalifierrest}; @@ -8631,17 +9933,18 @@ sub add_prefix_and_part { # ---------------------------------------------------------------- Get metadata my %metaentry; +my %importedpartids; sub metadata { my ($uri,$what,$liburi,$prefix,$depthcount)=@_; $uri=&declutter($uri); # 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)$})) || ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) { return undef; } - if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) + if (($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) && &Apache::lonxml::get_state('target') =~ /^(|meta)$/) { return undef; } @@ -8657,6 +9960,10 @@ sub metadata { if (defined($cached)) { return $result->{':'.$what}; } } { +# Imported parts would go here + my %importedids=(); + my @origfileimportpartids=(); + my $importedparts=0; # # Is this a recursive call for a library? # @@ -8674,7 +9981,7 @@ sub metadata { my %metathesekeys=(); unless ($filename=~/\.meta$/) { $filename.='.meta'; } my $metastring; - if ($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) { + if ($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) { my $which = &hreflocation('','/'.($liburi || $uri)); $metastring = &Apache::lonnet::ssi_body($which, @@ -8741,27 +10048,55 @@ sub metadata { # This is not a package - some other kind of start tag # my $entry=$token->[1]; - my $unikey; - if ($entry eq 'import') { - $unikey=''; - } else { - $unikey=$entry; - } - $unikey.=&add_prefix_and_part($prefix,$token->[2]->{'part'}); - - if (defined($token->[2]->{'id'})) { - $unikey.='_'.$token->[2]->{'id'}; - } + my $unikey=''; if ($entry eq 'import') { # # Importing a library here # + my $location=$parser->get_text('/import'); + my $dir=$filename; + $dir=~s|[^/]*$||; + $location=&filelocation($dir,$location); + + my $importmode=$token->[2]->{'importmode'}; + if ($importmode eq 'problem') { +# Import as problem/response + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + } elsif ($importmode eq 'part') { +# Import as part(s) + $importedparts=1; +# We need to get the original file and the imported file to get the part order correct +# Good news: we do not need to worry about nested libraries, since parts cannot be nested +# Load and inspect original file + if ($#origfileimportpartids<0) { + undef(%importedpartids); + my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri); + my $origfile=&getfile($origfilelocation); + @origfileimportpartids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + } + +# Load and inspect imported file + my $impfile=&getfile($location); + my @impfilepartids=($impfile=~/]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + if ($#impfilepartids>=0) { +# This problem had parts + $importedpartids{$token->[2]->{'id'}}=join(',',@impfilepartids); + } else { +# Importing by turning a single problem into a problem part +# It gets the import-tags ID as part-ID + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'id'}); + $importedpartids{$token->[2]->{'id'}}=$token->[2]->{'id'}; + } + } else { +# Normal import + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } + } + if ($depthcount<20) { - my $location=$parser->get_text('/import'); - my $dir=$filename; - $dir=~s|[^/]*$||; - $location=&filelocation($dir,$location); my $metadata = &metadata($uri,'keys', $location,$unikey, $depthcount+1); @@ -8769,8 +10104,16 @@ sub metadata { $metaentry{':'.$meta}=$metaentry{':'.$meta}; $metathesekeys{$meta}=1; } - } - } else { + + } + } else { +# +# Not importing, some other kind of non-package, non-library start tag +# + $unikey=$entry.&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } if (defined($token->[2]->{'name'})) { $unikey.='_'.$token->[2]->{'name'}; } @@ -8844,6 +10187,22 @@ sub metadata { grep { ! $seen{$_} ++ } (split(',',$metaentry{':packages'})); $metaentry{':packages'} = join(',',@uniq_packages); + if ($importedparts) { +# We had imported parts and need to rebuild partorder + $metaentry{':partorder'}=''; + $metathesekeys{'partorder'}=1; + for (my $index=0;$index<$#origfileimportpartids;$index+=2) { + if ($origfileimportpartids[$index] eq 'part') { +# original part, part of the problem + $metaentry{':partorder'}.=','.$origfileimportpartids[$index+1]; + } else { +# we have imported parts at this position + $metaentry{':partorder'}.=','.$importedpartids{$origfileimportpartids[$index+1]}; + } + } + $metaentry{':partorder'}=~s/^\,//; + } + $metaentry{':keys'} = join(',',keys(%metathesekeys)); &metadata_generate_part0(\%metathesekeys,\%metaentry,$uri); $metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys); @@ -8946,6 +10305,10 @@ sub gettitle { } $title=~s/\&colon\;/\:/gs; if ($title) { +# Remember both $symb and $title for dynamic metadata + $accesshash{$symb.'___crstitle'}=$title; + $accesshash{&declutter($map).'___'.&declutter($url).'___usage'}=time; +# Cache this title and then return it return &do_cache_new('title',$key,$title,600); } $urlsymb=$url; @@ -8955,6 +10318,78 @@ sub gettitle { return $title; } +sub getdocspath { + my ($symb) = @_; + my $path; + if ($symb) { + my ($mapurl,$id,$resurl) = &decode_symb($symb); + if ($resurl=~/\.(sequence|page)$/) { + $mapurl=$resurl; + } elsif ($resurl eq 'adm/navmaps') { + $mapurl=$env{'course.'.$env{'request.course.id'}.'.url'}; + } + my $mapresobj; + my $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + $mapresobj = $navmap->getResourceByUrl($mapurl); + } + $mapurl=~s{^.*/([^/]+)\.(\w+)$}{$1}; + my $type=$2; + if (ref($mapresobj)) { + my $pcslist = $mapresobj->map_hierarchy(); + if ($pcslist ne '') { + foreach my $pc (split(/,/,$pcslist)) { + next if ($pc <= 1); + my $res = $navmap->getByMapPc($pc); + if (ref($res)) { + my $thisurl = $res->src(); + $thisurl=~s{^.*/([^/]+)\.\w+$}{$1}; + my $thistitle = $res->title(); + $path .= '&'. + &Apache::lonhtmlcommon::entity_encode($thisurl).'&'. + &Apache::lonhtmlcommon::entity_encode($thistitle). + ':'.$res->randompick(). + ':'.$res->randomout(). + ':'.$res->encrypted(). + ':'.$res->randomorder(). + ':'.$res->is_page(); + } + } + } + $path =~ s/^\&//; + my $maptitle = $mapresobj->title(); + if ($mapurl eq 'default') { + $maptitle = 'Main Course Documents'; + } + $path .= ($path ne '')? '&' : ''. + &Apache::lonhtmlcommon::entity_encode($mapurl).'&'. + &Apache::lonhtmlcommon::entity_encode($maptitle). + ':'.$mapresobj->randompick(). + ':'.$mapresobj->randomout(). + ':'.$mapresobj->encrypted(). + ':'.$mapresobj->randomorder(). + ':'.$mapresobj->is_page(); + } else { + my $maptitle = &gettitle($mapurl); + my $ispage; + if ($mapurl =~ /\.page$/) { + $ispage = 1; + } + if ($mapurl eq 'default') { + $maptitle = 'Main Course Documents'; + } + $path = &Apache::lonhtmlcommon::entity_encode($mapurl).'&'. + &Apache::lonhtmlcommon::entity_encode($maptitle).':::::'.$ispage; + } + unless ($mapurl eq 'default') { + $path = 'default&'. + &Apache::lonhtmlcommon::entity_encode('Main Course Documents'). + ':::::&'.$path; + } + } + return $path; +} + sub get_slot { my ($which,$cnum,$cdom)=@_; if (!$cnum || !$cdom) { @@ -8978,6 +10413,84 @@ sub get_slot { } return $slotinfo{$which}; } + +sub get_reservable_slots { + my ($cnum,$cdom,$uname,$udom) = @_; + my $now = time; + my $reservable_info; + my $key=join("\0",'reservableslots',$cdom,$cnum,$uname,$udom); + if (exists($remembered{$key})) { + $reservable_info = $remembered{$key}; + } else { + my %resv; + ($resv{'now_order'},$resv{'now'},$resv{'future_order'},$resv{'future'}) = + &Apache::loncommon::get_future_slots($cnum,$cdom,$now); + $reservable_info = \%resv; + $remembered{$key} = $reservable_info; + } + return $reservable_info; +} + +sub get_course_slots { + my ($cnum,$cdom) = @_; + my $hashid=$cnum.':'.$cdom; + my ($result,$cached) = &Apache::lonnet::is_cached_new('allslots',$hashid); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + return %{$result}; + } + } else { + my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); + my ($tmp) = keys(%slots); + if ($tmp !~ /^(con_lost|error|no_such_host)/i) { + &Apache::lonnet::do_cache_new('allslots',$hashid,\%slots,600); + return %slots; + } + } + return; +} + +sub devalidate_slots_cache { + my ($cnum,$cdom)=@_; + my $hashid=$cnum.':'.$cdom; + &devalidate_cache_new('allslots',$hashid); +} + +sub get_coursechange { + my ($cdom,$cnum) = @_; + if ($cdom eq '' || $cnum eq '') { + return unless ($env{'request.course.id'}); + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } + my $hashid=$cdom.'_'.$cnum; + my ($change,$cached)=&is_cached_new('crschange',$hashid); + if ((defined($cached)) && ($change ne '')) { + return $change; + } else { + my %crshash; + %crshash = &get('environment',['internal.contentchange'],$cdom,$cnum); + if ($crshash{'internal.contentchange'} eq '') { + $change = $env{'course.'.$cdom.'_'.$cnum.'.internal.created'}; + if ($change eq '') { + %crshash = &get('environment',['internal.created'],$cdom,$cnum); + $change = $crshash{'internal.created'}; + } + } else { + $change = $crshash{'internal.contentchange'}; + } + my $cachetime = 600; + &do_cache_new('crschange',$hashid,$change,$cachetime); + } + return $change; +} + +sub devalidate_coursechange_cache { + my ($cnum,$cdom)=@_; + my $hashid=$cnum.':'.$cdom; + &devalidate_cache_new('crschange',$hashid); +} + # ------------------------------------------------- Update symbolic store links sub symblist { @@ -9005,7 +10518,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 @@ -9024,28 +10537,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=$bighash{'ids_'.&clutter($thisurl)}; - unless ($ids) { - my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl; - $ids=$bighash{$idkey}; + my $ids; + if ($noclutter) { + $ids=$bighash{'ids_'.$thisurl}; + } else { + $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; + ($thisurl eq '/adm/navmaps')) { + $okay=1; + last; } } } @@ -9121,10 +10649,14 @@ sub deversion { sub symbread { my ($thisfn,$donotrecurse)=@_; - my $cache_str='request.symbread.cached.'.$thisfn; - if (defined($env{$cache_str})) { return $env{$cache_str}; } + my $cache_str; + if ($thisfn ne '') { + $cache_str='request.symbread.cached.'.$thisfn; + if ($env{$cache_str} ne '') { + return $env{$cache_str}; + } + } else { # no filename provided? try from environment - unless ($thisfn) { if ($env{'request.symb'}) { return $env{$cache_str}=&symbclean($env{'request.symb'}); } @@ -9319,19 +10851,44 @@ sub getCODE { } return undef; } - +# +# Determines the random seed for a specific context: +# +# parameters: +# symb - in course context the symb for the seed. +# course_id - The course id of the form domain_coursenum. +# domain - Domain for the user. +# course - Course for the user. +# cenv - environment of the course. +# +# NOTE: +# All parameters are picked out of the environment if missing +# or not defined. +# If a symb cannot be determined the current time is used instead. +# +# For a given well defined symb, courside, domain, username, +# and course environment, the seed is reproducible. +# sub rndseed { - my ($symb,$courseid,$domain,$username)=@_; + my ($symb,$courseid,$domain,$username, $cenv)=@_; my ($wsymb,$wcourseid,$wdomain,$wusername)=&whichuser(); if (!defined($symb)) { unless ($symb=$wsymb) { return time; } } - if (!$courseid) { $courseid=$wcourseid; } - if (!$domain) { $domain=$wdomain; } - if (!$username) { $username=$wusername } - my $which=&get_rand_alg(); + if (!defined $courseid) { + $courseid=$wcourseid; + } + if (!defined $domain) { $domain=$wdomain; } + if (!defined $username) { $username=$wusername } + my $which; + if (defined($cenv->{'rndseed'})) { + $which = $cenv->{'rndseed'}; + } else { + $which =&get_rand_alg($courseid); + } if (defined(&getCODE())) { + if ($which eq '64bit5') { return &rndseed_CODE_64bit5($symb,$courseid,$domain,$username); } elsif ($which eq '64bit4') { @@ -9655,8 +11212,9 @@ sub getfile { sub repcopy_userfile { my ($file)=@_; - if ($file =~ m -^/*(uploaded|editupload)/-) { $file=&filelocation("",$file); } - if ($file =~ m|^/home/httpd/html/lonUsers/|) { return 'ok'; } + my $londocroot = $perlvar{'lonDocRoot'}; + if ($file =~ m{^/*(uploaded|editupload)/}) { $file=&filelocation("",$file); } + if ($file =~ m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; } my ($cdom,$cnum,$filename) = ($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+($match_domain)/+($match_name)/+(.*)|); my $uri="/uploaded/$cdom/$cnum/$filename"; @@ -9785,13 +11343,7 @@ sub filelocation { $file=~s-^/adm/coursedocs/showdoc/-/-; } - if ($file=~m:^/~:) { # is a contruction space reference - $location = $file; - $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:; - } elsif ($file=~m{^/home/$match_username/public_html/}) { - # is a correct contruction space reference - $location = $file; - } elsif ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) { + if ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) { $location = $file; } elsif ($file=~/^\/*(uploaded|editupload)/) { # is an uploaded file my ($udom,$uname,$filename)= @@ -9801,7 +11353,7 @@ sub filelocation { my @ids=¤t_machine_ids(); foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } } if ($is_me) { - $location=&propath($udom,$uname).'/userfiles/'.$filename; + $location=propath($udom,$uname).'/userfiles/'.$filename; } else { $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'. $udom.'/'.$uname.'/'.$filename; @@ -9810,11 +11362,12 @@ sub filelocation { $location = $perlvar{'lonDocRoot'}.'/'.$file; } else { $file=~s/^\Q$perlvar{'lonDocRoot'}\E//; - $file=~s:^/res/:/:; + $file=~s:^/(res|priv)/:/:; + my $space=$1; if ( !( $file =~ m:^/:) ) { $location = $dir. '/'.$file; } else { - $location = '/home/httpd/html/res'.$file; + $location = $perlvar{'lonDocRoot'}.'/'.$space.$file; } } $location=~s://+:/:g; # remove duplicate / @@ -9839,11 +11392,9 @@ sub hreflocation { } if ($file=~m-^\Q$perlvar{'lonDocRoot'}\E-) { $file=~s-^\Q$perlvar{'lonDocRoot'}\E--; - } elsif ($file=~m-/home/($match_username)/public_html/-) { - $file=~s-^/home/($match_username)/public_html/-/~$1/-; } elsif ($file=~m-^\Q$perlvar{'lonUsersDir'}\E-) { - $file=~s-^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/ - -/uploaded/$1/$2/-x; + $file=~s{^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/} + {/uploaded/$1/$2/}x; } if ($file=~ m{^/userfiles/}) { $file =~ s{^/userfiles/}{/uploaded/}; @@ -9851,6 +11402,10 @@ sub hreflocation { return $file; } + + + + sub current_machine_domains { return &machine_domains(&hostname($perlvar{'lonHostID'})); } @@ -9916,6 +11471,7 @@ sub declutter { $thisfn=~s|^adm/wrapper/||; $thisfn=~s|^adm/coursedocs/showdoc/||; $thisfn=~s/^res\///; + $thisfn=~s/^priv\///; unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) { $thisfn=~s/\?.+$//; } @@ -10015,12 +11571,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; } } @@ -10045,8 +11601,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) { + &Apache::lonnet::do_cache_new('dns',$url,\@content,30*24*60*60); + } + &$func(\@content,$hashref); return; } close($config); @@ -10054,9 +11612,62 @@ 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 $machine_dom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); + my $loncaparev = &get_server_loncaparev($machine_dom); + my ($release,$timestamp) = split(/\-/,$loncaparev); + my (%chksum,%revnum); + if (ref($lines) eq 'ARRAY') { + chomp(@{$lines}); + my $versions = shift(@{$lines}); + my %supported; + if ($versions =~ /^VERSIONS\:([\w\.\,]+)$/) { + my $releaseslist = $1; + if ($releaseslist =~ /,/) { + map { $supported{$_} = 1; } split(/,/,$releaseslist); + } elsif ($releaseslist) { + $supported{$releaseslist} = 1; + } + } + if ($supported{$release}) { + my $matchthis = 0; + foreach my $line (@{$lines}) { + if ($line =~ /^(\d[\w\.]+)$/) { + if ($matchthis) { + last; + } elsif ($1 eq $release) { + $matchthis = 1; + } + } elsif ($matchthis) { + my ($file,$version,$shasum) = split(/,/,$line); + $chksum{$file} = $shasum; + $revnum{$file} = $version; + } + } + if (ref($hashref) eq 'HASH') { + %{$hashref} = ( + sums => \%chksum, + versions => \%revnum, + ); + } + } + } return; } + +sub fetch_dns_checksums { + my %checksums; + &get_dns('/adm/dns/checksums',\&parse_dns_checksums_tab,1,1, + \%checksums); + return \%checksums; +} + # ------------------------------------------------------------ Read domain file { my $loaded; @@ -10131,7 +11742,7 @@ sub get_dns { foreach my $configline (@$file) { next if ($configline =~ /^(\#|\s*$ )/x); chomp($configline); - if ($configline =~ /^\^/) { + if ($configline =~ /^\^/) { if ($configline =~ /^\^([\w.\-]+)/) { $LC_dns_serv{$1} = 1; } @@ -10218,7 +11829,7 @@ sub get_dns { } sub unique_library { - #2x reverse removes all hostnames that appear more than once + #2x reverse removes all hostnames that appear more than once my %unique = reverse &all_library(); return reverse %unique; } @@ -10248,7 +11859,7 @@ sub get_dns { sub get_unique_servers { my %unique = reverse &get_servers(@_); - return reverse %unique; + return reverse %unique; } sub host_domain { @@ -10573,7 +12184,7 @@ BEGIN { # ------------- set up temporary directory { - $tmpdir = $perlvar{'lonDaemons'}.'/tmp/'; + $tmpdir = LONCAPA::tempdir(); } @@ -10594,6 +12205,17 @@ $readit=1; if ($test != 0) { $_64bit=1; } else { $_64bit=0; } &logthis(" Detected 64bit platform ($_64bit)"); } + + { + eval { + ($apache) = + (Apache2::ServerUtil::get_server_version() =~ m{Apache/(\d+\.\d+)}); + }; + if ($@) { + $apache = 1.3; + } + } + } } @@ -10808,7 +12430,7 @@ $checkdefauth is optional (value is 1 if authenticate user using default authentication method, and allow account creation if username does not have account in the domain). $clientcancheckhost is optional (value is 1 if checking whether the - server can host will occur on the client side in lonauth.pm). + server can host will occur on the client side in lonauth.pm). =item * X @@ -10834,7 +12456,13 @@ B: store away a list =item * X -B: get user privileges +B: get user privileges. +returns user role, first access and timer interval hashes + +=item * +X +B: returns a true if user has a +privileged and active role (i.e. su or dc), false otherwise. =item * X @@ -10875,6 +12503,21 @@ allowed($priv,$uri,$symb,$role) : check =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 * + definerole($rolename,$sysrole,$domrole,$courole) : define role; define a custom role rolename set privileges in format of lonTabs/roles.tab for system, domain, and course level @@ -10905,6 +12548,17 @@ 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 or Super User roles. + =back =head2 User Modification @@ -10997,7 +12651,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 @@ -11042,6 +12698,8 @@ Inputs: =item $context +=item $credits, number of credits student will earn from this class + =back @@ -11067,11 +12725,32 @@ revokecustomrole($udom,$uname,$url,$role =item * -coursedescription($courseid) : returns a hash of information about the +coursedescription($courseid,$options) : returns a hash of information about the specified course id, including all environment settings for the course, the description of the course will be in the hash under the key 'description' +$options is an optional parameter that if supplied is a hash reference that controls +what how this function works. It has the following key/values: + +=over 4 + +=item freshen_cache + +If defined, and the environment cache for the course is valid, it is +returned in the returned hash. + +=item one_time + +If defined, the last cache time is set to _now_ + +=item user + +If defined, the supplied username is used instead of the current user. + + +=back + =item * resdata($name,$domain,$type,@which) : request for current parameter @@ -11086,7 +12765,6 @@ data base, returning a hash that is keye values that are the resource value. I believe that the timestamps and versions are also returned. - =back =head2 Course Modification @@ -11106,6 +12784,19 @@ createcourse($udom,$description,$url,$co generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community). +=item * + +is_course($courseid), is_course($cdom, $cnum) + +Accepts either a combined $courseid (in the form of domain_courseid) or the +two component version $cdom, $cnum. It checks if the specified course exists. + +Returns: + undef if the course doesn't exist, otherwise + in scalar context the combined courseid. + in list context the two components of the course identifier, domain and + courseid. + =back =head2 Resource Subroutines @@ -11174,12 +12865,14 @@ 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 * @@ -11232,6 +12925,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 @@ -11402,7 +13123,82 @@ or lonTabs/domain.tab. =item * -dirlist($uri) : return directory list based on URI +dirlist() : return directory list based on URI (first arg). + +Inputs: 1 required, 5 optional. + +=over + +=item +$uri - path to file in filesystem (starts: /res or /userfiles/). Required. + +=item +$userdomain - domain of user/course to be listed. Extracted from $uri if absent. + +=item +$username - username of user/course to be listed. Extracted from $uri if absent. + +=item +$getpropath - boolean: 1 if prepend path using &propath(). + +=item +$getuserdir - boolean: 1 if prepend path for "userfiles". + +=item +$alternateRoot - path to prepend in place of path from $uri. + +=back + +Returns: Array of up to two items. + +=over + +a reference to an array of files/subdirectories + +=over + +Each element in the array of files/subdirectories is a & separated list of +item name and the result of running stat on the item. If dirlist was requested +for a file instead of a directory, the item name will be ''. For a directory +listing, if the item is a metadata file, the element will end &N&M +(where N amd M are either 0 or 1, corresponding to obsolete set (1), or +default copyright set (1). + +=back + +a scalar containing error condition (if encountered). + +=over + +=item +no_host (no homeserver identified for $username:$domain). + +=item +no_such_host (server contacted for listing not identified as valid host). + +=item +con_lost (connection to remote server failed). + +=item +refused (invalid $username:$domain received on lond side). + +=item +no_such_dir (directory at specified path on lond side does not exist). + +=item +empty (directory at specified path on lond side is empty). + +=over + +This is currently not encountered because the &ls3, &ls2, +&ls (_handler) routines on the lond side do not filter out +. and .. from a directory listing. + +=back + +=back + +=back =item * @@ -11464,6 +13260,7 @@ splitting on '&', supports elements that =head2 Logging Routines + These routines allow one to make log messages in the lonnet.log and lonnet.perm logfiles. @@ -11484,6 +13281,7 @@ logperm() : append a permanent message t file never gets deleted by any automated portion of the system, only messages of critical importance should go in here. + =back =head2 General File Helper Routines @@ -11558,7 +13356,7 @@ userfileupload(): main rotine for puttin the filename is in $env{'form.'.$formname.'.filename'} and the contents of the file is located in $env{'form.'.$formname} context - if coursedoc, store the file in the course of the active role - of the current user; + of the current user; if 'existingfile': store in 'overwrites' in /home/httpd/perl/tmp if 'canceloverwrite': delete file in tmp/overwrites directory subdir - required - subdirectory to put the file in under ../userfiles/ @@ -11604,7 +13402,7 @@ userspace, probably shouldn't be called returns either the url of the uploaded file (/uploaded/....) if successful and /adm/notfound.html if unsuccessful (or an error message if context was 'overwrite'). - + =item * @@ -11710,6 +13508,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 @@ -11727,7 +13527,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