--- loncom/interface/londocs.pm 2002/07/24 20:25:23 1.1 +++ loncom/interface/londocs.pm 2012/07/13 13:36:24 1.491 @@ -1,41 +1,4561 @@ # The LearningOnline Network -# Printout +# Documents # -# (Internal Server Error Handler +# $Id: londocs.pm,v 1.491 2012/07/13 13:36:24 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. # -package Apache::lonprintout; +# 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::lonuserstate(); +use HTML::Entities; +use HTML::TokeParser; +use GDBM_File; +use Apache::lonlocal; +use Cwd; +use LONCAPA qw(:DEFAULT :match); + +my $iconpath; + +my %hash; + +my $hashtied; +my %alreadyseen=(); + +my $hadchanges; + + +my %help=(); + + +sub mapread { + my ($coursenum,$coursedom,$map)=@_; + return + &LONCAPA::map::mapread('/uploaded/'.$coursedom.'/'.$coursenum.'/'. + $map); +} + +sub storemap { + my ($coursenum,$coursedom,$map)=@_; + my ($outtext,$errtext)= + &LONCAPA::map::storemap('/uploaded/'.$coursedom.'/'.$coursenum.'/'. + $map,1); + if ($errtext) { return ($errtext,2); } + + $hadchanges=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 dumpcourse { + my ($r) = @_; + my $crstype = &Apache::loncommon::course_type(); + $r->print(&Apache::loncommon::start_page('Dump '.$crstype.' Content to Authoring Space')."\n". + &Apache::lonhtmlcommon::breadcrumbs('Dump '.$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('
'.&mt('Paste into Supplemental Content unavailable for this type of content.').'
'); + } + $r->print(''); +} + +sub supp_pasteable { + my ($url) = @_; + if (($url =~ m{^(?:/adm/wrapper/ext|(?:http|https)(?::|:))//}) || + (($url =~ /\.sequence$/) && ($url =~ m{^/uploaded/})) || + ($url =~ m{^/uploaded/$match_domain/$match_courseid/(docs|supplemental)/(default|\d+)/\d+/}) || + ($url =~ m{^/adm/$match_domain/$match_username/aboutme}) || + ($url =~ m{^/public/$match_domain/$match_courseid/syllabus})) { + return 1; + } + return; +} + +sub do_paste_from_buffer { + my ($coursenum,$coursedom,$folder,$errors) = @_; + + if (!$env{'form.pastemarked'}) { + return; + } + +# Preparing to paste resource at end of list + my $url=&LONCAPA::map::qtescape($env{'docs.markedcopy_url'}); + my $title=&LONCAPA::map::qtescape($env{'docs.markedcopy_title'}); + + my ($is_map,$srcdom,$srcnum,$prefixchg,%before,%after,%mapchanges); + if ($url=~/\.(page|sequence)$/) { + $is_map = 1; + } + if ($url =~ m{^/uploaded/($match_domain)/($match_courseid)/([^/]+)}) { + $srcdom = $1; + $srcnum = $2; + my $oldprefix = $3; + if (($srcdom ne $coursedom) || ($srcnum ne $coursenum)) { + unless ($env{"user.priv.cm./$srcdom/$srcnum"} =~ /\Q:mdc&F\E/) { + return &mt('Paste failed: Item is from a different course which you do not have rights to edit.'); + } + } + if (($folder =~ /^supplemental/) && (($oldprefix =~ /^default/) || ($oldprefix eq 'docs'))) { + $prefixchg = 1; + %before = ( map => 'default', + doc => 'docs'); + %after = ( map => 'supplemental', + doc => 'supplemental' ); + } elsif (($folder =~ /^default/) && ($oldprefix =~ /^supplemental/)) { + $prefixchg = 1; + %before = ( map => 'supplemental', + doc => 'supplemental'); + %after = ( map => 'default', + doc => 'docs'); + } + } + +# Supplemental content may only include certain types of content + if ($folder =~ /^supplemental/) { + unless (&supp_pasteable($env{'docs.markedcopy_url'})) { + return &mt('Paste failed: content type is not supported within Supplemental Content'); + } + } + +# Maps need to be copied first + my ($oldurl,%removefrommap,%addedmaps,%rewrites,%retitles,%copies,%dbcopies,%zombies, + %params,%docmoves,%mapmoves); + $oldurl = $url; + if ($is_map) { +# If pasting a map, check if map contains other maps + my %allmaps; + &contained_map_check($url,$folder,\%removefrommap,\%addedmaps); + if ($folder =~ /^default/) { + if (keys(%addedmaps) > 0) { + &reinit_role($coursedom,$coursenum,$env{"course.$env{'request.course.id'}.home"}); + } + my $navmap = Apache::lonnavmaps::navmap->new(); + if (defined($navmap)) { + foreach my $res ($navmap->retrieveResources(undef,sub { $_[0]->is_map() },1,0,1)) { + $allmaps{$res->src()} = 1; + } + } + } + if ($url=~ m{^/uploaded/}) { + $title=&mt('Copy of').' '.$title; + } + my $now = time; + my $suffix=$$.int(rand(100)).$now; + my ($oldid,$ext) = ($url=~/^(.+)\.(\w+)$/); + if ($oldid =~ m{^(/uploaded/$match_domain/$match_courseid/)(\D+)(\d+)$}) { + my $path = $1; + my $prefix = $2; + my $ancestor = $3; + if (length($ancestor) > 10) { + $ancestor = substr($ancestor,-10,10); + } + my ($newurl,$newid); + if ($prefixchg) { + if ($folder =~ /^supplemental/) { + $prefix =~ s/^default/supplemental/; + } else { + $prefix =~ s/^supplemental/default/; + } + } + if (($srcdom eq $coursedom) && ($srcnum eq $coursenum)) { + $newurl = $path.$prefix.$ancestor.$suffix.'.'.$ext; + } else { + $newurl = "/uploaded/$coursedom/$coursenum/$prefix".$now.'.'.$ext; + } + my $counter = 0; + my $is_unique = &uniqueness_check($newurl); + if ($folder =~ /^default/) { + if ($allmaps{$newurl}) { + $is_unique = 0; + } + } + while (!$is_unique && $allmaps{$newurl} && $counter < 100) { + $counter ++; + $suffix ++; + if (($srcdom eq $coursedom) && ($srcnum eq $coursenum)) { + $newurl = $path.$prefix.$ancestor.$suffix.'.'.$ext; + } else { + $newurl = "/uploaded/$coursedom/$coursenum/$prefix".$ancestor.$suffix.'.'.$ext; + } + $is_unique = &uniqueness_check($newurl); + } + if ($is_unique) { + if ($newurl ne $oldurl) { + $mapchanges{$oldurl} = 1; + } + if (($srcdom ne $coursedom) || ($srcnum ne $coursenum) || ($prefixchg)) { + &url_paste_fixups($url,$prefixchg,$coursedom,$coursenum,\%allmaps, + \%rewrites,\%retitles,\%copies,\%dbcopies,\%zombies, + \%params,\%mapmoves,\%mapchanges); + } + } else { + if ($url=~/\.page$/) { + return &mt('Paste failed: an error occurred creating a unique URL for the composite page'); + } else { + return &mt('Paste failed: an error occurred creating a unique URL for the folder'); + } + } + my $storefn=$newurl; + $storefn=~s{^/\w+/$match_domain/$match_username/}{}; + my $paste_map_result = + &Apache::lonclonecourse::writefile($env{'request.course.id'},$storefn, + &Apache::lonnet::getfile($url)); + if ($paste_map_result eq '/adm/notfound.html') { + if ($url=~/\.page$/) { + return &mt('Paste failed: an error occurred saving the composite page.'); + } else { + return &mt('Paste failed: an error occurred saving the folder.'); + } + } + $url = $newurl; + } elsif ($url=~m {^/res/}) { +# published maps can only exists once, so remove it from paste buffer when done + &Apache::lonnet::delenv('docs.markedcopy'); + if ($folder =~ /^default/) { + if ($allmaps{$url}) { + return &mt('Paste failed: only one instance of a particular published sequence or page is allowed within each course.'); + } + } + } + } + if ($url=~ m{/smppg$}) { + my $db_name = &Apache::lonsimplepage::get_db_name($url); + if ($db_name =~ /^smppage_/) { + #simple pages, need to copy the db contents to a new one. + my %contents=&Apache::lonnet::dump($db_name,$coursedom,$coursenum); + my $now = time(); + $db_name =~ s{_\d*$ }{_$now}x; + my $dbresult=&Apache::lonnet::put($db_name,\%contents, + $coursedom,$coursenum); + if ($dbresult eq 'ok') { + $url =~ s{/(\d*)/smppg$ }{/$now/smppg}x; + $title=&mt('Copy of').' '.$title; + } else { + return &mt('Paste failed: An error occurred when copying the simple page.'); + } + } + } + $title = &LONCAPA::map::qtunescape($title); + my $ext='false'; + if ($url=~m{^http(|s)://}) { $ext='true'; } + $url = &LONCAPA::map::qtunescape($url); +# Now insert the URL at the bottom + my $newidx = &LONCAPA::map::getresidx($url); + +# For uploaded files (excluding pages/sequences) path in copied file is changed +# if paste is from Main to Supplemental (or vice versa), or if pasting between +# courses. + + unless ($is_map) { + if ($url =~ m{^/uploaded/$match_domain/$match_courseid/(?:docs|supplemental)/(.+)$}) { + my $relpath = $1; + if ($relpath ne '') { + my ($prefix,$subdir,$rem) = ($relpath =~ m{^(default|\d+)/(\d+)/(.+)$}); + my ($newloc,$newsubdir) = ($folder =~ /^(default|supplemental)_?(\d*)/); + my $newprefix = $newloc; + if ($newloc eq 'default') { + $newprefix = 'docs'; + } + if ($newsubdir eq '') { + $newsubdir = 'default'; + } + if (($prefixchg) || ($srcdom ne $coursedom) || ($srcnum ne $coursenum)) { + my $newpath = "$newprefix/$newsubdir/$newidx/$rem"; + $url = + &Apache::lonclonecourse::writefile($env{'request.course.id'},$newpath, + &Apache::lonnet::getfile($oldurl)); + if ($url eq '/adm/notfound.html') { + return &mt('Paste failed: an error occurred saving the file.'); + } else { + my ($newsubpath) = ($newpath =~ m{^(.*/)[^/]*$}); + $newsubpath =~ s{/+$}{/}; + $docmoves{$oldurl} = $newsubpath; + } + } + } + } + } + my $result = + &apply_fixups($is_map,$prefixchg,$coursedom,$coursenum,$oldurl,$url, + \%removefrommap,\%rewrites,\%retitles,\%copies,\%dbcopies, + \%zombies,\%params,\%docmoves,\%mapmoves,$errors,\%before,\%after); + if ($result eq 'ok') { + if ($env{'docs.markedcopy_supplemental'}) { + if ($folder =~ /^supplemental/) { + $title = $env{'docs.markedcopy_supplemental'}; + } else { + (undef,undef,$title) = + &Apache::loncommon::parse_supplemental_title($env{'docs.markedcopy_supplemental'}); + } + } else { + if ($folder=~/^supplemental/) { + $title=time.'___&&&___'.$env{'user.name'}.'___&&&___'. + $env{'user.domain'}.'___&&&___'.$title; + } + } + $LONCAPA::map::resources[$newidx]= $title.':'.$url.':'.$ext.':normal:res'; + push(@LONCAPA::map::order, $newidx); + } + return $result; +} + +sub dbcopy { + my ($url,$coursedom,$coursenum) = @_; + if ($url=~ m{/smppg$}) { + my $db_name = &Apache::lonsimplepage::get_db_name($url); + if ($db_name =~ /^smppage_/) { + #simple pages, need to copy the db contents to a new one. + my %contents=&Apache::lonnet::dump($db_name,$coursedom,$coursenum); + my $now = time(); + $db_name =~ s{_\d*$ }{_$now}x; + my $result=&Apache::lonnet::put($db_name,\%contents, + $coursedom,$coursenum); + $url =~ s{/(\d*)/smppg$ }{/$now/smppg}x; + } + } + return $url; +} + +sub uniqueness_check { + my ($newurl) = @_; + my $unique = 1; + foreach my $res (@LONCAPA::map::order) { + my ($name,$url)=split(/\:/,$LONCAPA::map::resources[$res]); + $url=&LONCAPA::map::qtescape($url); + if ($newurl eq $url) { + $unique = 0; + last; + } + } + return $unique; +} + +sub contained_map_check { + my ($url,$folder,$removefrommap,$addedmaps) = @_; + my $content = &Apache::lonnet::getfile($url); + unless ($content eq '-1') { + my $parser = HTML::TokeParser->new(\$content); + $parser->attr_encoded(1); + while (my $token = $parser->get_token) { + next if ($token->[0] ne 'S'); + if ($token->[1] eq 'resource') { + next if ($token->[2]->{'type'} eq 'zombie'); + my $ressrc = $token->[2]->{'src'}; + if ($folder =~ /^supplemental/) { + unless (&supp_pasteable($ressrc)) { + $removefrommap->{$url}{$token->[2]->{'id'}}; + next; + } + } + if ($ressrc =~ /\.(sequence|page)$/) { + if (ref($addedmaps->{$ressrc}) eq 'ARRAY') { + push(@{$addedmaps->{$ressrc}},$url); + } else { + $addedmaps->{$ressrc} = [$url]; + } + &contained_map_check($ressrc,$folder,$removefrommap,$addedmaps); + } + } elsif ($token->[1] !~ /^resource|map|link$/) { + if ($folder =~ /^supplemental/) { + $removefrommap->{$url}{$token->[1]}; + } + } + } + } + return; +} + +sub reinit_role { + my ($cdom,$cnum,$chome) = @_; + my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum"); + unless ($ferr) { + &Apache::loncommon::update_content_constraints($cdom,$cnum,$chome,$cdom.'_'.$cnum); + } + return; +} + +sub url_paste_fixups { + my ($oldurl,$prefixchg,$cdom,$cnum,$allmaps,$rewrites,$retitles,$copies, + $dbcopies,$zombies,$params,$mapmoves,$mapchanges) = @_; + my $checktitle; + if (($prefixchg) && + ($oldurl =~ m{^/uploaded/($match_domain)/($match_courseid)/supplemental})) { + $checktitle = 1; + } + my $file = &Apache::lonnet::getfile($oldurl); + return if ($file eq '-1'); + my $parser = HTML::TokeParser->new(\$file); + $parser->attr_encoded(1); + my $changed = 0; + while (my $token = $parser->get_token) { + next if ($token->[0] ne 'S'); + if ($token->[1] eq 'resource') { + my $ressrc = $token->[2]->{'src'}; + next if ($ressrc eq ''); + my $id = $token->[2]->{'id'}; + if ($checktitle) { + my $title = $token->[2]->{'title'}; + if ($title =~ m{\d+\Q___&&&___\E$match_username\Q___&&&___\E$match_domain\Q___&&&___\E(.+)$}) { + $retitles->{$oldurl}{$ressrc} = $id; + + } + } + next if ($token->[2]->{'type'} eq 'external'); + if ($token->[2]->{'type'} eq 'zombie') { + $zombies->{$oldurl}{$ressrc} = $id; + $changed = 1; + } elsif ($ressrc =~ m{^/uploaded/($match_domain)/($match_courseid)/(.+)$}) { + my $srccdom = $1; + my $srccnum = $2; + my $rem = $3; + if (($srccdom ne $cdom) || ($srccnum ne $cnum) || ($prefixchg) || + ($mapchanges->{$oldurl})) { + if ($rem =~ /^(default|supplemental)(_?\d*).(sequence|page)$/) { + $rewrites->{$oldurl}{$ressrc} = $id; + $mapchanges->{$ressrc} = 1; + unless (&url_paste_fixups($ressrc,$prefixchg,$cdom,$cnum,$allmaps, + $rewrites,$retitles,$copies,$dbcopies,$zombies, + $params,$mapmoves,$mapchanges)) { + $mapmoves->{$ressrc} = 1; + } + $changed = 1; + } else { + $rewrites->{$oldurl}{$ressrc} = $id; + $copies->{$oldurl}{$ressrc} = $id; + $changed = 1; + } + } + } elsif ($ressrc =~ m{^/adm/($match_domain)/($match_courseid)/(.+)$}) { + my $srccdom = $1; + my $srccnum = $2; + if (($srccdom ne $cdom) || ($srccnum ne $cnum)) { + $rewrites->{$oldurl}{$ressrc} = $id; + $dbcopies->{$oldurl}{$ressrc} = $id; + $changed = 1; + } + } elsif ($ressrc =~ m{^/public/($match_domain)/($match_courseid)/(.+)$}) { + my $srccdom = $1; + my $srccnum = $2; + if (($srccdom ne $cdom) || ($srccnum ne $cnum)) { + $rewrites->{$oldurl}{$ressrc} = $id; + $dbcopies->{$oldurl}{$ressrc} = $id; + $changed = 1; + } + } + } elsif ($token->[1] eq 'param') { + my $to = $token->[2]->{'to'}; + if ($to ne '') { + if (ref($params->{$oldurl}{$to}) eq 'ARRAY') { + push (@{$params->{$oldurl}{$to}},$token->[2]->{'name'}); + } else { + @{$params->{$oldurl}{$to}} = ($token->[2]->{'name'}); + } + } + } + } + return $changed; +} + +sub apply_fixups { + my ($is_map,$prefixchg,$cdom,$cnum,$oldurl,$url,$removefrommap,$rewrites, + $retitles,$copies,$dbcopies,$zombies,$params,$docmoves,$mapmoves,$errors, + $before,$after) = @_; + my ($oldsubdir,$newsubdir,$subdirchg); + if ($is_map) { + ($oldsubdir) = + ($oldurl =~ m{^/uploaded/$match_domain/$match_courseid/(?:default|supplemental)_?(\d*)}); + if ($oldsubdir eq '') { + $oldsubdir = 'default'; + } + ($newsubdir) = + ($url =~ m{^/uploaded/$match_domain/$match_courseid/(?:default|supplemental)_?(\d*)}); + if ($newsubdir eq '') { + $newsubdir = 'default'; + } + if ($oldsubdir ne $newsubdir) { + $subdirchg = 1; + } + } + foreach my $key (keys(%{$copies}),keys(%{$docmoves})) { + my @allcopies; + if (ref($copies->{$key}) eq 'HASH') { + my %added; + foreach my $innerkey (keys(%{$copies->{$key}})) { + if (($innerkey ne '') && (!$added{$innerkey})) { + push(@allcopies,$innerkey); + $added{$innerkey} = 1; + } + } + undef(%added); + } + if ($key eq $oldurl) { + if ((exists($docmoves->{$key}))) { + unless (grep(/^\Q$oldurl\E/,@allcopies)) { + push(@allcopies,$oldurl); + } + } + } + if (@allcopies > 0) { + foreach my $item (@allcopies) { + my ($relpath,$fname) = + ($item =~ m{^(/uploaded/$match_domain/$match_courseid/(?:docs|supplemental)/(?:default|\d+)/.*/)([^/]+)$}); + if ($fname ne '') { + my $content = &Apache::lonnet::getfile($item); + unless ($content eq '-1') { + my $storefn; + if (($key eq $oldurl) && (ref($docmoves) eq 'HASH') && (exists($docmoves->{$key}))) { + $storefn = $docmoves->{$key}; + } else { + $storefn = $relpath; + $storefn =~s{^/uploaded/$match_domain/$match_courseid/}{}; + if ($prefixchg) { + $storefn =~ s/^\Q$before->{'doc'}\E/$after->{'doc'}/; + } + if (($key eq $oldurl) && ($subdirchg)) { + $storefn =~ s{^(docs|supplemental)/\Q$oldsubdir\E/}{$1/$newsubdir/}; + } + } + ©_dependencies($item,$storefn,$relpath,$errors,\$content); + my $copyurl = + &Apache::lonclonecourse::writefile($env{'request.course.id'}, + $storefn.$fname,$content); + if ($copyurl eq '/adm/notfound.html') { + if ((ref($docmoves) eq 'HASH') && (exists($docmoves->{$oldurl}))) { + return &mt('Paste failed: an error occurred copying the file.'); + } elsif (ref($errors) eq 'HASH') { + $errors->{$item} = 1; + } + } + } + } + } + } + } + foreach my $key (keys(%{$mapmoves})) { + my $storefn=$key; + $storefn=~s{^/uploaded/$match_domain/$match_courseid/}{}; + if ($prefixchg) { + $storefn =~ s/^\Q$before->{'map'}\E/$after->{'map'}/; + } + my $mapcontent = &Apache::lonnet::getfile($key); + if ($mapcontent eq '-1') { + if (ref($errors) eq 'HASH') { + $errors->{$key} = 1; + } + } else { + my $newmap = + &Apache::lonclonecourse::writefile($env{'request.course.id'},$storefn, + $mapcontent); + if ($newmap eq '/adm/notfound.html') { + if (ref($errors) eq 'HASH') { + $errors->{$key} = 1; + } + } + } + } + my %updates; + if ($is_map) { + foreach my $key (keys(%{$rewrites})) { + $updates{$key} = 1; + } + foreach my $key (keys(%{$zombies})) { + $updates{$key} = 1; + } + foreach my $key (keys(%{$removefrommap})) { + $updates{$key} = 1; + } + foreach my $key (keys(%{$dbcopies})) { + $updates{$key} = 1; + } + foreach my $key (keys(%{$retitles})) { + $updates{$key} = 1; + } + foreach my $key (keys(%updates)) { + my (%torewrite,%toretitle,%toremove,%zombie,%newdb); + if (ref($rewrites->{$key}) eq 'HASH') { + %torewrite = %{$rewrites->{$key}}; + } + if (ref($retitles->{$key}) eq 'HASH') { + %toretitle = %{$retitles->{$key}}; + } + if (ref($removefrommap->{$key}) eq 'HASH') { + %toremove = %{$removefrommap->{$key}}; + } + if (ref($zombies->{$key}) eq 'HASH') { + %zombie = %{$zombies->{$key}}; + } + if (ref($dbcopies->{$key}) eq 'HASH') { + foreach my $item (keys(%{$dbcopies->{$key}})) { + $newdb{$item} = &dbcopy($item); + } + } + my $map = &Apache::lonnet::getfile($key); + my $newcontent; + if ($map eq '-1') { + return &mt('Paste failed: an error occurred reading a folder or page: [_1].',$key); + } else { + my $parser = HTML::TokeParser->new(\$map); + $parser->attr_encoded(1); + while (my $token = $parser->get_token) { + if ($token->[0] eq 'S') { + if ($token->[2]->{'type'} eq 'zombie') { + next if (($token->[2]->{'src'} ne '') && + ($zombie{$token->[2]->{'src'}} eq $token->[2]->{'id'})); + } + if ($token->[1] eq 'resource') { + my $src = $token->[2]->{'src'}; + my $id = $token->[2]->{'id'}; + my $title = $token->[2]->{'title'}; + my $changed; + if ((exists($toretitle{$src})) && ($toretitle{$src} eq $id)) { + if ($title =~ m{^\d+\Q___&&&___\E$match_username\Q___&&&___\E$match_domain\Q___&&&___\E(.+)$}) { + $token->[2]->{'title'} = $1; + $changed = 1; + } + } + if ((exists($torewrite{$src})) && ($torewrite{$src} eq $id)) { + $src =~ s{^/(uploaded|adm|public)/$match_domain/$match_courseid/}{/$1/$cdom/$cnum/}; + if ($src =~ m{^/uploaded/}) { + if ($prefixchg) { + if ($src =~ /\.(page|sequence)$/) { + $src =~ s#^(/uploaded/$match_domain/$match_courseid/)\Q$before->{'map'}\E#$1$after->{'map'}#; + } else { + $src =~ s#^(/uploaded/$match_domain/$match_courseid/)\Q$before->{'doc'}\E#$1$after->{'doc'}#; + } + } + if (($key eq $oldurl) && ($src !~ /\.(page|sequence)$/) && ($subdirchg)) { + $src =~ s{^(/uploaded/$match_domain/$match_courseid/\w+/)\Q$oldsubdir\E}{$1$newsubdir}; + } + } + $token->[2]->{'src'} = $src; + $changed = 1; + } elsif ($newdb{$src} ne '') { + $token->[2]->{'src'} = $newdb{$src}; + $changed = 1; + } + if ($changed) { + $newcontent .= "<$token->[1]"; + foreach my $attr (@{$token->[3]}) { + if ($attr =~ /^\w+$/) { + $newcontent .= ' '.$attr.'="'.$token->[2]->{$attr}.'"'; + } + } + $newcontent .= ' />'."\n"; + } else { + $newcontent .= $token->[4]."\n"; + } + } elsif (($token->[2]->{'id'} ne '') && + (exists($toremove{$token->[2]->{'id'}}))) { + next; + } else { + $newcontent .= $token->[4]."\n"; + } + } elsif ($token->[0] eq 'E') { + $newcontent .= $token->[2]."\n"; + } + } + } + my $storefn; + if ($key eq $oldurl) { + $storefn = $url; + $storefn=~s{^/uploaded/$match_domain/$match_courseid/}{}; + } else { + $storefn = $key; + $storefn=~s{^/uploaded/$match_domain/$match_courseid/}{}; + if ($prefixchg) { + $storefn =~ s/^\Q$before->{'map'}\E/$after->{'map'}/; + } + } + my $newmapurl = + &Apache::lonclonecourse::writefile($env{'request.course.id'},$storefn, + $newcontent); + if ($newmapurl eq '/adm/notfound.html') { + return &mt('Paste failed: an error occurred saving the folder or page.'); + } + } + } + return 'ok'; +} + +sub copy_dependencies { + my ($item,$storefn,$relpath,$errors,$contentref) = @_; + my $content; + if (ref($contentref)) { + $content = $$contentref; + } else { + $content = &Apache::lonnet::getfile($item); + } + unless ($content eq '-1') { + my $mm = new File::MMagic; + my $mimetype = $mm->checktype_contents($content); + if ($mimetype eq 'text/html') { + my (%allfiles,%codebase,$state); + my $res = &Apache::lonnet::extract_embedded_items(undef,\%allfiles,\%codebase,\$content); + if ($res eq 'ok') { + my ($numexisting,$numpathchanges,$existing); + (undef,$numexisting,$numpathchanges,$existing) = + &Apache::loncommon::ask_for_embedded_content( + '/adm/coursedocs',$state,\%allfiles,\%codebase, + {'error_on_invalid_names' => 1, + 'ignore_remote_references' => 1, + 'docs_url' => $item, + 'context' => 'paste'}); + if ($numexisting > 0) { + if (ref($existing) eq 'HASH') { + foreach my $dep (keys(%{$existing})) { + my $depfile = $dep; + unless ($depfile =~ m{^\Q$relpath\E}) { + $depfile = $relpath.$dep; + } + my $depcontent = &Apache::lonnet::getfile($depfile); + unless ($depcontent eq '-1') { + my $storedep = $dep; + $storedep =~ s{^\Q$relpath\E}{}; + my $dep_url = + &Apache::lonclonecourse::writefile( + $env{'request.course.id'}, + $storefn.$storedep,$depcontent); + if ($dep_url eq '/adm/notfound.html') { + if (ref($errors) eq 'HASH') { + $errors->{$depfile} = 1; + } + } else { + ©_dependencies($depfile,$storefn,$relpath,$errors,\$depcontent); + } + } + } + } + } + } + } + } + return; +} + +my %parameter_type = ( 'randompick' => 'int_pos', + 'hiddenresource' => 'string_yesno', + 'encrypturl' => 'string_yesno', + 'randomorder' => 'string_yesno',); +my $valid_parameters_re = join('|',keys(%parameter_type)); +# set parameters +sub update_parameter { + + return 0 if ($env{'form.changeparms'} !~ /^($valid_parameters_re)$/); + + my $which = $env{'form.changeparms'}; + my $idx = $env{'form.setparms'}; + if ($env{'form.'.$which.'_'.$idx}) { + my $value = ($which eq 'randompick') ? $env{'form.'.$which.'_'.$idx} + : 'yes'; + &LONCAPA::map::storeparameter($idx, 'parameter_'.$which, $value, + $parameter_type{$which}); + &remember_parms($idx,$which,'set',$value); + } else { + &LONCAPA::map::delparameter($idx,'parameter_'.$which); + + &remember_parms($idx,$which,'del'); + } + return 1; +} + + +sub handle_edit_cmd { + my ($coursenum,$coursedom) =@_; + my ($cmd,$idx)=split('_',$env{'form.cmd'}); + + my $ratstr = $LONCAPA::map::resources[$LONCAPA::map::order[$idx]]; + my ($title, $url, @rrest) = split(':', $ratstr); + + if ($cmd eq 'del') { + if (($url=~m|/+uploaded/\Q$coursedom\E/\Q$coursenum\E/|) && + ($url!~/$LONCAPA::assess_page_seq_re/)) { + &Apache::lonnet::removeuploadedurl($url); + } else { + &LONCAPA::map::makezombie($LONCAPA::map::order[$idx]); + } + splice(@LONCAPA::map::order, $idx, 1); + + } elsif ($cmd eq 'cut') { + &LONCAPA::map::makezombie($LONCAPA::map::order[$idx]); + splice(@LONCAPA::map::order, $idx, 1); + + } elsif ($cmd eq 'up' + && ($idx) && (defined($LONCAPA::map::order[$idx-1]))) { + @LONCAPA::map::order[$idx-1,$idx] = @LONCAPA::map::order[$idx,$idx-1]; + + } elsif ($cmd eq 'down' + && defined($LONCAPA::map::order[$idx+1])) { + @LONCAPA::map::order[$idx+1,$idx] = @LONCAPA::map::order[$idx,$idx+1]; + + } elsif ($cmd eq 'rename') { + + my $comment = &LONCAPA::map::qtunescape($env{'form.title'}); + if ($comment=~/\S/) { + $LONCAPA::map::resources[$LONCAPA::map::order[$idx]]= + $comment.':'.join(':', $url, @rrest); + } +# Devalidate title cache + my $renamed_url=&LONCAPA::map::qtescape($url); + &Apache::lonnet::devalidate_title_cache($renamed_url); + } else { + return 0; + } + return 1; +} + +sub editor { + my ($r,$coursenum,$coursedom,$folder,$allowed,$upload_output,$crstype, + $supplementalflag,$orderhash,$iconpath)=@_; + my $container= ($env{'form.pagepath'}) ? 'page' + : 'sequence'; + + my ($breadcrumbtrail,$randompick,$ishidden,$isencrypted,$plain,$is_random_order) = + &breadcrumbs($allowed,$crstype); + $r->print($breadcrumbtrail); + + my $jumpto = "uploaded/$coursedom/$coursenum/$folder.$container"; + + unless ($allowed) { + $randompick = -1; + } + + my ($errtext,$fatal) = &mapread($coursenum,$coursedom, + $folder.'.'.$container); + return $errtext if ($fatal); + + if ($#LONCAPA::map::order<1) { + my $idx=&LONCAPA::map::getresidx(); + if ($idx<=0) { $idx=1; } + $LONCAPA::map::order[0]=$idx; + $LONCAPA::map::resources[$idx]=''; + } + +# ------------------------------------------------------------ Process commands + +# ---------------- if they are for this folder and user allowed to make changes + if (($allowed) && ($env{'form.folder'} eq $folder)) { +# set parameters and change order + &snapshotbefore(); + + if (&update_parameter()) { + ($errtext,$fatal)=&storemap($coursenum,$coursedom,$folder.'.'.$container); + return $errtext if ($fatal); + } + + if ($env{'form.newpos'} && $env{'form.currentpos'}) { +# change order + my $res = splice(@LONCAPA::map::order,$env{'form.currentpos'}-1,1); + splice(@LONCAPA::map::order,$env{'form.newpos'}-1,0,$res); + + ($errtext,$fatal)=&storemap($coursenum,$coursedom,$folder.'.'.$container); + return $errtext if ($fatal); + } + + if ($env{'form.pastemarked'}) { + my %paste_errors; + my $paste_res = + &do_paste_from_buffer($coursenum,$coursedom,$folder,\%paste_errors); + if ($paste_res eq 'ok') { +# Store the result + ($errtext,$fatal) = &storemap($coursenum,$coursedom,$folder.'.'.$container); + return $errtext if ($fatal); + } elsif ($paste_res ne '') { + $r->print(''.$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". + '
'.&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(''.&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').''.&mt('Done').'
'); +} + + +sub devalidateversioncache { + my $src=shift; + &Apache::lonnet::devalidate_cache_new('courseresversion',$env{'request.course.id'}.'_'. + &Apache::lonnet::clutter($src)); +} + +sub checkversions { + my ($r) = @_; + my $crstype = &Apache::loncommon::course_type(); + $r->print(&Apache::loncommon::start_page("Check $crstype Document Versions")); + $r->print(&Apache::lonhtmlcommon::breadcrumbs("Check $crstype Document Versions")); + $r->print(&startContentScreen('tools')); + + my $header=''; + my $startsel=''; + my $monthsel=''; + my $weeksel=''; + my $daysel=''; + my $allsel=''; + my %changes=(); + my $starttime=0; + my $haschanged=0; + my %setversions=&Apache::lonnet::dump('resourceversions', + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + + $hashtied=0; + &tiehash(); + my %newsetversions=(); + if ($env{'form.setmostrecent'}) { + $haschanged=1; + foreach my $key (keys(%hash)) { + if ($key=~/^ids\_(\/res\/.+)$/) { + $newsetversions{$1}='mostrecent'; + &devalidateversioncache($1); + } + } + } elsif ($env{'form.setcurrent'}) { + $haschanged=1; + foreach my $key (keys(%hash)) { + if ($key=~/^ids\_(\/res\/.+)$/) { + my $getvers=&Apache::lonnet::getversion($1); + if ($getvers>0) { + $newsetversions{$1}=$getvers; + &devalidateversioncache($1); + } + } + } + } elsif ($env{'form.setversions'}) { + $haschanged=1; + foreach my $key (keys(%env)) { + if ($key=~/^form\.set_version_(.+)$/) { + my $src=$1; + if (($env{$key}) && ($env{$key} ne $setversions{$src})) { + $newsetversions{$src}=$env{$key}; + &devalidateversioncache($src); + } + } + } + } + if ($haschanged) { + if (&Apache::lonnet::put('resourceversions',\%newsetversions, + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}) eq 'ok') { + $r->print(&Apache::loncommon::confirmwrapper( + &Apache::lonhtmlcommon::confirm_success(&mt('Your Version Settings have been Saved')))); + } else { + $r->print(&Apache::loncommon::confirmwrapper( + &Apache::lonhtmlcommon::confirm_success(&mt('An Error Occured while Attempting to Save your Version Settings'),1))); + } + &mark_hash_old(); + } + &changewarning($r,''); + if ($env{'form.timerange'} eq 'all') { +# show all documents + $header=&mt('All Documents in '.$crstype); + $allsel=1; + foreach my $key (keys(%hash)) { + if ($key=~/^ids\_(\/res\/.+)$/) { + my $src=$1; + $changes{$src}=1; + } + } + } else { +# show documents which changed + %changes=&Apache::lonnet::dump + ('versionupdate',$env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + my $firstkey=(keys(%changes))[0]; + unless ($firstkey=~/^error\:/) { + unless ($env{'form.timerange'}) { + $env{'form.timerange'}=604800; + } + my $seltext=&mt('during the last').' '.$env{'form.timerange'}.' ' + .&mt('seconds'); + if ($env{'form.timerange'}==-1) { + $seltext='since start of course'; + $startsel='selected'; + $env{'form.timerange'}=time; + } + $starttime=time-$env{'form.timerange'}; + if ($env{'form.timerange'}==2592000) { + $seltext=&mt('during the last month').' ('.&Apache::lonlocal::locallocaltime($starttime).')'; + $monthsel='selected'; + } elsif ($env{'form.timerange'}==604800) { + $seltext=&mt('during the last week').' ('.&Apache::lonlocal::locallocaltime($starttime).')'; + $weeksel='selected'; + } elsif ($env{'form.timerange'}==86400) { + $seltext=&mt('since yesterday').' ('.&Apache::lonlocal::locallocaltime($starttime).')'; + $daysel='selected'; + } + $header=&mt('Content changed').' '.$seltext; + } else { + $header=&mt('No content modifications yet.'); + } + } + %setversions=&Apache::lonnet::dump('resourceversions', + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + my %lt=&Apache::lonlocal::texthash + ('st' => 'Version changes since start of '.$crstype, + 'lm' => 'Version changes since last Month', + 'lw' => 'Version changes since last Week', + 'sy' => 'Version changes since Yesterday', + 'al' => 'All Resources (possibly large output)', + 'cd' => 'Change display', + 'sd' => 'Display', + 'fi' => 'File', + 'md' => 'Modification Date', + 'mr' => 'Most recently published Version', + 've' => 'Version used in '.$crstype, + 'vu' => 'Set Version to be used in '.$crstype, +'sv' => 'Set Versions to be used in '.$crstype.' according to Selections below', +'sm' => 'Keep all Resources up-to-date with most recent Versions (default)', +'sc' => 'Set all Resource Versions to current Version (Fix Versions)', + 'di' => 'Differences', + 'save' => 'Save changes', + 'vers' => 'Version choice(s) for specific resources', + 'act' => 'Actions'); + $r->print(<