--- loncom/publisher/loncfile.pm 2009/08/28 01:08:48 1.102
+++ loncom/publisher/loncfile.pm 2024/09/26 22:43:36 1.131
@@ -9,7 +9,7 @@
# and displays a page showing the results of the action.
#
#
-# $Id: loncfile.pm,v 1.102 2009/08/28 01:08:48 raeburn Exp $
+# $Id: loncfile.pm,v 1.131 2024/09/26 22:43:36 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -37,16 +37,16 @@
=head1 NAME
-Apache::loncfile - Construction space file management.
+Apache::loncfile - Authoring space file management.
=head1 SYNOPSIS
-
- Content handler for buttons on the top frame of the construction space
+
+ Content handler for buttons on the top frame of the construction space
directory.
=head1 INTRODUCTION
- loncfile is invoked when buttons in the top frame of the construction
+ loncfile is invoked when buttons in the top frame of the construction
space directory listing are clicked. All operations proceed in two phases.
The first phase describes to the user exactly what will be done. If the user
confirms the operation, the second phase commits the operation and indicates
@@ -68,13 +68,12 @@ use File::Basename;
use File::Copy;
use HTML::Entities();
use Apache::Constants qw(:common :http :methods);
-use Apache::loncacc;
use Apache::lonnet;
use Apache::loncommon();
+use Apache::lonhtmlcommon;
use Apache::lonlocal;
use LONCAPA qw(:DEFAULT :match);
-
my $DEBUG=0;
my $r; # Needs to be global for some stuff RF.
@@ -89,20 +88,20 @@ my $r; # Needs to be global for some
Parameters:
=over 4
-
+
=item $request - The current request operation.
=item $message - The message to put in the log file.
=back
-
+
Returns:
nothing.
=cut
sub Debug {
- # Put out the indicated message butonly if DEBUG is true.
+ # Put out the indicated message but only if DEBUG is true.
if ($DEBUG) {
my ($r,$message) = @_;
$r->log_reason($message);
@@ -110,14 +109,15 @@ sub Debug {
}
sub done {
- my ($url)=@_;
- my $done=&mt("Done");
- return(< '
+ .&Apache::lonhtmlcommon::confirm_success(&mt("Done"))
+ .' '.&mt('Error: destination for operation is an existing directory.').' '.&mt('Error: destination for operation is an existing directory.').' '.&mt('Warning: target file exists, and has been published!').' '.&mt('Warning: target file exists, and has been published!').' '.&mt("Warning: a published $published_type of this name exists.").' '.&mt("Warning: a published $published_type of this name exists.").' '.&mt("Error: a published $published_type of this name exists.").' '.&mt("Error: a published $published_type of this name exists.").' '.&mt('Warning: target file exists!').' '.&mt('Warning: target file exists!').' '.&mt('Warning: change of MIME type!').' '.&mt('Warning: change of MIME type!').'> '
- .&mt("You have requested to create file in directory [_1] which doesn't exist. The requested directory path has been removed from the requested file name."
- ,''.&display($newpath).'')
- .' '
+ .&mt("You have requested to create file in directory [_1] which doesn't exist. The requested directory path has been removed from the requested filename."
+ ,&display($newpath))
+ .' '
- .&mt('Bad filename [_1]',''.&display($dest).'')
- .' '
+ .&mt('Bad filename [_1]',&display($dest))
+ .' '
- .&mt('Invalid characters in requested name have been removed.')
- .' '
+ .&mt('Invalid characters in requested name have been removed.')
+ .' '
- .&mt($action.' [_1] to [_2]?'
- ,&display($fn),&display($newfilename))
+ .&mt($action.' [_1] to [_2]?',
+ &display($fn),
+ &display($newfilename))
.' '
- .&mt('No such file: [_1]'
- ,''.&display($fn).'')
+ .&mt('No such file: [_1]',
+ &display($fn))
.' '
.&mt('Cannot delete non-obsolete published file.')
@@ -625,15 +643,15 @@ sub Delete1 {
}
}
$request->print(' '
- .&mt('Delete [_1]?'
- ,''.&display($fn).'')
+ .&mt('Delete [_1]?',
+ &display($fn))
.' '
- .&mt('No such file: [_1]'
- ,''.&display($fn).'')
+ .&mt('No such file: [_1]',
+ &display($fn))
.' '
- .&mt('Copy [_1] to [_2]?'
- ,''.&display($fn).''
- ,''.&display($newfilename).'')
- .' '
+ .&mt('Copy [_1] to [_2]?',
+ &display($fn),
+ &display($newfilename))
+ .' '
- .&mt('No such file: [_1]'
- ,''.&display($fn).'')
+ .&mt('No such file: [_1]',
+ &display($fn))
.' '
- .&mt('Make new directory [_1]?'
- ,''.&display($newfilename).'')
+ .&mt('Make new directory [_1]?',
+ &display($newfilename))
.' '
- .&mt('Decompress [_1]?'
- ,''.&display($fn).'')
+ .&mt('Decompress [_1]?',
+ &display($fn))
.' '
- .&mt('No such file: [_1]'
- ,''.&display($fn).'')
+ .&mt('No such file: [_1]',
+ &display($fn))
+ .' '.
+ &mt('This LON-CAPA instance does not seem to have either tar or zip installed.').' '
+ .&mt('No such directory: [_1]',
+ &display($fn))
.' '.&mt('Remove existing archive request?').' '."\n".
+ ''.
+ (' 'x2)."\n".
+ ' '.
&mt('Invalid filename: ').&display($newfilename).' '.
&mt('The name of the new file needs to end with an appropriate file extension to indicate the type of file to create.').'
'.&mt("Continue").''
+ .''
+ .'
'
- .&mt('[_1](name).(number).(extension)[_2] not allowed.','','')
- .'
'
- .&mt('Removing the [_1].number.[_2] from requested filename.','','')
- .'
'
+ .&mt('[_1](name).(number).(extension)[_2] not allowed.','','')
+ .'
'
+ .&mt('Removing the [_1].number.[_2] from requested filename.','','')
+ .'
'.&mt('Cancel').'');
+ $request->print('
'.&mt('Cancel').'');
return;
}
- $request->print(''
- .'
'.&mt('Cancel').'');
+ return;
+ }
+ $request->print(
+ ''
+ .'
'."\n"
+ );
+ &CloseForm1($request, $fn);
+ } else {
+ $request->print(''.$title.'
'."\n".
+ '
');
}
=pod
=item NewFile1
-
+
Does all phase 1 processing of file creation:
Ensures that the user provides a new filename, adds proper extension
if needed and that the file does not already exist, if it is a html,
@@ -811,10 +1053,13 @@ Parameters:
=item $domain - Name of the domain of the user
-=item $fn - Source file name
+=item $fn - Source filename
=item $newfilename
- Name of the file to be created; no path information
+
+=item $warnings - Information about changes to filename made by cleanDest().
+
=back
Side Effects:
@@ -824,15 +1069,15 @@ Side Effects:
=item 2 new forms are displayed. Clicking on the confirmation button
causes the browser to attempt to load the specfied URL, allowing the
proper handler to take care of file creation. There is also a Cancel
-button which returns you to the driectory listing you came from
+button which returns you to the directory listing you came from
=back
=cut
sub NewFile1 {
- my ($request, $user, $domain, $fn, $newfilename) = @_;
- return if (&filename_check($newfilename) ne 'ok');
+ my ($request, $user, $domain, $fn, $newfilename, $warnings) = @_;
+ return if (&filename_check($newfilename,$warnings) ne 'ok');
if ($env{'form.action'} =~ /new(.+)file/) {
my $extension=$1;
@@ -845,8 +1090,8 @@ sub NewFile1 {
}
}
my ($type, $result)=&exists($user,$domain,$newfilename);
- $request->print($result);
if ($type eq 'error') {
+ $request->print($warnings.$result);
$request->print('');
} else {
my $extension;
@@ -858,6 +1103,7 @@ sub NewFile1 {
my @okexts = qw(xml html xhtml htm xhtm problem page sequence rights sty task library js css txt);
if (($extension eq '') || (!grep(/^\Q$extension\E/,@okexts))) {
my $validexts = '.'.join(', .',@okexts);
+ $request->print($warnings.$result);
$request->print('
'.
@@ -866,20 +1112,26 @@ sub NewFile1 {
'
'.&mt('Make new file').' '.&display($newfilename).'?
'); + $request->print(''); + $request->print(''); + $request->print(''); } - - $request->print(''.&mt('Make new file').' '.&display($newfilename).'?
'); - $request->print(''); - - $request->print(''); - $request->print(''); } return; } @@ -899,7 +1151,7 @@ sub filename_check { ' '.&mt('Not Allowed').''); return; } - return 'ok'; + return 'ok'; } =pod @@ -910,7 +1162,7 @@ sub filename_check { are returned if the request cannot be performed (e.g. attempts to manipulate files that are nonexistent). If the operation can be performed, what is about to be done will be presented to the user for confirmation. If the -user confirms the request, then phase two is executed, the action +user confirms the request, then phase two is executed, the action performed and reported to the user. Parameters: @@ -919,12 +1171,12 @@ performed and reported to the user. =item $r - request object [in] - The Apache request being executed. -=item $fn = string [in] - The filename being manipulated by the +=item $fn = string [in] - The filename being manipulated by the request. =item $uname - string [in] Name of user logged in and doing this action. -=item $udom - string [in] Domain name under which the user logged in. +=item $udom - string [in] Domain name under which the user logged in. =back @@ -932,58 +1184,93 @@ performed and reported to the user. sub phaseone { my ($r,$fn,$uname,$udom)=@_; - + my $doingdir=0; if ($env{'form.action'} eq 'newdir') { $doingdir=1; } - my $newfilename=&cleanDest($r,$env{'form.newfilename'},$doingdir,$fn,$uname); - $newfilename=&relativeDest($fn,$newfilename,$uname); - $r->print('' - ); - } - } elsif ($env{'form.action'} eq 'newdir') { - my $mode = ''; - if (exists($env{'form.callingmode'}) ) { - $mode = $env{'form.callingmode'}; - } - &NewDir1($r, $uname, $udom, $fn, $newfilename, $mode); - } elsif ($env{'form.action'} eq 'newfile' || - $env{'form.action'} eq 'newhtmlfile' || - $env{'form.action'} eq 'newproblemfile' || - $env{'form.action'} eq 'newpagefile' || - $env{'form.action'} eq 'newsequencefile' || - $env{'form.action'} eq 'newrightsfile' || - $env{'form.action'} eq 'newstyfile' || - $env{'form.action'} eq 'newtaskfile' || - $env{'form.action'} eq 'newlibraryfile' || - $env{'form.action'} eq 'Select Action') { + my ($newfilename,$error,$warnings) = + &cleanDest($env{'form.newfilename'},$doingdir,$fn,$uname,$udom); + unless ($error) { + ($newfilename,$error)=&relativeDest($fn,$newfilename,$uname,$udom); + } + if ($error) { + my $dirlist; + if ($fn=~m{^(.*/)[^/]+$}) { + $dirlist=$1; + } else { + $dirlist=$fn; + } + if ($warnings) { + $r->print($warnings); + } + $r->print(''.&mt('Return to Directory'). + '
'); + return; + } + $r->print('' ); - } + } + } else { + if ($warnings) { + $r->print($warnings); + } + if ($env{'form.action'} eq 'rename') { + &Rename1($r, $uname, $udom, $fn, $newfilename, 'rename'); + } elsif ($env{'form.action'} eq 'move') { + &Rename1($r, $uname, $udom, $fn, $newfilename, 'move'); + } elsif ($env{'form.action'} eq 'delete') { + &Delete1($r, $uname, $udom, $fn); + } elsif ($env{'form.action'} eq 'decompress') { + &Decompress1($r, $uname, $udom, $fn); + } elsif ($env{'form.action'} eq 'archive') { + if (($uname eq $env{'user.name'}) && ($udom eq $env{'user.domain'})) { + &Archive1($r,$fn); + } else { + $r->print('' + .&mt('Archiving of Authoring Spaces is only permitted by Author') + .'
' + ); + } + } elsif ($env{'form.action'} eq 'copy') { + if ($newfilename) { + &Copy1($r, $uname, $udom, $fn, $newfilename); + } else { + $r->print('' + .&mt('No new filename specified.') + .'
' + ); + } + } elsif ($env{'form.action'} eq 'newdir') { + my $mode = ''; + if (exists($env{'form.callingmode'}) ) { + $mode = $env{'form.callingmode'}; + } + &NewDir1($r, $uname, $udom, $fn, $newfilename, $mode); + } } } @@ -1038,7 +1325,7 @@ sub Rename2 { return 0; } ## If old name.(extension) exits, move under new name. - ## If it doesn't exist and a new.(extension) exists + ## If it doesn't exist and a new.(extension) exists ## delete it (only concern when renaming over files) my $tmp1=$oRN.'.meta'; my $tmp2=$nRN.'.meta'; @@ -1069,10 +1356,11 @@ sub Rename2 { unlink $tmp2; } } else { - $request->print('' - .&mt('No such file: [_1]' - ,''.&display($oldfile).'') - .'
' + $request->print( + '' + .&mt('No such file: [_1]', + &display($oldfile)) + .'
' ); return 0; } @@ -1083,7 +1371,7 @@ sub Rename2 { =item Delete2($request, $user, $filename) - Performs phase two of a delete. The user has confirmed that they want + Performs phase two of a delete. The user has confirmed that they want to delete the selected file. The file is deleted and the results of the delete attempt are indicated. @@ -1110,11 +1398,11 @@ Returns: sub Delete2 { my ($request, $user, $filename) = @_; - if (-d $filename) { - unless (&empty_directory($filename,'Delete2')) { - $request->print(''.&mt('Error: Directory Non Empty').''); + if (-d $filename) { + unless (&empty_directory($filename,'Delete2')) { + $request->print(''.&mt('Error: Directory Non Empty').''); return 0; - } else { + } else { if(-e $filename) { unless(rmdir($filename)) { $request->print(''.&mt('Error').': '.$!.''); @@ -1143,7 +1431,7 @@ sub Delete2 { =item Copy2($request, $username, $dir, $oldfile, $newfile) - Performs phase 2 of a copy. The file is copied and the status + Performs phase 2 of a copy. The file is copied and the status of that copy is reported back to the user. =over 4 @@ -1181,7 +1469,7 @@ sub Copy2 { } elsif (!chmod(0660, $newfile)) { $request->print(''.&mt('chmod error').': '.$!.''); return 0; - } elsif (-e $oldfile.'.meta' && + } elsif (-e $oldfile.'.meta' && !copy($oldfile.'.meta', $newfile.'.meta') && !chmod(0660, $newfile.'.meta')) { $request->print(''.&mt('copy metadata error'). @@ -1221,7 +1509,7 @@ Returns 0 - failure 1 - success. sub NewDir2 { my ($request, $user, $newdirectory) = @_; - + unless(mkdir($newdirectory, 02770)) { $request->print(''.&mt('Error').': '.$!.''); return 0; @@ -1244,9 +1532,92 @@ sub decompress2 { return 1; } +sub Archive2 { + my ($r,$uname,$udom,$fn,$identifier) = @_; + my %options = ( + dir => $fn, + uname => $uname, + udom => $udom, + ); + if ($env{'form.adload'}) { + $options{'adload'} = 1; + if ($env{'form.archivefname'} ne '') { + $env{'form.archivefname'} =~ s{\.+}{.}g; + $options{'fname'} = $env{'form.archivefname'}; + } + if ($env{'form.archiveext'} ne '') { + $options{'extension'} = $env{'form.archiveext'}; + } + } + my @filetypes = qw(problem library sty sequence page task rights meta xml html xhtml htm xhtm css js tex txt gif jpg jpeg png svg other); + my (@include,%oktypes); + map { $oktypes{$_} = 1; } @filetypes; + my @posstypes = &Apache::loncommon::get_env_multiple('form.filetype'); + foreach my $type (@posstypes) { + if ($oktypes{$type}) { + push(@include,$type); + } + } + if (scalar(@include) == scalar(@filetypes)) { + $options{'types'} = 'all'; + } else { + $options{'types'} = join(',',@include); + } + if (exists($env{'form.recurse'})) { + $options{'recurse'} = 1; + } + if (exists($env{'form.encrypt'})) { + if ($env{'form.enckey'} ne '') { + $options{'encrypt'} = $env{'form.enckey'}; + } + } + $options{'format'} = 'tar'; + $options{'compress'} = 'gzip'; + if ((exists($env{'form.format'})) && $env{'form.format'} =~ /^zip$/i) { + $options{'format'} = 'zip'; + delete($options{'compress'}); + } elsif ((exists($env{'form.compress'})) && ($env{'form.compress'} =~ /^(xz|bzip2)$/i)) { + $options{'compress'} = lc($env{'form.compress'}); + } + my $key = 'cgi.'.$identifier.'.archive'; + my $storestring = &Apache::lonnet::freeze_escape(\%options); + &Apache::lonnet::appenv({$key => $storestring, + 'cgi.author.archive' => $identifier}); + return 1; +} + +sub Archive3 { + my ($hashref) = @_; + if (ref($hashref) eq 'HASH') { + if (($hashref->{'uname'} eq $env{'user.name'}) && + ($hashref->{'udom'} eq $env{'user.domain'}) && + ($env{'environment.canarchive'}) && + ($env{'form.delarchive'})) { + my $filesdest = $Apache::lonnet::perlvar{'lonPrtDir'}.'/'.$env{'user.name'}.'_'.$env{'user.domain'}.'_archive_'.$env{'form.delarchive'}; + if (-e $filesdest) { + my $size = (stat($filesdest))[7]; + if (unlink($filesdest)) { + my ($identifier,$suffix) = split(/\./,$env{'form.delarchive'},2); + if (($identifier) && (exists($env{'cgi.'.$identifier.'.archive'}))) { + my $delres = &Apache::lonnet::delenv('cgi.'.$identifier.'.archive'); + if (($delres eq 'ok') && + (exists($env{'cgi.author.archive'})) && + ($env{'cgi.author.archive'} eq $identifier)) { + &Apache::lonnet::authorarchivelog($hashref,$size,$filesdest,'delete'); + &Apache::lonnet::delenv('cgi.author.archive'); + } + } + return 1; + } + } + } + } + return 0; +} + =pod -=item phasetwo($r, $fn, $uname, $udom) +=item phasetwo($r, $fn, $uname, $udom,$identifier) Controls the phase 2 processing of file management requests for construction space. In phase one, the user @@ -1254,7 +1625,7 @@ sub decompress2 { is performed and the result is shown. The strategy is to break out the processing into specific action processors - named action2 where action is the requested action and the 2 denotes + named action2 where action is the requested action and the 2 denotes phase 2 processing. Parameters: @@ -1277,12 +1648,12 @@ Parameters: =cut sub phasetwo { - my ($r,$fn,$uname,$udom)=@_; - + my ($r,$fn,$uname,$udom,$identifier)=@_; + &Debug($r, "loncfile - Entering phase 2 for $fn"); - + # Break down the file into its component pieces. - + my $dir; # Directory path my $main; # Filename. my $suffix; # Extension. @@ -1302,24 +1673,33 @@ sub phasetwo { &Debug($r," newfilename = ".$env{'form.newfilename'}); my $conspace=$fn; - + &Debug($r,"loncfile::phase2 Full construction space name: $conspace"); - + &Debug($r,"loncfie::phase2 action is $env{'form.action'}"); - + # Select the appropriate processing sub. - if ($env{'form.action'} eq 'decompress') { + if ($env{'form.action'} eq 'decompress') { $main .= '.'.$suffix; if(!&decompress2($r, $uname, $dir, $main)) { return ; } $dest = $dir."/."; + } elsif ($env{'form.action'} eq 'archive') { + if (($env{'environment.canarchive'}) && + ($env{'user.name'} eq $uname) && + ($env{'user.domain'} eq $udom)) { + &Archive2($r,$uname,$udom,$fn,$identifier); + } else { + $r->print(&mt('You do not have permission to export to an archive file in this Authoring Space')); + } + return; } elsif ($env{'form.action'} eq 'rename' || $env{'form.action'} eq 'move') { if($env{'form.newfilename'}) { if (!defined($dir)) { $fn=~m:^(.*)/:; - $dir=$1; + $dir=$1; } if(!&Rename2($r, $uname, $dir, $fn, $env{'form.newfilename'})) { return; @@ -1330,7 +1710,7 @@ sub phasetwo { $disp_newname = $1; $disp_newname =~ s/\///; } - } elsif ($env{'form.action'} eq 'delete') { + } elsif ($env{'form.action'} eq 'delete') { if(!&Delete2($r, $uname, $env{'form.newfilename'})) { return ; } @@ -1338,7 +1718,7 @@ sub phasetwo { # previously held it. # $dest = $dir."/."; # Parent dir. - } elsif ($env{'form.action'} eq 'copy') { + } elsif ($env{'form.action'} eq 'copy') { if($env{'form.newfilename'}) { if(!&Copy2($r, $uname, $dir, $fn, $env{'form.newfilename'})) { return ; @@ -1357,13 +1737,21 @@ sub phasetwo { $dest = $newdir."/"; } if ( ($env{'form.action'} eq 'newdir') && ($env{'form.phase'} eq 'two') && ( ($env{'form.callingmode'} eq 'testbank') || ($env{'form.callingmode'} eq 'imsimport') ) ) { - $r->print(''
+ .&Apache::lonhtmlcommon::confirm_success(&mt('Done'))
+ .'
'.&mt('Continue').''
+ .'
'.&Apache::lonhtmlcommon::confirm_success(&mt('Done')).'
' + .&Apache::lonhtmlcommon::actionbox( + [''.&mt('Return to Directory').'', + ''.$disp_newname.''])); } else { - $r->print(&done(&url($dest))); + $r->print(&done($dest)); } } } @@ -1372,7 +1760,7 @@ sub handler { $r=shift; - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['decompress','action','filename','newfilename']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['decompress','action','filename','newfilename','mode']); &Debug($r, "loncfile.pm - handler entered"); &Debug($r, " filename: ".$env{'form.filename'}); @@ -1380,144 +1768,415 @@ sub handler { # # Determine the root filename # This could come in as "filename", which actually is a URL, or -# as "qualifiedfilename", which is indeed a real filename in filesystem +# as "qualifiedfilename", which is indeed a real filename in filesystem, +# or in value of decompress form element, or need to be extracted +# from %env from hashref retrieved for cgi.'.&mt('Location').': '.&display($fn).'
'); + } + if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) { - $r->print('' - .&mt('Co-Author [_1]',$uname.':'.$udom) - .'
' - ); + unless ($crsauthor) { + $r->print('' + .&mt('Co-Author [_1]',$uname.':'.$udom) + .'
' + ); + } } &Debug($r, "loncfile::handler Form action is $env{'form.action'} "); - if ($env{'form.action'} eq 'delete') { - $r->print('' + .&mt('Creation of a new file of type: [_1] is not permitted in Course Authoring Space',$newtype) + .'
' + .&Apache::loncommon::end_page() + ); + return OK; + } + if ($env{'form.action'} eq 'archive') { + $r->print(''.&mt('Location').': '.&display($fn).'
'."\n". + ''. + &mt('Export to an archive file is not permitted in Course Authoring Space'). + '
'."\n". + &Apache::loncommon::end_page()); + return OK; + } + } elsif ($env{'form.action'} eq 'archive') { + if ($env{'environment.canarchive'}) { + if ($archive_earlyout) { + my $fname = &url($fn); + my $title = $action{$env{'form.action'}}; + &cancel_archive_form($r,$title,$fname,$archive_earlyout,$archive_idnum); + &CloseForm1($r,$fn); + $r->print(&Apache::loncommon::end_page()); + return OK; + } + } else { + $r->print(''.&mt('Location').': '.&display($fn).'
'."\n". + ''. + &mt('You do not have permission to export to an archive file in this Authoring Space'). + '
'."\n". + &Apache::loncommon::end_page()); + return OK; + } + } + $r->print('' - .&mt('Unknown Action').' '.$env{'form.action'} + .&mt('Unknown Action: [_1]',$env{'form.action'}) .'
' .&Apache::loncommon::end_page() ); - return OK; + return OK; } + if ($env{'form.phase'} eq 'two') { &Debug($r, "loncfile::handler entering phase2"); - &phasetwo($r,$fn,$uname,$udom); + &phasetwo($r,$fn,$uname,$udom,$identifier); } else { &Debug($r, "loncfile::handler entering phase1"); &phaseone($r,$fn,$uname,$udom); } $r->print(&Apache::loncommon::end_page()); - return OK; + return OK; } 1;