#
# publish
# Publish site (generate static HTML)
#
# Based loosely on GenHTMLPlugin
# Copyright (C) 2001 Motorola
#
# Revisions Copyright (C) 2002, Eric Scouten
# Cairo updates Copyright (C) 2004 Crawford Currie http://c-dot.co.uk
#
# TWiki WikiClone (see TWiki.pm for $wikiversion and other info)
#
# Copyright (C) 2001 Peter Thoeny, Peter@Thoeny.com
# Copyright (C) 2001 Sven Dowideit, svenud@ozemail.com.au
#
# This program 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.
#
# This program 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, published at
# http://www.gnu.org/copyleft/gpl.html
#
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use CGI::Carp qw(fatalsToBrowser);
use CGI;
use File::Copy;
use File::Path;
use lib ('.');
use lib ('../lib');
use TWiki;
use TWiki::Func;
use strict;
package TWiki::Contrib::Publish;
use vars qw($query $thePathInfo $theRemoteUser $theUrl $debug $ZipPubUrl $publishDir $publishUrlPath $blah $TWikiPubDir $PubUrlPath $VERSION );
$VERSION = 1.200;
$blah = "."; # x 16384;
# Main rendering loop.
sub main {
# Read command-line arguments and make
# Fill in default environment variables if invoked from command-line.
$ENV{HTTP_USER_AGENT} = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" if (!exists $ENV{HTTP_USER_AGENT});
$ENV{REMOTE_ADDR} = "127.0.0.1" if (!exists $ENV{REMOTE_ADDR});
$ENV{REMOTE_PORT} = "2509" if (!exists $ENV{REMOTE_PORT});
$ENV{REMOTE_USER} = "TWikiGuest" if (!exists $ENV{REMOTE_USER});
$ENV{REQUEST_METHOD} = "GET" if (!exists $ENV{REQUEST_METHOD});
$ENV{QUERY_STRING} = "" if (!exists $ENV{QUERY_STRING});
# Tweak environment variables depending on command-line arguments.
foreach my $arg (@ARGV) {
if ($arg =~ /^\-/) {
if ($arg eq "-y") {
if ($ENV{QUERY_STRING}) {
$ENV{QUERY_STRING} .= "&goAhead=yes";
} else {
$ENV{QUERY_STRING} = "goAhead=yes";
}
} elsif ( $arg eq "-d" ) {
$debug = 1;
} else {
die "Unknown command-line option $arg\n";
}
} else {
$ENV{PATH_INFO} = "/$arg" if (!exists $ENV{PATH_INFO});
}
}
die "Don't know what web to publish\n" if (!exists $ENV{PATH_INFO});
# Now do normal CGI init.
$query = new CGI;
my $thePathInfo = $query->path_info();
my $theRemoteUser = $query->remote_user();
my $theTopic = $query->param('topic');
my $theUrl = $query->url;
my $goAhead = $query->param('goAhead');
my $configtopic = $query->param('configtopic');
if ( defined($query->param('debug')) ) {
$debug = $query->param('debug');
}
print "Content-type: text/plain\n\n" if ($debug);
$| = 0 if ($debug);
my ($topic, $web, $scriptUrlPath, $userName, $dataDir)
= TWiki::initialize($thePathInfo, $theRemoteUser, $theTopic, $theUrl, $query);
print "INIT : $topic, $web, $scriptUrlPath, $userName, $dataDir\n" if ($debug);
# Load defaults from a config topic if one was specified
my ($config_meta, $config_text);
my ($inclusions, $exclusions, $topicsearch, $skin);
my $notify="off";
if ( $configtopic && ! &TWiki::Store::topicExists($web, $configtopic) ) {
die "Specified configuration topic does not exist!\n";
} elsif ( $configtopic ) {
my ($meta, $text) = &TWiki::Store::readTopic($web, $configtopic);
$text =~ s|\cM|\n|sgmio;
#print "got configtopic text = '$text'\n";
while ( $text =~ m|^\s+\*\s+Set\s+([A-Z]+)\s*=(.*?)$|smgio ) {
my $k = $1;
my $v = $2;
$v =~ s/^\s*(.*?)\s*$/$1/go;
if ( $k eq "INCLUSIONS" ) { $inclusions = $v; }
elsif ( $k eq "EXCLUSIONS" ) { $exclusions = $v; }
elsif ( $k eq "NOTIFY" ) { $notify = $v; }
elsif ( $k eq "TOPICSEARCH" ) { $topicsearch = $v; }
elsif ( $k eq "SKIN" ) { $skin = $v; }
}
}
if ( defined($query->param('notify')) ) {
$notify = $query->param('notify');
}
if ( defined($query->param('inclusions')) ) {
$inclusions = $query->param('inclusions');
}
if ( defined($query->param('exclusions')) ) {
$exclusions = $query->param('exclusions');
}
if ( defined($query->param('topicsearch')) ) {
$topicsearch = $query->param('topicsearch');
}
if ( defined($query->param('skin')) ) {
$skin = $query->param('skin');
}
# Choose skin if nothing provided
if ( ! $skin ) {
$skin = TWiki::Func::getPreferencesValue("SKIN");
}
my $wikiUserName = TWiki::Func::userToWikiName($userName);
# Print environment variables for debugging.
if ($debug) {
foreach my $key (sort keys %ENV) {
print "$key => $ENV{$key}\n";
}
}
# Make sure TWiki.cfg has proper variables.
$publishDir = TWiki::Func::getPreferencesValue("PUBLISH_DIR");
$publishUrlPath = TWiki::Func::getPreferencesValue("PUBLISH_URL_PATH");
$TWikiPubDir = TWiki::Func::getPubDir();
$PubUrlPath = TWiki::Func::getPubUrlPath();
if (!$publishDir || !$publishUrlPath) {
TWiki::Func::redirectCgiQuery($query, TWiki::Func::getOopsUrl($web, $topic, "oopspublisherr"));
return;
}
# Make sure appropriate directories exist. Create them if they don't.
if (!-d $publishDir && ! -e $publishDir) {
print "mkdir $publishDir\n" if ($debug);
mkdir($publishDir, 0777);
} elsif ( !-d $publishDir && -e $publishDir ) {
print "FAIL: $publishDir exists but isn't a directory\n" if ($debug);
&TWiki::redirect($query, &TWiki::getOopsUrl($web, $topic,
"oopspublisherr"));
return;
}
if (!-d "$publishDir") {
print "FAIL: $publishDir didn't get created\n" if ($debug);
TWiki::redirect($query, &TWiki::getOopsUrl($web, $topic,
"oopspublisherr"));
return;
}
my $tmp = TWiki::Func::formatTime(time());
$tmp =~s/^(\d+)\s+(\w+)\s+(\d+).*/$1_$2_$3/g;
my $zipfilename=$theRemoteUser . "_" . $web . "_" . $tmp .".zip";
# Has user selected topic(s) yet?
if (!$goAhead) {
&chooseTopicScreen($web, $topic);
} else {
print "START PUBLISHING...\n" if ($debug);
my $succeeded = publishWeb($web, $topic, $inclusions,
$exclusions, $skin, $topicsearch,
"$publishDir/$zipfilename");
my $successURL = &TWiki::getOopsUrl($web, $topic,
"oopspublished",
"$publishUrlPath/$zipfilename");
# Send e-mail notification.
my @notifylist = TWiki::getEmailNotifyList($web);
if ($notify ne "off" && @notifylist && ($#notifylist >= 0)) {
my $text = "From: " . TWiki::Prefs::getPreferencesValue("WIKIWEBMASTER") . "\n";
$text .= "To: " . (join ', ', @notifylist) . "\n";
$text .= "Subject: \%WIKITOOLNAME\%.\%WEB\% - Automated notification of publication\n\n";
$text .= "This is an automated email notification of \%WIKITOOLNAME\%.\n\n";
$text .= "The web \%WEB\% has been published in ZIP file $TWiki::urlHost$publishUrlPath/$zipfilename\n\n\n";
$text .= "Review recent changes in:\n";
$text .= " $TWiki::urlHost$scriptUrlPath/view%SCRIPTSUFFIX%/%WEB%/WebChanges\n\n";
$text .= "Subscribe / Unsubscribe in:\n";
$text .= " $TWiki::urlHost$scriptUrlPath/view%SCRIPTSUFFIX%/%WEB%/%NOTIFYTOPIC%\n\n";
$text = TWiki::Func::expandCommonVariables( $text, $topic, $web );
my $error = TWiki::Net::sendEmail( $text );
}
# Succeeded.
TWiki::redirect($query, $successURL) if ($succeeded);
}
}
# Display screen so user can decide which pages to publish.
#
# @param $web the web to publish
# @param $topic topic that was selected
sub chooseTopicScreen {
my ($web, $topic) = @_;
# Write HTTP headers.
TWiki::writeHeader($query);
# Render publish confirm screen.
my $tmpl = TWiki::Store::readTemplate("publish");
$tmpl = TWiki::Func::expandCommonVariables($tmpl, $topic, $web);
$tmpl = TWiki::Func::renderText($tmpl, $web);
$tmpl =~ s/%RESEARCH/%SEARCH/go; # Pre search result from being rendered
$tmpl = TWiki::Func::expandCommonVariables($tmpl, $topic, $web);
print $tmpl;
}
# Publish the contents of one web.
#
# @param $web which web to publish
# @param $topic topic that was selected
# @param $inclusions REs describing which topics to include
# @param $exclusions REs describing which topics to exclude
#
# @return 1 if succeeded; 0 if failed (will have redirected to error page)
sub publishWeb {
my ($web, $topic, $inclusions, $exclusions, $skin, $topicsearch, $destZip) = @_;
# Get list of topics from this web.
my @topics = TWiki::Func::getTopicList($web);
# Parse list of includes/excludes.
$inclusions = ".*" unless (defined ($inclusions));
# BCD Modified to include any whitespace as a separator
my @include = split( /[\s]+/, $inclusions );
$exclusions = "Web.*" unless (defined ($exclusions));
# BCD Modified to include any whitespace as a separator
my @exclude = split( /[\s]+/, $exclusions );
my $zip = Archive::Zip->new();
# Attempt to render each included page.
my %copied;
foreach my $topic (@topics) {
# BCD - biggest change 10 Nov 2004
# Replaced greps with old _inSet call from older version
print "TOPICS : $topic\n" if ($debug);
# next unless grep { /^$topic/ } @include;
# next if grep { /^$topic/ } @exclude;
# publishTopic($web, $topic, $skin, \%copied,
# $topicsearch, $zip);
publishTopic($web, $topic, $skin, \%copied, $topicsearch, $zip) if (&_inSet($topic, @include) && !&_inSet($topic, @exclude));
}
print "ZIP CREATED : $destZip\n" if ($debug);
$zip->writeToFileNamed( $destZip );
return 1;
}
# Publish one topic from web.
#
# @param $web which web to publish
# @param $topic which topic to publish
# @param $skin which skin to use
# @param \%copied map of copied resources to new locations
sub publishTopic {
my ($web, $topic, $skin, $copied, $topicsearch, $zip) = @_;
print "\npublishTopic($web, $topic, $skin, ...) $blah\n" if ($debug);
# Used to set the topic when traversing the web.
# THIS IS NASTY - but I can't find any other reliable way
# to force TWiki to change topic.
my $thePathInfo = $query->path_info();
my $theRemoteUser = $query->remote_user();
my $theUrl = $query->url;
my ($itopic, $iweb, $iscriptUrlPath, $iuserName, $idataDir) =
TWiki::initialize($thePathInfo, $theRemoteUser, $topic, $theUrl, $query);
die "Bad re-init (web = $iweb, should be $web)\n" if ($iweb ne $web);
die "Bad re-init (topic = $itopic, should be $topic)\n" if ($itopic ne $topic);
# Choose template.
print " Read template$blah\n" if ($debug);
my $tmpl = TWiki::Func::readTemplate("view", $skin);
die "Couldn't find template\n" if(!$tmpl);
# FIXME should have an oops template...
# Read topic data.
print " Read topic$blah\n" if ($debug);
my ($meta, $text) = TWiki::Func::readTopic( $web, $topic );
if ( $topicsearch ) {
if ( $text !~ /$topicsearch/ ) {
print " Topic doesn't match search criteria ($topicsearch), skipping.\n" if ($debug);
return;
}
}
my ($revdate, $revuser, $maxrev) = TWiki::Store::getRevisionInfoFromMeta($web, $topic, $meta, "isoFormat");
$revuser = TWiki::userToWikiName($revuser);
# TO DO: Check page permissions.
print " Check page permissions$blah\n" if ($debug);
# Swap in revision info.
# [scouten 12/08/02]: Omit revision number and author.
my $shortRevDate = TWiki::Func::renderText("$revdate GMT", $web);
$shortRevDate =~ s( \- \d\d:\d\d GMT)()o;
$tmpl =~ s/%REVINFO%/$shortRevDate/go;
# Handle standard formatting.
print " Handle standard formatting (text)$blah\n" if ($debug);
$text = TWiki::Func::expandCommonVariables($text, $topic, $web);
$text = TWiki::Func::renderText($text);
print " Handle standard formatting (topic)$blah\n" if ($debug);
$tmpl = TWiki::Func::expandCommonVariables($tmpl, $topic, $web);
$tmpl = TWiki::handleMetaTags($web, $topic, $tmpl, $meta, 1);
print " Handle standard formatting (meta)$blah\n" if ($debug);
#print "--- \$tmpl:\n$tmpl\n\n";
#print "--- \$meta:\n";
#foreach my $key (sort keys %$meta) {
#print " $key > $meta->{$key}\n";
#}
#print "\n\n----\n\n$blah";
$tmpl = TWiki::Func::renderText($tmpl, "", $meta); ## better to use meta rendering?
print " Merge content$blah\n" if ($debug);
$tmpl =~ s/%TEXT%/$text/go;
$tmpl =~ s/%MAXREV%/1.$maxrev/go;
$tmpl =~ s/%CURRREV%/1.$maxrev/go;
$tmpl =~ s/%REVTITLE%//go;
$tmpl =~ s|( ?) **nop/*>\n?|$1|gois; # remove tags (PTh 06 Nov 2000)
# Strip unsatisfied WikiWords.
my $ult = getUnsatisfiedLinkTemplate($web);
$tmpl =~ s/$ult/$1/g;
# Copy files from pub dir to rsrc dir in static dir.
print " Copy files if [$PubUrlPath] $blah\n" if ($debug);
$tmpl =~ s((?<==\")(?:http://localhost)?$PubUrlPath\/([^\"]+)(?=\"))(©Resource($web, $1, $copied, $zip))geo;
$tmpl =~ s((?<==\")$PubUrlPath\/([^\"]+)(?=\"))(©Resource($web, $1, $copied, $zip))geo;
# Modify internal links.
print " Update internal links $blah\n" if ($debug);
my $ilt = getInternalLinkTemplate($web, $topic);
$tmpl =~ s/$ilt/$4<\/a>/g;
# Scrap anything inside elements.
print " Scrap $blah\n" if ($debug);
$tmpl =~ s(.*?<\/nopublish>)()gos;
# Remove base tag - DZA
$tmpl =~ s/]+\/>//;
# Fix some links - DZA
$tmpl =~ s!addString( "$tmpl", "$topic.html" );
print " Done $blah\n" if ($debug);
}
# Copy a resource (image, style sheet, etc.) from twiki/pub/%WEB% to
# static HTML's rsrc directory.
#
# @param $web name of web
# @param $rsrcName name of resource (relative to pub/%WEB%)
# @param \%copied map of copied resources to new locations
sub copyResource {
my ($web, $rsrcName, $copied, $zip) = @_;
# See if we've already copied this resource.
if (exists $copied->{$rsrcName}) {
print " Skip resource $rsrcName -- already copied$blah\n" if ($debug);
} else {
# Nope, it's new. Gotta copy it to new location.
# Split resource name into path (relative to pub/%WEB%) and leaf name.
my $file = $rsrcName;
$file =~ s(^(.*)\/)()o;
my $path = "";
if ($rsrcName =~ "/") {
$path = $rsrcName;
$path =~ s(\/[^\/]*$)()o;
}
# Copy resource to rsrc directory.
print " Copy resource $TWikiPubDir/$rsrcName\n in rsrc/$path/$file \n" if ($debug);
if ( -f "$TWikiPubDir/$rsrcName" ) {
$zip->addDirectory( "rsrc/$path" );
$zip->addFile( "$TWikiPubDir/$rsrcName" , "rsrc/$path/$file" );
}
# Record copy so we don't duplicate it later.
my $destURL = "rsrc/$path/$file";
$destURL =~ s(//)(/)go;
$copied->{$rsrcName} = "$destURL";
}
return $copied->{$rsrcName};
}
# Returns a pattern that will match the HTML used by TWiki to represent an
# unsatisfied link. THIS IS NASTY, but I don't know how else to do it.
sub getUnsatisfiedLinkTemplate {
my ($web) = @_;
my $t = "!£%^&*(){}";# must _not_ exist!
my $linkFmt = TWiki::Render::internalLink("", $web, $t, "TheLink", undef, 1);
$linkFmt =~ s/\//\\\//go;
my $pre = $linkFmt;
$pre =~ s/TheLink.*//o;
my $post = $linkFmt;
$post =~ s/.*TheLink//o;
$post =~ s/\"[^\"]*\"/\"[^\"]*\"/o;
$post =~ s/\?/\\?/o;
return $pre . "(.*?)" . $post;
}
# Returns a pattern that will match the HTML used by TWiki to represent an
# internal link. THIS IS NASTY, but I don't know how else to do it.
sub getInternalLinkTemplate {
my ($web, $topic) = @_;
my $linkFmt = TWiki::Render::internalLink("", $web, $topic, "TheLink", undef, 1);
$linkFmt =~ s/$web\/$topic/$web\/([^"#]*)([^"]*)/g;
$linkFmt =~ s/\//\\\//go;
$linkFmt =~ s/>TheLink/([^>]*?)>(.*?)/go;
return $linkFmt;
}
# ******************************************************************************
#
# Return 1 if $topic is in @set.
#
# BCD - Brought this back in from the old version since it seems to work
sub _inSet {
my ($topic, @set) = @_;
foreach my $item (@set) {
return 1 if ($item ne "" && $topic =~ /^$item/);
}
return 0;
}
1;