--- loncom/lonnet/perl/lonnet.pm 2012/03/14 20:53:50 1.1158 +++ loncom/lonnet/perl/lonnet.pm 2012/08/14 15:45:34 1.1183 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1158 2012/03/14 20:53:50 www Exp $ +# $Id: lonnet.pm,v 1.1183 2012/08/14 15:45:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -75,6 +75,9 @@ use LWP::UserAgent(); use HTTP::Date; use Image::Magick; + +use Encode; + use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease %managerstab); @@ -96,6 +99,8 @@ use Math::Random; use File::MMagic; use LONCAPA qw(:DEFAULT :match); use LONCAPA::Configuration; +use LONCAPA::lonmetadata; +use LONCAPA::Lond; use File::Copy; @@ -1235,6 +1240,7 @@ sub check_loadbalancing { my ($is_balancer,$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); @@ -1261,7 +1267,6 @@ sub check_loadbalancing { my $currtargets = $result->{'targets'}; my $currrules = $result->{'rules'}; if ($currbalancer ne '') { - my @hosts = ¤t_machine_ids(); if (grep(/^\Q$currbalancer\E$/,@hosts)) { $is_balancer = 1; } @@ -1349,31 +1354,43 @@ sub check_loadbalancing { $offloadto = &this_host_spares($dom_in_use); } } - my $lowest_load = 30000; - if (ref($offloadto) eq 'HASH') { - if (ref($offloadto->{'primary'}) eq 'ARRAY') { - foreach my $try_server (@{$offloadto->{'primary'}}) { - ($otherserver,$lowest_load) = - &compare_server_load($try_server,$otherserver,$lowest_load); + if ($is_balancer) { + my $lowest_load = 30000; + if (ref($offloadto) eq 'HASH') { + if (ref($offloadto->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'primary'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } } - } - my $found_server = ($otherserver ne '' && $lowest_load < 100); + my $found_server = ($otherserver ne '' && $lowest_load < 100); - if (!$found_server) { - if (ref($offloadto->{'default'}) eq 'ARRAY') { - foreach my $try_server (@{$offloadto->{'default'}}) { + if (!$found_server) { + if (ref($offloadto->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'default'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + } elsif (ref($offloadto) eq 'ARRAY') { + if (@{$offloadto} == 1) { + $otherserver = $offloadto->[0]; + } elsif (@{$offloadto} > 1) { + foreach my $try_server (@{$offloadto}) { ($otherserver,$lowest_load) = &compare_server_load($try_server,$otherserver,$lowest_load); } } } - } elsif (ref($offloadto) eq 'ARRAY') { - if (@{$offloadto} == 1) { - $otherserver = $offloadto->[0]; - } elsif (@{$offloadto} > 1) { - foreach my $try_server (@{$offloadto}) { - ($otherserver,$lowest_load) = - &compare_server_load($try_server,$otherserver,$lowest_load); + if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) { + $is_balancer = 0; + if ($uname ne '' && $udom ne '') { + if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { + + &appenv({'user.loadbalexempt' => $lonhost, + 'user.loadbalcheck.time' => time}); + } } } } @@ -1383,7 +1400,9 @@ sub check_loadbalancing { sub get_loadbalancer_targets { my ($rule_in_effect,$currtargets,$uname,$udom) = @_; my $offloadto; - if ($rule_in_effect eq '') { + if ($rule_in_effect eq 'none') { + return [$perlvar{'lonHostID'}]; + } elsif ($rule_in_effect eq '') { $offloadto = $currtargets; } else { if ($rule_in_effect eq 'homeserver') { @@ -1401,7 +1420,7 @@ sub get_loadbalancer_targets { } } } else { - my %servers = &dom_servers($udom); + my %servers = &internet_dom_servers($udom); my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers); if (&hostname($remotebalancer) ne '') { $offloadto = [$remotebalancer]; @@ -1534,16 +1553,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 @@ -1923,7 +1939,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'}; @@ -1942,7 +1959,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}; @@ -1954,6 +1971,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}; @@ -2157,8 +2177,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; @@ -2442,7 +2461,7 @@ sub repcopy { $filename=~s/\/+/\//g; my $londocroot = $perlvar{'lonDocRoot'}; if ($filename=~m{^\Q$londocroot/adm/\E}) { return 'ok'; } - if ($filename=~m{^\Q$londocroot/lonUsers/\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); @@ -2570,12 +2589,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; } } @@ -3032,6 +3053,7 @@ sub finishuserfileupload { sub extract_embedded_items { my ($fullpath,$allfiles,$codebase,$content) = @_; my @state = (); + my (%lastids,%related,%shockwave,%flashvars); my %javafiles = ( codebase => '', code => '', @@ -3061,10 +3083,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') { @@ -3077,6 +3119,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'}); @@ -3086,12 +3131,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)) { @@ -3106,7 +3161,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 '') { @@ -3126,6 +3190,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'; } @@ -3140,6 +3221,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); @@ -3269,6 +3365,7 @@ sub flushcourselogs { } } 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}; @@ -3397,31 +3494,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} @@ -3530,8 +3633,7 @@ sub get_my_roles { 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); + %dumphash = &dump('roles',$udom,$uname); } else { %dumphash= &dump('nohist_userroles',$udom,$uname); @@ -3727,18 +3829,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); @@ -3880,7 +3996,7 @@ my $cachedtime=(); sub load_all_first_access { my ($uname,$udom)=@_; if (($cachedkey eq $uname.':'.$udom) && - (abs($cachedtime-time)<5)) { + (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) { return; } $cachedtime=time; @@ -3889,10 +4005,11 @@ sub load_all_first_access { } 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') { @@ -3905,7 +4022,7 @@ sub get_first_access { } 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') { @@ -3916,9 +4033,22 @@ sub set_first_access { $res=$symb; } $cachedkey=''; - my $firstaccess=&get_first_access($type,$symb); + 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'; } @@ -4510,95 +4640,127 @@ 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 %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); - next if ($tstart eq '-1'); - ($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 { @@ -4932,23 +5094,50 @@ 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=(); if (!($rep =~ /^error/ )) { foreach my $item (@pairs) { my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); + $key = unescape($key) unless $escapedkeys; + #$key = &unescape($key); next if ($key =~ /^error: 2 /); $returnhash{$key}=&thaw_unescape($value); } @@ -4961,23 +5150,9 @@ sub dump { sub dumpstore { my ($namespace,$udomain,$uname,$regexp,$range)=@_; - if (!$udomain) { $udomain=$env{'user.domain'}; } - if (!$uname) { $uname=$env{'user.name'}; } - my $uhome=&homeserver($uname,$udomain); - if ($regexp) { - $regexp=&escape($regexp); - } else { - $regexp='.'; - } - my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); - my @pairs=split(/\&/,$rep); - my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); - } - return %returnhash; + # same as dump but keys must be escaped. They may contain colon separated + # lists of values that may themself contain colons (e.g. symbs). + return &dump($namespace, $udomain, $uname, $regexp, $range, 1); } # -------------------------------------------------------------- keys interface @@ -5003,7 +5178,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=(); @@ -5501,10 +5684,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, ); } @@ -5519,25 +5707,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'}; } } @@ -5603,7 +5798,7 @@ sub usertools_access { } } } else { - if ($context eq 'tools') { + if (($context eq 'tools') && ($tool ne 'webdav')) { $access = 1; } else { $access = 0; @@ -5979,7 +6174,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}; @@ -5990,7 +6190,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'; + } } } } @@ -6042,7 +6247,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; } } @@ -6070,7 +6285,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; } @@ -6229,6 +6454,164 @@ sub allowed { } return 'F'; } + +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. @@ -6964,8 +7347,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; @@ -7137,6 +7519,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. @@ -7778,13 +8195,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 { @@ -9404,6 +9824,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; @@ -9479,6 +9903,41 @@ sub devalidate_slots_cache { &devalidate_cache_new('allslots',$hashid); } +sub get_coursechange { + my ($cdom,$cnum) = @_; + if ($cdom eq '' || $cnum eq '') { + return unless ($env{'request.course.id'}); + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + } + my $hashid=$cdom.'_'.$cnum; + my ($change,$cached)=&is_cached_new('crschange',$hashid); + if ((defined($cached)) && ($change ne '')) { + return $change; + } else { + my %crshash; + %crshash = &get('environment',['internal.contentchange'],$cdom,$cnum); + if ($crshash{'internal.contentchange'} eq '') { + $change = $env{'course.'.$cdom.'_'.$cnum.'.internal.created'}; + if ($change eq '') { + %crshash = &get('environment',['internal.created'],$cdom,$cnum); + $change = $crshash{'internal.created'}; + } + } else { + $change = $crshash{'internal.contentchange'}; + } + my $cachetime = 600; + &do_cache_new('crschange',$hashid,$change,$cachetime); + } + return $change; +} + +sub devalidate_coursechange_cache { + my ($cnum,$cdom)=@_; + my $hashid=$cnum.':'.$cdom; + &devalidate_cache_new('crschange',$hashid); +} + # ------------------------------------------------- Update symbolic store links sub symblist { @@ -9623,7 +10082,11 @@ sub deversion { sub symbread { my ($thisfn,$donotrecurse)=@_; my $cache_str='request.symbread.cached.'.$thisfn; - if (defined($env{$cache_str})) { return $env{$cache_str}; } + if (defined($env{$cache_str})) { + if (($thisfn) || ($env{$cache_str} ne '')) { + return $env{$cache_str}; + } + } # no filename provided? try from environment unless ($thisfn) { if ($env{'request.symb'}) { @@ -10183,7 +10646,7 @@ sub repcopy_userfile { my ($file)=@_; my $londocroot = $perlvar{'lonDocRoot'}; if ($file =~ m{^/*(uploaded|editupload)/}) { $file=&filelocation("",$file); } - if ($file =~ m{^\Q$londocroot/lonUsers/\E}) { return 'ok'; } + 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"; @@ -10440,6 +10903,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/\?.+$//; } @@ -11358,7 +11822,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 @@ -11651,6 +12121,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