# The LearningOnline Network with CAPA
# Handler to rename files, etc, in construction space
#
# This file responds to the various buttons and events
# in the top frame of the construction space directory.
# Each event is processed in two phases. The first phase
# presents a page that describes the proposed action to the user
# and requests confirmation. The second phase commits the action
# and displays a page showing the results of the action.
#
#
# $Id: loncfile.pm,v 1.90 2008/09/24 17:30:18 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/
#
=pod
=head1 NAME
Apache::loncfile - Construction space file management.
=head1 SYNOPSIS
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
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
completion. When the user dismisses the output of phase2, they are returned to
an "appropriate" directory listing in general.
This is part of the LearningOnline Network with CAPA project
described at http://www.lon-capa.org.
=head2 Subroutines
=cut
package Apache::loncfile;
use strict;
use Apache::File;
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::lonlocal;
use LONCAPA qw(:DEFAULT :match);
my $DEBUG=0;
my $r; # Needs to be global for some stuff RF.
=pod
=item Debug($request, $message)
If debugging is enabled puts out a debugging message determined by the
caller. The debug message goes to the Apache error log file. Debugging
is enabled by setting the module global DEBUG variable to nonzero (TRUE).
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.
if ($DEBUG) {
my ($r,$message) = @_;
$r->log_reason($message);
}
}
sub done {
my ($url)=@_;
my $done=&mt("Done");
return(< '.&mt('Error: destination for operation is an existing directory.').' '.&mt('Warning: target file exists, and has been published!').' '.&mt("Warning: 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: 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('Invalid characters in requested name have been removed.')." '.$action.' '.&display($fn).
'
(name).(number).(extension) not allowed.
Removing the .number. from requested filename.',&display($dest))
.'');
$dest =~ s/\.(\d+)(\.\w+)$/$2/;
}
if ($foundbad) {
$request->print("
'.
&mt('Cannot change MIME type of a directory').
''.
'
'.&mt('Cancel').'');
return;
}
$newfilename=~s/\/[^\/]+\/([^\/]+)$/\/$1/;
}
$newfilename=~s://+:/:g; # remove duplicate /
while ($newfilename=~m:/\.\./:) {
$newfilename=~ s:/[^/]+/\.\./:/:g; #remove dir/..
}
my ($type, $return)=&exists($user, $domain, $newfilename);
$request->print($return);
if ($type eq 'error') {
$request->print('
'.&mt('Cancel').'');
return;
}
unless (&obsolete_unpub($user,$domain,$fn)) {
$request->print(''.&mt('Cannot rename or move non-obsolete published file').'
'.
'
'.&mt('Cancel').'');
return;
}
my $action;
if ($style eq 'rename') {
$action=&mt('Rename');
} else {
$action=&mt('Move');
}
$request->print('
to '.&display($newfilename).'?
'.&mt('No new filename specified.').'
'); return; } } else { $request->print(''.&mt('No such file').': '.&display($fn).'
'); return; } } =pod =item Delete1 Performs phase 1 processing of the delete operation. In phase one we just check to be sure the file exists. Parameters: =over 4 =item $request - Apache Request Object [in] request object for the current request. =item $user - string [in] Name of the user initiating the request. =item $domain - string [in] Domain the initiating user is logged in as =item $filename - string [in] Source filename. =back =cut sub Delete1 { my ($request, $user, $domain, $fn) = @_; if( -e $fn) { $request->print(''); if (-d $fn) { unless (&empty_directory($fn,'Delete1')) { $request->print(''.&mt('Delete').' '.&display($fn).'?
'); &CloseForm1($request, $fn); } else { $request->print(''.&mt('No such file').': '.&display($fn).'
'); } } =pod =item Copy1($request, $user, $domain, $filename, $newfilename) Performs phase 1 processing of the construction space copy command. Ensure that the source file exists. Ensure that a destination exists, also warn if the destination already exists. Parameters: =over 4 =item $request - Apache Request Object [in] request object for the current request. =item $user - string [in] Name of the user initiating the request. =item $domain - string [in] Domain the initiating user is logged in as =item $fn - string [in] Source filename. =item $newfilename-string [in] Destination filename. =back =cut sub Copy1 { my ($request, $user, $domain, $fn, $newfilename) = @_; if(-e $fn) { # is dest a dir if (-d $newfilename) { if ($fn =~ m|/([^/]*)$|) { $newfilename .= '/'.$1; } } if ($newfilename =~ m|/[^\.]+$|) { #no extension add on original extension if ($fn =~ m|/[^\.]*\.([^\.]+)$|) { $newfilename.='.'.$1; } } $newfilename=~s://+:/:g; # remove duplicate / while ($newfilename=~m:/\.\./:) { $newfilename=~ s:/[^/]+/\.\./:/:g; #remove dir/.. } $request->print(&checksuffix($fn,$newfilename)); my ($type,$return)=&exists($user, $domain, $newfilename); $request->print($return); if ($type eq 'error') { $request->print(''.&mt('Copy').' '.&display($fn).'
to '.
&display($newfilename).'?
'.&mt('No such file').': '.&display($fn).'
'); } } =pod =item NewDir1 Does all phase 1 processing of directory creation: Ensures that the user provides a new directory name, and that the directory does not already exist. Parameters: =over 4 =item $request - Apache Request Object [in] - Server request object for the current url. =item $username - Name of the user that is requesting the directory creation. =item $domain - Domain user is in =item $fn - source file. =item $newdir - Name of the directory to be created; path relative to the top level of construction space. =back Side Effects: =over 4 =item A new form is displayed. Clicking on the confirmation button causes the newdir operation to transition into phase 2. The hidden field "newfilename" is set with the construction space path to the new directory. =back =cut sub NewDir1 { my ($request, $username, $domain, $fn, $newfilename, $mode) = @_; my ($type, $result)=&exists($username,$domain,$newfilename,'directory'); $request->print($result); if ($type eq 'error') { $request->print(''); } else { if ($mode eq 'testbank') { $request->print(''); } elsif ($mode eq 'imsimport') { $request->print(''); } $request->print(''.&mt('Make new directory').' '. &display($newfilename).'?
'); &CloseForm1($request, $fn); } } sub Decompress1 { my ($request, $user, $domain, $fn) = @_; if( -e $fn) { $request->print(''); $request->print(''.&mt('Decompress').' '.&display($fn).'?
'); &CloseForm1($request, $fn); } else { $request->print(''.&mt('No such file').': '.&display($fn).'
'); } } =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, problem, page, or sequence, it then creates a form link to hand the actual creation off to the proper handler. Parameters: =over 4 =item $request - Apache Request Object [in] - Server request object for the current url. =item $username - Name of the user that is requesting the directory creation. =item $domain - Name of the domain of the user =item $fn - Source file name =item $newfilename - Name of the file to be created; no path information =back Side Effects: =over 4 =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 =back =cut sub NewFile1 { my ($request, $user, $domain, $fn, $newfilename) = @_; if ($env{'form.action'} =~ /new(.+)file/) { my $extension=$1; ##Informs User (name).(number).(extension) not allowed if($newfilename =~ /\.(\d+)\.(\w+)$/){ $r->print(''.$newfilename. ' - '.&mt('Bad Filename').''.&mt('Make new file').' '.&display($newfilename).'?
'); $request->print(''); $request->print(''); $request->print(''); } } =pod =item phaseone($r, $fn, $uname, $udom) Peforms phase one processing of the request. In phase one, error messages 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 performed and reported to the user. Parameters: =over 4 =item $r - request object [in] - The Apache request being executed. =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. =back =cut 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 $empty=&mt('Type Name Here'); if (($newfilename!~/\/$/) && ($newfilename!~/$empty$/)) { &NewFile1($r, $uname, $udom, $fn, $newfilename); } else { $r->print(''.&mt('No new filename specified.').'
'); } } } =pod =item Rename2($request, $user, $directory, $oldfile, $newfile) Performs phase 2 processing of a rename reequest. This is where the actual rename is performed. Parameters =over 4 =item $request - Apache request object [in] The request being processed. =item $user - string [in] The name of the user initiating the request. =item $directory - string [in] The name of the directory relative to the construction space top level of the renamed file. =item $oldfile - Name of the file. =item $newfile - Name of the new file. =back Returns: =over 4 =item 1 Success. =item 0 Failure. =cut sub Rename2 { my ($request, $user, $directory, $oldfile, $newfile) = @_; &Debug($request, "Rename2 directory: ".$directory." old file: ".$oldfile. " new file ".$newfile."\n"); &Debug($request, "Target is: ".$directory.'/'. $newfile); if (-e $oldfile) { my $oRN=$oldfile; my $nRN=$newfile; unless (rename($oldfile,$newfile)) { $request->print(''.&mt('Error').': '.$!.''); return 0; } ## If old name.(extension) exits, move under new name. ## 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'; if(-e $tmp1){ unless(rename($tmp1,$tmp2)){ } } elsif(-e $tmp2){ unlink $tmp2; } $tmp1=$oRN.'.save'; $tmp2=$nRN.'.save'; if(-e $tmp1){ unless(rename($tmp1,$tmp2)){ } } elsif(-e $tmp2){ unlink $tmp2; } $tmp1=$oRN.'.log'; $tmp2=$nRN.'.log'; if(-e $tmp1){ unless(rename($tmp1,$tmp2)){ } } elsif(-e $tmp2){ unlink $tmp2; } $tmp1=$oRN.'.bak'; $tmp2=$nRN.'.bak'; if(-e $tmp1){ unless(rename($tmp1,$tmp2)){ } } elsif(-e $tmp2){ unlink $tmp2; } } else { $request->print("".&mt('No such file').": ".&display($oldfile).'
'); return 0; } return 1; } =pod =item Delete2($request, $user, $filename) 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. Parameters: =over 4 =item $request - Apache Request object [in] the request object for the current delete operation. =item $user - string [in] The name of the user initiating the delete request. =item $filename - string [in] The name of the file, relative to construction space, to delete. =back Returns: 1 - success. 0 - Failure. =cut sub Delete2 { my ($request, $user, $filename) = @_; if (-d $filename) { unless (&empty_directory($filename,'Delete2')) { $request->print(''.&mt('Error: Directory Non Empty').''); return 0; } else { if(-e $filename) { unless(rmdir($filename)) { $request->print(''.&mt('Error').': '.$!.''); return 0; } } else { $request->print(''.&mt('No such file').'.
'); return 0; } } } else { if(-e $filename) { unless(unlink($filename)) { $request->print(''.&mt('Error').': '.$!.''); return 0; } } else { $request->print(''.&mt('No such file').'.
'); return 0; } } return 1; } =pod =item Copy2($request, $username, $dir, $oldfile, $newfile) Performs phase 2 of a copy. The file is copied and the status of that copy is reported back to the user. =over 4 =item $request - Apache request object [in]; the apache request currently being executed. =item $username - string [in] Name of the user who is requesting the copy. =item $dir - string [in] Directory path relative to the construction space of the destination file. =item $oldfile - string [in] Name of the source file. =item $newfile - string [in] Name of the destination file. =back Returns 0 failure, and 1 successs. =cut sub Copy2 { my ($request, $username, $dir, $oldfile, $newfile) = @_; &Debug($request ,"Will try to copy $oldfile to $newfile"); if(-e $oldfile) { if ($oldfile eq $newfile) { $request->print(''.&mt('Warning').': '.&mt('Name of new file is the same as name of old file').' - '.&mt('no action taken').'.'); return 1; } unless (copy($oldfile, $newfile)) { $request->print(''.&mt('copy Error').': '.$!.''); return 0; } elsif (!chmod(0660, $newfile)) { $request->print(''.&mt('chmod error').': '.$!.''); return 0; } elsif (-e $oldfile.'.meta' && !copy($oldfile.'.meta', $newfile.'.meta') && !chmod(0660, $newfile.'.meta')) { $request->print(''.&mt('copy metadata error'). ': '.$!.''); return 0; } else { return 1; } } else { $request->print(''.&mt('No such file').'
'); return 0; } return 1; } =pod =item NewDir2($request, $user, $newdirectory) Performs phase 2 processing of directory creation. This involves creating the directory and reporting the results of that creation to the user. Parameters: =over 4 =item $request - Apache request object [in]. Object representing the current HTTP request. =item $user - string [in] The name of the user that is initiating the request. =item $newdirectory - string [in] The full path of the directory being created. =back Returns 0 - failure 1 - success. =cut sub NewDir2 { my ($request, $user, $newdirectory) = @_; unless(mkdir($newdirectory, 02770)) { $request->print(''.&mt('Error').': '.$!.''); return 0; } unless(chmod(02770, ($newdirectory))) { $request->print(''.&mt('Error').': '.$!.''); return 0; } return 1; } sub decompress2 { my ($r, $user, $dir, $file) = @_; &Apache::lonnet::appenv({'cgi.file' => $file}); &Apache::lonnet::appenv({'cgi.dir' => $dir}); my $result=&Apache::lonnet::ssi_body('/cgi-bin/decompress.pl'); $r->print($result); &Apache::lonnet::delenv('cgi.file'); &Apache::lonnet::delenv('cgi.dir'); return 1; } =pod =item phasetwo($r, $fn, $uname, $udom) Controls the phase 2 processing of file management requests for construction space. In phase one, the user was asked to confirm the operation. In phase 2, the operation 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 phase 2 processing. Parameters: =over 4 =item $r - Apache Request object [in] The request object for this httpd transaction. =item $fn - string [in] A filename indicating the object that is being manipulated. =item $uname - string [in] The name of the user initiating the file management request. =item $udom - string [in] The login domain of the user initiating the file management request. =back =cut sub phasetwo { my ($r,$fn,$uname,$udom)=@_; &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. if ($fn=~m:(.*)/([^/]+):) { $dir=$1; # Directory path $main=$2; # Filename. } if($main=~m:\.(\w+)$:){ # Fixes problems with filenames with no extensions $suffix=$1; #This is the actually filename extension if it exists $main=~s/\.\w+$//; #strip the extension } my $dest; # my $dest_dir; # On success this is where we'll go. my $disp_newname; # my $dest_newname; # &Debug($r,"loncfile::phase2 dir = $dir main = $main suffix = $suffix"); &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') { $main .= '.'.$suffix; if(!&decompress2($r, $uname, $dir, $main)) { return ; } $dest = $dir."/."; } elsif ($env{'form.action'} eq 'rename' || $env{'form.action'} eq 'move') { if($env{'form.newfilename'}) { if (!defined($dir)) { $fn=~m:^(.*)/:; $dir=$1; } if(!&Rename2($r, $uname, $dir, $fn, $env{'form.newfilename'})) { return; } $dest = $dir."/"; $dest_newname = $env{'form.newfilename'}; $env{'form.newfilename'} =~ /.+(\/.+$)/; $disp_newname = $1; $disp_newname =~ s/\///; } } elsif ($env{'form.action'} eq 'delete') { if(!&Delete2($r, $uname, $env{'form.newfilename'})) { return ; } # Once a resource is deleted, we just list the directory that # previously held it. # $dest = $dir."/."; # Parent dir. } elsif ($env{'form.action'} eq 'copy') { if($env{'form.newfilename'}) { if(!&Copy2($r, $uname, $dir, $fn, $env{'form.newfilename'})) { return ; } $dest = $env{'form.newfilename'}; } else { $r->print(''.&mt('No New filename specified').'
'); return; } } elsif ($env{'form.action'} eq 'newdir') { my $newdir= $env{'form.newfilename'}; if(!&NewDir2($r, $uname, $newdir)) { return; } $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(''.&mt('Unknown Action').' '.$env{'form.action'}.'
'. &Apache::loncommon::end_page()); return OK; } if ($env{'form.phase'} eq 'two') { &Debug($r, "loncfile::handler entering phase2"); &phasetwo($r,$fn,$uname,$udom); } else { &Debug($r, "loncfile::handler entering phase1"); &phaseone($r,$fn,$uname,$udom); } $r->print(&Apache::loncommon::end_page()); return OK; } 1; __END__