Annotation of loncom/publisher/lonupload.pm, revision 1.29

1.12      foxr        1: 
1.1       www         2: # The LearningOnline Network with CAPA
                      3: # Handler to upload files into construction space
                      4: #
1.29    ! albertel    5: # $Id: lonupload.pm,v 1.28 2004/12/07 22:11:02 raeburn Exp $
1.8       matthew     6: #
                      7: # Copyright Michigan State University Board of Trustees
                      8: #
                      9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     10: #
                     11: # LON-CAPA is free software; you can redistribute it and/or modify
                     12: # it under the terms of the GNU General Public License as published by
                     13: # the Free Software Foundation; either version 2 of the License, or
                     14: # (at your option) any later version.
                     15: #
                     16: # LON-CAPA is distributed in the hope that it will be useful,
                     17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     19: # GNU General Public License for more details.
                     20: #
                     21: # You should have received a copy of the GNU General Public License
                     22: # along with LON-CAPA; if not, write to the Free Software
                     23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     24: #
                     25: # /home/httpd/html/adm/gpl.txt
                     26: #
                     27: # http://www.lon-capa.org/
                     28: #
1.10      harris41   29: ###
1.1       www        30: 
                     31: package Apache::lonupload;
                     32: 
                     33: use strict;
                     34: use Apache::File;
                     35: use File::Copy;
