--- loncom/lonnet/perl/lonnet.pm 2012/10/31 12:54:23 1.1193 +++ loncom/lonnet/perl/lonnet.pm 2013/02/14 16:52:11 1.1215 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1193 2012/10/31 12:54:23 raeburn Exp $ +# $Id: lonnet.pm,v 1.1215 2013/02/14 16:52:11 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -78,7 +78,7 @@ use Image::Magick; use Encode; -use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir +use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir $apache $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease %managerstab); @@ -634,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; } @@ -2653,29 +2660,164 @@ sub allowuploaded { # (b) When displaying folder contents in course editor, used to determine if # "Edit" link will be displayed alongside resource. # -# input: 3 args -- filename (decluttered), course number and course domain. -# output: array of four scalars -- +# 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. -# $uploaded -- 1 if resource is a file uploaded to a course. +# $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) = @_; - my ($cfile,$home,$switchserver,$uploaded); + 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/)) { - $uploaded = &is_course_upload($file,$cnum,$cdom); - if ($uploaded) { - $home=&homeserver($cnum,$cdom); + 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; } } } - unless ($uploaded) { + 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 @@ -2686,27 +2828,77 @@ sub can_edit_resource { $cfile=$file; } } - if (($cfile ne '') && (($home ne '') && ($home ne 'no_host'))) { + 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,$uploaded); + 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/}) || - ($file =~ m{^userfiles/\Q$cdom\E/\Q$cnum\E/docs/})) { + 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, @@ -4878,9 +5070,11 @@ sub rolesinit { } sub set_arearole { - my ($trole,$area,$tstart,$tend,$domain,$username) = @_; + my ($trole,$area,$tstart,$tend,$domain,$username,$nolog) = @_; + unless ($nolog) { # log the associated role with the area - &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + &userrolelog($trole,$username,$domain,$area,$tstart,$tend); + } return ('user.role.'.$trole.'.'.$area => $tstart.'.'.$tend); } @@ -5149,7 +5343,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); @@ -5547,6 +5741,88 @@ sub tmpdel { return &reply("tmpdel:$token",$server); } +# ------------------------------------------------------------ get_timebased_id + +sub get_timebased_id { + my ($prefix,$keyid,$namespace,$cdom,$cnum,$idtype,$who,$locktries, + $maxtries) = @_; + my ($newid,$error,$dellock); + unless (($prefix =~ /^\w+$/) && ($keyid =~ /^\w+$/) && ($namespace ne '')) { + return ('','ok','invalid call to get suffix'); + } + +# set defaults for any optional args for which values were not supplied + if ($who eq '') { + $who = $env{'user.name'}.':'.$env{'user.domain'}; + } + if (!$locktries) { + $locktries = 3; + } + if (!$maxtries) { + $maxtries = 10; + } + + if (($cdom eq '') || ($cnum eq '')) { + if ($env{'request.course.id'}) { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } + if (($cdom eq '') || ($cnum eq '')) { + return ('','ok','call to get suffix not in course context'); + } + } + +# construct locking item + my $lockhash = { + $prefix."\0".'locked_'.$keyid => $who, + }; + my $tries = 0; + +# attempt to get lock on nohist_$namespace file + my $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + while (($gotlock ne 'ok') && $tries <$locktries) { + $tries ++; + sleep 1; + $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum); + } + +# attempt to get unique identifier, based on current timestamp + if ($gotlock eq 'ok') { + my %inuse = &Apache::lonnet::dump('nohist_'.$namespace,$cdom,$cnum,$prefix); + my $id = time; + $newid = $id; + 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 { @@ -6626,7 +6902,7 @@ sub constructaccess { if (($allowed eq 'F') || ($allowed eq 'U')) { # Grant temporary access my $then=$env{'user.login.time'}; - my $update==$env{'user.update.time'}; + my $update=$env{'user.update.time'}; if (!$update) { $update = $then; } my $refresh=$env{'user.refresh.time'}; if (!$refresh) { $refresh = $update; } @@ -7788,16 +8064,16 @@ sub assignrole { # log new user role if status is ok if ($answer eq 'ok') { &userrolelog($role,$uname,$udom,$url,$start,$end); -# for course roles, perform group memberships changes triggered by role change. - unless ($role =~ /^gr/) { - &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend, - $origstart,$selfenroll,$context); - } 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. + 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') || @@ -10036,6 +10312,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) { @@ -10183,21 +10531,32 @@ sub symbverify { if (tie(%bighash,'GDBM_File',$env{'request.course.fn'}.'.db', &GDBM_READER(),0640)) { + my $noclutter; if (($thisurl =~ m{^/adm/wrapper/ext/}) || ($thisurl =~ m{^ext/})) { $thisurl =~ s/\?.+$//; + if ($map =~ m{^uploaded/.+\.page$}) { + $thisurl =~ s{^(/adm/wrapper|)/ext/}{http://}; + $thisurl =~ s{^\Qhttp://https://\E}{https://}; + $noclutter = 1; + } + } + my $ids; + if ($noclutter) { + $ids=$bighash{'ids_'.$thisurl}; + } else { + $ids=$bighash{'ids_'.&clutter($thisurl)}; } - my $ids=$bighash{'ids_'.&clutter($thisurl)}; unless ($ids) { my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl; $ids=$bighash{$idkey}; } if ($ids) { # ------------------------------------------------------------------- Has ID(s) + if ($thisfn =~ m{^/adm/wrapper/ext/}) { + $symb =~ s/\?.+$//; + } foreach my $id (split(/\,/,$ids)) { my ($mapid,$resid)=split(/\./,$id); - if ($thisfn =~ m{^/adm/wrapper/ext/}) { - $symb =~ s/\?.+$//; - } if ( &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn) eq $symb) { @@ -10208,6 +10567,7 @@ sub symbverify { ($bighash{'encrypted_'.$id} eq $env{'request.enc'}) || ($thisurl eq '/adm/navmaps')) { $okay=1; + last; } } } @@ -10283,14 +10643,14 @@ sub deversion { sub symbread { my ($thisfn,$donotrecurse)=@_; - my $cache_str='request.symbread.cached.'.$thisfn; - if (defined($env{$cache_str})) { - if (($thisfn) || ($env{$cache_str} ne '')) { + 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'}); } @@ -11205,12 +11565,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; } } @@ -11235,8 +11595,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); @@ -11244,9 +11606,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; @@ -11784,6 +12199,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; + } + } + } } @@ -12116,6 +12542,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 @@ -12318,7 +12755,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 @@ -12479,6 +12915,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 @@ -13034,6 +13498,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 @@ -13051,7 +13517,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