--- loncom/lonnet/perl/lonnet.pm 2022/10/22 17:24:55 1.1497 +++ loncom/lonnet/perl/lonnet.pm 2023/04/11 20:35:19 1.1508 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1497 2022/10/22 17:24:55 raeburn Exp $ +# $Id: lonnet.pm,v 1.1508 2023/04/11 20:35:19 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -2698,7 +2698,9 @@ sub get_domain_defaults { 'coursedefaults','usersessions', 'requestauthor','selfenrollment', 'coursecategories','ssl','autoenroll', - 'trust','helpsettings','wafproxy','ltisec'],$domain); + 'trust','helpsettings','wafproxy', + 'ltisec','toolsec','domexttool', + 'exttool',],$domain); my @coursetypes = ('official','unofficial','community','textbook','placement'); if (ref($domconfig{'defaults'}) eq 'HASH') { $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; @@ -2770,6 +2772,16 @@ sub get_domain_defaults { $domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}{$type}; } } + if (ref($domconfig{'coursedefaults'}{'domexttool'}) eq 'HASH') { + $domdefaults{$type.'domexttool'} = $domconfig{'coursedefaults'}{'domexttool'}{$type}; + } else { + $domdefaults{$type.'domexttool'} = 1; + } + if (ref($domconfig{'coursedefaults'}{'exttool'}) eq 'HASH') { + $domdefaults{$type.'exttool'} = $domconfig{'coursedefaults'}{'exttool'}{$type}; + } else { + $domdefaults{$type.'exttool'} = 0; + } } if (ref($domconfig{'coursedefaults'}{'canclone'}) eq 'HASH') { if (ref($domconfig{'coursedefaults'}{'canclone'}{'instcode'}) eq 'ARRAY') { @@ -2884,7 +2896,18 @@ sub get_domain_defaults { } if (ref($domconfig{'ltisec'}{'private'}) eq 'HASH') { if (ref($domconfig{'ltisec'}{'private'}{'keys'}) eq 'ARRAY') { - $domdefaults{'privhosts'} = $domconfig{'ltisec'}{'private'}{'keys'}; + $domdefaults{'ltiprivhosts'} = $domconfig{'ltisec'}{'private'}{'keys'}; + } + } + } + if (ref($domconfig{'toolsec'}) eq 'HASH') { + if (ref($domconfig{'toolsec'}{'encrypt'}) eq 'HASH') { + $domdefaults{'toolenc_crs'} = $domconfig{'toolsec'}{'encrypt'}{'crs'}; + $domdefaults{'toolenc_dom'} = $domconfig{'toolsec'}{'encrypt'}{'dom'}; + } + if (ref($domconfig{'toolsec'}{'private'}) eq 'HASH') { + if (ref($domconfig{'toolsec'}{'private'}{'keys'}) eq 'ARRAY') { + $domdefaults{'toolprivhosts'} = $domconfig{'toolsec'}{'private'}{'keys'}; } } } @@ -2924,6 +2947,7 @@ sub get_dom_instcats { if (&auto_instcode_format($caller,$dom,\%coursecodes,\%codes, \@codetitles,\%cat_titles,\%cat_order) eq 'ok') { $instcats = { + totcodes => $totcodes, codes => \%codes, codetitles => \@codetitles, cat_titles => \%cat_titles, @@ -3856,10 +3880,15 @@ sub can_edit_resource { return; } } elsif (!$crsedit) { + if ($env{'request.role'} =~ m{^st\./$cdom/$cnum}) { # # No edit allowed where CC has switched to student role. # - return; + return; + } elsif (($resurl !~ m{^/res/$match_domain/$match_username/}) || + ($resurl =~ m{^/res/lib/templates/})) { + return; + } } } } @@ -3885,7 +3914,7 @@ sub can_edit_resource { $forceedit = 1; } $cfile = $resurl; - } elsif (($resurl ne '') && (&is_on_map($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'}) { @@ -4268,7 +4297,7 @@ 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, scantron or ''. +# canceloverwrite, scantron, toollogo or ''. # if 'coursedoc': upload to the current course # if 'existingfile': write file to tmp/overwrites directory # if 'canceloverwrite': delete file written to tmp/overwrites directory @@ -4280,8 +4309,8 @@ sub resizeImage { # Section => 4, CODE => 5, FirstQuestion => 9 }). # $allfiles - reference to hash for embedded objects # $codebase - reference to hash for codebase of java objects -# $desuname - username for permanent storage of uploaded file -# $dsetudom - domain for permanaent storage of uploaded file +# $destuname - username for permanent storage of uploaded file +# $destudom - domain for permanaent storage of uploaded file # $thumbwidth - width (pixels) of thumbnail to make for uploaded image # $thumbheight - height (pixels) of thumbnail to make for uploaded image # $resizewidth - width (pixels) to which to resize uploaded image @@ -4491,11 +4520,24 @@ sub finishuserfileupload { if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) { my $input = $filepath.'/'.$file; my $output = $filepath.'/'.'tn-'.$file; + my $makethumb; my $thumbsize = $thumbwidth.'x'.$thumbheight; - my @args = ('convert','-sample',$thumbsize,$input,$output); - system({$args[0]} @args); - if (-e $filepath.'/'.'tn-'.$file) { - $fetchthumb = 1; + if ($context eq 'toollogo') { + my ($fullwidth,$fullheight) = &check_dimensions($input); + if ($fullwidth ne '' && $fullheight ne '') { + if ($fullwidth > $thumbwidth && $fullheight > $thumbheight) { + $makethumb = 1; + } + } + } else { + $makethumb = 1; + } + if ($makethumb) { + my @args = ('convert','-sample',$thumbsize,$input,$output); + system({$args[0]} @args); + if (-e $filepath.'/'.'tn-'.$file) { + $fetchthumb = 1; + } } } @@ -4727,6 +4769,30 @@ sub embedded_dependency { return; } +sub check_dimensions { + my ($inputfile) = @_; + my ($fullwidth,$fullheight); + if (($inputfile =~ m|^[/\w.\-]+$|) && (-e $inputfile)) { + my $mm = new File::MMagic; + my $mime_type = $mm->checktype_filename($inputfile); + if ($mime_type =~ m{^image/}) { + if (open(PIPE,"identify $inputfile 2>&1 |")) { + my $imageinfo = <PIPE>; + if (!close(PIPE)) { + &Apache::lonnet::logthis("Failed to close PIPE opened to retrieve image information for $inputfile"); + } + chomp($imageinfo); + my ($fullsize) = + ($imageinfo =~ /^\Q$inputfile\E\s+\w+\s+(\d+x\d+)/); + if ($fullsize) { + ($fullwidth,$fullheight) = split(/x/,$fullsize); + } + } + } + } + return ($fullwidth,$fullheight); +} + sub bubblesheet_converter { my ($cdom,$fullpath,$config,$format) = @_; if ((&domain($cdom) ne '') && @@ -7954,6 +8020,17 @@ sub is_portfolio_file { return; } +sub is_coursetool_logo { + my ($uri) = @_; + if ($env{'request.course.id'}) { + my $courseurl = &courseid_to_courseurl($env{'request.course.id'}); + if ($uri =~ m{^/*uploaded\Q$courseurl\E/toollogo/\d+/[^/]+$}) { + return 1; + } + } + return; +} + sub usertools_access { my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_; my ($access,%tools); @@ -8582,6 +8659,12 @@ sub allowed { if ($env{'request.course.id'}) { + if ($priv eq 'bre') { + if (&is_coursetool_logo($uri)) { + return 'F'; + } + } + # If this is modifying password (internal auth) domains must match for user and user's role. if ($priv eq 'mip') { @@ -8902,13 +8985,8 @@ sub constructaccess { 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); - } + } elsif (&is_course($ownerdomain,$ownername)) { +# Course Authoring Space? if ($env{'request.course.id'}) { if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) && ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) { @@ -8918,6 +8996,14 @@ sub constructaccess { } } } + return ''; + } 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, @@ -10333,7 +10419,7 @@ sub assignrole { if ($role =~ /^cr\//) { my $cwosec=$url; $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/; - unless (&allowed('ccr',$cwosec)) { + if ((!&allowed('ccr',$cwosec)) && (!&allowed('ccr',$udom))) { my $refused = 1; if ($context eq 'requestcourses') { if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) { @@ -12025,14 +12111,20 @@ sub stat_file { # or corresponding Published Resource Space, and populate the hash ref: # $dirhashref with URLs of all directories, and if $filehashref hash # ref arg is provided, the URLs of any files, excluding versioned, .meta, -# or .rights files in resource space, and .meta, .save, .log, and .bak -# files in Authoring Space. +# or .rights files in resource space, and .meta, .save, .log, .bak and +# .rights files in Authoring Space. # # Inputs: # # $is_home - true if current server is home server for user's space -# $context - either: priv, or res respectively for Authoring or Resource Space. -# $docroot - Document root (i.e., /home/httpd/html +# $recurse - if true will also traverse subdirectories recursively +# $include - reference to hash containing allowed file extensions. If provided, +# files which do not have a matching extension will be ignored. +# $exclude - reference to hash containing excluded file extensions. If provided, +# files which have a matching extension will be ignored. +# $nonemptydir - if true, will only populate $fileshashref hash entry for a particular +# directory with first file found (with acceptable extension). +# $addtopdir - if true, set $dirhashref->{'/'} = 1 # $toppath - Top level directory (i.e., /res/$dom/$uname or /priv/$dom/$uname # $relpath - Current path (relative to top level). # $dirhashref - reference to hash to populate with URLs of directories (Required) @@ -12049,39 +12141,61 @@ sub stat_file { # sub recursedirs { - my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_; + my ($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$relpath,$dirhashref,$filehashref) = @_; return unless (ref($dirhashref) eq 'HASH'); + my $docroot = $perlvar{'lonDocRoot'}; my $currpath = $docroot.$toppath; - if ($relpath) { + if ($relpath ne '') { $currpath .= "/$relpath"; } - my $savefile; + my ($savefile,$checkinc,$checkexc); if (ref($filehashref)) { $savefile = 1; } + if (ref($include) eq 'HASH') { + $checkinc = 1; + } + if (ref($exclude) eq 'HASH') { + $checkexc = 1; + } if ($is_home) { - if (opendir(my $dirh,$currpath)) { + if ((-e $currpath) && (opendir(my $dirh,$currpath))) { + my $filecount = 0; foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) { next if ($item eq ''); if (-d "$currpath/$item") { my $newpath; - if ($relpath) { + if ($relpath ne '') { $newpath = "$relpath/$item"; } else { $newpath = $item; } $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1; - &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref); - } elsif ($savefile) { - if ($context eq 'priv') { - unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) { - $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1; - } - } else { - unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/) || ($item =~ /\.rights$/)) { + if ($recurse) { + &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref); + } + } elsif (($savefile) || ($relpath eq '')) { + next if ($nonemptydir && $filecount); + if ($checkinc || $checkexc) { + my ($extension) = ($item =~ /\.(\w+)$/); + if ($checkinc) { + next unless ($extension && $include->{$extension}); + } + if ($checkexc) { + next if ($extension && $exclude->{$extension}); + } + } + if (($relpath eq '') && (!exists($dirhashref->{'/'}))) { + $dirhashref->{'/'} = 1; + } + if ($savefile) { + if ($relpath eq '') { + $filehashref->{'/'}{$item} = 1; + } else { $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1; } } + $filecount ++; } } closedir($dirh); @@ -12092,6 +12206,7 @@ sub recursedirs { my @dir_lines; my $dirptr=16384; if (ref($dirlistref) eq 'ARRAY') { + my $filecount = 0; foreach my $dir_line (sort { my ($afile)=split('&',$a,2); @@ -12107,28 +12222,57 @@ sub recursedirs { if ($relpath) { $newpath = "$relpath/$item"; } else { - $relpath = '/'; $newpath = $item; } $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1; - &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref); - } elsif ($savefile) { - if ($context eq 'priv') { - unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) { - $filehashref->{$relpath}{$item} = 1; - } - } else { - unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/)) { - $filehashref->{$relpath}{$item} = 1; + if ($recurse) { + &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref); + } + } elsif (($savefile) || ($relpath eq '')) { + next if ($nonemptydir && $filecount); + if ($checkinc || $checkexc) { + my $extension; + if ($checkinc) { + next unless ($extension && $include->{$extension}); + } + if ($checkexc) { + next if ($extension && $exclude->{$extension}); + } + } + if (($relpath eq '') && (!exists($dirhashref->{'/'}))) { + $dirhashref->{'/'} = 1; + } + if ($savefile) { + if ($relpath eq '') { + $filehashref->{'/'}{$item} = 1; + } else { + $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1; } } + $filecount ++; } } } } + if ($addtopdir) { + if (($relpath eq '') && (!exists($dirhashref->{'/'}))) { + $dirhashref->{'/'} = 1; + } + } return; } +sub priv_exclude { + return { + meta => 1, + save => 1, + log => 1, + bak => 1, + rights => 1, + DS_Store => 1, + }; +} + # -------------------------------------------------------- Value of a Condition # gets the value of a specific preevaluated condition @@ -12468,7 +12612,7 @@ sub count_supptools { my $chome=&homeserver($cnum,$cdom); $numexttools = 0; unless ($chome eq 'no_host') { - my ($supplemental) = &get_supplemental($cnum,$cdom,$reload); + my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$reload); if (ref($supplemental) eq 'HASH') { if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) { foreach my $key (keys(%{$supplemental->{'ids'}})) { @@ -12485,7 +12629,7 @@ sub count_supptools { } sub has_unhidden_suppfiles { - my ($cnum,$cdom,$ignorecache,$possdell)=@_; + my ($cnum,$cdom,$ignorecache,$possdel)=@_; my $hashid=$cnum.':'.$cdom; my ($showsupp,$cached); unless ($ignorecache) { @@ -12494,7 +12638,7 @@ sub has_unhidden_suppfiles { unless (defined($cached)) { my $chome=&homeserver($cnum,$cdom); unless ($chome eq 'no_host') { - my ($supplemental) = &get_supplemental($cnum,$cdom,$ignorecache,$possdel); + my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$ignorecache,$possdel); if (ref($supplemental) eq 'HASH') { if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) { foreach my $key (keys(%{$supplemental->{'ids'}})) { @@ -12517,35 +12661,6 @@ sub has_unhidden_suppfiles { return $showsupp; } -sub get_supplemental { - my ($cnum,$cdom,$ignorecache,$possdel)=@_; - my $hashid=$cnum.':'.$cdom; - my ($supplemental,$cached,$set_httprefs); - unless ($ignorecache) { - ($supplemental,$cached) = &is_cached_new('supplemental',$hashid); - } - unless (defined($cached)) { - my $chome=&homeserver($cnum,$cdom); - unless ($chome eq 'no_host') { - my ($errors,%ids,%hidden); - $errors = - &Apache::loncommon::recurse_supplemental($cnum,$cdom, - 'supplemental.sequence', - $errors,$possdel,\%ids,\%hidden); - $set_httprefs = 1; - if ($env{'request.course.id'} eq $cdom.'_'.$cnum) { - &Apache::lonnet::appenv({'request.course.suppupdated' => time}); - } - $supplemental = { - ids => \%ids, - hidden => \%hidden, - }; - &do_cache_new('supplemental',$hashid,$supplemental,600); - } - } - return ($supplemental,$set_httprefs); -} - # # EXT resource caching routines #