1.13      foxr       36: use File::Basename;
1.1       www        37: use Apache::Constants qw(:common :http :methods);
1.3       www        38: use Apache::loncacc;
1.10      harris41   39: use Apache::loncommon();
1.12      foxr       40: use Apache::Log();
1.13      foxr       41: use Apache::lonnet;
1.14      foxr       42: use HTML::Entities();
1.20      www        43: use Apache::lonlocal;
1.29    ! albertel   44: use Apache::lonnet;
1.12      foxr       45: 
                     46: my $DEBUG=0;
                     47: 
                     48: sub Debug {
                     49:   
1.22      albertel   50:     # Marshall the parameters.
1.12      foxr       51:   
1.22      albertel   52:     my $r       = shift;
                     53:     my $log     = $r->log;
                     54:     my $message = shift;
1.12      foxr       55:   
1.22      albertel   56:     # Put out the indicated message butonly if DEBUG is false.
1.12      foxr       57:   
1.22      albertel   58:     if ($DEBUG) {
                     59: 	$log->debug($message);
                     60:     }
1.12      foxr       61: }
1.1       www        62: 
1.2       www        63: sub upfile_store {
                     64:     my $r=shift;
                     65: 	
1.29    ! albertel   66:     my $fname=$env{'form.upfile.filename'};
1.2       www        67:     $fname=~s/\W//g;
                     68:     
1.29    ! albertel   69:     chomp($env{'form.upfile'});
1.1       www        70:   
1.29    ! albertel   71:     my $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
1.2       www        72: 		  '_upload_'.$fname.'_'.time.'_'.$$;
                     73:     {
                     74:        my $fh=Apache::File->new('>'.$r->dir_config('lonDaemons').
                     75:                                    '/tmp/'.$datatoken.'.tmp');
1.29    ! albertel   76:        print $fh $env{'form.upfile'};
1.1       www        77:     }
1.2       www        78:     return $datatoken;
                     79: }
                     80: 
                     81: 
                     82: sub phaseone {
1.25      raeburn    83:     my ($r,$fn,$uname,$udom,$mode)=@_;
                     84:     my $action = '/adm/upload';
                     85:     if ($mode eq 'testbank') {
                     86:         $action = '/adm/testbank';
                     87:     } elsif ($mode eq 'imsimport') {
                     88:         $action = '/adm/imsimport';
                     89:     }
1.29    ! albertel   90:     $env{'form.upfile.filename'}=~s/\\/\//g;
        !            91:     $env{'form.upfile.filename'}=~s/^.*\/([^\/]+)$/$1/;
        !            92:     if ($env{'form.upfile.filename'}) {
1.22      albertel   93: 	$fn=~s/\/[^\/]+$//;
                     94: 	$fn=~s/([^\/])$/$1\//;
1.29    ! albertel   95: 	$fn.=$env{'form.upfile.filename'};
1.22      albertel   96: 	$fn=~s/^\///;
                     97: 	$fn=~s/(\/)+/\//g;
1.13      foxr       98: 
                     99: #    Fn is the full path to the destination filename.
                    100: #    
                    101: 
1.22      albertel  102: 	&Debug($r, "Filename for upload: $fn");
                    103: 	if (($fn) && ($fn!~/\/$/)) {
1.28      raeburn   104: 	    $r->print('<form action="'.$action.'" method="post" name="fileupload">'.
1.23      albertel  105: 		      '<input type="hidden" name="phase" value="two" />'.
                    106: 		      '<input type="hidden" name="datatoken" value="'.
                    107: 		      &upfile_store.'" />'.
                    108: 		      '<input type="hidden" name="uploaduname" value="'.$uname.
                    109: 		      '" />'.&mt('Store uploaded file as ').
1.25      raeburn   110:                       "<tt>/priv/$uname/</tt>".
                    111:                       '<input type="text" size="50" name="filename" value="'.$fn.
                    112:                       '" /><br />');
                    113:             $r->print('<br />'.&mt('Please indicate the type of file you are uploading. The possible types of file are as follows:').'
                    114: <ul>
                    115:  <li><b>'.&mt('Regular file:').'</b>'.&mt(' A file that requires no special handling during upload. The "Regular file" designation applies to html files, image files etc., as well as to zip, tar or gzip files that you wish to decompress after upload. In the case of a zip/tar/gz file etc., once the file has been uploaded, a "Decompress" link will automatically be displayed adjacent to the name of the file in the display of construction space directory contents. You will be able to decompress this file by clicking the link.').'</li>     
                    116:  <li><b>'.&mt('Testbank file:').'</b>'.&mt(' a testbank file containing plain text (ascii) questions and answers, which you plan to convert to LON-CAPA problems. The following question types can be converted: 1 of N multiple choice questions, individual True/False questions, groups of True/False questions, Fill-in-the-blank questions, Ranking questions, and Essay/short answer questions. Specific information about the format of the questions, foils, and correct answers is available ').'<a href="javascript:testbankWin()">'.&mt('here').'</a>,'.&mt(' and is also included in the pages displayed during step-by-step conversion of the testbank. The original testbank file can be removed from your construction space later, once the testbank questions have been converted.').'</li>
                    117: <li><b>'.&mt('IMS package').':</b>'.&mt(' a file containing course content from another Course Management System (e.g., Blackboard or ANGEL) packaged according to the IMS 1.1 specification.  The original IMS package file can be removed from your construction space later, once the package has been decompressed and the files converted to LON-CAPA sequence, page, problem, or bulletin board files, or stored as html, image or movie files etc., as appropriate.').'</li>
                    118: </ul>
                    119: <br />'.&mt('Choose file type:').'
                    120: <select name="filetype">
                    121:  <option value="standard" selected>'.&mt('Regular file').'
                    122:  <option value="testbank">'.&mt('Testbank file').'
                    123:  <option value="imsimport">'.&mt('IMS package').'
                    124: </select>
                    125: <br />
                    126: <br />
                    127: ');
                    128:             $r->print('<input type="button" value="'.&mt('Store').'" onClick="javascript:verifyForm()"/></form>');
1.22      albertel  129: 	    # Check for bad extension and warn user
                    130: 	    if ($fn=~/\.(\w+)$/ && 
                    131: 		(&Apache::loncommon::fileembstyle($1) eq 'hdn')) {
1.23      albertel  132: 		$r->print('<font color="red">'.&mt('The extension on this file,').
1.22      albertel  133: 			  ' "'.$1.'"'.&mt(', is reserved internally by LON-CAPA.').
1.23      albertel  134: 			  ' <br />'.&mt('Please change the extension.').'</font>');
1.22      albertel  135: 	    } elsif($fn=~/\.(\w+)$/ && 
                    136: 		    !defined(&Apache::loncommon::fileembstyle($1))) {
1.23      albertel  137: 		$r->print('<font color="red">'.&mt('The extension on this file,').
1.22      albertel  138: 			  ' "'.$1.'"'.&mt(', is not recognized by LON-CAPA.').
1.23      albertel  139: 			  ' <br />'.&mt('Please change the extension.').
1.22      albertel  140: 			  '</font>');
                    141: 	    }
                    142: 	} else {
1.23      albertel  143: 	    $r->print('<font color="red">'.&mt('Illegal filename.').'</font>');
1.22      albertel  144: 	}
                    145:     } else {
1.23      albertel  146: 	$r->print('<font color="red">'.&mt('No upload file specified.').'</font>');
1.22      albertel  147:     }
1.1       www       148: }
                    149: 
                    150: sub phasetwo {
1.25      raeburn   151:     my ($r,$tfn,$uname,$udom,$mode)=@_;
                    152:     my $action = '/adm/upload';
                    153:     my $returnflag = '';
                    154:     if ($mode eq 'testbank') {
                    155:         $action = '/adm/testbank';
                    156:     } elsif ($mode eq 'imsimport') {
                    157:         $action = '/adm/imsimport';
                    158:     }
1.22      albertel  159:     my $fn='/priv/'.$uname.'/'.$tfn;
                    160:     $fn=~s/\/+/\//g;
                    161:     &Debug($r, "Filename is ".$tfn);
                    162:     if ($tfn) {
                    163: 	&Debug($r, "Filename for tfn = ".$tfn);
                    164: 	my $target='/home/'.$uname.'/public_html'.$tfn;
                    165: 	&Debug($r, "target -> ".$target);
1.13      foxr      166: #     target is the full filesystem path of the destination file.
1.22      albertel  167: 	my $base = &File::Basename::basename($fn);
                    168: 	my $path = &File::Basename::dirname($fn);
1.26      albertel  169: 	$base    = &HTML::Entities::encode($base,'<>&"');
1.22      albertel  170: 	my $url  = $path."/".$base; 
                    171: 	&Debug($r, "URL is now ".$url);
1.29    ! albertel  172: 	my $datatoken=$env{'form.datatoken'};
1.22      albertel  173: 	if (($fn) && ($datatoken)) {
1.29    ! albertel  174: 	    if ((-e $target) && ($env{'form.override'} ne 'Yes')) {
1.25      raeburn   175: 		$r->print('<form action="'.$action.'" method="post">'.
1.22      albertel  176: 			  &mt('File').' <tt>'.$fn.'</tt> '.
                    177: 			  &mt('exists. Overwrite?').' '.
1.23      albertel  178: 			  '<input type="hidden" name="phase" value="two" />'.
                    179: 			  '<input type="hidden" name="filename" value="'."$url".'" />'.
                    180: 			  '<input type="hidden" name="datatoken" value="'.$datatoken.'" />'.
                    181: 			  '<input type="submit" name="override" value="'.&mt('Yes').'" /></form>');
1.22      albertel  182: 	    } else {
                    183: 		my $source=$r->dir_config('lonDaemons').'/tmp/'.$datatoken.'.tmp';
1.27      www       184: 		my $dirpath=$path.'/';
                    185: 		$dirpath=~s/\/+/\//g;
1.22      albertel  186: 		# Check for bad extension and disallow upload
                    187: 		if ($fn=~/\.(\w+)$/ && 
                    188: 		    (&Apache::loncommon::fileembstyle($1) eq 'hdn')) {
                    189: 		    $r->print(&mt('File').' <tt>'.$fn.'</tt> '.
                    190: 			      &mt('could not be copied.').'<br />'.
1.23      albertel  191: 			      '<font color="red">'.
1.22      albertel  192: 			      &mt('The extension on this file is reserved internally by LON-CAPA.').
                    193: 			      '</font>');
1.27      www       194: 		    $r->print('<br /><font size=+2><a href="'.$dirpath.'">'.
1.22      albertel  195: 			      &mt('Back to Directory').'</a></font>');
                    196: 		} elsif ($fn=~/\.(\w+)$/ && 
                    197: 			 !defined(&Apache::loncommon::fileembstyle($1))) {
                    198: 		    $r->print(&mt('File').' <tt>'.$fn.'</tt> '.
                    199: 			      &mt('could not be copied.').'<br />'.
1.23      albertel  200: 			      '<font color="red">'.
1.22      albertel  201: 			      &mt('The extension on this file is not recognized by LON-CAPA.').
                    202: 			      '</font>');
1.27      www       203: 		    $r->print('<br /><font size="+2"><a href="'.$dirpath.'">'.
1.22      albertel  204: 			      &mt('Back to Directory').'</a></font>');
                    205: 		} elsif (-d $target) {
                    206: 		    $r->print('File <tt>'.$fn.'</tt> could not be copied.<br />'.
1.23      albertel  207: 			      '<font color="red">'.
1.22      albertel  208: 			      &mt('The target is an existing directory.').
1.23      albertel  209: 			      '</font><br />');
1.27      www       210: 		    $r->print('<font size="+2"><a href="'.$dirpath.'">'.
1.22      albertel  211: 			      &mt('Back to Directory').'</a></font>');
                    212: 		} elsif (copy($source,$target)) {
                    213: 		    chmod(0660, $target); # Set permissions to rw-rw---.
1.25      raeburn   214:                     if ($mode eq 'testbank' || $mode eq 'imsimport') {
                    215:                         $r->print(&mt("Your file - $fn - was uploaded successfully")."<br /><br />");
                    216:                         $returnflag = 'ok';
                    217:                     } else {
                    218:                         $r->print(&mt('File copied.'));
                    219: 		        $r->print('<br /><font size="+2"><a href="'.$url.'">'.
1.22      albertel  220: 			      &mt('View file').'</a></font>');
1.27      www       221: 		        $r->print('<br /><font size="+2"><a href="'.$dirpath.'">'.
1.23      albertel  222: 			      &mt('Back to Directory').'</a></font><br />');
1.25      raeburn   223:                     }
1.22      albertel  224: 		} else {
                    225: 		    $r->print('Failed to copy: '.$!);
1.23      albertel  226: 		    $r->print('<br /><font size="+2"><a href="'.$path.'">'.
1.22      albertel  227: 			      &mt('Back to Directory').'</a></font>');
                    228: 		}
                    229: 	    }
                    230: 	} else {
1.23      albertel  231: 	    $r->print('<font size="+1" color="red">'.
1.22      albertel  232: 		      &mt('Please use browser "Back" button and pick a filename').
1.24      albertel  233: 		      '</font><br />');
1.22      albertel  234: 	}
1.1       www       235:     } else {
1.22      albertel  236: 	$r->print('<font size=+1 color=red>'.
                    237: 		  &mt('Please use browser "Back" button and pick a filename').
1.24      albertel  238: 		  '</font><br />>');
1.1       www       239:     }
1.25      raeburn   240:     return $returnflag;
1.1       www       241: }
                    242: 
1.10      harris41  243: # ---------------------------------------------------------------- Main Handler
1.1       www       244: sub handler {
                    245: 
1.22      albertel  246:     my $r=shift;
1.1       www       247: 
1.22      albertel  248:     my $uname;
                    249:     my $udom;
1.25      raeburn   250:     my $javascript = '';
1.18      www       251: #
                    252: # phase two: re-attach user
                    253: #
1.29    ! albertel  254:     if ($env{'form.uploaduname'}) {
        !           255: 	$env{'form.filename'}='/priv/'.$env{'form.uploaduname'}.'/'.
        !           256: 	    $env{'form.filename'};
1.22      albertel  257:     }
                    258: 
1.29    ! albertel  259:     unless ($env{'form.phase'} eq 'two') {
1.25      raeburn   260:         $javascript = qq|
                    261: function verifyForm() {
1.28      raeburn   262:     var mode = document.fileupload.filetype.options[document.fileupload.filetype.selectedIndex].value
1.25      raeburn   263:     if (mode == "testbank") {
1.28      raeburn   264:         document.fileupload.action = "/adm/testbank";
1.25      raeburn   265:     }
                    266:     if (mode == "imsimport") {
1.28      raeburn   267:         document.fileupload.action = "/adm/imsimport";
1.25      raeburn   268:     }
                    269:     if (mode == "standard") {
1.28      raeburn   270:         document.fileupload.action = "/adm/upload";
1.25      raeburn   271:     }
1.28      raeburn   272:     document.fileupload.submit();
1.25      raeburn   273: }
                    274: 
                    275: function testbankWin() {
                    276:   newWindow = window.open("","testbankinfo","HEIGHT=400,WIDTH=750,scrollbars=yes")
                    277:   newWindow.document.open()
                    278:   newWindow.document.write("<html><head><title>'Importing a Testbank file into LON-CAPA</title><meta http-equiv='pragma' content='no-cache'>\\n")
                    279:   newWindow.document.write("</head><body bgcolor='#CCFFDD' topmargin='0' leftmargin='0' marginheight='0'marginwidth='0' rightmargin='0'>\\n")
                    280:   newWindow.document.write("<img border='0' src='/adm/lonInterFace/author.jpg' alt='[Author Header]'>\\n")
                    281:   newWindow.document.write("<table border='0' cellspacing='0' cellpadding='0' width='95%' bgcolor='#CCFFDD'>\\n")
                    282:   newWindow.document.write("<tr><td width='2'>&nbsp;</td><td width='3'>&nbsp;</td>\\n")
                    283:   newWindow.document.write("<td><font face='arial,helvetica,sans-serif'><h3>Importing Testbank questions into LON-CAPA</h3>")
                    284:   newWindow.document.write("<font face='arial,helvetica,sans-serif'><br />Four requirements must be met to ensure that you will succeed in building LON-CAPA problem files using your plain text file containing testbank questions.")
                    285:   newWindow.document.write("<ol><li>The questions and answers you upload must be in plain text format.  Any header lines should occur before the text containing the questions and answers.</li>")
                    286:   newWindow.document.write("<li>All questions must occur before any of the answers.  Each question should be numbered sequentially using a number followed immediately by a space, a period, or enclosed in parentheses, i.e., 1 , 1., (1), 1), or (1 .</li>")
                    287:    newWindow.document.write("<li>One or more correct answers should be provided for all questions (although blank answers may be provided for <i>essay</i> questions).  Answers should be numbered sequentially, using the same scheme as used for the questions, and must occur after all the questions.")
                    288:     newWindow.document.write("<li><i>Multiple choice</i> and <i>multiple answer correct</i> questions should consist of (i) the question number followed by (ii) a question stem beginning on the same line and (iii) two or more foils, with each foil beginning on a new line and prefixed by a unique letter, or Roman numeral, listed in alphabetic or numeric order, beginning at a (alphabetic) or i (Roman numeral), followed by a period, or enclosed in parentheses, i.e., a., (a), i., or (i) .</li>")
                    289:      newWindow.document.write("<li>If <i>fill-in-the-blank</i> or <i>multiple answer</i> questions have more than one correct answer, each answer should appear in a comma-, tab-, space-, or new line-delimited list. </li></ol>")
                    290:   newWindow.document.write("</td></tr>\\n")
                    291:   newWindow.document.write("</table></body></html>")
                    292:   newWindow.document.close()
                    293:   newWindow.focus()
                    294: }
                    295: |;
                    296:     }
1.22      albertel  297:     ($uname,$udom)=
1.29    ! albertel  298: 	&Apache::loncacc::constructaccess($env{'form.filename'},
1.22      albertel  299: 					  $r->dir_config('lonDefDomain'));
                    300:     unless (($uname) && ($udom)) {
                    301: 	$r->log_reason($uname.' at '.$udom.
1.29    ! albertel  302: 		       ' trying to publish file '.$env{'form.filename'}.
1.22      albertel  303: 		       ' - not authorized', 
                    304: 		       $r->filename); 
                    305: 	return HTTP_NOT_ACCEPTABLE;
                    306:     }
                    307:     
                    308:     my $fn;
1.29    ! albertel  309:     if ($env{'form.filename'}) {
        !           310: 	$fn=$env{'form.filename'};
1.22      albertel  311: 	$fn=~s/^http\:\/\/[^\/]+\///;
                    312: 	$fn=~s/^\///;
                    313: 	$fn=~s/(\~|priv\/)(\w+)//;
                    314: 	$fn=~s/\/+/\//g;
                    315:     } else {
1.29    ! albertel  316: 	$r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}.
1.22      albertel  317: 		       ' unspecified filename for upload', $r->filename); 
                    318: 	return HTTP_NOT_FOUND;
                    319:     }
