Diff for /loncom/publisher/lonpublisher.pm between versions 1.3 and 1.174

version 1.3, 2000/11/29 19:52:34 version 1.174, 2004/06/18 16:52:19
Line 1 Line 1
 # The LearningOnline Network with CAPA  # The LearningOnline Network with CAPA
 # Publication Handler  # Publication Handler
 #   
 # (TeX Content Handler  
 #  #
 # 05/29/00,05/30,10/11 Gerd Kortemeyer)  # $Id$
 #  #
 # 11/28,11/29 Gerd Kortemeyer  # 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/
   #
   ###
   
   ###############################################################################
   ##                                                                           ##
   ## ORGANIZATION OF THIS PERL MODULE                                          ##
   ##                                                                           ##
   ## 1. Modules used by this module                                            ##
   ## 2. Various subroutines                                                    ##
   ## 3. Publication Step One                                                   ##
   ## 4. Phase Two                                                              ##
   ## 5. Main Handler                                                           ##
   ##                                                                           ##
   ###############################################################################
   
   
   ######################################################################
   ######################################################################
   
   =pod 
   
   =head1 NAME
   
   lonpublisher - LON-CAPA publishing handler
   
   =head1 SYNOPSIS
   
   B<lonpublisher> is used by B<mod_perl> inside B<Apache>.  This is the
   invocation by F<loncapa_apache.conf>:
   
     <Location /adm/publish>
     PerlAccessHandler       Apache::lonacc
     SetHandler perl-script
     PerlHandler Apache::lonpublisher
     ErrorDocument     403 /adm/login
     ErrorDocument     404 /adm/notfound.html
     ErrorDocument     406 /adm/unauthorized.html
     ErrorDocument     500 /adm/errorhandler
     </Location>
   
   =head1 OVERVIEW
   
   Authors can only write-access the C</~authorname/> space. They can
   copy resources into the resource area through the publication step,
   and move them back through a recover step. Authors do not have direct
   write-access to their resource space.
   
   During the publication step, several events will be
   triggered. Metadata is gathered, where a wizard manages default
   entries on a hierarchical per-directory base: The wizard imports the
   metadata (including access privileges and royalty information) from
   the most recent published resource in the current directory, and if
   that is not available, from the next directory above, etc. The Network
   keeps all previous versions of a resource and makes them available by
   an explicit version number, which is inserted between the file name
   and extension, for example C<foo.2.html>, while the most recent
   version does not carry a version number (C<foo.html>). Servers
   subscribing to a changed resource are notified that a new version is
   available.
   
   =head1 DESCRIPTION
   
   B<lonpublisher> takes the proper steps to add resources to the LON-CAPA
   digital library.  This includes updating the metadata table in the
   LON-CAPA database.
   
   B<lonpublisher> is many things to many people.  
   
   This module publishes a file.  This involves gathering metadata,
   versioning the file, copying file from construction space to
   publication space, and copying metadata from construction space
   to publication space.
   
   =head2 SUBROUTINES
   
   Many of the undocumented subroutines implement various magical
   parsing shortcuts.
   
   =over 4
   
   =cut
   
   ######################################################################
   ######################################################################
   
   
 package Apache::lonpublisher;  package Apache::lonpublisher;
   
   # ------------------------------------------------- modules used by this module
 use strict;  use strict;
 use Apache::File;  use Apache::File;
   use File::Copy;
 use Apache::Constants qw(:common :http :methods);  use Apache::Constants qw(:common :http :methods);
 use HTML::TokeParser;  use HTML::LCParser;
   use Apache::lonxml;
   use Apache::loncacc;
   use DBI;
   use Apache::lonnet();
   use Apache::loncommon();
   use Apache::lonmysql;
   use Apache::lonlocal;
   use Apache::loncfile;
   use LONCAPA::lonmetadata;
   use Apache::lonmsg;
   use vars qw(%metadatafields %metadatakeys);
   
 my %addid;  my %addid;
   my %nokey;
   
   my $docroot;
   
   my $cuname;
   my $cudom;
   
   =pod
   
   =item B<metaeval>
   
   Evaluates a string that contains metadata.  This subroutine
   stores values inside I<%metadatafields> and I<%metadatakeys>.
   The hash key is a I<$unikey> corresponding to a unique id
   that is descriptive of the parser location inside the XML tree.
   
   Parameters:
   
   =over 4
   
   =item I<$metastring>
   
   A string that contains metadata.
   
   =back
   
   Returns:
   
   nothing
   
   =cut
   
   #########################################
   #########################################
   #
   # Modifies global %metadatafields %metadatakeys 
   #
   
   sub metaeval {
       my ($metastring,$prefix)=@_;
      
       my $parser=HTML::LCParser->new(\$metastring);
       my $token;
       while ($token=$parser->get_token) {
    if ($token->[0] eq 'S') {
       my $entry=$token->[1];
       my $unikey=$entry;
       if (defined($token->[2]->{'package'})) { 
    $unikey.='_package_'.$token->[2]->{'package'};
       } 
       if (defined($token->[2]->{'part'})) { 
    $unikey.='_'.$token->[2]->{'part'}; 
       }
       if (defined($token->[2]->{'id'})) { 
    $unikey.='_'.$token->[2]->{'id'};
       } 
       if (defined($token->[2]->{'name'})) { 
    $unikey.='_'.$token->[2]->{'name'}; 
       }
       foreach (@{$token->[3]}) {
    $metadatafields{$unikey.'.'.$_}=$token->[2]->{$_};
    if ($metadatakeys{$unikey}) {
       $metadatakeys{$unikey}.=','.$_;
    } else {
       $metadatakeys{$unikey}=$_;
    }
       }
       my $newentry=$parser->get_text('/'.$entry);
       if (($entry eq 'customdistributionfile') ||
    ($entry eq 'sourcerights')) {
    $newentry=~s/^\s*//;
    if ($newentry !~m|^/res|) { $newentry=$prefix.$newentry; }
       }
   # actually store
       if ( $entry eq 'rule' && exists($metadatafields{$unikey})) {
    $metadatafields{$unikey}.=','.$newentry;
       } else {
    $metadatafields{$unikey}=$newentry;
       }
    }
       }
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<metaread>
   
   Read a metadata file
   
   Parameters:
   
   =over
   
   =item I<$logfile>
   
   File output stream to output errors and warnings to.
   
   =item I<$fn>
   
   File name (including path).
   
   =back
   
   Returns:
   
   =over 4
   
   =item Scalar string (if successful)
   
   XHTML text that indicates successful reading of the metadata.
   
   =back
   
   =cut
   
   #########################################
   #########################################
   sub metaread {
       my ($logfile,$fn,$prefix)=@_;
       unless (-e $fn) {
    print($logfile 'No file '.$fn."\n");
           return '<br /><b>'.&mt('No file').':</b> <tt>'.
       &Apache::loncfile::display($fn).'</tt>';
       }
       print($logfile 'Processing '.$fn."\n");
       my $metastring;
       {
    my $metafh=Apache::File->new($fn);
    $metastring=join('',<$metafh>);
       }
       &metaeval($metastring,$prefix);
       return '<br /><b>'.&mt('Processed file').':</b> <tt>'.
    &Apache::loncfile::display($fn).'</tt>';
   }
   
   #########################################
   #########################################
   
   sub coursedependencies {
       my $url=&Apache::lonnet::declutter(shift);
       $url=~s/\.meta$//;
       my ($adomain,$aauthor)=($url=~/^(\w+)\/(\w+)\//);
       my $regexp=$url;
       $regexp=~s/(\W)/\\$1/g;
       $regexp='___'.$regexp.'___course';
       my %evaldata=&Apache::lonnet::dump('nohist_resevaldata',$adomain,
          $aauthor,$regexp);
       my %courses=();
       foreach (keys %evaldata) {
    if ($_=~/^([a-zA-Z0-9]+_[a-zA-Z0-9]+)___.+___course$/) {
       $courses{$1}=1;
           }
       }
       return %courses;
   }
   #########################################
   #########################################
   
   
   =pod
   
   =item Form-field-generating subroutines.
   
   For input parameters, these subroutines take in values
   such as I<$name>, I<$value> and other form field metadata.
   The output (scalar string that is returned) is an XHTML
   string which presents the form field (foreseeably inside
   <form></form> tags).
   
   =over 4
   
   =item B<textfield>
   
   =item B<hiddenfield>
   
   =item B<selectbox>
   
   =back
   
   =cut
   
   #########################################
   #########################################
   sub textfield {
       my ($title,$name,$value)=@_;
       $value=~s/^\s+//gs;
       $value=~s/\s+$//gs;
       $value=~s/\s+/ /gs;
       $title=&mt($title);
       $ENV{'form.'.$name}=$value;
       return "\n<p><font color=\"#800000\" face=\"helvetica\"><b>$title:".
              "</b></font></p><br />".
              '<input type="text" name="'.$name.'" size=80 value="'.$value.'" />';
   }
   
   sub hiddenfield {
       my ($name,$value)=@_;
       $ENV{'form.'.$name}=$value;
       return "\n".'<input type="hidden" name="'.$name.'" value="'.$value.'" />';
   }
   
   sub selectbox {
       my ($title,$name,$value,$functionref,@idlist)=@_;
       $title=&mt($title);
       $value=(split(/\s*,\s*/,$value))[-1];
       if (defined($value)) {
    $ENV{'form.'.$name}=$value;
       } else {
    $ENV{'form.'.$name}=$idlist[0];
       }
       my $selout="\n<p><font color=\"#800000\" face=\"helvetica\"><b>$title:".
    '</b></font></p><br /><select name="'.$name.'">';
       foreach (@idlist) {
           $selout.='<option value=\''.$_.'\'';
           if ($_ eq $value) {
       $selout.=' selected>'.&{$functionref}($_).'</option>';
    }
           else {$selout.='>'.&{$functionref}($_).'</option>';}
       }
       return $selout.'</select>';
   }
   
   sub select_level_form {
       my ($value,$name)=@_;
       $ENV{'form.'.$name}=$value;
       if (!defined($value)) { $ENV{'form.'.$name}=0; }
       return  &Apache::loncommon::select_level_form($value,$name);
   }
   #########################################
   #########################################
   
   =pod
   
   =item B<urlfixup>
   
   Fix up a url?  First step of publication
   
   =cut
   
   #########################################
   #########################################
   sub urlfixup {
       my ($url,$target)=@_;
       unless ($url) { return ''; }
       #javascript code needs no fixing
       if ($url =~ /^javascript:/i) { return $url; }
       if ($url =~ /^mailto:/i) { return $url; }
       #internal document links need no fixing
       if ($url =~ /^\#/) { return $url; } 
       my ($host)=($url=~/(?:http\:\/\/)*([^\/]+)/);
       foreach (values %Apache::lonnet::hostname) {
    if ($_ eq $host) {
       $url=~s/^http\:\/\///;
               $url=~s/^$host//;
           }
       }
       if ($url=~/^http\:\/\//) { return $url; }
       $url=~s/\~$cuname/res\/$cudom\/$cuname/;
       return $url;
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<absoluteurl>
   
   Currently undocumented.
   
   =cut
   
   #########################################
   #########################################
   sub absoluteurl {
       my ($url,$target)=@_;
       unless ($url) { return ''; }
       if ($target) {
    $target=~s/\/[^\/]+$//;
          $url=&Apache::lonnet::hreflocation($target,$url);
       }
       return $url;
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<set_allow>
   
   Currently undocumented    
   
   =cut
   
   #########################################
   #########################################
   sub set_allow {
       my ($allow,$logfile,$target,$tag,$oldurl)=@_;
       my $newurl=&urlfixup($oldurl,$target);
       my $return_url=$oldurl;
       print $logfile 'GUYURL: '.$tag.':'.$oldurl.' - '.$newurl."\n";
       if ($newurl ne $oldurl) {
    $return_url=$newurl;
    print $logfile 'URL: '.$tag.':'.$oldurl.' - '.$newurl."\n";
       }
       if (($newurl !~ /^javascript:/i) &&
    ($newurl !~ /^mailto:/i) &&
    ($newurl !~ /^http:/i) &&
    ($newurl !~ /^\#/)) {
    $$allow{&absoluteurl($newurl,$target)}=1;
       }
       return $return_url
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<get_subscribed_hosts>
   
   Currently undocumented    
   
   =cut
   
   #########################################
   #########################################
   sub get_subscribed_hosts {
       my ($target)=@_;
       my @subscribed;
       my $filename;
       $target=~/(.*)\/([^\/]+)$/;
       my $srcf=$2;
       opendir(DIR,$1);
       while ($filename=readdir(DIR)) {
    if ($filename=~/\Q$srcf\E\.(\w+)$/) {
       my $subhost=$1;
       if (($subhost ne 'meta' && $subhost ne 'subscription') &&
                   ($subhost ne $Apache::lonnet::perlvar{'lonHostID'})) {
    push(@subscribed,$subhost);
       }
    }
       }
       closedir(DIR);
       my $sh;
       if ( $sh=Apache::File->new("$target.subscription") ) {
    &Apache::lonnet::logthis("opened $target.subscription");
    while (my $subline=<$sh>) {
       &Apache::lonnet::logthis("Trying $subline");
       if ($subline =~ /(^\w+):/) { 
                   if ($1 ne $Apache::lonnet::perlvar{'lonHostID'}) { 
                      push(@subscribed,$1);
           }
               } else {
    &Apache::lonnet::logthis("No Match for $subline");
       }
    }
       } else {
    &Apache::lonnet::logthis("Unable to open $target.subscription");
       }
       return @subscribed;
   }
   
   
   #########################################
   #########################################
   
   =pod
   
   =item B<get_max_ids_indices>
   
   Currently undocumented    
   
   =cut
   
   #########################################
   #########################################
   sub get_max_ids_indices {
       my ($content)=@_;
       my $maxindex=10;
       my $maxid=10;
       my $needsfixup=0;
       my $duplicateids=0;
   
       my %allids;
       my %duplicatedids;
   
       my $parser=HTML::LCParser->new($content);
       my $token;
       while ($token=$parser->get_token) {
    if ($token->[0] eq 'S') {
       my $counter;
       if ($counter=$addid{$token->[1]}) {
    if ($counter eq 'id') {
       if (defined($token->[2]->{'id'})) {
    $maxid=($token->[2]->{'id'}>$maxid)?$token->[2]->{'id'}:$maxid;
    if (exists($allids{$token->[2]->{'id'}})) {
       $duplicateids=1;
       $duplicatedids{$token->[2]->{'id'}}=1;
    } else {
       $allids{$token->[2]->{'id'}}=1;
    }
       } else {
    $needsfixup=1;
       }
    } else {
       if (defined($token->[2]->{'index'})) {
    $maxindex=($token->[2]->{'index'}>$maxindex)?$token->[2]->{'index'}:$maxindex;
       } else {
    $needsfixup=1;
       }
    }
       }
    }
       }
       return ($needsfixup,$maxid,$maxindex,$duplicateids,
       (keys(%duplicatedids)));
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<get_all_text_unbalanced>
   
   Currently undocumented    
   
   =cut
   
   #########################################
   #########################################
   sub get_all_text_unbalanced {
       #there is a copy of this in lonxml.pm
       my($tag,$pars)= @_;
       my $token;
       my $result='';
       $tag='<'.$tag.'>';
       while ($token = $$pars[-1]->get_token) {
    if (($token->[0] eq 'T')||($token->[0] eq 'C')||($token->[0] eq 'D')) {
       $result.=$token->[1];
    } elsif ($token->[0] eq 'PI') {
       $result.=$token->[2];
    } elsif ($token->[0] eq 'S') {
       $result.=$token->[4];
    } elsif ($token->[0] eq 'E')  {
       $result.=$token->[2];
    }
    if ($result =~ /(.*)\Q$tag\E(.*)/s) {
       #&Apache::lonnet::logthis('Got a winner with leftovers ::'.$2);
       #&Apache::lonnet::logthis('Result is :'.$1);
       $result=$1;
       my $redo=$tag.$2;
       push (@$pars,HTML::LCParser->new(\$redo));
       $$pars[-1]->xml_mode('1');
       last;
    }
       }
       return $result
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<fix_ids_and_indices>
   
   Currently undocumented    
   
   =cut
   
   #########################################
   #########################################
   #Arguably this should all be done as a lonnet::ssi instead
   sub fix_ids_and_indices {
       my ($logfile,$source,$target)=@_;
   
       my %allow;
       my $content;
       {
    my $org=Apache::File->new($source);
    $content=join('',<$org>);
       }
   
       my ($needsfixup,$maxid,$maxindex,$duplicateids,@duplicatedids)=
    &get_max_ids_indices(\$content);
   
       print $logfile ("Got $needsfixup,$maxid,$maxindex,$duplicateids--".
      join(', ',@duplicatedids));
       if ($duplicateids) {
    print $logfile "Duplicate ID(s) exist, ".join(', ',@duplicatedids)."\n";
    my $outstring='<font color="red">'.&mt('Unable to publish file, it contains duplicated ID(s), ID(s) need to be unique. The duplicated ID(s) are').': '.join(', ',@duplicatedids).'</font>';
    return ($outstring,1);
       }
       if ($needsfixup) {
    print $logfile "Needs ID and/or index fixup\n".
       "Max ID   : $maxid (min 10)\n".
                   "Max Index: $maxindex (min 10)\n";
       }
       my $outstring='';
       my @parser;
       $parser[0]=HTML::LCParser->new(\$content);
       $parser[-1]->xml_mode(1);
       my $token;
       while (@parser) {
    while ($token=$parser[-1]->get_token) {
       if ($token->[0] eq 'S') {
    my $counter;
    my $tag=$token->[1];
    my $lctag=lc($tag);
    if ($lctag eq 'allow') {
       $allow{$token->[2]->{'src'}}=1;
       next;
    }
    my %parms=%{$token->[2]};
    $counter=$addid{$tag};
    if (!$counter) { $counter=$addid{$lctag}; }
    if ($counter) {
       if ($counter eq 'id') {
    unless (defined($parms{'id'})) {
       $maxid++;
       $parms{'id'}=$maxid;
       print $logfile 'ID: '.$tag.':'.$maxid."\n";
    }
       } elsif ($counter eq 'index') {
    unless (defined($parms{'index'})) {
       $maxindex++;
       $parms{'index'}=$maxindex;
       print $logfile 'Index: '.$tag.':'.$maxindex."\n";
    }
       }
    }
    foreach my $type ('src','href','background','bgimg') {
       foreach my $key (keys(%parms)) {
    if ($key =~ /^$type$/i) {
       $parms{$key}=&set_allow(\%allow,$logfile,
       $target,$tag,
       $parms{$key});
    }
       }
    }
    # probably a <randomlabel> image type <label>
    # or a <image> tag inside <imageresponse>
    if (($lctag eq 'label' && defined($parms{'description'}))
       ||
       ($lctag eq 'image')) {
       my $next_token=$parser[-1]->get_token();
       if ($next_token->[0] eq 'T') {
    $next_token->[1]=&set_allow(\%allow,$logfile,
       $target,$tag,
       $next_token->[1]);
       }
       $parser[-1]->unget_token($next_token);
    }
    if ($lctag eq 'applet') {
       my $codebase='';
       my $havecodebase=0;
       foreach my $key (keys(%parms)) {
    if (lc($key) eq 'codebase') { 
       $codebase=$parms{$key};
       $havecodebase=1; 
    }
       }
       if ($havecodebase) {
    my $oldcodebase=$codebase;
    unless ($oldcodebase=~/\/$/) {
       $oldcodebase.='/';
    }
    $codebase=&urlfixup($oldcodebase,$target);
    $codebase=~s/\/$//;    
    if ($codebase ne $oldcodebase) {
       $parms{'codebase'}=$codebase;
       print $logfile 'URL codebase: '.$tag.':'.
    $oldcodebase.' - '.
       $codebase."\n";
    }
    $allow{&absoluteurl($codebase,$target).'/*'}=1;
       } else {
    foreach my $key (keys(%parms)) {
       if ($key =~ /(archive|code|object)/i) {
    my $oldurl=$parms{$key};
    my $newurl=&urlfixup($oldurl,$target);
    $newurl=~s/\/[^\/]+$/\/\*/;
    print $logfile 'Allow: applet '.lc($key).':'.
       $oldurl.' allows '.$newurl."\n";
    $allow{&absoluteurl($newurl,$target)}=1;
       }
    }
       }
    }
    my $newparmstring='';
    my $endtag='';
    foreach (keys %parms) {
       if ($_ eq '/') {
    $endtag=' /';
       } else { 
    my $quote=($parms{$_}=~/\"/?"'":'"');
    $newparmstring.=' '.$_.'='.$quote.$parms{$_}.$quote;
       }
    }
    if (!$endtag) { if ($token->[4]=~m:/>$:) { $endtag=' /'; }; }
    $outstring.='<'.$tag.$newparmstring.$endtag.'>';
    if ($lctag eq 'm' || $lctag eq 'script' 
                       || $lctag eq 'display' || $lctag eq 'tex') {
       $outstring.=&get_all_text_unbalanced('/'.$lctag,\@parser);
    }
       } elsif ($token->[0] eq 'E') {
    if ($token->[2]) {
       unless ($token->[1] eq 'allow') {
    $outstring.='</'.$token->[1].'>';
       }
    }
       } else {
    $outstring.=$token->[1];
       }
    }
    pop(@parser);
       }
   
       if ($needsfixup) {
    print $logfile "End of ID and/or index fixup\n".
       "Max ID   : $maxid (min 10)\n".
    "Max Index: $maxindex (min 10)\n";
       } else {
    print $logfile "Does not need ID and/or index fixup\n";
       }
   
       return ($outstring,0,%allow);
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<store_metadata>
   
   Store the metadata in the metadata table in the loncapa database.
   Uses lonmysql to access the database.
   
   Inputs: \%metadata
   
   Returns: (error,status).  error is undef on success, status is undef on error.
   
   =cut
   
   #########################################
   #########################################
   sub store_metadata {
       my %metadata = @_;
       my $error;
       # Determine if the table exists
       my $status = &Apache::lonmysql::check_table('metadata');
       if (! defined($status)) {
           $error='<font color="red">WARNING: Cannot connect to '.
               'database!</font>';
           &Apache::lonnet::logthis($error);
           return ($error,undef);
       }
       if ($status == 0) {
           # It would be nice to actually create the table....
           $error ='<font color="red">WARNING: The metadata table does not '.
               'exist in the LON-CAPA database.</font>';
           &Apache::lonnet::logthis($error);
           return ($error,undef);
       }
       my $dbh = &Apache::lonmysql::get_dbh();
       if (($metadata{'obsolete'}) || ($metadata{'copyright'} eq 'priv') ||
    ($metadata{'copyright'} eq 'custom')) {
           # remove this entry
    $status=&LONCAPA::lonmetadata::delete_metadata($dbh,undef,
                                                          $metadata{'url'});
       } else {
           $status = &LONCAPA::lonmetadata::update_metadata($dbh,undef,
                                                            \%metadata);
       }
       if (defined($status) && $status ne '') {
           $error='<font color="red">Error occured storing new values in '.
               'metadata table in LON-CAPA database</font>';
           &Apache::lonnet::logthis($error);
           &Apache::lonnet::logthis($status);
           return ($error,undef);
       }
       return (undef,$status);
   }
   
   
   # ============================================== Parse file itself for metadata
   #
   # parses a file with target meta, sets global %metadatafields %metadatakeys 
   
   sub parseformeta {
       my ($source,$style)=@_;
       my $allmeta='';
       if (($style eq 'ssi') || ($style eq 'prv')) {
    my $dir=$source;
    $dir=~s-/[^/]*$--;
    my $file=$source;
    $file=(split('/',$file))[-1];
           $source=&Apache::lonnet::hreflocation($dir,$file);
    $allmeta=&Apache::lonnet::ssi_body($source,('grade_target' => 'meta'));
           &metaeval($allmeta);
       }
       return $allmeta;
   }
   
   #########################################
   #########################################
   
   =pod
   
   =item B<publish>
   
   This is the workhorse function of this module.  This subroutine generates
   backup copies, performs any automatic processing (prior to publication,
   especially for rat and ssi files),
   
   Returns a 2 element array, the first is the string to be shown to the
   user, the second is an error code, either 1 (an error occured) or 0
   (no error occurred)
   
   I<Additional documentation needed.>
   
   =cut
   
   #########################################
   #########################################
 sub publish {  sub publish {
     my ($source,$target,$style)=@_;  
       my ($source,$target,$style,$batch)=@_;
     my $logfile;      my $logfile;
       my $scrout='';
       my $allmeta='';
       my $content='';
       my %allow=();
   
     unless ($logfile=Apache::File->new('>>'.$source.'.log')) {      unless ($logfile=Apache::File->new('>>'.$source.'.log')) {
  return 'No write permission to user directory, FAIL';   return ('<font color="red">'.&mt('No write permission to user directory, FAIL').'</font>',1);
     }      }
     print $logfile       print $logfile 
 "\n\n================== Publish ".localtime()." =================\n";  "\n\n================= Publish ".localtime()." Phase One  ================\n".$ENV{'user.name'}.'@'.$ENV{'user.domain'}."\n";
   
     if (($style eq 'ssi') || ($style eq 'rat')) {      if (($style eq 'ssi') || ($style eq 'rat') || ($style eq 'prv')) {
 # ------------------------------------------------------- This needs processing  # ------------------------------------------------------- This needs processing
   
   # ----------------------------------------------------------------- Backup Copy
  my $copyfile=$source.'.save';   my $copyfile=$source.'.save';
           if (copy($source,$copyfile)) {
       print $logfile "Copied original file to ".$copyfile."\n";
           } else {
       print $logfile "Unable to write backup ".$copyfile.':'.$!."\n";
       return ("<font color=\"red\">Failed to write backup copy, $!,FAIL</font>",1);
           }
   # ------------------------------------------------------------- IDs and indices
   
    my ($outstring,$error);
    ($outstring,$error,%allow)=&fix_ids_and_indices($logfile,$source,
    $target);
    if ($error) { return ($outstring,$error); }
   # ------------------------------------------------------------ Construct Allows
       
    $scrout.='<h3>'.&mt('Dependencies').'</h3>';
           my $allowstr='';
           foreach (sort(keys(%allow))) {
      my $thisdep=$_;
      if ($thisdep !~ /[^\s]/) { next; }
              unless ($style eq 'rat') { 
                 $allowstr.="\n".'<allow src="'.$thisdep.'" />';
      }
              $scrout.='<br />';
              if ($thisdep!~/\*/ && $thisdep!~m|^/adm/|) {
          $scrout.='<a href="'.$thisdep.'">';
              }
              $scrout.='<tt>'.$thisdep.'</tt>';
              if ($thisdep!~/\*/ && $thisdep!~m|^/adm/|) {
          $scrout.='</a>';
                  if (
          &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.'/'.
                                               $thisdep.'.meta') eq '-1') {
      $scrout.= ' - <font color="red">'.&mt('Currently not available').
          '</font>';
                  } else {
                      my %temphash=(&Apache::lonnet::declutter($target).'___'.
                                &Apache::lonnet::declutter($thisdep).'___usage'
                                    => time);
                      $thisdep=~/^\/res\/(\w+)\/(\w+)\//;
                      if ((defined($1)) && (defined($2))) {
                         &Apache::lonnet::put('nohist_resevaldata',\%temphash,
      $1,$2);
      }
          }
              }
           }
           $outstring=~s/\n*(\<\/[^\>]+\>)\s*$/$allowstr\n$1\n/s;
   
   # ------------------------------------------------------------- Write modified.
   
         {          {
     my $org=Apache::File->new($source);            my $org;
             my $cop=Apache::File->new('>'.$copyfile);            unless ($org=Apache::File->new('>'.$source)) {
             while (my $line=<$org>) { print $cop $line; }               print $logfile "No write permit to $source\n";
                return ('<font color="red">'.&mt('No write permission to').
        ' '.$source.
        ', '.&mt('FAIL').'</font>',1);
     }
             print($org $outstring);
         }          }
         if (-e $copyfile) {    $content=$outstring;
     print $logfile "Copied original file to ".$copyfile."\n";  
       }
   # -------------------------------------------- Initial step done, now metadata.
   
   # --------------------------------------- Storage for metadata keys and fields.
   # these are globals
   #
        %metadatafields=();
        %metadatakeys=();
        
        my %oldparmstores=();
        
       unless ($batch) {
        $scrout.='<h3>'.&mt('Metadata Information').' ' .
          Apache::loncommon::help_open_topic("Metadata_Description")
          . '</h3>';
       }
   
   # ------------------------------------------------ First, check out environment
        unless (-e $source.'.meta') {
           $metadatafields{'author'}=$ENV{'environment.firstname'}.' '.
                             $ENV{'environment.middlename'}.' '.
                     $ENV{'environment.lastname'}.' '.
                     $ENV{'environment.generation'};
           $metadatafields{'author'}=~s/\s+/ /g;
           $metadatafields{'author'}=~s/\s+$//;
           $metadatafields{'owner'}=$cuname.'@'.$cudom;
   
   # ------------------------------------------------ Check out directory hierachy
   
           my $thisdisfn=$source;
           $thisdisfn=~s/^\/home\/\Q$cuname\E\///;
   
           my @urlparts=split(/\//,$thisdisfn);
           $#urlparts--;
   
           my $currentpath='/home/'.$cuname.'/';
   
    my $prefix='../'x($#urlparts);
           foreach (@urlparts) {
       $currentpath.=$_.'/';
               $scrout.=&metaread($logfile,$currentpath.'default.meta',$prefix);
       $prefix=~s|^\.\./||;
           }
   # ----------------------------------------------------------- Parse file itself
   # read %metadatafields from file itself
    
    $allmeta=&parseformeta($source,$style);
   
   # ------------------- Clear out parameters and stores (there should not be any)
   
           foreach (keys %metadatafields) {
       if (($_=~/^parameter/) || ($_=~/^stores/)) {
    delete $metadatafields{$_};
               }
           }
   
       } else {
   # ---------------------- Read previous metafile, remember parameters and stores
   
           $scrout.=&metaread($logfile,$source.'.meta');
   
           foreach (keys %metadatafields) {
       if (($_=~/^parameter/) || ($_=~/^stores/)) {
                   $oldparmstores{$_}=1;
    delete $metadatafields{$_};
               }
           }
   # ------------------------------------------ See if anything new in file itself
    
    $allmeta=&parseformeta($source,$style);
   
      }
   
          
   # ---------------- Find and document discrepancies in the parameters and stores
   
       my $chparms='';
       foreach (sort keys %metadatafields) {
    if (($_=~/^parameter/) || ($_=~/^stores/)) {
       unless ($_=~/\.\w+$/) { 
    unless ($oldparmstores{$_}) {
       print $logfile 'New: '.$_."\n";
       $chparms.=$_.' ';
    }
       }
    }
       }
       if ($chparms) {
    $scrout.='<p><b>'.&mt('New parameters or stored values').
       ':</b> '.$chparms.'</p>';
       }
   
       $chparms='';
       foreach (sort keys %oldparmstores) {
    if (($_=~/^parameter/) || ($_=~/^stores/)) {
       unless (($metadatafields{$_.'.name'}) ||
       ($metadatafields{$_.'.package'}) || ($_=~/\.\w+$/)) {
    print $logfile 'Obsolete: '.$_."\n";
    $chparms.=$_.' ';
       }
    }
       }
       if ($chparms) {
    $scrout.='<p><b>'.&mt('Obsolete parameters or stored values').':</b> '.
       $chparms.'</p><h1><font color="red">'.&mt('Warning!').
       '</font></h1><p><font color="red" size="+1">'.
       &mt('If this resource is in active use, student performance data from the previous version may become inaccessible.').'</font></p><hr />';
       }
   
   # ------------------------------------------------------- Now have all metadata
   
       my %keywords=();
           
       if (length($content)<500000) {
    my $textonly=$content;
    $textonly=~s/\<script[^\<]+\<\/script\>//g;
    $textonly=~s/\<m\>[^\<]+\<\/m\>//g;
    $textonly=~s/\<[^\>]*\>//g;
    $textonly=~tr/A-Z/a-z/;
    $textonly=~s/[\$\&][a-z]\w*//g;
    $textonly=~s/[^a-z\s]//g;
   
    foreach ($textonly=~m/(\w+)/g) {
       unless ($nokey{$_}) {
    $keywords{$_}=1;
       } 
    }
       }
   
               
       foreach my $addkey (split(/[\"\'\,\;]/,$metadatafields{'keywords'})) {
    $addkey=~s/\s+/ /g;
    $addkey=~s/^\s//;
    $addkey=~s/\s$//;
    if ($addkey=~/\w/) {
       $keywords{$addkey}=1;
    }
       }
   # --------------------------------------------------- Now we also have keywords
   # =============================================================================
   # interactive mode html goes into $intr_scrout
   # batch mode throws away this HTML
   # additionally all of the field functions have a by product of setting
   #   $ENV{'from.'..} so that it can be used by the phase two handler in
   #    batch mode
   
       my $intr_scrout.=
    '<form name="pubform" action="/adm/publish" method="post">'.
    '<p><input type="submit" value="'.&mt('Finalize Publication').'" /></p>'.
    &hiddenfield('phase','two').
    &hiddenfield('filename',$ENV{'form.filename'}).
    &hiddenfield('allmeta',&Apache::lonnet::escape($allmeta)).
    &hiddenfield('dependencies',join(',',keys %allow)).
    &textfield('Title','title',$metadatafields{'title'}).
    &textfield('Author(s)','author',$metadatafields{'author'}).
    &textfield('Subject','subject',$metadatafields{'subject'});
   
   # --------------------------------------------------- Scan content for keywords
   
       my $keywords_help = Apache::loncommon::help_open_topic("Publishing_Keywords");
       my $KEYWORDS=&mt('Keywords');
       my $CheckAll=&mt('check all');
       my $UncheckAll=&mt('uncheck all');
       my $keywordout=<<"END";
   <script>
   function checkAll(field) {
       for (i = 0; i < field.length; i++)
           field[i].checked = true ;
   }
   
   function uncheckAll(field) {
       for (i = 0; i < field.length; i++)
           field[i].checked = false ;
   }
   </script>
   <p><font color="#800000" face="helvetica"><b>$KEYWORDS:</b></font>
    $keywords_help</b>
   <input type="button" value="$CheckAll" onclick="javascript:checkAll(document.pubform.keywords)" /> 
   <input type="button" value="$UncheckAll" onclick="javascript:uncheckAll(document.pubform.keywords)" /> 
   </p>
   <br />
   END
       $keywordout.='<table border="2"><tr>';
       my $colcount=0;
   
       foreach (sort keys %keywords) {
    $keywordout.='<td><input type="checkbox" name="keywords" value="'.$_.'"';
    if ($metadatafields{'keywords'}) {
       if ($metadatafields{'keywords'}=~/\Q$_\E/) {
    $keywordout.=' checked="on"';
    $ENV{'form.keywords'}.=$_.',';
       }
    } elsif (&Apache::loncommon::keyword($_)) {
       $keywordout.=' checked="on"';
       $ENV{'form.keywords'}.=$_.',';
    }
    $keywordout.=' />'.$_.'</td>';
    if ($colcount>10) {
       $keywordout.="</tr><tr>\n";
       $colcount=0;
    }
    $colcount++;
       }
       $ENV{'form.keywords'}=~s/\,$//;
   
       $keywordout.='</tr></table>';
   
       $intr_scrout.=$keywordout;
   
       $intr_scrout.=&textfield('Additional Keywords','addkey','');
   
       $intr_scrout.=&textfield('Notes','notes',$metadatafields{'notes'});
   
       $intr_scrout.=
    "\n<p><font color=\"#800000\" face=\"helvetica\"><b>".&mt('Abstract').":".
    "</b></font></p><br />".
    '<textarea cols="80" rows="5" name="abstract">'.
    $metadatafields{'abstract'}.'</textarea></p>';
   
       $source=~/\.(\w+)$/;
   
   
       $intr_scrout.=
    "\n<p><font color=\"#800000\" face=\"helvetica\"><b>".
    &mt('Lowest Grade Level').':'.
    "</b></font></p><br />".
    &select_level_form($metadatafields{'lowestgradelevel'},'lowestgradelevel').
    "\n<p><font color=\"#800000\" face=\"helvetica\"><b>".
    &mt('Highest Grade Level').':'.
    "</b></font></p><br />".
    &select_level_form($metadatafields{'highestgradelevel'},'highestgradelevel').
    &textfield('Standards','standards',$metadatafields{'standards'});
   
   
   
   
       $intr_scrout.=&hiddenfield('mime',$1);
   
       my $defaultlanguage=$metadatafields{'language'};
       $defaultlanguage =~ s/\s*notset\s*//g;
       $defaultlanguage =~ s/^,\s*//g;
       $defaultlanguage =~ s/,\s*$//g;
   
       $intr_scrout.=&selectbox('Language','language',
        $defaultlanguage,
        \&Apache::loncommon::languagedescription,
        (&Apache::loncommon::languageids),
        );
   
       unless ($metadatafields{'creationdate'}) {
    $metadatafields{'creationdate'}=time;
       }
       $intr_scrout.=&hiddenfield('creationdate',
          &Apache::lonmysql::unsqltime($metadatafields{'creationdate'}));
   
       $intr_scrout.=&hiddenfield('lastrevisiondate',time);
   
   
       $intr_scrout.=&textfield('Publisher/Owner','owner',
        $metadatafields{'owner'});
   
   # ---------------------------------------------- Retrofix for unused copyright
       if ($metadatafields{'copyright'} eq 'free') {
    $metadatafields{'copyright'}='default';
    $metadatafields{'sourceavail'}='open';
       }
   # ------------------------------------------------ Dial in reasonable defaults
       my $defaultoption=$metadatafields{'copyright'};
       unless ($defaultoption) { $defaultoption='default'; }
       my $defaultsourceoption=$metadatafields{'sourceavail'};
       unless ($defaultsourceoption) { $defaultsourceoption='closed'; }
       unless ($style eq 'prv') {
   # -------------------------------------------------- Correct copyright for rat.
    if ($style eq 'rat') {
   # -------------------------------------- Retrofix for non-applicable copyright
       if ($metadatafields{'copyright'} eq 'public') { 
    delete $metadatafields{'copyright'};
    $defaultoption='default';
       }
       $intr_scrout.=&selectbox('Copyright/Distribution','copyright',
        $defaultoption,
        \&Apache::loncommon::copyrightdescription,
       (grep !/^public$/,(&Apache::loncommon::copyrightids)));
    } else {
       $intr_scrout.=&selectbox('Copyright/Distribution','copyright',
        $defaultoption,
        \&Apache::loncommon::copyrightdescription,
        (&Apache::loncommon::copyrightids));
    }
    my $copyright_help =
       Apache::loncommon::help_open_topic('Publishing_Copyright');
    $intr_scrout =~ s/DISTRIBUTION:/'DISTRIBUTION: ' . $copyright_help/ge;
    $intr_scrout.=&textfield('Custom Distribution File','customdistributionfile',
    $metadatafields{'customdistributionfile'}).
        $copyright_help;
    $intr_scrout.=&selectbox('Source Distribution','sourceavail',
    $defaultsourceoption,
    \&Apache::loncommon::source_copyrightdescription,
    (&Apache::loncommon::source_copyrightids));
    $intr_scrout.=&textfield('Source Custom Distribution File','sourcerights',
     $metadatafields{'sourcerights'});
    my $uctitle=&mt('Obsolete');
    $intr_scrout.=
       "\n<p><font color=\"#800000\" face=\"helvetica\"><b>$uctitle:".
       '</b></font> <input type="checkbox" name="obsolete" ';
    if ($metadatafields{'obsolete'}) {
       $intr_scrout.=' checked="1" ';
    }
    $intr_scrout.='/ ></p>'.
       &textfield('Suggested Replacement for Obsolete File',
          'obsoletereplacement',
          $metadatafields{'obsoletereplacement'});
       } else {
    $intr_scrout.=&hiddenfield('copyright','private');
       }
       if (!$batch) {
    $scrout.=$intr_scrout.'<p><input type="submit" value="'.
       &mt('Finalize Publication').'" /></p></form>';
       }
       return($scrout,0);
   }
   
   #########################################
   #########################################
   
   =pod 
   
   =item B<phasetwo>
   
   Render second interface showing status of publication steps.
   This is publication step two.
   
   Parameters:
   
   =over 4
   
   =item I<$source>
   
   =item I<$target>
   
   =item I<$style>
   
   =item I<$distarget>
   
   =back
   
   Returns:
   
   =over 4
   
   =item Scalar string
   
   String contains status (errors and warnings) and information associated with
   the server's attempts at publication.     
   
   =cut
   
   #'stupid emacs
   #########################################
   #########################################
   sub phasetwo {
   
       my ($r,$source,$target,$style,$distarget,$batch)=@_;
       $source=~s/\/+/\//g;
       $target=~s/\/+/\//g;
   
       if ($target=~/\_\_\_/) {
    $r->print(
    '<font color="red">'.&mt('Unsupported character combination').
     ' "<tt>___</tt>" '.&mt('in filename, FAIL').'</font>');
           return 0;
       }
       $distarget=~s/\/+/\//g;
       my $logfile;
       unless ($logfile=Apache::File->new('>>'.$source.'.log')) {
    $r->print(
           '<font color="red">'.
    &mt('No write permission to user directory, FAIL').'</font>');
           return 0;
       }
       print $logfile 
           "\n================= Publish ".localtime()." Phase Two  ================\n".$ENV{'user.name'}.'@'.$ENV{'user.domain'}."\n";
       
       %metadatafields=();
       %metadatakeys=();
   
       &metaeval(&Apache::lonnet::unescape($ENV{'form.allmeta'}));
       
       $metadatafields{'title'}=$ENV{'form.title'};
       $metadatafields{'author'}=$ENV{'form.author'};
       $metadatafields{'subject'}=$ENV{'form.subject'};
       $metadatafields{'notes'}=$ENV{'form.notes'};
       $metadatafields{'abstract'}=$ENV{'form.abstract'};
       $metadatafields{'mime'}=$ENV{'form.mime'};
       $metadatafields{'language'}=$ENV{'form.language'};
       $metadatafields{'creationdate'}=$ENV{'form.creationdate'};
       $metadatafields{'lastrevisiondate'}=$ENV{'form.lastrevisiondate'};
       $metadatafields{'owner'}=$ENV{'form.owner'};
       $metadatafields{'copyright'}=$ENV{'form.copyright'};
       $metadatafields{'standards'}=$ENV{'form.standards'};
       $metadatafields{'lowestgradelevel'}=$ENV{'form.lowestgradelevel'};
       $metadatafields{'highestgradelevel'}=$ENV{'form.highestgradelevel'};
       $metadatafields{'customdistributionfile'}=
                                    $ENV{'form.customdistributionfile'};
       $metadatafields{'sourceavail'}=$ENV{'form.sourceavail'};
       $metadatafields{'obsolete'}=$ENV{'form.obsolete'};
       $metadatafields{'obsoletereplacement'}=
                           $ENV{'form.obsoletereplacement'};
       $metadatafields{'dependencies'}=$ENV{'form.dependencies'};
       $metadatafields{'modifyinguser'}=$ENV{'user.name'}.'@'.
                                    $ENV{'user.domain'};
       $metadatafields{'authorspace'}=$cuname.'@'.$cudom;
       
       my $allkeywords=$ENV{'form.addkey'};
       if (exists($ENV{'form.keywords'})) {
           if (ref($ENV{'form.keywords'})) {
               $allkeywords .= ','.join(',',@{$ENV{'form.keywords'}});
         } else {          } else {
             return "Failed to write backup copy, FAIL";              $allkeywords .= ','.$ENV{'form.keywords'};
           }
       }
       $allkeywords=~s/[\"\']//g;
       $allkeywords=~s/\s*[\;\,]\s*/\,/g;
       $allkeywords=~s/\s+/ /g;
       $allkeywords=~s/^[ \,]//;
       $allkeywords=~s/[ \,]$//;
       $metadatafields{'keywords'}=$allkeywords;
       
   # check if custom distribution file is specified
       if ($metadatafields{'copyright'} eq 'custom') {
    my $file=$metadatafields{'customdistributionfile'};
    unless ($file=~/\.rights$/) {
               return 
                   '<font color="red">'.&mt('No valid custom distribution rights file specified, FAIL').
    '</font>';
           }
       }
       {
           print $logfile "\nWrite metadata file for ".$source;
           my $mfh;
           unless ($mfh=Apache::File->new('>'.$source.'.meta')) {
               return 
                   '<font color="red">'.&mt('Could not write metadata, FAIL').
    '</font>';
         }          }
           foreach (sort keys %metadatafields) {
               unless ($_=~/\./) {
                   my $unikey=$_;
                   $unikey=~/^([A-Za-z]+)/;
                   my $tag=$1;
                   $tag=~tr/A-Z/a-z/;
                   print $mfh "\n\<$tag";
                   foreach (split(/\,/,$metadatakeys{$unikey})) {
                       my $value=$metadatafields{$unikey.'.'.$_};
                       $value=~s/\"/\'\'/g;
                       print $mfh ' '.$_.'="'.$value.'"';
                   }
                   print $mfh '>'.
                       &HTML::Entities::encode($metadatafields{$unikey},'<>&"')
                           .'</'.$tag.'>';
               }
           }
           $r->print('<p>'.&mt('Wrote Metadata').'</p>');
           print $logfile "\nWrote metadata";
       }
       
   # -------------------------------- Synchronize entry with SQL metadata database
   
       $metadatafields{'url'} = $distarget;
       $metadatafields{'version'} = 'current';
   
       my ($error,$success) = &store_metadata(%metadatafields);
       if ($success) {
    $r->print('<p>'.&mt('Synchronized SQL metadata database').'</p>');
    print $logfile "\nSynchronized SQL metadata database";
       } else {
    $r->print($error);
    print $logfile "\n".$error;
     }      }
     my $version='';  # --------------------------------------------- Delete author resource messages
       my $delresult=&Apache::lonmsg::del_url_author_res_msg($target); 
       $r->print('<p>'.&mt('Removing error messages:').' '.$delresult.'</p>');
       print $logfile "\nRemoving error messages: $delresult";
   # ----------------------------------------------------------- Copy old versions
         
     return 'Version '.$version.', SUCCESS';      if (-e $target) {
           my $filename;
           my $maxversion=0;
           $target=~/(.*)\/([^\/]+)\.(\w+)$/;
           my $srcf=$2;
           my $srct=$3;
           my $srcd=$1;
           unless ($srcd=~/^\/home\/httpd\/html\/res/) {
               print $logfile "\nPANIC: Target dir is ".$srcd;
               return "<font color=\"red\">Invalid target directory, FAIL</font>";
           }
           opendir(DIR,$srcd);
           while ($filename=readdir(DIR)) {
               if (-l $srcd.'/'.$filename) {
                   unlink($srcd.'/'.$filename);
                   unlink($srcd.'/'.$filename.'.meta');
               } else {
                   if ($filename=~/\Q$srcf\E\.(\d+)\.\Q$srct\E$/) {
                       $maxversion=($1>$maxversion)?$1:$maxversion;
                   }
               }
           }
           closedir(DIR);
           $maxversion++;
           $r->print('<p>Creating old version '.$maxversion.'</p>');
           print $logfile "\nCreating old version ".$maxversion."\n";
           
           my $copyfile=$srcd.'/'.$srcf.'.'.$maxversion.'.'.$srct;
           
           if (copy($target,$copyfile)) {
       print $logfile "Copied old target to ".$copyfile."\n";
               $r->print('<p>'.&mt('Copied old target file').'</p>');
           } else {
       print $logfile "Unable to write ".$copyfile.':'.$!."\n";
               return "<font color=\"red\">".&mt('Failed to copy old target').
    ", $!, ".&mt('FAIL')."</font>";
           }
           
   # --------------------------------------------------------------- Copy Metadata
   
    $copyfile=$copyfile.'.meta';
           
           if (copy($target.'.meta',$copyfile)) {
       print $logfile "Copied old target metadata to ".$copyfile."\n";
               $r->print('<p>'.&mt('Copied old metadata').'</p>')
           } else {
       print $logfile "Unable to write metadata ".$copyfile.':'.$!."\n";
               if (-e $target.'.meta') {
                   return 
                       "<font color=\"red\">".
   &mt('Failed to write old metadata copy').", $!, ".&mt('FAIL')."</font>";
       }
           }
           
           
       } else {
           $r->print('<p>'.&mt('Initial version').'</p>');
           print $logfile "\nInitial version";
       }
   
   # ---------------------------------------------------------------- Write Source
       my $copyfile=$target;
       
       my @parts=split(/\//,$copyfile);
       my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]";
       
       my $count;
       for ($count=5;$count<$#parts;$count++) {
           $path.="/$parts[$count]";
           if ((-e $path)!=1) {
               print $logfile "\nCreating directory ".$path;
               $r->print('<p>'.&mt('Created directory').' '.$parts[$count].'</p>');
               mkdir($path,0777);
           }
       }
       
       if (copy($source,$copyfile)) {
           print $logfile "\nCopied original source to ".$copyfile."\n";
           $r->print('<p>'.&mt('Copied source file').'</p>');
       } else {
           print $logfile "\nUnable to write ".$copyfile.':'.$!."\n";
           return "<font color=\"red\">".
       &mt('Failed to copy source').", $!, ".&mt('FAIL')."</font>";
       }
       
   # --------------------------------------------------------------- Copy Metadata
   
       $copyfile=$copyfile.'.meta';
       
       if (copy($source.'.meta',$copyfile)) {
           print $logfile "\nCopied original metadata to ".$copyfile."\n";
           $r->print('<p>'.&mt('Copied metadata').'</p>');
       } else {
           print $logfile "\nUnable to write metadata ".$copyfile.':'.$!."\n";
           return 
               "<font color=\"red\">".&mt('Failed to write metadata copy').", $!, ".&mt('FAIL')."</font>";
       }
       $r->rflush;
   # --------------------------------------------------- Send update notifications
   
       my @subscribed=&get_subscribed_hosts($target);
       foreach my $subhost (@subscribed) {
    $r->print('<p>'.&mt('Notifying host').' '.$subhost.':');$r->rflush;
    print $logfile "\nNotifying host ".$subhost.':';
    my $reply=&Apache::lonnet::critical('update:'.$target,$subhost);
    $r->print($reply.'</p><br />');$r->rflush;
    print $logfile $reply;
       }
       
   # ---------------------------------------- Send update notifications, meta only
   
       my @subscribedmeta=&get_subscribed_hosts("$target.meta");
       foreach my $subhost (@subscribedmeta) {
    $r->print('<p>'.
   &mt('Notifying host for metadata only').' '.$subhost.':');$r->rflush;
    print $logfile "\nNotifying host for metadata only ".$subhost.':';
    my $reply=&Apache::lonnet::critical('update:'.$target.'.meta',
       $subhost);
    $r->print($reply.'</p><br />');$r->rflush;
    print $logfile $reply;
       }
       
   # --------------------------------------------------- Notify subscribed courses
       my %courses=&coursedependencies($target);
       my $now=time;
       foreach (keys %courses) {
    $r->print('<p>'.&mt('Notifying course').' '.$_.':');$r->rflush;
    print $logfile "\nNotifying host ".$_.':';
           my ($cdom,$cname)=split(/\_/,$_);
    my $reply=&Apache::lonnet::cput
                     ('versionupdate',{$target => $now},$cdom,$cname);
    $r->print($reply.'</p><br />');$r->rflush;
    print $logfile $reply;
       }
   # ------------------------------------------------ Provide link to new resource
       unless ($batch) {
           my $thisdistarget=$target;
           $thisdistarget=~s/^\Q$docroot\E//;
           
           my $thissrc=$source;
           $thissrc=~s/^\/home\/(\w+)\/public_html/\/priv\/$1/;
           
           my $thissrcdir=$thissrc;
           $thissrcdir=~s/\/[^\/]+$/\//;
           
           
           $r->print(
              '<hr /><a href="'.$thisdistarget.'"><font size="+2">'.
              &mt('View Published Version').'</font></a>'.
              '<p><a href="'.$thissrc.'"><font size=+2>'.
     &mt('Back to Source').'</font></a></p>'.
              '<p><a href="'.$thissrcdir.
                      '"><font size="+2">'.
     &mt('Back to Source Directory').'</font></a></p>');
       }
       return '<p><font color="green">'.&mt('Done').'</font></p>';
   }
   
   #########################################
   
   sub batchpublish {
       my ($r,$srcfile,$targetfile)=@_;
       #publication pollutes %ENV with form.* values
       my %oldENV=%ENV;
       $srcfile=~s/\/+/\//g;
       $targetfile=~s/\/+/\//g;
       my $thisdisfn=$srcfile;
       $thisdisfn=~s/\/home\/korte\/public_html\///;
       $srcfile=~s/\/+/\//g;
   
       my $docroot=$r->dir_config('lonDocRoot');
       my $thisdistarget=$targetfile;
       $thisdistarget=~s/^\Q$docroot\E//;
   
   
       %metadatafields=();
       %metadatakeys=();
       $srcfile=~/\.(\w+)$/;
       my $thistype=$1;
   
   
       my $thisembstyle=&Apache::loncommon::fileembstyle($thistype);
        
       $r->print('<h2>'.&mt('Publishing').' <tt>'.$thisdisfn.'</tt></h2>');
   
   # phase one takes
   #  my ($source,$target,$style,$batch)=@_;
       my ($outstring,$error)=&publish($srcfile,$targetfile,$thisembstyle,1);
       $r->print('<p>'.$outstring.'</p>');
   # phase two takes
   # my ($source,$target,$style,$distarget,batch)=@_;
   # $ENV{'form.allmeta'},$ENV{'form.title'},$ENV{'form.author'},...
       if (!$error) {
    $r->print('<p>');
    &phasetwo($r,$srcfile,$targetfile,$thisembstyle,$thisdistarget,1);
    $r->print('</p>');
       }
       %ENV=%oldENV;
       return '';
   }
   
   #########################################
   
   sub publishdirectory {
       my ($r,$fn,$thisdisfn)=@_;
       $fn=~s/\/+/\//g;
       $thisdisfn=~s/\/+/\//g;
       my $resdir=
    $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cudom.'/'.$cuname.'/'.
    $thisdisfn;
       $r->print('<h1>'.&mt('Directory').' <tt>'.$thisdisfn.'</tt></h1>'.
         &mt('Target').': <tt>'.$resdir.'</tt><br />');
   
       my $dirptr=16384; # Mask indicating a directory in stat.cmode.
   
       opendir(DIR,$fn);
       my @files=sort(readdir(DIR));
       foreach my $filename (@files) {
    my ($cdev,$cino,$cmode,$cnlink,
               $cuid,$cgid,$crdev,$csize,
               $catime,$cmtime,$cctime,
               $cblksize,$cblocks)=stat($fn.'/'.$filename);
   
    my $extension='';
    if ($filename=~/\.(\w+)$/) { $extension=$1; }
    if ($cmode&$dirptr) {
       if (($filename!~/^\./) && ($ENV{'form.pubrec'})) {
    &publishdirectory($r,$fn.'/'.$filename,$thisdisfn.'/'.$filename);
       }
    } elsif ((&Apache::loncommon::fileembstyle($extension) ne 'hdn') &&
    ($filename!~/^[\#\.]/) && ($filename!~/\~$/)) {
   # find out publication status and/or exiting metadata
       my $publishthis=0;
       if (-e $resdir.'/'.$filename) {
           my ($rdev,$rino,$rmode,$rnlink,
       $ruid,$rgid,$rrdev,$rsize,
       $ratime,$rmtime,$rctime,
       $rblksize,$rblocks)=stat($resdir.'/'.$filename);
           if (($rmtime<$cmtime) || ($ENV{'form.forcerepub'})) {
   # previously published, modified now
       $publishthis=1;
                   }
       } else {
   # never published
    $publishthis=1;
       }
       if ($publishthis) {
                   &batchpublish($r,$fn.'/'.$filename,$resdir.'/'.$filename);
       } else {
    $r->print('<br />'.&mt('Skipping').' '.$filename.'<br />');
       }
       $r->rflush();
    }
       }
       closedir(DIR);
   }
   
   #########################################
   # publish a default.meta file
   
   sub defaultmetapublish {
       my ($r,$fn,$cuname,$cudom)=@_;
       $fn=~s/^\/\~$cuname\//\/home\/$cuname\/public_html\//;
       unless (-e $fn) {
          return HTTP_NOT_FOUND;
       }
       my $target=$fn;
       $target=~s/^\/home\/$cuname\/public_html\//$Apache::lonnet::perlvar{'lonDocRoot'}\/res\/$cudom\/$cuname\//;
   
   
       &Apache::loncommon::content_type($r,'text/html');
       $r->send_http_header;
   
       $r->print('<html><head><title>LON-CAPA Publishing</title></head>');
       $r->print(&Apache::loncommon::bodytag('Catalog Information Publication'));
   
   # ---------------------------------------------------------------- Write Source
       my $copyfile=$target;
       
       my @parts=split(/\//,$copyfile);
       my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]";
       
       my $count;
       for ($count=5;$count<$#parts;$count++) {
           $path.="/$parts[$count]";
           if ((-e $path)!=1) {
               $r->print('<p>'.&mt('Created directory').' '.$parts[$count].'</p>');
               mkdir($path,0777);
           }
       }
       
       if (copy($fn,$copyfile)) {
           $r->print('<p>'.&mt('Copied source file').'</p>');
       } else {
           return "<font color=\"red\">".
       &mt('Failed to copy source').", $!, ".&mt('FAIL')."</font>";
       }
   
   # --------------------------------------------------- Send update notifications
   
       my @subscribed=&get_subscribed_hosts($target);
       foreach my $subhost (@subscribed) {
    $r->print('<p>'.&mt('Notifying host').' '.$subhost.':');$r->rflush;
    my $reply=&Apache::lonnet::critical('update:'.$target,$subhost);
    $r->print($reply.'</p><br />');$r->rflush;
       }
   # ------------------------------------------------------------------- Link back
       my $link=$fn;
       $link=~s/^\/home\/$cuname\/public_html\//\/priv\/$cuname\//;
       $r->print("<a href='$link'>".&mt('Back to Catalog Information').'</a>');
       $r->print('</body></html>');
       return OK;
 }  }
   #########################################
   
   =pod
   
   =item B<handler>
   
   A basic outline of the handler subroutine follows.
   
   =over 4
   
   =item *
   
   Get query string for limited number of parameters.
   
   =item *
   
   Check filename.
   
 # ================================================================ Main Handler  =item *
   
   File is there and owned, init lookup tables.
   
   =item *
   
   Start page output.
   
   =item *
   
   Evaluate individual file, and then output information.
   
   =item *
   
   Publishing from $thisfn to $thistarget with $thisembstyle.
   
   =back
   
   =cut
   
   #########################################
   #########################################
 sub handler {  sub handler {
   my $r=shift;      my $r=shift;
   
       if ($r->header_only) {
    &Apache::loncommon::content_type($r,'text/html');
    $r->send_http_header;
    return OK;
       }
   
   if ($r->header_only) {  # Get query string for limited number of parameters
      $r->content_type('text/html');  
      $r->send_http_header;      &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
      return OK;                                              ['filename']);
   }  
   
 # -------------------------------------------------------------- Check filename  # -------------------------------------------------------------- Check filename
   
   my $fn=$ENV{'form.filename'};      my $fn=&Apache::lonnet::unescape($ENV{'form.filename'});
   
       ($cuname,$cudom)=
    &Apache::loncacc::constructaccess($fn,$r->dir_config('lonDefDomain'));
   
   # special publication: default.meta file
       if ($fn=~/\/default.meta$/) {
    return &defaultmetapublish($r,$fn,$cuname,$cudom); 
       }
       $fn=~s/\.meta$//;
     
       unless ($fn) { 
    $r->log_reason($cuname.' at '.$cudom.
          ' trying to publish empty filename', $r->filename); 
    return HTTP_NOT_FOUND;
       } 
   
       unless (($cuname) && ($cudom)) {
    $r->log_reason($cuname.' at '.$cudom.
          ' trying to publish file '.$ENV{'form.filename'}.
          ' ('.$fn.') - not authorized', 
          $r->filename); 
    return HTTP_NOT_ACCEPTABLE;
       }
   
   unless ($fn) {       my $home=&Apache::lonnet::homeserver($cuname,$cudom);
      $r->log_reason($ENV{'user.name'}.' at '.$ENV{'user.domain'}.      my $allowed=0;
          ' trying to publish empty filename', $r->filename);       my @ids=&Apache::lonnet::current_machine_ids();
      return HTTP_NOT_FOUND;      foreach my $id (@ids) { if ($id eq $home) { $allowed = 1; }  }
   }       unless ($allowed) {
    $r->log_reason($cuname.' at '.$cudom.
   $fn=~s/^http\:\/\/[^\/]+\/\~(\w+)/\/home\/$1\/public_html/;         ' trying to publish file '.$ENV{'form.filename'}.
          ' ('.$fn.') - not homeserver ('.$home.')', 
   my $targetdir='';         $r->filename); 
   my $docroot=$r->dir_config('lonDocRoot');    return HTTP_NOT_ACCEPTABLE;
   if ($1 ne $ENV{'user.name'}) {      }
      $r->log_reason($ENV{'user.name'}.' at '.$ENV{'user.domain'}.  
          ' trying to publish unowned file '.$ENV{'form.filename'}.      $fn=~s/^http\:\/\/[^\/]+//;
          ' ('.$fn.')',       $fn=~s/^\/\~(\w+)/\/home\/$1\/public_html/;
          $r->filename);   
      return HTTP_NOT_ACCEPTABLE;      my $targetdir='';
   } else {      $docroot=$r->dir_config('lonDocRoot'); 
       $targetdir=$docroot.'/res/'.$ENV{'user.domain'};      if ($1 ne $cuname) {
   }   $r->log_reason($cuname.' at '.$cudom.
          ' trying to publish unowned file '.
          $ENV{'form.filename'}.' ('.$fn.')', 
          $r->filename); 
    return HTTP_NOT_ACCEPTABLE;
       } else {
    $targetdir=$docroot.'/res/'.$cudom;
       }
                                                                     
       
   unless (-e $fn) {       unless (-e $fn) { 
      $r->log_reason($ENV{'user.name'}.' at '.$ENV{'user.domain'}.   $r->log_reason($cuname.' at '.$cudom.
          ' trying to publish non-existing file '.$ENV{'form.filename'}.         ' trying to publish non-existing file '.
          ' ('.$fn.')',          $ENV{'form.filename'}.' ('.$fn.')', 
          $r->filename);          $r->filename); 
      return HTTP_NOT_FOUND;   return HTTP_NOT_FOUND;
   }       } 
   
 # --------------------------------- File is there and owned, init lookup tables      unless ($ENV{'form.phase'} eq 'two') {
   
   %addid=();  # -------------------------------- File is there and owned, init lookup tables.
   
   {   %addid=();
       my $fh=Apache::File->new($r->dir_config('lonTabDir').'/addid.tab');  
       while (<$fh>=~/(\w+)\s+(\w+)/) {   {
           $addid{$1}=$2;      my $fh=Apache::File->new($r->dir_config('lonTabDir').'/addid.tab');
       }      while (<$fh>=~/(\w+)\s+(\w+)/) {
   }   $addid{$1}=$2;
 # ----------------------------------------------------------- Start page output      }
    }
   $r->content_type('text/html');  
   $r->send_http_header;   %nokey=();
   
   $r->print('<html><head><title>LON-CAPA Publishing</title></head>');   {
   $r->print('<body bgcolor="#FFFFFF">');      my $fh=Apache::File->new($r->dir_config('lonIncludes').'/un_keyword.tab');
   my $thisfn=$fn;      while (<$fh>) {
       my $word=$_;
 # ------------------------------------------------------------- Individual file   chomp($word);
   {   $nokey{$word}=1;
       $thisfn=~/\.(\w+)$/;      }
       my $thistype=$1;   }
       my $thisembstyle=&Apache::lonnet::fileembstyle($thistype);  
   
       my $thistarget=$thisfn;      }
         
       $thistarget=~s/^\/home/$targetdir/;  # ---------------------------------------------------------- Start page output.
       $thistarget=~s/\/public\_html//;  
   
       my $thisdistarget=$thistarget;      &Apache::loncommon::content_type($r,'text/html');
       $thisdistarget=~s/^$docroot//;      $r->send_http_header;
   
       my $thisdisfn=$thisfn;      $r->print('<html><head><title>LON-CAPA Publishing</title></head>');
       $thisdisfn=~s/^\/home\/$ENV{'user.name'}\/public_html\///;      $r->print(&Apache::loncommon::bodytag('Resource Publication'));
   
       $r->print('<h2>Publishing '.  
         &Apache::lonnet::filedescription($thistype).' <tt>'.  
         $thisdisfn.'</tt></h2><b>Target:</b> <tt>'.$thisdistarget.'</tt><p>');  
   
 # ------------ We are publishing from $thisfn to $thistarget with $thisembstyle      my $thisfn=$fn;
   
       $r->print('<b>Result:</b> '.&publish($thisfn,$thistarget,$thisembstyle));      my $thistarget=$thisfn;
               
   }        $thistarget=~s/^\/home/$targetdir/;
       $thistarget=~s/\/public\_html//;
   
   $r->print('</body></html>');      my $thisdistarget=$thistarget;
       $thisdistarget=~s/^\Q$docroot\E//;
   
   return OK;      my $thisdisfn=$thisfn;
       $thisdisfn=~s/^\/home\/\Q$cuname\E\/public_html\///;
   
       if ($fn=~/\/$/) {
   # -------------------------------------------------------- This is a directory
    &publishdirectory($r,$fn,$thisdisfn);
    $r->print('<hr><font size="+2">'.&mt('Done').'</font><br><a href="/priv/'
     .$cuname.'/'.$thisdisfn
     .'">'.&mt('Return to Directory').'</a>');
   
   
       } else {
   # ---------------------- Evaluate individual file, and then output information.
    $thisfn=~/\.(\w+)$/;
    my $thistype=$1;
    my $thisembstyle=&Apache::loncommon::fileembstyle($thistype);
    $r->print('<h2>'.&mt('Publishing').' '.
     &Apache::loncommon::filedescription($thistype).' <tt>');
   
    $r->print(<<ENDCAPTION);
   <a href='javascript:void(window.open("/~$cuname/$thisdisfn","cat","height=300,width=500,scrollbars=1,resizable=1,menubar=0,location=1"))'>
   $thisdisfn</a>
   ENDCAPTION
           $r->print('</tt></h2><b>'.&mt('Target').':</b> <tt>'.
     $thisdistarget.'</tt><br />');
      
    if (($cuname ne $ENV{'user.name'})||($cudom ne $ENV{'user.domain'})) {
       $r->print('<h3><font color="red">'.&mt('Co-Author').': '.
         $cuname.&mt(' at ').$cudom.'</font></h3>');
    }
   
    if (&Apache::loncommon::fileembstyle($thistype) eq 'ssi') {
       $r->print(<<ENDDIFF);
   <br />
   <a href='javascript:void(window.open("/adm/diff?filename=/~$cuname/$thisdisfn&versiontwo=priv","cat","height=300,width=500,scrollbars=1,resizable=1,menubar=0,location=1"))'>
   ENDDIFF
               $r->print(&mt('Diffs with Current Version').'</a><br />');
    }
     
   # ------------------ Publishing from $thisfn to $thistarget with $thisembstyle.
   
    unless ($ENV{'form.phase'} eq 'two') {
       my ($outstring,$error)=&publish($thisfn,$thistarget,$thisembstyle);
       $r->print('<hr />'.$outstring);
    } else {
       $r->print('<hr />'.
       &phasetwo($r,$thisfn,$thistarget,$thisembstyle,$thisdistarget)); 
    }
       }
       $r->print('</body></html>');
   
       return OK;
 }  }
   
 1;  1;
 __END__  __END__
   
   =pod
   
   =back
   
   =back
   
   =cut
   
   

Removed from v.1.3  
changed lines
  Added in v.1.174


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