# The LearningOnline Network
# Documents
#
# $Id: londocs.pm,v 1.600 2016/03/27 20:22:52 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# 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 :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);
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 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('
'.&mt('Copying Files').'
');
my $title=$env{'form.authorfolder'};
$title=&clean($title);
my ($navmap,$errormsg) =
&Apache::loncourserespicker::get_navmap_object($crstype,'dumpdocs');
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
my (%maps,%resources,%titles);
if (!ref($navmap)) {
$r->print($errormsg.
&endContentScreen());
return '';
} else {
&Apache::loncourserespicker::enumerate_course_contents($navmap,\%maps,\%resources,\%titles,
'dumpdocs',$cdom,$cnum);
}
my @todump = &Apache::loncommon::get_env_multiple('form.archive');
my (%tocopy,%replacehash,%lookup,%deps,%display,%result,%depresult,%simpleproblems,%simplepages,
%newcontent,%has_simpleprobs);
foreach my $item (sort {$a <=> $b} (@todump)) {
my $name = $env{'form.namefor_'.$item};
if ($resources{$item}) {
my ($map,$id,$res) = &Apache::lonnet::decode_symb($resources{$item});
if ($res =~ m{^uploaded/$cdom/$cnum/\E((?:docs|supplemental)/.+)$}) {
$tocopy{$1} = $name;
$display{$item} = $1;
$lookup{$1} = $item;
} elsif ($res eq 'lib/templates/simpleproblem.problem') {
$simpleproblems{$item} = {
symb => $resources{$item},
name => $name,
};
$display{$item} = 'simpleproblem_'.$name;
if ($map =~ m{^\Quploaded/$cdom/$cnum/\E(.+)$}) {
$has_simpleprobs{$1}{$id} = $item;
}
} elsif ($res =~ m{^adm/$match_domain/$match_username/(\d+)/smppg}) {
my $marker = $1;
my $db_name = &Apache::lonsimplepage::get_db_name($res,$marker,$cdom,$cnum);
$simplepages{$item} = {
res => $res,
title => $titles{$item},
db => $db_name,
marker => $marker,
symb => $resources{$item},
name => $name,
};
$display{$item} = '/'.$res;
}
} elsif ($maps{$item}) {
if ($maps{$item} =~ m{^\Quploaded/$cdom/$cnum/\E((?:default|supplemental)_\d+\.(?:sequence|page))$}) {
$tocopy{$1} = $name;
$display{$item} = $1;
$lookup{$1} = $item;
}
} else {
next;
}
}
my $crs='/uploaded/'.$env{'request.course.id'}.'/';
$crs=~s/\_/\//g;
my $mm = new File::MMagic;
my $prefix = "/uploaded/$cdom/$cnum/";
%replacehash = %tocopy;
foreach my $item (sort(keys(%simpleproblems))) {
my $content = &Apache::imsexport::simpleproblem($simpleproblems{$item}{'symb'});
$newcontent{$display{$item}} = $content;
}
my $gateway = Apache::lonhtmlgateway->new('web');
foreach my $item (sort(keys(%simplepages))) {
if (ref($simplepages{$item}) eq 'HASH') {
my $pagetitle = $simplepages{$item}{'title'};
my %fields = &Apache::lonnet::dump($simplepages{$item}{'db'},$cdom,$cnum);
my %contents;
foreach my $field (keys(%fields)) {
if ($field =~ /^(?:aaa|bbb|ccc)_(\w+)$/) {
my $name = $1;
my $msg = $fields{$field};
if ($name eq 'webreferences') {
if ($msg =~ m{^https?://}) {
$contents{$name} = ''.$msg.'';
}
} else {
$msg = &Encode::decode('utf8',$msg);
$msg = $gateway->process_outgoing_html($msg,1);
$contents{$name} = $msg;
}
} elsif ($field eq 'uploaded.photourl') {
my $marker = $simplepages{$item}{marker};
if ($fields{$field} =~ m{^\Q$prefix\E(simplepage/$marker/.+)$}) {
my $filepath = $1;
my ($relpath,$fname) = ($filepath =~ m{^(.+/)([^/]+)$});
if ($fname ne '') {
$fname=~s/\.(\w+)$//;
my $ext=$1;
$fname = &clean($fname);
$fname.='.'.$ext;
$contents{image} = '';
$replacehash{$filepath} = $relpath.$fname;
$deps{$item}{$filepath} = 1;
}
}
}
}
$replacehash{'/'.$simplepages{$item}{'res'}} = $simplepages{$item}{'name'};
$lookup{'/'.$simplepages{$item}{'res'}} = $item;
my $content = '
'.$pagetitle.'
';
if ($contents{title}) {
$content .= "\n".'
'.$contents{title}.'
';
}
if ($contents{image}) {
$content .= "\n".$contents{image};
}
if ($contents{content}) {
$content .= '
'.&mt('Content').'
'.
$contents{content}.'
';
}
if ($contents{webreferences}) {
$content .= '
'.&mt('Web References').'
'.
$contents{webreferences}.'
';
}
$content .= '
';
$newcontent{'/'.$simplepages{$item}{res}} = $content;
}
}
foreach my $item (keys(%tocopy)) {
unless ($item=~/\.(sequence|page)$/) {
my $currurlpath = $prefix.$item;
my $currdirpath = &Apache::lonnet::filelocation('',$currurlpath);
&recurse_html($mm,$prefix,$currdirpath,$currurlpath,$item,$lookup{$item},\%replacehash,\%deps);
}
}
foreach my $num (sort {$a <=> $b} (@todump)) {
my $src = $display{$num};
next if ($src eq '');
my @needcopy = ();
if ($replacehash{$src}) {
push(@needcopy,$src);
if (ref($deps{$num}) eq 'HASH') {
foreach my $dep (sort(keys(%{$deps{$num}}))) {
if ($replacehash{$dep}) {
push(@needcopy,$dep);
}
}
}
} elsif ($src =~ /^simpleproblem_/) {
push(@needcopy,$src);
}
next if (@needcopy == 0);
my ($result,$depresult);
for (my $i=0; $i<@needcopy; $i++) {
my $item = $needcopy[$i];
my $newfilename;
if ($simpleproblems{$num}) {
$newfilename=$title.'/'.$simpleproblems{$num}{'name'};
} else {
$newfilename=$title.'/'.$replacehash{$item};
}
$newfilename=~s/\.(\w+)$//;
my $ext=$1;
$newfilename=&clean($newfilename);
$newfilename.='.'.$ext;
my ($newrelpath) = ($newfilename =~ m{^\Q$title/\E(.+)$});
if ($newrelpath ne $replacehash{$item}) {
$replacehash{$item} = $newrelpath;
}
my @dirs=split(/\//,$newfilename);
my $path=$r->dir_config('lonDocRoot')."/priv/$cd/$ca";
my $makepath=$path;
my $fail;
my $origin;
for (my $i=0;$i<$#dirs;$i++) {
$makepath.='/'.$dirs[$i];
unless (-e $makepath) {
unless(mkdir($makepath,0755)) {
$fail = &mt('Directory creation failed.');
}
}
}
if ($i == 0) {
$result = ' '.$item.' => '.$newfilename.': ';
} else {
$depresult .= '
'.$item.' => '.$newfilename.' '.
''.
&mt('(dependency)').': ';
}
if (-e $path.'/'.$newfilename) {
$fail = &mt('Destination already exists -- not overwriting.');
} else {
if (my $fh=Apache::File->new('>'.$path.'/'.$newfilename)) {
if (($item =~ m{^/adm/$match_domain/$match_username/\d+/smppg}) ||
($item =~ /^simpleproblem_/)) {
print $fh $newcontent{$item};
} else {
my $fileloc = &Apache::lonnet::filelocation('',$prefix.$item);
if (-e $fileloc) {
if ($item=~/\.(sequence|page|html|htm|xml|xhtml)$/) {
if ((($1 eq 'sequence') || ($1 eq 'page')) &&
(ref($has_simpleprobs{$item}) eq 'HASH')) {
my %changes = %{$has_simpleprobs{$item}};
my $content = &Apache::lonclonecourse::rewritefile(
&Apache::lonclonecourse::readfile($env{'request.course.id'},$item),
(%replacehash,$crs => '')
);
my $updatedcontent = '';
my $parser = HTML::TokeParser->new(\$content);
$parser->attr_encoded(1);
while (my $token = $parser->get_token) {
if ($token->[0] eq 'S') {
if (($token->[1] eq 'resource') &&
($token->[2]->{'src'} eq '/res/lib/templates/simpleproblem.problem') &&
($changes{$token->[2]->{'id'}})) {
my $id = $token->[2]->{'id'};
$updatedcontent .= '<'.$token->[1];
foreach my $attrib (@{$token->[3]}) {
next unless ($attrib =~ /^(src|type|title|id)$/);
if ($attrib eq 'src') {
my ($file) = ($display{$changes{$id}} =~ /^\Qsimpleproblem_\E(.+)$/);
if ($file) {
$updatedcontent .= ' '.$attrib.'="'.$file.'"';
} else {
$updatedcontent .= ' '.$attrib.'="'.$token->[2]->{$attrib}.'"';
}
} else {
$updatedcontent .= ' '.$attrib.'="'.$token->[2]->{$attrib}.'"';
}
}
$updatedcontent .= ' />'."\n";
} else {
$updatedcontent .= $token->[4]."\n";
}
} else {
$updatedcontent .= $token->[2];
}
}
print $fh $updatedcontent;
} else {
print $fh &Apache::lonclonecourse::rewritefile(
&Apache::lonclonecourse::readfile($env{'request.course.id'},$item),
(%replacehash,$crs => '')
);
}
} else {
print $fh
&Apache::lonclonecourse::readfile($env{'request.course.id'},$item);
}
} else {
$fail = &mt('Source does not exist.');
}
}
$fh->close();
} else {
$fail = &mt('Could not write to destination.');
}
}
my $text;
if ($fail) {
$text = ''.&mt('fail').(' 'x3).$fail.'';
} else {
$text = ''.&mt('ok').'';
}
if ($i == 0) {
$result .= $text;
} else {
$depresult .= $text.'
';
}
}
$r->print($result);
if ($depresult) {
$r->print('
'.$depresult.'
');
}
}
} else {
my ($navmap,$errormsg) =
&Apache::loncourserespicker::get_navmap_object($crstype,'dumpdocs');
if (!ref($navmap)) {
$r->print($errormsg);
} else {
$r->print('