1.1       www       320: 
                    321: # ----------------------------------------------------------- Start page output
                    322: 
                    323: 
1.22      albertel  324:     &Apache::loncommon::content_type($r,'text/html');
                    325:     $r->send_http_header;
1.1       www       326: 
1.25      raeburn   327:     $r->print("<html><head><title>LON-CAPA Construction Space</title><script type=\"text/javascript\">\n//<!--\n$javascript\n// --></script>\n</head>");
1.1       www       328: 
1.22      albertel  329:     $r->print(&Apache::loncommon::bodytag('Upload file to Construction Space'));
1.3       www       330:   
1.29    ! albertel  331:     if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
1.22      albertel  332: 	$r->print('<h3><font color=red>'.&mt('Co-Author').': '.$uname.
                    333: 		  &mt(' at ').$udom.'</font></h3>');
                    334:     }
                    335: 
1.29    ! albertel  336:     if ($env{'form.phase'} eq 'two') {
1.22      albertel  337: 	&phasetwo($r,$fn,$uname,$udom);
                    338:     } else {
                    339: 	&phaseone($r,$fn,$uname,$udom);
                    340:     }
1.1       www       341: 
1.22      albertel  342:     $r->print('</body></html>');
                    343:     return OK;  
1.1       www       344: }
1.7       www       345: 
                    346: 1;
                    347: __END__
