--- loncom/interface/londocs.pm 2002/07/24 20:40:58 1.2 +++ loncom/interface/londocs.pm 2024/07/01 18:29:41 1.484.2.94 @@ -1,41 +1,7877 @@ # The LearningOnline Network # Documents # -# (Internal Server Error Handler +# $Id: londocs.pm,v 1.484.2.94 2024/07/01 18:29:41 raeburn Exp $ # -# (Login Screen -# 5/21/99,5/22,5/25,5/26,5/31,6/2,6/10,7/12,7/14, -# 1/14/00,5/29,5/30,6/1,6/29,7/1,11/9 Gerd Kortemeyer) +# Copyright Michigan State University Board of Trustees # -# 3/1/1 Gerd Kortemeyer) +# This file is part of the LearningOnline Network with CAPA (LON-CAPA). # -# 3/1 Gerd Kortemeyer +# LON-CAPA is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. # +# LON-CAPA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LON-CAPA; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# /home/httpd/html/adm/gpl.txt +# +# http://www.lon-capa.org/ +# + package Apache::londocs; use strict; -use Apache::Constants qw(:common); +use Apache::Constants qw(:common :http); +use Apache::imsexport; +use Apache::lonnet; +use Apache::loncommon; +use Apache::lonhtmlcommon; +use LONCAPA::map(); +use Apache::lonratedt(); +use Apache::lonxml; +use Apache::lonclonecourse; +use Apache::lonnavmaps; +use Apache::lonnavdisplay(); +use Apache::lonextresedit(); +use Apache::lontemplate(); +use Apache::lonsimplepage(); +use HTML::Entities; +use HTML::TokeParser; +use GDBM_File; +use File::MMagic; +use Apache::lonlocal; +use Cwd; +use LONCAPA qw(:DEFAULT :match); -sub handler { - my $r = shift; - $r->content_type('text/html'); - $r->send_http_header; - return OK if $r->header_only; +my $iconpath; + +my %hash; + +my $hashtied; +my %alreadyseen=(); + +my $hadchanges; +my $suppchanges; + + +my %help=(); + + +sub mapread { + my ($coursenum,$coursedom,$map)=@_; + return + &LONCAPA::map::mapread('/uploaded/'.$coursedom.'/'.$coursenum.'/'. + $map); +} + +sub storemap { + my ($coursenum,$coursedom,$map,$contentchg)=@_; + my $report; + if (($contentchg) && ($map =~ /^default/)) { + $report = 1; + } + my ($outtext,$errtext)= + &LONCAPA::map::storemap('/uploaded/'.$coursedom.'/'.$coursenum.'/'. + $map,1,$report); + if ($errtext) { return ($errtext,2); } + + if ($map =~ /^default/) { + $hadchanges=1; + } else { + $suppchanges=1; + } + return ($errtext,0); +} + + + +sub authorhosts { + my %outhash=(); + my $home=0; + my $other=0; + foreach my $key (keys(%env)) { + if ($key=~/^user\.role\.(au|ca)\.(.+)$/) { + my $role=$1; + my $realm=$2; + my ($start,$end)=split(/\./,$env{$key}); + if (($start) && ($start>time)) { next; } + if (($end) && (time>$end)) { next; } + my ($ca,$cd); + if ($1 eq 'au') { + $ca=$env{'user.name'}; + $cd=$env{'user.domain'}; + } else { + ($cd,$ca)=($realm=~/^\/($match_domain)\/($match_username)$/); + } + my $allowed=0; + my $myhome=&Apache::lonnet::homeserver($ca,$cd); + my @ids=&Apache::lonnet::current_machine_ids(); + foreach my $id (@ids) { + if ($id eq $myhome) { + $allowed=1; + last; + } + } + if ($allowed) { + $home++; + $outhash{'home_'.$ca.':'.$cd}=1; + } else { + $outhash{'otherhome_'.$ca.':'.$cd}=$myhome; + $other++; + } + } + } + return ($home,$other,%outhash); +} + + +sub clean { + my ($title)=@_; + $title=~s/[^\w\/\!\$\%\^\*\-\_\=\+\;\:\,\\\|\`\~]+/\_/gs; + return $title; +} + +sub default_folderpath { + my ($coursenum,$coursedom,$navmapref) = @_; + return unless ($coursenum && $coursedom && ref($navmapref)); +# Check if entire course is hidden and/or encrypted + my ($hiddenmap,$encryptmap,$folderpath,$hiddentop); + my $toplevel = "uploaded/$coursedom/$coursenum/default.sequence"; + unless (ref($$navmapref)) { + $$navmapref = Apache::lonnavmaps::navmap->new(); + } + if (ref($$navmapref)) { + if (lc($$navmapref->get_mapparam(undef,$toplevel,"0.hiddenresource")) eq 'yes') { + my $filterFunc = sub { my $res = shift; return (!$res->randomout() && !$res->is_map()) }; + my @resources = $$navmapref->retrieveResources($toplevel,$filterFunc,1,1); + unless (@resources) { + $hiddenmap = 1; + unless ($env{'request.role.adv'}) { + $hiddentop = 1; + if ($env{'form.folder'}) { + undef($env{'form.folder'}); + } + } + } + } + if (lc($$navmapref->get_mapparam(undef,$toplevel,"0.encrypturl")) eq 'yes') { + $encryptmap = 1; + } + } + unless ($hiddentop) { + $folderpath='default&'.&escape(&mt('Main Content')). + '::'.$hiddenmap.':'.$encryptmap.'::'; + } + if (wantarray) { + return ($folderpath,$hiddentop); + } else { + return $folderpath; + } +} + +sub validate_folderpath { + my ($supplementalflag) = @_; + if ($env{'form.folderpath'} ne '') { + my @items = split(/\&/,$env{'form.folderpath'}); + my $badpath; + for (my $i=0; $i<@items; $i++) { + my $odd = $i%2; + if (($odd) && (!$supplementalflag) && ($items[$i] !~ /^[^:]*:(|\d+):(|1):(|1):(|1):(|1)$/)) { + $badpath = 1; + } elsif ((!$odd) && ($items[$i] !~ /^(default|supplemental)(|_\d+)$/)) { + $badpath = 1; + } + last if ($badpath); + } + if ($badpath) { + delete($env{'form.folderpath'}); + } + } + return; +} + +sub validate_suppath { + if ($env{'form.supppath'} ne '') { + my @items = split(/\&/,$env{'form.supppath'}); + my $badpath; + for (my $i=0; $i<@items; $i++) { + my $odd = $i%2; + if ((!$odd) && ($items[$i] !~ /^supplemental(|_\d+)$/)) { + $badpath = 1; + } + last if ($badpath); + } + if ($badpath) { + delete($env{'form.supppath'}); + } + } + return; +} + +sub dumpcourse { + my ($r) = @_; + my $crstype = &Apache::loncommon::course_type(); + my ($starthash,$js); + unless (($env{'form.authorspace'}) && ($env{'form.authorfolder'}=~/\w/)) { + $js = <<"ENDJS"; + +ENDJS + $starthash = { + add_entries => {'onload' => "hide_searching();"}, + }; + } + $r->print(&Apache::loncommon::start_page('Copy '.$crstype.' Content to Authoring Space',$js,$starthash)."\n". + &Apache::lonhtmlcommon::breadcrumbs('Copy '.$crstype.' Content to Authoring Space')."\n"); + $r->print(&startContentScreen('tools')); + my ($home,$other,%outhash)=&authorhosts(); + unless ($home) { + $r->print(&endContentScreen()); + return ''; + } + my $origcrsid=$env{'request.course.id'}; + my %origcrsdata=&Apache::lonnet::coursedescription($origcrsid); + if (($env{'form.authorspace'}) && ($env{'form.authorfolder'}=~/\w/)) { +# Do the dumping + unless ($outhash{'home_'.$env{'form.authorspace'}}) { + $r->print(&endContentScreen()); + return ''; + } + my ($ca,$cd)=split(/\:/,$env{'form.authorspace'}); + $r->print('
'."\n". + &mt('The following files are either dependencies of a web page or references within a folder and/or composite page for which errors occurred during import:')."\n". + '
'.
+ join('
',@{$msgsarray}).
+ '
'. + $lockerror. + '
'; + } + } + my ($errtext,$fatal) = + &storemap($coursenum, $coursedom, $folder.'.'.$container,1); + unless ($fatal) { + if ($folder =~ /^supplemental/) { + &Apache::lonnet::get_numsuppfiles($coursenum,$coursedom,1); + my ($errtext,$fatal) = &mapread($coursenum,$coursedom, + $folder.'.'.$container); + } + } + return ($errtext,$fatal,$fixuperrors); +} + +sub log_docs { + return &Apache::lonnet::write_log('course','docslog',@_); +} + +{ + my @oldresources=(); + my @oldorder=(); + my $parmidx; + my %parmaction=(); + my %parmvalue=(); + my $changedflag; + + sub snapshotbefore { + @oldresources=@LONCAPA::map::resources; + @oldorder=@LONCAPA::map::order; + $parmidx=undef; + %parmaction=(); + %parmvalue=(); + $changedflag=0; + } + + sub remember_parms { + my ($idx,$parameter,$action,$value)=@_; + $parmidx=$idx; + $parmaction{$parameter}=$action; + $parmvalue{$parameter}=$value; + $changedflag=1; + } + + sub log_differences { + my ($plain)=@_; + my %storehash=('folder' => $plain, + 'currentfolder' => $env{'form.folder'}); + if ($parmidx) { + $storehash{'parameter_res'}=$oldresources[$parmidx]; + foreach my $parm (keys(%parmaction)) { + $storehash{'parameter_action_'.$parm}=$parmaction{$parm}; + $storehash{'parameter_value_'.$parm}=$parmvalue{$parm}; + } + } + my $maxidx=$#oldresources; + if ($#LONCAPA::map::resources>$#oldresources) { + $maxidx=$#LONCAPA::map::resources; + } + for (my $idx=0; $idx<=$maxidx; $idx++) { + if ($LONCAPA::map::resources[$idx] ne $oldresources[$idx]) { + $storehash{'before_resources_'.$idx}=$oldresources[$idx]; + $storehash{'after_resources_'.$idx}=$LONCAPA::map::resources[$idx]; + $changedflag=1; + } + if ($LONCAPA::map::order[$idx] ne $oldorder[$idx]) { + $storehash{'before_order_res_'.$idx}=$oldresources[$oldorder[$idx]]; + $storehash{'after_order_res_'.$idx}=$LONCAPA::map::resources[$LONCAPA::map::order[$idx]]; + $changedflag=1; + } + } + $storehash{'maxidx'}=$maxidx; + if ($changedflag) { &log_docs(\%storehash); } + } +} + +sub docs_change_log { + my ($r,$coursenum,$coursedom,$folder,$allowed,$crstype,$iconpath,$canedit)=@_; + my $supplementalflag=($env{'form.folderpath'}=~/^supplemental/); + my $navmap; + my $js = ''."\n"; + $r->print(&Apache::loncommon::start_page('Content Change Log',$js)); + $r->print(&Apache::lonhtmlcommon::breadcrumbs('Content Change Log')); + $r->print(&startContentScreen(($supplementalflag?'suppdocs':'docs'))); + my %orderhash; + my $container='sequence'; + my $pathitem; + if ($env{'form.folderpath'} =~ /\:1$/) { + $container='page'; + } + my $folderpath=$env{'form.folderpath'}; + if ($folderpath eq '') { + $folderpath = &default_folderpath($coursenum,$coursedom,\$navmap); + } + undef($navmap); + $pathitem = ''; + my $readfile="/uploaded/$coursedom/$coursenum/$folder.$container"; + my $jumpto = $readfile; + $jumpto =~ s{^/}{}; + my $tid = 1; + if ($supplementalflag) { + $tid = 2; + } + my ($breadcrumbtrail) = + &Apache::lonhtmlcommon::docs_breadcrumbs($allowed,$crstype,1); + $r->print($breadcrumbtrail. + &generate_edit_table($tid,\%orderhash,undef,$iconpath,$jumpto, + $readfile)); + my %docslog=&Apache::lonnet::dump('nohist_docslog', + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + + if ((keys(%docslog))[0]=~/^error\:/) { undef(%docslog); } + + my %saveable_parameters = ('show' => 'scalar',); + &Apache::loncommon::store_course_settings('docs_log', + \%saveable_parameters); + &Apache::loncommon::restore_course_settings('docs_log', + \%saveable_parameters); + if (!$env{'form.show'}) { $env{'form.show'}=10; } +# FIXME: internationalization seems wrong here + my %lt=('hiddenresource' => 'Resources hidden', + 'encrypturl' => 'URL hidden', + 'randompick' => 'Randomly pick', + 'randomorder' => 'Randomly ordered', + 'set' => 'set to', + 'del' => 'deleted'); + my $filter = &Apache::loncommon::display_filter('docslog')."\n". + $pathitem."\n". + ''. + (' 'x2).''; + $r->print(''.
+ join('
',@{$pastemsgarray}).
+ '
'. + $lockerror. + '
'); + } + if ($save_error ne '') { + return $save_error; + } + if ($paste_res) { + my %errortext = &Apache::lonlocal::texthash ( + fail => 'Storage of folder contents failed', + failread => 'Reading folder contents failed', + failstore => 'Storage of folder contents failed', + ); + if ($errortext{$paste_res}) { + $r->print(''.$errortext{$paste_res}.'
'); + } + } + if (keys(%paste_errors) > 0) { + $r->print(''."\n". + &mt('The following files are either dependencies of a web page or references within a folder and/or composite page which could not be copied during the paste operation:')."\n". + '
'.$output.'
'); + } + } + + $r->print($upload_output); + +# Rename, cut, copy or remove a single resource + if (&handle_edit_cmd($coursenum,$coursedom)) { + my $contentchg; + if ($env{'form.cmd'} =~ m{^(remove|cut)_}) { + $contentchg = 1; + } + ($errtext,$fatal)=&storemap($coursenum,$coursedom,$folder.'.'.$container,$contentchg); + return $errtext if ($fatal); + } + +# Cut, copy and/or remove multiple resources + if ($env{'form.multichange'}) { + my %allchecked = ( + cut => {}, + remove => {}, + ); + my $needsupdate; + foreach my $which (keys(%allchecked)) { + $env{'form.multi'.$which} =~ s/,$//; + if ($env{'form.multi'.$which}) { + map { $allchecked{$which}{$_} = 1; } split(/,/,$env{'form.multi'.$which}); + if (ref($allchecked{$which}) eq 'HASH') { + $needsupdate += scalar(keys(%{$allchecked{$which}})); + } + } + } + if ($needsupdate) { + my $haschanges = 0; + my %curr_groups = &Apache::longroup::coursegroups(); + my $total = scalar(@LONCAPA::map::order) - 1; + for (my $i=$total; $i>=0; $i--) { + my $res = $LONCAPA::map::order[$i]; + my ($name,$url)=split(/\:/,$LONCAPA::map::resources[$res]); + $name=&LONCAPA::map::qtescape($name); + $url=&LONCAPA::map::qtescape($url); + next unless $url; + my %denied = + &action_restrictions($coursenum,$coursedom,$url, + $env{'form.folderpath'},\%curr_groups); + foreach my $which (keys(%allchecked)) { + next if ($denied{$which}); + next unless ($allchecked{$which}{$res}); + if ($which eq 'remove') { + if (($url=~m|/+uploaded/\Q$coursedom\E/\Q$coursenum\E/|) && + ($url!~/$LONCAPA::assess_page_seq_re/)) { + &Apache::lonnet::removeuploadedurl($url); + } else { + &LONCAPA::map::makezombie($res); + } + splice(@LONCAPA::map::order,$i,1); + $haschanges ++; + } elsif ($which eq 'cut') { + &LONCAPA::map::makezombie($res); + splice(@LONCAPA::map::order,$i,1); + $haschanges ++; + } + } + } + if ($haschanges) { + ($errtext,$fatal) = + &storemap($coursenum,$coursedom,$folder.'.'.$container,1); + return $errtext if ($fatal); + } + } + } + +# Group import/search + if ($env{'form.importdetail'}) { + my @imports; + foreach my $item (split(/\&/,$env{'form.importdetail'})) { + if (defined($item)) { + my ($name,$url,$residx)= + map { &unescape($_); } split(/\=/,$item); + if ($url =~ m{^\Q/uploaded/$coursedom/$coursenum/\E(default|supplemental)_new\.(sequence|page)$}) { + my ($suffix,$errortxt,$locknotfreed) = + &new_timebased_suffix($coursedom,$coursenum,'map',$1,$2); + if ($locknotfreed) { + $r->print($locknotfreed); + } + if ($suffix) { + $url =~ s/_new\./_$suffix./; + } else { + return $errortxt; + } + } elsif ($url =~ m{^/adm/$match_domain/$match_username/new/(smppg|bulletinboard)$}) { + my $type = $1; + my ($suffix,$errortxt,$locknotfreed) = + &new_timebased_suffix($coursedom,$coursenum,$type); + if ($locknotfreed) { + $r->print($locknotfreed); + } + if ($suffix) { + $url =~ s{^(/adm/$match_domain/$match_username)/new}{$1/$suffix}; + } else { + return $errortxt; + } + } elsif ($url =~ m{^/uploaded/$coursedom/$coursenum/(docs|supplemental)/(default|\d+)/new.html$}) { + if ($supplementalflag) { + next unless ($1 eq 'supplemental'); + if ($folder eq 'supplemental') { + next unless ($2 eq 'default'); + } else { + next unless ($folder eq 'supplemental_'.$2); + } + } else { + next unless ($1 eq 'docs'); + if ($folder eq 'default') { + next unless ($2 eq 'default'); + } else { + next unless ($folder eq 'default_'.$2); + } + } + } + push(@imports, [$name, $url, $residx]); + } + } + ($errtext,$fatal,my $fixuperrors) = + &group_import($coursenum, $coursedom, $folder,$container, + 'londocs',@imports); + return $errtext if ($fatal); + if ($fixuperrors) { + $r->print($fixuperrors); + } + } +# Loading a complete map + if ($env{'form.loadmap'}) { + if ($env{'form.importmap'}=~/\w/) { + foreach my $res (&Apache::lonsequence::attemptread(&Apache::lonnet::filelocation('',$env{'form.importmap'}))) { + my ($title,$url,$ext,$type)=split(/\:/,$res); + my $idx=&LONCAPA::map::getresidx($url); + $LONCAPA::map::resources[$idx]=$res; + $LONCAPA::map::order[$#LONCAPA::map::order+1]=$idx; + } + ($errtext,$fatal)=&storemap($coursenum,$coursedom, + $folder.'.'.$container,1); + return $errtext if ($fatal); + } else { + $r->print(''.&mt('No map selected.').'
'); + + } + } + &log_differences($plain); + } +# ---------------------------------------------------------------- End commands +# ---------------------------------------------------------------- Print screen + my $idx=0; + my $shown=0; + if (($ishidden) || ($isencrypted) || ($randompick>=0) || ($is_random_order)) { + $r->print('' + .&mt('Caution: this folder is set to randomly pick a subset' + .' of resources. Adding or removing resources from this' + .' folder will change the set of resources that the' + .' students see, resulting in spurious or missing credit' + .' for completed problems, not limited to ones you' + .' modify. Do not modify the contents of this folder if' + .' it is in active student use.') + .'
' + ); + } + if ($is_random_order) { + $r->print('' + .&mt('Caution: this folder is set to randomly order its' + .' contents. Adding or removing resources from this folder' + .' will change the order of resources shown.') + .'
' + ); + } + $r->print('' + .&Apache::loncommon::help_open_menu('Navigation Screen', + 'Navigation_Screen',undef,'RAT') + .' | '.&mt('Tools:').' | ' + .'
|
'.&mt('Uploaded [_1]',''. + $stored.'').'
'; + } else { + my ($filename) = ($env{'form.uploaddoc.filename'} =~ m{([^/]+)$}); + + $$upload_output = ''.&mt('This file contains embedded multimedia objects, which need to be uploaded.').'
'.$embedded; + $nextphase = $uploadphase; + } else { + $$upload_output .= $embedded; + } + } else { + $$upload_output .= &mt('Embedded item(s) already present, so no additional upload(s) required').'