File:  [LON-CAPA] / loncom / imspackages / imsimportdocs.pm
Revision 1.31: download - view: text, annotated - select for diffs
Sat Jul 27 22:04:49 2013 UTC (10 years, 9 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_11_0_RC1, HEAD
- Support import of Blackboard Vista question banks (IMS format) containing
  questions with images.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: imsimportdocs.pm,v 1.31 2013/07/27 22:04:49 raeburn Exp $
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: #
    9: # LON-CAPA is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2 of the License, or
   12: # (at your option) any later version.
   13: #
   14: # LON-CAPA is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with LON-CAPA; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22: #
   23: # /home/httpd/html/adm/gpl.txt
   24: #
   25: # http://www.lon-capa.org/
   26: #
   27: 
   28: package Apache::imsimportdocs;
   29: 
   30: use Apache::Constants qw(:common :http :methods);
   31: use Apache::lonnet;
   32: use Apache::londocs;
   33: use Apache::loncommon;
   34: use Apache::lonlocal;
   35: use Apache::imsprocessor;
   36: use LONCAPA::map();
   37: use lib '/home/httpd/lib/perl/';
   38: use LONCAPA;
   39:  
   40: use strict;
   41: 
   42: sub jscript_one {
   43:     my %lt = &Apache::lonlocal::texthash(
   44:                se => 'Select',
   45:                to => 'Import topics only',
   46:                tp => 'Import topics + posts (with author)',
   47:                tn => 'Import topics + posts (no author)',
   48:                es => 'Enroll students only',
   49:                ea => 'Enroll all users',
   50:                nr => 'Not required',
   51:                id => 'You must select one of the additional options when importing Disussion Boards',
   52:                ie => 'You must select one of the additional options when importing Enrollment',
   53:                ct => 'You must check at least one Content Type', 
   54:     );
   55:     return <<"ENDJS";
   56: function setOptions(caller,itemnum) {
   57:   var opForm = document.forms.pickoptions
   58:   var menu = 1 + itemnum*2
   59:   opForm.elements[menu].length = 0
   60:   if (opForm.elements[itemnum*2].checked == true) {
   61:     if (caller == "board") {
   62:       opForm.elements[menu].options[0] = new Option("$lt{'se'}","-1",true,true)
   63:       opForm.elements[menu].options[1] = new Option("$lt{'to'}","topics",true,true)
   64:       opForm.elements[menu].options[2] = new Option("$lt{'tp'}","allpost",true,true)
   65:       opForm.elements[menu].options[3] = new Option("$lt{'tn'}","allanon",true,true)
   66:     }
   67:     else { 
   68:       if (caller == "users") {
   69:         opForm.elements[menu].length = 0
   70:         opForm.elements[menu].options[0] = new Option("$lt{'se'}","-1",true,true)
   71:         opForm.elements[menu].options[1] = new Option("$lt{'es'}","students",true,true)
   72:         opForm.elements[menu].options[2] = new Option("$lt{'ea'}","all",true,true)
   73:       }
   74:     }
   75:   }
   76:   else {
   77:     opForm.elements[menu].options[0] = new Option("$lt{'nr'}","0",true,true)
   78:   }
   79:   opForm.elements[menu].selectedIndex = 0
   80: }
   81: 
   82: function verify(caller) { 
   83:   var opForm = document.forms.pickoptions
   84:   var totcheck = 0;
   85:   for (var i=0; i<caller; i++) {
   86:     if (opForm.elements[2*i].checked == true) {
   87:       totcheck ++
   88:       if (opForm.elements[2*i].name == "board") { 
   89:         if (opForm.elements[2*i+1].selectedIndex == 0) {     
   90:           alert("$lt{'id'}")
   91:           return false
   92:         }
   93:       }
   94:       if (opForm.elements[2*i].name == "users") {
   95:         if (opForm.elements[2*i+1].selectedIndex == 0) {     
   96:           alert("$lt{'ie'}")
   97:           return false
   98:         }
   99:       }
  100:     }
  101:   }
  102:   if (totcheck == 0) {
  103:     alert("$lt{'ct'}");
  104:     return false
  105:   }
  106:   return true
  107: }
  108: 
  109: function nextPage(caller) {
  110:  if (verify(caller)) {
  111:    document.forms.pickoptions.submit()
  112:  }
  113: }
  114: 
  115: ENDJS
  116: 
  117: }
  118: 
  119: sub jscript_two {
  120:     return <<"ENDJS";
  121: function init(tf) {
  122:     setTimeout("self.close()",3000)
  123:     tf.submit();   
  124: }
  125:  
  126: ENDJS
  127: }
  128: 
  129: sub handler {
  130:     my $r = shift;
  131:     &Apache::loncommon::content_type($r,'text/html');
  132:     $r->send_http_header;
  133:     return OK if $r->header_only;
  134: 
  135:     my @areas = ();
  136:     my %cmsmap = ();
  137:     my %areaname = ();
  138:     &Apache::imsprocessor::ims_config(\@areas,\%cmsmap,\%areaname);
  139: 
  140: # get course data
  141:     my $coursenum=$env{'course.'.$env{'request.course.id'}.'.num'};
  142:     my $coursedom=$env{'course.'.$env{'request.course.id'}.'.domain'};
  143: 
  144: # get personal data
  145:  
  146:     my $uname=$env{'user.name'};
  147:     my $udom=$env{'user.domain'};
  148:     my $plainname=&escape(
  149:                      &Apache::loncommon::plainname($uname,$udom));
  150: 
  151: # does this user have privileges to post, etc?
  152:     my $allowed=&Apache::lonnet::allowed('mdc',$env{'request.course.id'});
  153:     unless ($allowed) {
  154:         $r->print(&Apache::loncommon::start_page('Import IMS package',undef,
  155:                                                  {'only_body' => 1,}));  
  156:         $r->print(
  157:             '<p class="LC_error">'
  158:            .&mt('Modification of Course Contents Disallowed')
  159:            .'</p><p>'
  160:            .&mt('Your current role does not grant you the right to modify course content in this course.')
  161:            .'</p>'
  162:            .&Apache::loncommon::end_page()
  163:         );
  164:         return OK;
  165:     }
  166: 
  167:     my $javascript;
  168:     if ($env{'form.phase'} eq 'one') {
  169:         $javascript = &jscript_one();
  170:     } elsif ($env{'form.phase'} eq 'two') {
  171:         $javascript = &jscript_two();
  172:     }
  173: 
  174:     $javascript = 
  175:         "<script type=\"text/javascript\">\n".
  176:         "//<!--\n$javascript\n// --></script>\n";
  177:     my $headline = 'Import IMS package';
  178:     my $start_page = &Apache::loncommon::start_page($headline,
  179:                                                     $javascript,
  180:                                                     {'only_body' => 1,})
  181:                     .'<h1>'.&mt($headline).'</h1>';
  182: # print screen
  183:     $r->print($start_page);
  184: 
  185:     if ($env{'form.phase'} eq 'one') {
  186:         &display_one($r,$coursenum,\@areas,\%areaname,%cmsmap);
  187:     } elsif ($env{'form.phase'} eq 'two') {
  188:         &display_two($r,$coursenum,$coursedom,$uname,$udom,\@areas,%cmsmap);
  189:     }   
  190:     $r->print(&Apache::loncommon::end_page());
  191:     return OK;
  192: }
  193: 
  194: sub display_one {
  195:     my ($r,$crs,$areasref,$areaname,%cmsmap) = @_;
  196:     my $cms = $env{'form.source'};
  197:     my $timenow = time;
  198:     my $tempdir = &Apache::imsprocessor::create_tempdir('DOCS',$crs,$timenow);
  199:     my $fname = &Apache::imsprocessor::uploadzip('DOCS',$tempdir);
  200:     my $unzip_result = '';
  201:     my $manifest_result = '';
  202:     unless ($tempdir eq '') {
  203:         $unzip_result = &Apache::imsprocessor::expand_zip($tempdir,$fname);
  204:     }
  205:     my %resources = ();
  206:     my %includedres = ();
  207:     my %includeditems = ();
  208:     my %items = ();
  209:     my %hrefs = ();
  210:     my %resinfo = ();
  211:     my %count = ();
  212:     my $counter = 0;
  213:     my %count = (
  214:                 announce => 0,
  215:                 board => 0,
  216:                 doc => 0,
  217:                 extlink => 0,
  218:                 msg => 0,
  219:                 pool => 0,
  220:                 quiz => 0,
  221:                 staff => 0,
  222:                 survey => 0,
  223:                 users => 0,
  224:                 );
  225: 
  226:     if ($unzip_result ne 'ok') {
  227:         $r->print(
  228:             '<p class="LC_warning">'
  229:            .&mt('Processing of your IMS package failed because the file you'
  230:                .' uploaded could not be unzipped.')
  231:            .'</p>'
  232:         );
  233:         return();
  234:     }
  235: 
  236:     # Get manifest file from package
  237:     $manifest_result = &Apache::imsprocessor::process_manifest(
  238:                            $cms,$tempdir,\%resources,\%items,\%hrefs,
  239:                            \%resinfo,'choose',\%includedres,\%includeditems);
  240:     if ($manifest_result ne 'ok') {
  241:         $r->print(
  242:            '<p class="LC_warning">'
  243:           .&mt('Unpacking of your IMS package failed because an IMS manifest file was not located in the package.')
  244:           .'</p>'
  245:         );
  246:         return();
  247:     }
  248: 
  249:     # Count areas depending on cms version
  250:     foreach my $res (sort keys %resources) {
  251:         if ($cms eq 'bb5' || $cms eq 'bb6' || $cms eq 'webctce4') {
  252:             foreach my $area (keys %{$cmsmap{$cms}}) {
  253:                 if ($resources{$res}{type} eq $cmsmap{$cms}{$area}) {
  254:                     $count{$area} ++;
  255:                 }
  256:             }
  257:         } elsif ($cms eq 'angel5') {
  258:             foreach my $area (keys %{$cmsmap{$cms}}) {
  259:                 if ($area eq 'doc') {
  260:                     if (grep/^$resources{$res}{type}$/,@{$cmsmap{$cms}{doc}}) {
  261:                         $count{$area} ++;
  262:                     }
  263:                 } elsif ($resources{$res}{type} eq $cmsmap{$cms}{$area}) {
  264:                     $count{$area} ++;
  265:                 }
  266:             }
  267:         } else { # Unknown cms format
  268:             $r->print(
  269:                 '<span class="LC_warning">'
  270:                .&mt('Unsupported IMS format: [_1]',$cms)
  271:                .'</span><br />'
  272:             );
  273:             # return();
  274:         }
  275:     }
  276: 
  277: 
  278:     # Start output: Step 1 and step 2
  279: 
  280:     $r->print(
  281:         '<form name="pickoptions" method="post" action="">'
  282:        .&Apache::lonhtmlcommon::topic_bar(
  283:             1,&mt('Choose which content types you wish to import'))
  284:        .'<p>'
  285:        .&mt('Check the checkboxes for all areas you wish to import from the IMS package:')
  286:        .'</p>'
  287:     );
  288: 
  289:     $r->print(
  290:         &Apache::loncommon::start_data_table()
  291:        .&Apache::loncommon::start_data_table_header_row()
  292:        .'<th>'.&mt('Import?').'</th>'
  293:        .'<th>'.&mt('Content type').'</th>'
  294:        .'<th>'.&mt('Additional options').'</th>'
  295:        .&Apache::loncommon::end_data_table_header_row()
  296:     );
  297: 
  298:     # Display import row for each area/content type
  299:     foreach my $area (@{$areasref}) {
  300:         unless ($count{$area} > 0) { next };
  301: 
  302:         my $count_tag = 'flag_'.$counter;
  303: 
  304:         # Checkbox: Import?
  305:         $r->print(
  306:             &Apache::loncommon::start_data_table_row()
  307:            .'<td><input name="'.$area.'" type="checkbox"'
  308:         );
  309:         if ($area eq 'board' || $area eq 'users') {
  310:             $r->print(qq| onclick='javascript:setOptions("$area","$counter")'|);
  311:         }
  312: 
  313:         $r->print(' /></td>');
  314: 
  315:         # Content Type
  316:         $r->print(
  317:             '<td>'
  318:            .$$areaname{$area}.'&nbsp;&nbsp; - '
  319:            .&mt('[quant,_1,item]',$count{$area})
  320:            .'</td>'
  321:         );
  322: 
  323:         # Additional Options
  324:         $r->print('<td>');
  325:         if ($area eq 'board') {
  326:             $r->print(
  327:                 '<select name="db_handling">'
  328:                .'<option value="-2">&lt;-- '.&mt('Check Import first').'</option>'
  329:                .'</select>'
  330:             );
  331:         } elsif ($area eq 'users') {
  332:             $r->print(
  333:                 '<select name="user_handling">'
  334:                .'<option value="-2">&lt;-- '.&mt('Check Import first').'</option>'
  335:                .'</select>'
  336:             );
  337:         } else {
  338:             $r->print(
  339:                 &mt('None')
  340:                .'<input type="hidden" name="'.$count_tag.'" />'
  341:             );
  342:         }
  343:         $r->print('</td>');
  344: 
  345:         $r->print(&Apache::loncommon::end_data_table_row());
  346:         $counter ++;
  347:     }
  348: 
  349:     $r->print(&Apache::loncommon::end_data_table());
  350: 
  351:     $r->print(
  352:         &Apache::lonhtmlcommon::topic_bar(
  353:             2,&mt('Choose display options for listing of contents of top level of package'))
  354:        .'<p>'
  355:        .&mt('Select a display option for the package content:')
  356:        .'</p>'
  357:     );
  358:     $r->print(
  359:         '<label>'
  360:        .'<input type="radio" name="toplevel" value="newfolder" />'
  361:        .&mt('Display listing of contents in a new folder, with folder name:')
  362:        .'</label>'
  363:        .' <input type="text" name="foldername" size="15" value="'.&mt('Type Name Here').'" />'
  364:        .'<br />'
  365:        .'<label>'
  366:        .'<input type="radio" name="toplevel" value="oldfolder" checked="checked" />'
  367:        .&mt('Append listing of contents of top level of package to contents list for the current folder.')
  368:        .'</label>'
  369:     );
  370: 
  371:     # Buttons
  372:     $r->print(
  373:         '<input type="hidden" name="folder" value="'.$env{'form.folder'}.'" />'
  374:        .'<input type="hidden" name="source" value="'.$cms.'" />'
  375:        .'<input type="hidden" name="tempdir" value="'.$tempdir.'" />'
  376:        .'<input type="hidden" name="phase" value="two" />'
  377:     );
  378:     $r->print(
  379:         '<hr />'
  380:        .'<p>'
  381:        .'<input type="button" name="exitpage" value="'.&mt('Cancel').'"'
  382:        .' onclick="javascript:self.close()" />'
  383:        .' '
  384:        .'<input type="button" name="nextpage" value="'.&mt('Finish Import').'"'
  385:        .' onclick="javascript:nextPage('.$counter.')" />'
  386:        .'</p>'
  387:     );
  388: 
  389:    $r->print('</form>');
  390: }
  391: 
  392: sub display_two {
  393:     my ($r,$crs,$cdom,$uname,$udom,$areas,%cmsmap) = @_;
  394:     my $folder = $env{'form.folder'};
  395:     my $cms = $env{'form.source'};
  396:     my $tempdir = $env{'form.tempdir'};
  397:     my $longcrs = '';
  398:     if ($crs =~ m/^(\d)(\d)(\d)/) {
  399:         $longcrs = $1.'/'.$2.'/'.$3.'/'.$crs;
  400:     }
  401:     my %importareas = ();
  402:     my %includedres = ();
  403:     my %includeditems = ();
  404:     my @targets = ();
  405:     my %resources = ();
  406:     my %items = ();
  407:     my %hrefs = ();
  408:     my %urls = ();
  409:     my %resinfo = ();
  410:     my %total = (
  411:                  page => 0,
  412:                  prob => 0,
  413:                  seq => 0,
  414:                  board => 0,         
  415:                  quiz => 0,
  416:                  surv => 0,
  417:                 );
  418:     my @pages = ();
  419:     my @sequences = ();
  420:     my @resrcfiles = ();
  421: 
  422:     my $timenow = time;
  423: 
  424:     my $destdir = $Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'.$cdom.'/'.$crs.'/'.$timenow;
  425:     my $seqstem = "/uploaded/$cdom/$crs/$timenow";
  426:     my $db_handling = '';
  427:     my $user_handling = '';
  428: 
  429:     my $toplevel = '';
  430:     my $foldername = '';
  431:     my %topitems = ();
  432:     if (defined($env{'form.toplevel'}) ) {
  433:         $toplevel = $env{'form.toplevel'};     
  434:     }
  435:     if (defined($env{'form.foldername'}) ) {
  436:         $foldername = $env{'form.foldername'}; 
  437:     }
  438: 
  439:     foreach my $area (@{$areas}) {
  440:         if (defined($env{"form.$area"}) && ($env{'form.'.$area} ne '')) {
  441:             if ($cms eq 'angel5' && $area eq 'doc') {
  442:                 foreach (@{$cmsmap{$cms}{$area}}) {
  443:                     $importareas{$_} = 1;
  444:                 }
  445:             } else {
  446:                 $importareas{$cmsmap{$cms}{$area}} = 1;
  447:             }
  448:             if ($area eq 'board') {
  449:                 $db_handling = $env{'form.db_handling'};
  450:             } elsif ($area eq 'users') {
  451:                 $user_handling = $env{'form.user_handling'};
  452:             }
  453:         }
  454:     }
  455: 
  456:     my $manifest_result = &Apache::imsprocessor::process_manifest($cms,$tempdir,\%resources,\%items,\%hrefs,\%resinfo,'prepare',\%includedres,\%includeditems);
  457:     if ($manifest_result eq 'ok') {
  458:         foreach my $res (sort keys %resources) {
  459:             if ($importareas{$resources{$res}{type}}) {
  460:                 $includedres{$res} = 1;
  461:             }
  462:         }
  463:         foreach my $itm (sort keys %items) {
  464:             &Apache::imsprocessor::get_imports(\%includeditems,\%items,\%resources,\%importareas,$itm);
  465:         }
  466:     }
  467:     foreach my $itm (sort keys %includeditems) {
  468:         &Apache::imsprocessor::get_parents(\%includeditems,\%items,$itm);
  469:     }
  470: 
  471:     $manifest_result = &Apache::imsprocessor::process_manifest($cms,$tempdir,\%resources,\%items,\%hrefs,\%resinfo,'build',\%includedres,\%includeditems);
  472:     if ($manifest_result eq 'ok') {
  473: 
  474:         my @path = ($cdom,$crs,$timenow);
  475:         my $fullpath = $Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles';
  476:         foreach my $item (@path) {
  477:             $fullpath .= '/'.$item;
  478:             if (!-e "$fullpath") {
  479:                 mkdir("$fullpath",0770);
  480:             }
  481:         }
  482:         my @namedirs = ("resfiles","sequences","pages","problems");
  483:         foreach my $name (@namedirs) {
  484:             if (!-e "$fullpath/$name") {
  485:                 mkdir("$fullpath/$name",0770);
  486:             }
  487:         }
  488:         &Apache::imsprocessor::target_resources(\%resources,\%importareas,\@targets);
  489: 
  490:         my @boards = ();
  491:         my @announcements = ();
  492:         my @quizzes = ();
  493:         my @surveys = ();
  494:         my @pools = ();
  495:         my @groups = ();
  496:         my %messages = ();
  497:         my @timestamp = ();
  498:         my %boardnum = ();
  499:         my @topurls = ();
  500:         my @topnames = ();
  501:         my @packages = ();
  502: 
  503:         &Apache::imsprocessor::process_resinfo($cms,'DOCS',$tempdir,$destdir,\%items,\%resources,\@targets,\@boards,\@announcements,\@quizzes,\@surveys,\@pools,\@groups,\%messages,\@timestamp,\%boardnum,\%resinfo,$udom,$uname,$cdom,$crs,$db_handling,$user_handling,\%total,$seqstem,$seqstem,\@resrcfiles,\@packages,\%hrefs,\@pages,\@sequences);
  504: 
  505:         my $copy_result = &Apache::imsprocessor::copy_resources('DOCS',$cms,\%hrefs,\%resources,$tempdir,\@targets,\%urls,$crs,$cdom,$destdir,$timenow,\%importareas);
  506: 
  507:         &Apache::imsprocessor::build_structure($cms,'DOCS',$destdir,\%items,\%resinfo,\%resources,\@targets,\%hrefs,$udom,$uname,'',$timenow,$cdom,$crs,\@timestamp,\%total,\@boards,\@announcements,\@quizzes,\@surveys,\@pools,\%boardnum,\@pages,\@sequences,\@topurls,\@topnames,\@packages,\%includeditems);
  508: 
  509:         foreach my $item (@pages) {
  510:             my $filename = $timenow.'/pages/'.$item;
  511:             my $fetchresult= &Apache::lonnet::process_coursefile('propagate',$crs,$cdom,$filename,'');
  512:         }
  513:         foreach my $item (@sequences) {
  514:             unless ($item eq 'Top.sequence' && $toplevel eq 'oldfolder') {
  515:                 my $filename = $timenow.'/sequences/'.$item;
  516:                 my $fetchresult= &Apache::lonnet::process_coursefile('propagate',$crs,$cdom,$filename,'');
  517:             }
  518:         }
  519:         foreach my $item (@resrcfiles) {
  520:             my $filename = $timenow.'/resfiles/'.$item;
  521:             my $fetchresult= &Apache::lonnet::process_coursefile('propagate',$crs,$cdom,$filename,'');
  522:         }
  523: 
  524:         my @imports = ();
  525:         if ($toplevel eq 'oldfolder') {
  526:             for (my $i=0; $i<@topurls; $i++) {
  527:                 my $url = &unescape($topurls[$i]);
  528:                 my $name = &unescape($topnames[$i]);
  529:                 push(@imports, [$name, $url]);
  530:             }
  531:         } elsif ($toplevel eq 'newfolder') {
  532:             my $url = &unescape("/uploaded/$cdom/$crs/$timenow/sequences/Top.sequence");
  533:             my $name = &unescape("$env{'form.foldername'}");
  534:             push(@imports, [$name, $url]);
  535:         }
  536:         my $errtext='';
  537:         my $fatal=0;
  538:         ($errtext,$fatal)=  &Apache::londocs::mapread($crs,$cdom,$folder.'.sequence');
  539:         if ($#LONCAPA::map::order<1) {
  540:             $LONCAPA::map::order[0]=1;
  541:             $LONCAPA::map::resources[1]='';
  542:         }
  543:         my ($errtext,$fatal)=&Apache::londocs::group_import($crs,$cdom,$folder,'sequence','imsimport',@imports);
  544:         if ($fatal) {
  545:             &Apache::lonnet::logthis("Fatal error during group_import.");
  546:         }
  547:     }
  548:     if ($tempdir =~ m/^\/home\/httpd\/perl\/tmp\/$crs\/\d{10}/) {
  549:         system("rm -r -f $tempdir");
  550:     }
  551: 
  552:     # All done, display success message
  553:     $r->print(
  554:         '<p class="LC_success">'
  555:        .&mt('Your import is complete.')
  556:        .'</p>'
  557:     );
  558:     # Re-initialize Button
  559:     my $initbutton =
  560:            '<input type="button" value="'
  561:           .&mt('re-initializing course')
  562:           .'" onclick="javascript:init(this.form)" />';
  563:     $r->print(
  564:         '<form method="post" action="/adm/roles" target="loncapaclient" name="importDone">'
  565:        .'<input type="hidden" name="orgurl" value="/adm/coursedocs" />'
  566:        .'<input type="hidden" name="selectrole" value="1" />'
  567:        .'<input type="hidden" name="'.$env{'request.role'}.'" value="1" />'
  568:        .'<p class="LC_warning">'
  569:        .&mt('Changes will become active for your current session after [_1]'
  570:            .', or the next time you log in.'
  571:             ,$initbutton)
  572:        .'</p>'
  573:        .'</form>'
  574:     );
  575: }
  576: 
  577: 1;
  578: __END__

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