1.10      harris41  348: 
                    349: =head1 NAME
                    350: 
                    351: Apache::lonupload - upload files into construction space
                    352: 
                    353: =head1 SYNOPSIS
                    354: 
                    355: Invoked by /etc/httpd/conf/srm.conf:
                    356: 
                    357:  <Location /adm/upload>
                    358:  PerlAccessHandler       Apache::lonacc
                    359:  SetHandler perl-script
                    360:  PerlHandler Apache::lonupload
                    361:  ErrorDocument     403 /adm/login
                    362:  ErrorDocument     404 /adm/notfound.html
                    363:  ErrorDocument     406 /adm/unauthorized.html
                    364:  ErrorDocument	  500 /adm/errorhandler
                    365:  </Location>
                    366: 
                    367: =head1 INTRODUCTION
                    368: 
                    369: This module uploads a file sitting on a client computer into 
                    370: library server construction space.
                    371: 
                    372: This is part of the LearningOnline Network with CAPA project
                    373: described at http://www.lon-capa.org.
                    374: 
                    375: =head1 HANDLER SUBROUTINE
                    376: 
                    377: This routine is called by Apache and mod_perl.
                    378: 
                    379: =over 4
                    380: 
                    381: =item *
                    382: 
                    383: Initialize variables
                    384: 
                    385: =item *
                    386: 
                    387: Start page output
                    388: 
                    389: =item *
                    390: 
                    391: output relevant interface phase (phaseone or phasetwo)
                    392: 
                    393: =item *
                    394: 
                    395: (phase one is to specify upload file; phase two is to handle conditions
                    396: subsequent to specification--like overwriting an existing file)
                    397: 
                    398: =back
                    399: 
                    400: =head1 OTHER SUBROUTINES
                    401: 
                    402: =over 4
                    403: 
                    404: =item *
                    405: 
                    406: phaseone() : Interface for specifying file to upload.
                    407: 
                    408: =item *
                    409: 
                    410: phasetwo() : Interface for handling post-conditions about uploading (such
                    411: as overwriting an existing file).
                    412: 
                    413: =item *
                    414: 
                    415: upfile_store() : Store contents of uploaded file into temporary space.  Invoked
                    416: by phaseone subroutine.
                    417: 
                    418: =back
                    419: 
                    420: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>