#!/usr/bin/perl -w
#
# TWiki Collaboration Platform, http://TWiki.org/
#
# For licensing info read license.txt file in the TWiki root.
# 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
#
# Configuration script for TWiki. Once you have a basic webserver
# configuration that lets you access this script, the rest of the
# configuration process is done from here. This script replaces
# the old "testenv" script.
#
# The script works by accepting values into a CGI form, and then
# submitting those values back to itself with a parameter (update)
# set to 1. This causes it to write config changes to LocalSite.cfg.
# Note that changes are only written if there is a real change in the
# value.
#
# The values available to configuration are determined by parsing
# TWiki.cfg. Special full-line comments guide the parse:
# Any comment of the form
#---+ Some text
# is taken as a foldable block, and following comments are dragged in too.
# ---++ is H3, ---+++ is H4 etc
# Comments of the form
# **TYPE opts**
# where TYPE is one of URL, PATH, URLPATH, BOOLEAN, STRING, REGEX, SELECT
# are used to indicate that a following cfg var is configurable through
# the interface. All intermediate comments are taken as documentation for
# the value.
#
package TWiki;

# BASIC checks. Without these, nothing works.

use strict;

$SIG{__DIE__} = sub { Carp::confess( $_[0] || '' ) };
$SIG{'__WARN__'} = sub { die @_ };

use vars qw( %cfg
             $perlver
             $perlvernum
             $perlverRequired
             $perlverRequiredString
             $perlverRecommended
             $perlVerPreferred
             $ActivePerlRecommendedBuild
             $GUESSED
             $cgiModVerRecommended
             $modPerlVersionRecommended
             $rcsverRequired
           );

# Constants
$perlver;
$perlvernum = $];
$perlverRequired = 5.00503;        # Oldest supported version of Perl
$perlverRequiredString = '5.005_03';
$perlverRecommended = '5.6.1';
$perlVerPreferred = 5.006;    # 5.6 or higher has [:lower:] etc
$ActivePerlRecommendedBuild = 631;    # Fixes PERL5SHELL bugs
$GUESSED = <<'HERE';
I guessed this setting. You are advised to confirm this setting (and any other guessed settings) and hit 'Next' to save before changing any other settings.
HERE

# CGI.pm version, on some platforms - actually need CGI 2.93 for mod_perl
# 2.0 and CGI 2.90 for Cygwin Perl 5.8.0.  See 
# http://perl.apache.org/products/apache-modules.html#Porting_CPAN_modules_to_mod_perl_2_0_Status
$cgiModVerRecommended = '2.93';

# Recommended mod_perl version if using mod_perl 2.0
# (see Support.RegistryCookerBadFileDescriptor)
$modPerlVersionRecommended = '1.99_12';

$rcsverRequired = 5.7;

# constants used in TWiki.cfg
use vars qw($TRUE $FALSE );
use vars qw( $basicMods $requiredMods $requiredModsNonUnix $optionalMods
             $I18Mods $I18Mods_perl56 $I18Mods_perl58 );

BEGIN {
    $TRUE = 1;
    $FALSE = 0;
    # Set default current working directory
    if( $ENV{SCRIPT_FILENAME} && $ENV{SCRIPT_FILENAME} =~ /^(.+)\/[^\/]+$/ ) {
        chdir $1;
    }
    # Get Perl version
    if (defined $^V) {
        $perlver = $^V;             # New in Perl 5.6.1, one byte per part
        $perlver = ord(substr($perlver,0)) . "." . ord(substr($perlver,1))
          . "." . ord(substr($perlver,2));
    } else {
        $perlver = $perlvernum
    }

    # Required for configure to work
    $basicMods =
      {
          'CGI'             => "basic TWiki",
          'CGI::Carp'       => "basic TWiki",
          'Error'           => 'basic TWiki',
      };
    $requiredMods =
      {
          'File::Copy'      => 'basic TWiki',
          'File::Spec'      => 'basic TWiki',
          'FileHandle'      => 'basic TWiki',
          'Algorithm::Diff' => 'basic TWiki',
      };

    # Required on non-Unix platforms (mainly Windows)
    $requiredModsNonUnix =
      {
          'Digest::SHA1' => "register script",
          'MIME::Base64' => "register script",
          'Net::SMTP'    => "registration emails and mailnotify",
      };
    # Optional modules on all platforms
    $optionalMods =
      {
          'MIME::Base64'     => "HTTP Authentication to proxies",
          'POSIX'            => "I18N (core module) and Security",
          'Digest::MD5'      => "MD5 encoded passwords",
          'Text::Diff'       => 'UpgradeTWiki',
          'CGI::Cookie'      => "sessions",
          'CGI::Session'     => "sessions",
      };

    $I18Mods = 
    {
        'Locale::Maketext::Lexicon' => "I18N translations",
    };

    $I18Mods_perl56 =
      {
          'Unicode::String'  => 'I18N conversions',
          'Unicode::MapUTF8' => "I18N conversions",
          'Unicode::Map'     => "I18N conversions",
          'Unicode::Map8'    => "I18N conversions",
          'Jcode'            => "I18N conversions",
      };

    $I18Mods_perl58 =
      {
          'Encode'           => "I18N conversions (core module in Perl 5.8)",
      };
};

########################################################################
##################### GLOBAL VARIABLES #################################
########################################################################

use CGI qw( :any );

use vars qw( $cygwinRcsVerNum $perltype $query $action );

$query = new CGI;
$action = $query->param('action') || '';

if( $action eq 'image' ) {
    _serveImage( $query->param('type'), $query->param('image' ));
    exit 0;
}

use vars qw( $errors $toterrors $warnings $totwarnings $url $nextid );

$errors = 0; # reset for each block
$toterrors = 0;
$warnings = 0; # reset for each block
$totwarnings = 0;
$url = $query->url();
$nextid = 0;

########################################################################
########################## FORMATTING ##################################
########################################################################

# a note
sub NOTE {
    return CGI::p({class=>"info"}, join("\n",@_));
}

# a warning
sub WARN {
    $warnings++;
    $totwarnings++;
    return CGI::div(CGI::span({class=>'warn'}, CGI::strong('Warning: ').join("\n",@_)));
}

# an error
sub ERROR {
    $errors++;
    $toterrors++;
    return CGI::div(CGI::span({class=>'error'}, CGI::strong('Error: ').join("\n",@_)));
}

# Generate a foldable block (twisty). This is a DIV with a table in it
# that contains the settings and doc rows.
sub _foldableBlock {
    my( $head, $attr, $body ) = @_;
    my $headText = $head . CGI::span({ class => 'blockLinkAttribute' }, $attr);
    $body = CGI::start_table({width => '100%', -border => 0, -cellspacing => 0, -cellpadding => 0}).$body.CGI::end_table();
    my $mess = '';
    my $errorsMess = ($errors > 1) ? ' errors' : ' error';
    my $warningsMess = ($warnings > 1) ? ' warnings' : ' warning';
    $mess .= CGI::span({class=>'error'}, $errors . $errorsMess) if $errors;
    if ($errors && $warnings) {
        $mess .= '&nbsp;';
    }
    $mess .= CGI::span({class=>'warn'}, $warnings . $warningsMess) if $warnings;
    $errors = $warnings = 0;

    my $anchor = _makeAnchor( $head );
    my $id = $anchor;
    my $blockId = $id;
    my $linkId = 'blockLink'.$id;
    my $linkAnchor = $anchor.'link';
    return CGI::a({ name => $linkAnchor }).
           CGI::a( {id => $linkId,
                    class => 'blockLink blockLinkOff',
                    href => '#'.$linkAnchor,
                    rel => 'nofollow',
                    onclick => 'foldBlock(\'' . $id . '\'); return false;'}, $headText.$mess).
           CGI::div( { id => $blockId,
                       class=> 'foldableBlock foldableBlockClosed' }, $body ).
                       "\n";
}

# Generate an ordinary inline headedblock
sub _ordinaryBlock {
    my( $depth, $head, $attr, $body ) = @_;
    $head .= CGI::span({ class => 'blockLinkAttribute' }, $attr) if $attr;
    if( $depth == 2 ) { $head = CGI::h1( $head ); }
    elsif( $depth == 3 ) { $head = CGI::h2( $head ); }
    elsif( $depth == 4 ) { $head = CGI::h3( $head ); }
    elsif( $depth == 5 ) { $head = CGI::h4( $head ); }
    elsif( $depth == 6 ) { $head = CGI::h5( $head ); }
    else { $head = CGI::h6( $head ); }
    return CGI::Tr(CGI::td( { colspan => 2 } , $head)).
        $body;
}

# Generate a sub-heading table row
sub _subHead {
    my $text = shift;
    return CGI::Tr(CGI::Td({class=>'subHead', colspan=>2}, $text)).
      "\n";
}

# Generate a variable - prompt row
sub _setting {
    my $key = shift;
    return CGI::Tr(CGI::td({class=>'firstCol'}, $key).
                   CGI::td({class=>'secondCol'}, join(' ', @_)));
}

# generate a documentation table row
sub _docBlock {
    my $desc = shift || '';
    my $hidden = shift;
    if (length($desc) == 0 || $desc =~ /\A\s\Z/) { 
        return '';
    }
    if ($hidden) {
        return CGI::Tr( {class => 'hiddenRow' }, CGI::td( { colspan => 2, class=>'docdata info' }, $desc )).
      "\n";
    }
    return CGI::Tr( CGI::td( { colspan => 2, class=>'docdata info' }, $desc )).
      "\n";
}

# encode a string to make an HTML anchor
sub _makeAnchor {
    my $str = shift;

    $str =~ s/\s(\w)/uc($1)/ge;
    $str =~ s/\W//g;
    return $str;
}

########################################################################
################### CHECKING SUPPORT ###################################
########################################################################

# Since Windows (without Cygwin) makes it hard to capture stderr
# ('2>&1' works only on Win2000 or higher), and Windows will usually have
# GNU tools in any case (installed for TWiki since there's no built-in
# diff, grep, patch, etc), we only check for these tools on Unix/Linux
# and Cygwin.
sub _checkGnuProgram {
    my $prog = shift;
    my $n = '';

    if( $TWiki::cfg{OS} eq 'UNIX' ||
          $TWiki::cfg{OS} eq 'WINDOWS' && $perltype eq 'Cygwin' ) {
        $prog =~ s/^\s*(\S+)\s.*$/$1/;
        $prog =~ /^(.*)$/;
        $prog = $1;
        # check for taintedness
        die "$prog is tainted" unless eval { $n = $prog, kill 0; 1 };
        my $diffOut = ( `$prog --version 2>&1` || "");
        my $notFound = ( $? == -1 );
        if( $notFound ) {
            $n = WARN("'$prog' program was not found on the",
                      "current PATH.");
        } elsif ( $diffOut !~ /\bGNU\b/ ) {
            # Program found on path, complain if no GNU in version output
            $n = WARN("'$prog' program was found on the PATH",
                      "but is not GNU $prog - this may cause",
                      "problems. $diffOut");
        } else {
            $diffOut =~ /(\d+(\.\d+)+)/;
            $n = "($prog is version $1).";
        }
    }

    return $n;
}

sub _checkRCSProgram {
    my $key = shift;

    return 'Not used in this configuration.'
      unless $TWiki::cfg{StoreImpl} eq 'RcsWrap';
    my $mess = '';
    my $err = '';
    my $prog = $TWiki::cfg{RCS}{$key} || '';
    $prog =~ s/^\s*(\S+)\s.*$/$1/;
    $prog =~ /^(.*)$/; $prog = $1;
    if( !$prog ) {
        $err .= $key.' is not set';
    } else {
        my $version = `$prog -V` || '';
        if( $@ ) {
            $err .= ERROR($prog.' returned an error: '.$@ );
        } elsif ( $version ne '' ) {
            $version =~ /(\d+(\.\d+)+)/;
            $version = $1;
            $mess .= " ($prog is version $version)";
        } else {
            $err .= ERROR($prog.' did not return a version number (or might not exist..)');
        }
        if( defined( $cygwinRcsVerNum )) {
            $mess .= " (Cygwin package <tt>rcs-$cygwinRcsVerNum</tt>)";
        }
        if( $version && $version < $rcsverRequired ) {
            # RCS too old
            $err .= $prog.' is too old, upgrade to version '.
              $rcsverRequired.' or higher.';
        }
    }
    if( $err ) {
        $mess .= ERROR( $err .<<HERE
TWiki will probably not work with this RCS setup. Either correct the setup, or
switch to RcsLite. To enable RCSLite you need to change the setting of
{StoreImpl} to 'RcsLite'.
HERE
                       );
    }
    return $mess;
}

sub _checkBinDir {
    my $dir = $ENV{SCRIPT_FILENAME} || '.';
    $dir =~ s(/+configure[^/]*$)();
    my $ext = $TWiki::cfg{ScriptSuffix} || '';
    my $errs = '';
    opendir(D, $dir) or return ERROR("Cannot open $dir for read - check it exists, and that permissions are correct");
    foreach my $script (grep { !/(^\.|\.cfg|\.txt)/ } readdir D) {
        next if( $ext && $script !~ /\.$ext$/ );
        if( !-x "$dir/$script" ) {
            $errs .= WARN($script . ' is not executable');
        }
    }
    closedir(D);
    return $dir.CGI::br().$errs;
}

sub _checkCanCreateFile {
    my $name = shift;
    if (-e $name) {
        # if the file exists just check perms and return
        return _checkTreePerms($name,'rw');
    }
    my $txt1 = "test 1 2 3";
    unlink $name if( -e $name );
    open( FILE, ">$name" ) ||
      return 'Could not create test file '. $name;
    print FILE $txt1;
    close( FILE);
    open( IN_FILE, "<$name" ) ||
      return 'Could not read test file '. $name;
    my $txt2 = <IN_FILE>;
    close( IN_FILE );
    unlink $name if( -e $name );
    unless ( $txt2 eq $txt1 ) {
        return 'Could not write and then read '.$name;
    }
    return '';
}

sub _checkTreePerms {
    my( $path, $perms, $filter ) = @_;

    return '' if( defined($filter) && $path !~ $filter && !-d $path);

    #lets ignore Subversion directories
    return '' if( $path !~ /_svn/ );
    return '' if( $path !~ /.svn/ );

    my $errs = '';

    return $path. ' cannot be found'.CGI::br() unless( -e $path );

    if( $perms =~ /r/ && !-r $path) {
        $errs .= ' readable';
    }

    if( $perms =~ /w/ && !-d $path && !-w $path) {
        $errs .= ' writable';
    }

    if( $perms =~ /x/ && !-x $path) {
        $errs .= ' executable';
    }

    return $path.' is not '.$errs.CGI::br() if $errs;

    return '' unless -d $path;

    opendir(D, $path) ||
      return 'Directory '.$path.' is not readable.'.CGI::br();

    foreach my $e ( grep { !/^\./ } readdir( D )) {
        my $p = $path.'/'.$e;
        $errs .= _checkTreePerms( $p, $perms, $filter );
    }
    closedir(D);
    return $errs;
}

sub _findFileOnPath {
    my $file = shift;
    $file =~ s(::)(/)g;

    foreach my $dir ( @INC ) {
        if ( -e "$dir/$file" ) {
            return "$dir/$file";
        }
    }
    return undef;
}

# Try and locate a required directory
sub _findMajorDir {
    my( $cfg, $dir ) = @_;
    return '' if( $TWiki::cfg{$cfg} && $TWiki::cfg{$cfg} ne 'NOT SET');
    my $guess = $ENV{SCRIPT_FILENAME};
    unless( $guess ) {
        return WARN("This web server does not set SCRIPT_FILENAME so I can't guess a value for this");
    }
    $guess =~ s(bin/*configure$)();
    $guess .= $dir;
    $TWiki::cfg{$cfg} = $guess;
    return WARN($GUESSED);
}

sub _warnAboutWindowsBackSlashes {
   my ( $path ) = @_;
   if ( $path =~ /\\/ ) {
      return WARN('You should use c:/path style slashes, not c:\path in "'.$path.'"');
   }
}

########################################################################
##################### PROMPT GENERATORS ################################
########################################################################

# generate an input field for string types
sub _PROMPT_FOR_STRING {
    my( $id, $opts, $value, $keys ) = @_;
    my $size = 60;
    if( $opts =~ /\s(\d+)\s/ ) {
        $size = $1;
    }
    # support upgrade from old configuration, where LowerNational and UpperNational
    # were stored as REGEX'es (now they are STRING's):
    if ( $id eq "LowerNational" || $id eq "UpperNational" ) { 
        if ($value =~ /^\(\?-xism:(.*)\)$/) {
            $value = $1;
        }
    }

    return CGI::textfield( -name => $id, -size=>$size, -default=>$value );
}

# generate an input field for URL types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_URL {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>60, -default=>$value );
}

# generate an input field for URLPATH types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_URLPATH {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>60, -default=>$value );
}

# generate an input field for PATH types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_PATH {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>60, -default=>$value );
}

# generate an input field for BOOLEAN types
sub _PROMPT_FOR_BOOLEAN {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::checkbox( -name => $id, -checked => ( $value ? 1 : 0),
                          -value => 1, -label => '' );
}

# generate an input field for REGEX types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_REGEX {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>60, -default=>$value );
}

# generate an input field for COMMAND types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_COMMAND {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>60, -default=>$value );
}

# generate an input field for NUMBER types
# This has its own type in case someone wants to add javascript validation
sub _PROMPT_FOR_NUMBER {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>20, -default=>$value );
}

# generate an input field for OCTAL number types (protections)
sub _PROMPT_FOR_OCTAL {
    my( $id, $opts, $value, $keys ) = @_;
    return CGI::textfield( -name => $id, -size=>20,
                           -default=>sprintf('0%o',$value) );
}

# generate an input field for SELECT types
sub _PROMPT_FOR_SELECT {
    my( $id, $opts, $value, $keys ) = @_;
    $opts =~ s/^\s+//;
    $opts =~ s/\s.*$//;
    my $sopts = '';
    foreach my $opt (split( /\s*,\s*/, $opts)) {
        if( $opt eq $value ) {
            $sopts .= '<option selected="selected">'.$opt.'</option>';
        } else {
            $sopts .= '<option>'.$opt.'</option>';
        }
    }
    return CGI::Select({ name => $id, size=>1 }, $sopts);
}

########################################################################
###################### VARIABLE CHECKERS ###############################
########################################################################

sub _CHECKVAR_DefaultUrlHost {
    my $keys = shift;

    if( $TWiki::cfg{DefaultUrlHost} &&
       $TWiki::cfg{DefaultUrlHost} ne 'NOT SET' ) {
        my $host = $ENV{HTTP_HOST};
        if( $host && $TWiki::cfg{DefaultUrlHost} !~ /$host/ ) {
            return WARN('Current setting does not match HTTP_HOST ',
                        $ENV{HTTP_HOST});
        }
    } else {
        my $protocol = $url || 'http://'.$ENV{HTTP_HOST};
        $protocol =~ s(^(.*?://.*?)/.*$)($1);
        $TWiki::cfg{DefaultUrlHost} = $protocol;
        return ERROR($GUESSED);
    }
    return '';
}

sub _CHECKVAR_ScriptUrlPath {
    # Check Script URL Path against REQUEST_URI
    my $n;
    my $val = $TWiki::cfg{ScriptUrlPath};
    my $guess = $ENV{REQUEST_URI} || $ENV{SCRIPT_NAME} || '';
    $guess =~ s(/+configure\b.*$)();

    if( $val && $val ne 'NOT SET' ) {
        unless( $guess ) {
            return WARN(<<HERE
This web server does not set REQUEST_URI or SCRIPT_NAME
so it isn't possible to check the correctness of this setting.
HERE
                       );
        };
        if ( $guess !~ /^$val/ ) {
            return WARN('I expected this to look like "'.$guess.'"');
        }
    } else {
        unless( $guess ) {
            return WARN(<<HERE
This web server does not set REQUEST_URI or SCRIPT_NAME
so it isn't possible to guess this setting.
HERE
                       );
        };
        $TWiki::cfg{ScriptUrlPath} = $guess;
        return ERROR($GUESSED);
    }
    return '';
}

sub _CHECKVAR_DispScriptUrlPath {
    # Check Script URL Path against REQUEST_URI
    my $n;
    my $val = $TWiki::cfg{DispScriptUrlPath};
    my $guess = $ENV{REQUEST_URI} || $ENV{SCRIPT_NAME} || '';
    $guess =~ s(/+configure[^/]*$)();

    if( !defined($val) || $val eq 'NOT SET' ) {
        $TWiki::cfg{DispScriptUrlPath} = $TWiki::cfg{ScriptUrlPath};
        return WARN($GUESSED);
    }
    return '';
}

sub _CHECKVAR_PubUrlPath {
    unless( $TWiki::cfg{PubUrlPath} && $TWiki::cfg{PubUrlPath} ne 'NOT SET') {
        my $guess = $TWiki::cfg{ScriptUrlPath};
        $guess =~ s/bin$/pub/;
        $TWiki::cfg{PubUrlPath} = $guess;
        return WARN($GUESSED);
    }
    return 'This is not set correctly if the link below is broken:'.CGI::br().
      '<a rel="nofollow" href="'.$TWiki::cfg{PubUrlPath}.'">Go to &quot;pub&quot; directory</a>';
}

sub _CHECKVAR_PubDir {
    my $e = _findMajorDir('PubDir', 'pub');
    $e .= _warnAboutWindowsBackSlashes($TWiki::cfg{PubDir});
    my $e2 = _checkTreePerms( $TWiki::cfg{PubDir}, 'rw' );
    $e .= WARN($e2) if $e2;
    return $e;
}

sub _CHECKVAR_TemplateDir {
    my $e = _findMajorDir('TemplateDir', 'templates');
    $e .= _warnAboutWindowsBackSlashes($TWiki::cfg{TemplateDir});
    my $e2 = _checkTreePerms( $TWiki::cfg{TemplateDir}, 'r' );
    $e .= ERROR($e2) if $e2;
    return $e;
}

sub _CHECKVAR_DataDir {
    my $e = _findMajorDir('DataDir', 'data');
    my $e2 = _checkTreePerms( $TWiki::cfg{DataDir}, "r" );
    $e .= _warnAboutWindowsBackSlashes($TWiki::cfg{DataDir});
    $e2 = _checkTreePerms( $TWiki::cfg{DataDir}, "w", qr/\.txt$/ )
      unless $e2;
    $e .= WARN($e2) if $e2;
    return $e;
}

sub _CHECKVAR_LocalesDir {
    my $e = _findMajorDir('LocalesDir', 'locale');
    my $e2 = _checkTreePerms( $TWiki::cfg{LocalesDir}, "r" );
    $e .= _warnAboutWindowsBackSlashes($TWiki::cfg{DataDir});
    $e .= ERROR($e2) if $e2;
    return $e;
}

sub _CHECKVAR_MailProgram {
    eval "use Net::SMTP";
    my $n;
    if ($@) {
        $n = "Net::SMTP is <b>not</b> installed in this environment. ";
        my $val = $TWiki::cfg{MailProgram} || '';
        $val =~ s/\s.*$//g;
        if( ! ( -e $val ) ) {
            return WARN("<tt>$val</tt> was not found. Check the path.");
        }
    } else {
        $n = 'Net::SMTP is installed in this environment, so this setting will <b>not</b> be used.';
    }
    return $n;
}

sub _CHECKVAR_LogFileName {
    my $logFile = $TWiki::cfg{LogFileName} || "";
    $logFile =~ s/%DATE%/DATE/;
    my $e = _checkCanCreateFile( $logFile );
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_ConfigurationLogName {
    my $logFile = $TWiki::cfg{ConfigurationLogName} || "";
    $logFile =~ s/%DATE%/DATE/;
    my $e = _checkCanCreateFile( $logFile );
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_WarningFileName {
    my $logFile = $TWiki::cfg{WarningFileName} || "";
    $logFile =~ s/%DATE%/DATE/;
    my $e = _checkCanCreateFile( $logFile );
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_DebugFileName {
    my $logFile = $TWiki::cfg{DebugFileName} || "";
    $logFile =~ s/%DATE%/DATE/;
    my $e = _checkCanCreateFile( $logFile );
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_MimeTypesFileName {
    my $e = _checkTreePerms($TWiki::cfg{MimeTypesFileName}, 'r');
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_Htpasswd_FileName {
    my $e = _checkTreePerms($TWiki::cfg{Htpasswd}{FileName}, 'r');
    $e = ERROR($e) if $e;
    return $e;
}

sub _CHECKVAR_RegistrationApprovals {
    my $file = $TWiki::cfg{RegistrationApprovals};
    my $e = _checkTreePerms( $file, 'rw' );
    $e = WARN($e) if $e;
    return $e;
}

sub _CHECKVAR_UseLocale {
    my $on = $TWiki::cfg{UseLocale};

    my $n = '';
    if( $TWiki::cfg{OS} eq 'WINDOWS' ) {
        # Warn re known broken locale setup
        $n .= WARN(<<HERE
Using Perl on Windows, which may have missing or incorrect locales (in Cygwin
or ActiveState Perl, respectively) - turning off {Site}{LocaleRegexes} is
recommended unless you know your version of Perl has working locale support.
HERE
                  );
    }

    # Warn against Perl 5.6 or lower for UTF-8
    if ( $perlvernum < 5.008 ) {
        $n .= WARN("Perl 5.8 is required if you are using TWiki's",
                   "experimental UTF-8 support\n");
    }

    # Check for 'useperlio' in Config on Perl 5.8 or higher - required
    # for use of ':utf8' layer.
    if ( $perlvernum >= 5.008 and 
         not ( exists $Config::Config{useperlio} and
               $Config::Config{useperlio} eq 'define' ) ) {
        $n .= WARN(<<HERE
This version of Perl was not compiled to use PerlIO by default ('useperlio'
not set in Config.pm, see <i>Perl's Unicode Model</i> in 'perldoc
perluniintro') - re-compilation of Perl will be required before it can be
used to enable TWiki's experimental UTF-8 support.
HERE
                  );
    }

    # Check for d_setlocale in Config (same as 'perl -V:d_setlocale')
    eval "use Config";
    if ( !( exists $Config::Config{d_setlocale} &&
            $Config::Config{d_setlocale} eq 'define' ) ) {
        $n .= WARN(<<HERE
This version of Perl was not compiled with locale support ('d_setlocale' not
set in Config.pm) - re-compilation of Perl will be required before it can be
used to support TWiki internationalisation.
HERE
                  );
    }
    return $n;
}

sub _CHECKVAR_Site_CharSet {
    # Extract the character set from locale and use in HTML templates
    # and HTTP headers
    unless( defined $TWiki::cfg{Site}{CharSet} ) {
        $TWiki::cfg{Site}{Locale} =~ m/\.([a-z0-9_-]+)$/i;
        $TWiki::cfg{Site}{CharSet} = $1 if defined $1;
        $TWiki::cfg{Site}{CharSet} =~ s/^utf8$/utf-8/i;
        $TWiki::cfg{Site}{CharSet} =~ s/^eucjp$/euc-jp/i;
        $TWiki::cfg{Site}{CharSet} = lc $TWiki::cfg{Site}{CharSet};
    }
    return '';
}

sub _CHECKVAR_UpperNational {
    if( $perlvernum < $perlVerPreferred || 1) {
        # Locales are off/broken, or using pre-5.6 Perl, so have to 
        # explicitly list the accented characters (but not if using UTF-8)
        my $forUpperNat = join '', grep { uc($_) ne $_ and m/[^a-z]/ } map { chr($_) } 1..255;

        if ($forUpperNat) {
            return WARN( <<HERE
The following upper case accented characters have been found in this locale
and should be considered for use in this parameter:
<strong>$forUpperNat</strong>
HERE
                       );
        }
    }
    return '';
}

sub _CHECKVAR_LowerNational {
    if( $perlvernum < $perlVerPreferred || 1) {
        # Locales are off/broken, or using pre-5.6 Perl, so have to 
        # explicitly list the accented characters (but not if using UTF-8)
        my $forLowerNat = join '', grep { uc($_) ne $_ and m/[^a-z]/ } map { chr($_) } 1..255;

        if ($forLowerNat) {
            return WARN( <<HERE
The following lower case accented characters have been found in this locale
and should be considered for use in this parameter:
<strong>$forLowerNat</strong>
HERE
                       );
        }
    }
    return '';
}

sub _CHECKVAR_Site_Locale {
    my $e = '';
    my $locale = $TWiki::cfg{Site}{Locale};
    setlocale(&LC_CTYPE, $locale);
    my $currentLocale = setlocale(&LC_CTYPE);
    if ( $currentLocale ne $locale ) {
        $e .= WARN(<<HERE
Unable to set locale to '$locale'. The actual locale is '$currentLocale'
- please test your locale settings. This warning can be ignored if you are
not planning to use locales (e.g. your site uses English only) - or you can
set  {Site}{Locale} to <code>C</code>, which should always work.
HERE
                   );
    }
    if( $locale !~ /[a-z]/i && $TWiki::cfg{UseLocale} ) {
        $e = WARN(<<HERE
UseLocale set but {Site}{Locale} '$locale' has no alphabetic characters
HERE
                 );
    }

    # Set the default site charset
    unless( defined( $TWiki::cfg{Site}{CharSet}) ) {
        $TWiki::cfg{Site}{CharSet} = 'iso-8859-15';
    }

    # Extract the default site language - ignores '@euro' part of
    # 'fr_BE@euro'-type locales.
    unless( defined( $TWiki::cfg{Site}{Lang} )) {
        $TWiki::cfg{Site}{Locale} =~ m/^([a-z]+)_([a-z]+)/i;
        $TWiki::cfg{Site}{Lang} = (lc $1) if defined $1;
    }

    unless( defined( $TWiki::cfg{Site}{FullLang} )) {
        $TWiki::cfg{Site}{Locale} =~ m/^([a-z]+)_([a-z]+)/i;
        $TWiki::cfg{Site}{FullLang} = (lc "$1-$2" )
          if defined $1 and defined $2;
    }

    # Check for unusable multi-byte encodings as site character set
    # - anything that enables a single ASCII character such as '[' to be
    # matched within a multi-byte character cannot be used for TWiki.
    # Refuse to work with character sets that allow TWiki syntax
    # to be recognised within multi-byte characters.
    # FIXME: match other problematic multi-byte character sets
    if( $TWiki::cfg{UseLocale} &&
        $TWiki::cfg{Site}{CharSet} =~
        m/^(?:iso-?2022-?|hz-?|gb2312|gbk|gb18030|.*big5|.*shift_?jis|ms.kanji|johab|uhc)/i ) {

        $e .= ERROR(<<HERE
Cannot use this multi-byte encoding ('$TWiki::cfg{Site}{CharSet}') as site character
encoding. Please set a different character encoding in the {Site}{Locale}
setting.
HERE
                   );
    }

    return $e;
}

sub _CHECKVAR_ScriptSuffix {
    # SMELL: should check to see what the extension on _this_ script
    # is, and generate a helpful message
    if ( defined $TWiki::cfg{ScriptSuffix} && $TWiki::cfg{ScriptSuffix} ne '' ) {
    if ( ! $query->path_info() =~ /$TWiki::cfg{ScriptSuffix}$/ ) {
        return ERROR('this script ('.$query->pather_info().') called with different ScriptSuffix setting'.$TWiki::cfg{ScriptSuffix});
    }
    }
    return '';
}

sub _CHECKVAR_RCS_EgrepCmd { return _checkGnuProgram($TWiki::cfg{RCS}{EgrepCmd}); }
sub _CHECKVAR_RCS_FgrepCmd { return _checkGnuProgram($TWiki::cfg{RCS}{FgrepCmd}); }
sub _CHECKVAR_RCS_initTextCmd { return _checkRCSProgram('initTextCmd'); }
sub _CHECKVAR_RCS_initBinaryCmd { return _checkRCSProgram('initBinaryCmd'); }
sub _CHECKVAR_RCS_tmpBinaryCmd { return _checkRCSProgram('tmpBinaryCmd'); }
sub _CHECKVAR_RCS_ciCmd { return _checkRCSProgram('ciCmd'); }
sub _CHECKVAR_RCS_ciDateCmd { return _checkRCSProgram('ciDateCmd'); }
sub _CHECKVAR_RCS_coCmd { return _checkRCSProgram('coCmd'); }
sub _CHECKVAR_RCS_histCmd { return _checkRCSProgram('histCmd'); }
sub _CHECKVAR_RCS_infoCmd { return _checkRCSProgram('infoCmd'); }
sub _CHECKVAR_RCS_rlogDateCmd { return _checkRCSProgram('rlogDateCmd'); }
sub _CHECKVAR_RCS_diffCmd { return _checkRCSProgram('diffCmd'); }
sub _CHECKVAR_RCS_lockCmd { return _checkRCSProgram('lockCmd'); }
sub _CHECKVAR_RCS_unlockCmd { return _checkRCSProgram('unlockCmd'); }
sub _CHECKVAR_RCS_delRevCmd { return _checkRCSProgram('delRevCmd'); }

sub _CHECKVAR_StoreImpl {
    my $mess = '';
    if( $TWiki::cfg{StoreImpl} eq 'RcsWrap') {
        # Check that GNU diff is found in PATH; used by rcsdiff
        $mess .= NOTE( "Note: The 'diff' program found on the path is used by RcsWrap to compare revisions ".
                         _checkGnuProgram( "diff"));
    }

    return $mess;
};

sub _CHECKVAR_UseClientSessions {
    my $mess = '';
    if (!eval "use CGI::Cookie; 1") {
        $mess .= <<HERE;
The CGI::Cookie Perl module is required for session support, but is not
available.
HERE
    }
    if (!eval "use CGI::Session; 1") {
        $mess .= <<HERE;
The CGI::Session Perl module is required for session support, but is not
available.
HERE
    }
    if( $mess ) {
        if ($TWiki::cfg{UseClientSessions} ) {
            $mess = ERROR( $mess );
        } else {
            $mess = WARN( $mess );
        }
    }
    return $mess;
}

sub _CHECKVAR_AuthScripts {
    if( $TWiki::cfg{AuthScripts} ) {
        if( $TWiki::cfg{LoginManager} eq "TWiki::Client::NoLogin" ) {
            return WARN("
You've asked that some scripts require authentication, but haven't
specified a way for users to log in. Please pick a LoginManager
(above) other than NoLogin.
                     ");
        }
    }
    return '';
}

# Generates the appropriate HTML for getting a value to configure the
# entry. The opts are additional parameters, and by convention may
# be a number (for a string length), a comma separated list of values
# (for a select) and may also have an M for mandatory. The actual
# input field is decided by the type, which is used to compose a function
# name by prepending _PROMPT_FOR_.
#.
# The method also builds a function name by prepending _CHECK_VAR_ to the
# field name, and calls it if it exists to perform additional checking
# for that specific field (see _CHECK_VAR_DataDir as an example)
#
sub _checkAndBuildValueGrabber {
    my( $type, $opts, $desc, $keys) = @_;
    my $output = '';
    my $mandatory = ($opts =~ /\sM\s/);
    # field id
    my $id = $keys;
    $id =~ s/}{/_/g;
    $id =~ s/[{}\[\]]//g;
    $output .= CGI::hidden( 'TYPEOF:'.$id, $type ). "\n";
    my $checker = '_CHECKVAR_'.$id;
    if( defined &$checker ) {
        no strict 'refs';
        my $check = &$checker();
        die "$checker" unless defined $check;
        $desc .= $check;
        use strict 'refs';
    }

    my $prompter = '_PROMPT_FOR_'.$type;
    my $value = eval '$TWiki::cfg'.$keys;
    no strict 'refs';
    $prompter = &$prompter($id, $opts, $value, $keys, $mandatory);
    my $class = $type;
    $class .= ' mandatory' if $mandatory;
    $prompter = CGI::span({class=>$class}, $prompter) if $mandatory;
    use strict 'refs';

    $keys = CGI::span({class=>'mandatory'}, $keys) if $mandatory;
    my $hidden = 0;
    if (length($desc) == 0 || $desc =~ /\A\s\Z/) { 
        $hidden = 1;
    }
    return _docBlock( $output.$desc, $hidden )._setting( $keys, $prompter );
}

sub _showPlugins {
    my %modules;
    foreach my $libDir ( @INC ) {
        if( opendir( DIR, "$libDir/TWiki/Plugins" ) ) {
            foreach my $file ( grep { /^[A-Za-z0-9_]+Plugin\.pm$/ }
                                 readdir DIR ) {
                my $module = $file;
                $module =~ s/\.pm$//;
                my $discovered = $libDir.'/'.$file;
                $TWiki::cfg{Plugins}{$module}{Enabled} ||= 0;
                $module =~ /^(.*)$/;    # untaint
                $module = $1;
                # only add the first instance of any plugin, as only
                # the first can get loaded from @INC.
                unless( $modules{$module} ) {
                    $modules{$module} =  $libDir;
                }
                closedir( DIR );
            }
        }
    }
    my $block = '';
    foreach my $m ( sort keys %modules ) {
        $block .= _checkAndBuildValueGrabber
          ( 'BOOLEAN', '', 
#SMELL - i'm assuming that the Plugin topic is in the SystemWeb :(
"<a rel=\"nofollow\" href=\"$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}/$TWiki::cfg{SystemWebName}/$m\">$m</a>",
	  , '{Plugins}{'.$m.'}{Enabled}');
    }
    return $block;
}

########################################################################
##################### WRITING NEW VALUES ###############################
########################################################################

sub setConfig {
    my ($path, $updates) = @_;
    my $txt = '';
    if( open(F, "<$path")) {
        undef $/;
        $txt = <F>;
        close(F);
    }

    foreach my $config ( keys %$updates ) {
        # kill the old settings if any are there
        $txt =~ s/\$TWiki::cfg\{$config\}\s*=.*?;//s;
    }
    $txt =~ s/^1;$//gm;

    open(F, ">$path") ||
      die "Failed to open $path for write";
    print F $txt if $txt;
    foreach my $config ( keys %$updates ) {
        print F '$TWiki::cfg{',$config,'} = ',$updates->{$config},";\n";
    }
    print F "1;\n";
    close(F);

    if( defined( $TWiki::cfg{ConfigurationLogName} ) &&
        open(F, '>>'.$TWiki::cfg{ConfigurationLogName} )) {
        my $date = gmtime();
        my $user = $query->remote_user() || 'guest';
        foreach my $config ( keys %$updates ) {
            print F '| ',$date,' | ',$user,' | ',$config,' | ',
              $updates->{$config}," |\n";
        }
        close(F);
    }
}

# Convert value to a canonical perl representation suitable for writing
# to LocalSite.cfg
sub _perlifyType {
    my ($val,$type) = @_;

    if ($type eq 'REGEX') {
        return "qr($val)";
    } elsif ($type eq 'BOOLEAN') {
        return ($val ? 1 : 0);
    } elsif ($type eq 'NUMBER') {
        return 0+$val;
    } elsif ($type eq 'OCTAL') {
        $val = '0'.$val unless $val =~ /^0/;
        return $val;
    } else {
        $val =~ s/'/\\'/g;
        return "'$val'";
    }
}

sub _perlModulesCheck {
    my $mods = shift;
    my $e = '';
    foreach my $mod (keys %$mods) {
        my $n = '';
        eval "use $mod";
        if ($@) {
            $n = WARN('not installed. May be required for ',
                      $mods->{$mod});
        } else {
            my $mod_version;
            no strict 'refs';
            eval '$mod_version = ${'.$mod.'::VERSION}';
            use strict 'refs';
            $n = $mod_version || 'unknown';
        }
        $e .= _setting($mod, $n);
    }
    return $e;
}

########################################################################
#################### MAIN ACTION ENTRY POINTS ##########################
########################################################################

sub _serveImage {
    my($type, $image )= @_;

    print "Content-type: $type\n\n";
    if( open(F, "logos/$image")) {
        local $/ = undef;
        print <F>;
        close(F);
    }
}

sub handleUpdate {
    my $path = shift;
    my $pass = $query->param( 'cfgAccess' );
    my $param;
    my $output = '';
    unless( defined( $pass )) {
        $output .= CGI::start_form({ action=>$ENV{SCRIPT_NAME}, method=>"post" });
        # Pass all URL params through
        foreach $param ( $query->param ) {
            $output .= CGI::hidden( $param, $query->param( $param ));
            $output .= "\n";
        }

        my $changed = calculateChanges();
        my $itemText = ($changed == 1) ? 'item' : 'items';
        $output .= CGI::div({ class => 'explanation'},
                            CGI::strong( 'Changing ' . $changed.
                                           ' configuration ' . $itemText.
                                             '.').
                  (($changed == 0) ? CGI::br() .
                     CGI::a( { href=>$url.'?t='.time(),
                               rel => 'nofollow' },
                             'Return to configuration') : CGI::br() .
               'Proceed with the steps below to save your changes.'));

        # and add a few more
        $output .= CGI::h2('Enter the configuration password');
        $output .= CGI::p(CGI::strong("Your Password:").CGI::br());
        $output .= CGI::span({class => 'twikiSmall'}, <<'HERE'
First time users: leave this field empty and create a new password below at
New Password. If no password is set, anyone will be able to make changes.
HERE
                               .CGI::br());
        $output .= CGI::password_field( 'cfgAccess', '', 20, 80 );
        $output .= CGI::submit(-class=>'twikiSubmit', -value=>'Save changes');
        $output .= CGI::br();
        $output .= CGI::div({ class => 'explanation'},
                            CGI::span({class => 'twikiSmall'},
                                      CGI::strong("Forgotten the password?").
                                          CGI::br().
                                              <<'HERE'
To reset the password, log in to the server and delete the <code>
$TWiki::cfg{Password} = '...';</code> line in <code>lib/LocalSite.cfg</code>
HERE
                         ));
        $output .= CGI::p( <<'HERE'
Set your password for the first time, or optionally change your password here:
HERE
                          );
        $output .= CGI::div({ class => 'formElem'},
                            CGI::strong("New Password:").
                            CGI::br().
                            CGI::password_field( 'newCfgP', '', 20, 80 ).
                            CGI::br().
                            CGI::strong("Confirm Password:").CGI::br().
                            CGI::password_field( 'confCfgP', '', 20, 80 ).
                            CGI::br().
                            CGI::submit(-class=>'twikiSubmit', -value=>'Set Password and Save changes'));
        $output .= CGI::end_form();
        $output .= CGI::end_html();
        return $output;
    }

    unless( crypt( $pass, $TWiki::cfg{Password}) eq
            $TWiki::cfg{Password} || $TWiki::cfg{Password} eq '') {
        $output .= CGI::span( {class => 'error' }, "Incorrect password" ).
          CGI::end_html();
        return $output;
    }

    my $changed = 0;
    my %updates;

    if( $query->param( 'newCfgP' )) {
        if( $query->param( 'newCfgP' ) eq
                $query->param( 'confCfgP' )) {
            my @saltchars = ( 'a'..'z', 'A'..'Z', '0'..'9', '.', '/' );
            my $salt = $saltchars[int(rand($#saltchars+1))] .
              $saltchars[int(rand($#saltchars+1)) ];
            $updates{Password} =
              _perlifyType(
                           crypt( $query->param( 'newCfgP' ), $salt ),
                           'STRING' );
            $changed++;
            $output .= "Password changed";
        } else {
            $output .= "New password and confirmation do not match";
            return $output;
        }
    }

    $output .= CGI::h2('Updating configuration');
    foreach $param ( $query->param ) {
        next unless $param =~ /^^TYPEOF:(.*)/;
        my $type = $query->param( $param );
        $param =~ s/^TYPEOF:(.*)$/$1/;
        my $basevar = $1;
        $basevar =~ s/_/}{/g;
        my $var = '$TWiki::cfg{'.$basevar.'}';
        my $val = $query->param( $param );
        my $def;
        eval "\$def = defined( $var );";
        if( $type ) {
            eval "\$def = $var;" if $def;
            next if( $type eq 'OCTAL' && sprintf('0%o', $def) =~ /^0*$val$/ );
            next if( $type eq 'NUMBER' && $val + 1 == $def + 1 );
            next if( $type eq 'BOOLEAN' && ($val && $def || !$val && !$def));
            next if( $val eq $def );
            $output .= CGI::h3($var).
              CGI::b('old ').
              CGI::code($def).
              CGI::br().
              CGI::b('new  ').
              CGI::code($val);
            $updates{$basevar} = _perlifyType($val, $type);
            $changed++;
        }
    }
    $output .= CGI::p();
    setConfig($path, \%updates);
    my $itemText = ($changed == 1) ? 'item' : 'items';
    $output .= CGI::hr();
    $output .= CGI::p(CGI::strong($changed.' configuration ' . $itemText . ' changed. '));
    $output .= CGI::p(CGI::a({ rel => 'nofollow',
                               href=>$url.'?t='.time() },
                             'Return to configuration'));
    return $output;
}

sub calculateChanges {
    my $param;
    my $changed = 0;
    foreach $param ( $query->param ) {
        next unless $param =~ /^^TYPEOF:(.*)/;
        my $type = $query->param( $param );
        $param =~ s/^TYPEOF:(.*$)/$1/;
        my $basevar = $1;
        $basevar =~ s/_/}{/g;
        my $var = '$TWiki::cfg{'.$basevar.'}';
        my $val = $query->param( $param );
        my $def;
        eval "\$def = defined( $var );";
        if( $type ) {
            eval "\$def = $var;" if $def;
            next if( $type eq 'OCTAL' && sprintf('0%o', $def) =~ /^0*$val$/ );
            next if( $type eq 'NUMBER' && $val + 1 == $def + 1 );
            next if( $type eq 'BOOLEAN' && ($val && $def || !$val && !$def));
            next if( $val eq $def );
            $changed++;
        }
    }
    return $changed;
}

sub performSanityChecks {
    my( $brokenTWikiCfgError, $brokenLocalSiteError ) = @_;
    my $output = '';

    if ($brokenTWikiCfgError) {
        $output .= CGI::h2('WARNING:').
          CGI::p('TWiki.cfg is unreadable or has a configuration problem that is causing a Perl error - the following message(s) should help locate the problem.');
        $output .= $brokenTWikiCfgError;

        # EARLY EXIT
        $output .= CGI::end_html();
        return $output;
    }

    if ($brokenLocalSiteError) {
        $output .= CGI::h2('WARNING:').
          ERROR('LocalSite.cfg is unreadable or has a configuration problem that is causing a Perl error - the following message(s) was generated:').
          CGI::pre($brokenLocalSiteError).
          'The @INC path is '.
          CGI::pre(join(":", @INC)).
          NOTE('This may be because this is the first time you have run configure. In this case you can simply ignore this error until you have filled in your <a rel="nofollow" href="#" onclick="foldBlock(\'GeneralPathSettings\'); return false;">General path settings</a>. Otherwise, check that the file exists, and the webserver user is allowed to read it.');
    }

    # Check whether basic CGI modules exist (some broken installations of
    # Perl don't have this, even though they are standard modules), and warn user
    my $modMissing = 0;
    foreach my $mod (keys %$basicMods) {
        eval "use $mod";
        if ($@) {
            unless ($modMissing) {
                $output .= ERROR( 'Perl Module(s) missing');
            }
            $modMissing = 1;
            $output .= ERROR( 'Essential Perl Module \'',$mod,
                         '\' not installed - please check the setting ',
                         'of @INC.' );
        }
    }

    # If any critical modules missing, display @INC and give up 
    if ($modMissing) {
        $output .= NOTE( '@INC = ', join( ' ', @INC ));
        return $output;
    }

    return $output;
}

sub presentReadOnlyInfo {
    # use strict;        # Recommended for mod_perl, enable for Perl 5.6.1 only
    # Doesn't work well here, due to 'do "TWiki.cfg"'
    # use diagnostics;    # Debug only

    # Load CGI modules (run-time, after checking they are accessible)
    require CGI;
    import CGI qw( -any );
    require CGI::Carp;
    import CGI::Carp qw( fatalsToBrowser );

    $errors = 0;
    $warnings = 0;

    my $output = '';
    my $block = '';
    for my $key ( sort keys %ENV ) {
        $block .= _setting($key, $ENV{$key});
    }
    $output .= _foldableBlock(CGI::em( 'Environment variables' ),
                              '(read only) ', $block);

    $block = '';

    # Make %ENV safer for CGI (should reflect TWiki.pm)
    my $originalPath = $ENV{PATH} || '';
    if( $TWiki::cfg{SafeEnvPath} ) {
        # SMELL: this untaint probably isn't needed
        my $ut = $TWiki::cfg{SafeEnvPath};
        $ut =~ /^(.*)$/;
        $ENV{PATH} = $1;
    }
    delete @ENV{ qw( IFS CDPATH ENV BASH_ENV ) };
    my $perlverMsg = $perlver;        # Default version message

    # Load Config module - used here and elsewhere
    require Config;

    # Set $TWiki::cfg{DetailedOS} if not using later versions of TWiki.cfg for BeijingRelease
    # - this code enables the latest testenv to be used with Dec 2001 and 
    # earlier releases.
    if ( !defined $TWiki::cfg{DetailedOS} ) {
        $TWiki::cfg{DetailedOS} = $Config::Config{'osname'};
    }

    # Detect Perl flavour on Windows, and Cygwin Perl/RCS package versions

    if ($TWiki::cfg{DetailedOS} eq 'cygwin') {
        $perltype = 'Cygwin';                # Cygwin Perl only
        my ($pkg, $pkgName);

        # Get Cygwin perl's package version number
        $pkgName = 'perl';
        $pkg = `/bin/cygcheck -c $pkgName | /bin/grep $pkgName 2>/dev/null`; 
        if ($?) { 
            $pkg = " [Cannot identify package - cygcheck or grep not installed]";
            $perlverMsg = $perlver . $pkg
        } else {
            $pkg = (split ' ', $pkg)[1];    # Package version
            $perlverMsg = $pkg;
        }

        # Get Cygwin RCS's package version number
        $pkgName = 'rcs';
        $pkg = `/bin/cygcheck -c $pkgName | /bin/grep $pkgName 2>/dev/null`; 
        if ($?) { 
            $pkg = " [Cannot identify package - cygcheck or grep not installed]";
            $cygwinRcsVerNum = $pkg;    
        } else {
            $pkg = (split ' ', $pkg)[1];    # Package version
            $cygwinRcsVerNum = $pkg;    
        }
    } elsif ($TWiki::cfg{DetailedOS} =~ /win/i && $TWiki::cfg{DetailedOS} !~ /darwin/i ) {
        # Windows Perl - try ActivePerl-only function: returns number if
        # successful, otherwise treated as a literal (bareword).
        my $isActivePerl= eval 'Win32::BuildNumber !~ /Win32/';
        if( $isActivePerl ) {
            $perltype = 'ActiveState';
            $perlverMsg = $perlver . ", build " . Win32::BuildNumber();
        } else {
            # Could be SiePerl or some other Win32 port of Perl
            $perltype = 'SiePerl or other Windows Perl';
        }
    } else {
        $perltype = 'generic';
    }

    # Detect executable name suffix, e.g. .exe on Windows or '' on Unix
    # Avoid testing for .exe suffixes on Cygwin, since the built-in
    # grep and ls don't end in '.exe', even though Perl's '_exe' setting
    # indicates they should.
    my $exeSuffix='';
    if ( $Config::Config{'_exe'} and ($TWiki::cfg{OS} eq 'WINDOWS' and $perltype ne 'Cygwin') ) { 
        if ( ! $ENV{INTERIX_ROOT} ) { #this is set is we are using UnixServicesForWindows (or INTERIX funnily enough) and they don't use .exe either
            $exeSuffix = $Config::Config{'_exe'};
        }
    }

    # Detect whether mod_perl was loaded into Apache
    my $modPerlLoaded = ( exists $ENV{SERVER_SOFTWARE} && 
                          ( $ENV{SERVER_SOFTWARE} =~ /mod_perl/ ));
    # Detect whether we are actually running under mod_perl
    # - test for MOD_PERL alone, which is enough.
    my $usingModPerl = ( exists $ENV{MOD_PERL} );
    my $modPerlVersion;

    # Get the version of mod_perl if it's being used
    if ( $usingModPerl ) {
        $modPerlVersion = eval 'use mod_perl; return $mod_perl::VERSION';
        $block .= _setting('',
                           WARN(<<HERE
You are running <tt>configure</tt> with <tt>mod_perl</tt>. This
is risky because mod_perl will remember old values of configuration
variables. You are *highly* recommended not to run configure under
mod_perl (though the rest of TWiki can be run with mod_perl, of course)
HERE
                               ));
    }

    my $n = ucfirst(lc($TWiki::cfg{OS}));
    $n .= " ($TWiki::cfg{DetailedOS})" if ( $TWiki::cfg{DetailedOS} ne '' );
    # OS
    $block .= _setting("Operating system", $n);

    # Perl version and type
    $perlverMsg .= " ($perltype)" if $perltype ne 'generic';
    $block .= _setting("Perl version", $perlverMsg);

    if ( $perlvernum < $perlverRequired ) {
        $block .= _setting('',
                           WARN(<<HERE
This version of Perl is too old for use with TWiki -
upgrade to at least Perl $perlverRequiredString
and preferably to Perl $perlverRecommended.
HERE
                               ));
    }

    # Perl @INC (lib path)
    $block .= _setting('@INC library path', join(CGI::br(), @INC ),
                       NOTE(<<HERE
This is the Perl library path, used to load TWiki modules,
third-party modules used by some plugins, and Perl built-in modules.
HERE
                           ));

    $block .= _setting('CGI bin directory', _checkBinDir());

    # Turn off fatalsToBrowser while checking module loads, to avoid load errors in
    # browser in some environments.  
    $CGI::Carp::WRAP = $CGI::Carp::WRAP = 0;    # Avoid warnings...

    # Add to list of required modules if non-Unix, or MacOS X (detected by
    # Perl as 'Darwin') - $TWiki::cfg{DetailedOS} is set in TWiki.cfg.
    $TWiki::cfg{DetailedOS} ||= $TWiki::cfg{DetailedOS};

    # Check that the TWiki.pm module can be found
    eval "require TWiki";
    my $twikiFound = 0;
    my $mess = '';
    if ($@) {
        $mess = $@;
        $mess = ERROR("'TWiki.pm' could not be loaded. The error was:").
                CGI::pre($mess).
                ERROR("Check path to <code>twiki/lib</code> and check that LocalSite.cfg is present and readable");
    } else {
        $twikiFound = 1;
        my $mod_version = eval '$TWiki::wikiversion || $TWiki::VERSION';
        $mod_version ||= 'unknown';
        $mess = 'OK, TWiki.pm found (Version: <strong>'.$mod_version.'</strong>)';
    }
    $block .= _setting('TWiki module in @INC path', $mess);

    #add in the basic Modules so that we list their versions in the UI
    map { $requiredMods->{$_} = $basicMods->{$_} }
       keys %$basicMods;

    if ( defined $TWiki::cfg{DetailedOS} and ($TWiki::cfg{DetailedOS} =~ /darwin/i or $TWiki::cfg{OS} ne 'UNIX') ) {
        map { $requiredMods->{$_} = $requiredModsNonUnix->{$_} }
          keys %$requiredModsNonUnix;
    } else {
        # these are optional on Unix
        map { $optionalMods->{$_} = $requiredModsNonUnix->{$_} }
          keys %$requiredModsNonUnix;
    }

    # Check that each of the required Perl modules can be loaded, and
    # print its version number.
    my $set = '';
    foreach my $mod (keys %$requiredMods) {
        eval "use $mod";
        if ($@) {
            $set .= _setting($mod, ERROR("not installed. Required for ",
                                         $requiredMods->{$mod}));
        } else {
            my $mod_version;
            no strict 'refs';
            eval '$mod_version = ${'.$mod.'::VERSION}';
            use strict 'refs';
            $n = $mod_version || 'unknown';
            # Check for potential CGI.pm module upgrade 
            if( $mod eq 'CGI' and $mod_version < $cgiModVerRecommended ) {
                if ( $perltype eq 'Cygwin' and $perlver eq '5.8.0' ) {
                    # Recommend CGI.pm upgrade if using Cygwin Perl 5.8.0 
                    $n .= WARN( "CGI.pm version $cgiModVerRecommended or higher",
                                "is recommended to avoid problems with attachment",
                                "uploads on Cygwin Perl $perlver.");
                } elsif ( $usingModPerl and $modPerlVersion >= 1.99 ) {
                    # Recommend CGI.pm upgrade if using mod_perl 2.0, which
                    # is reported as version 1.99 and implies Apache 2.0
                    $n .= WARN("CGI.pm version $cgiModVerRecommended or higher is",
                               "recommended to avoid problems with mod_perl version",
                               "$modPerlVersion on Apache 2.0 or higher.");
                }
            }
            $set .= _setting( $mod, $n );
        }
    }
    $block .= _setting("Required Perl modules",
                       CGI::start_table({width=>'100%'}).
                       $set.CGI::end_table());

    # Check that each of the optional Perl modules can be loaded, and
    # print its version number.
    $set = _perlModulesCheck( $optionalMods );
    $set .= _perlModulesCheck( $I18Mods );
    $set .= _perlModulesCheck( $] >= 5.008 ? $I18Mods_perl58 : $I18Mods_perl56 );
    $block .= _setting("Optional Perl Modules",
                       CGI::start_table({width=>'100%'}).
                       $set.CGI::end_table());

    # All module checks done, OK to enable fatalsToBrowser
    import CGI::Carp qw( fatalsToBrowser );

    # PATH_INFO
    $block .= _setting(CGI::a({name=>'PATH_INFO'},'PATH_INFO'), $query->path_info().
              NOTE(<<HERE
For a URL such as <strong>$url/foo/bar</strong>,
the correct PATH_INFO is <strong>/foo/bar</strong>, without any prefixed path
components. <a rel="nofollow" href="$url/foo/bar#PATH_INFO">
<strong>Click here to test this</strong></a>
- particularly if you are using mod_perl, Apache or IIS, or are using
a web hosting provider.
Look at the new path info here. It should be <strong>/foo/bar</strong>.
HERE
                  ));

    # mod_perl
    if( $usingModPerl ) {
        $n = "Used for this script";
    } else {
        $n = "Not used for this script";
    }
    $n .= NOTE( 'mod_perl is ', $modPerlLoaded ? '' : 'not',
                ' loaded into Apache' );
    if ( $modPerlVersion ) {
        $n .= NOTE( 'mod_perl version ', $modPerlVersion );
    }

    # Check for a broken version of mod_perl 2.0
    if ( $usingModPerl && $modPerlVersion =~ /1\.99_?11/ ) {
        # Recommend mod_perl upgrade if using a mod_perl 2.0 version
        # with PATH_INFO bug (see Support.RegistryCookerBadFileDescriptor
        # and Bugs:Item82)
        $n .= ERROR(<<HERE
Version $modPerlVersion of mod_perl is known to have major bugs that prevent
its use with TWiki. $modPerlVersionRecommended or higher is recommended, or
you could always use SpeedyCGI.
HERE
                   );
    }
    $block .= _setting('mod_perl', $n);

    # Get web server's user and group info
    my $usr = "";
    my $grp = "";
    if( $TWiki::cfg{OS} eq 'UNIX' or  ($TWiki::cfg{OS} eq 'WINDOWS' and $perltype eq 'Cygwin' ) ) {
        $usr = lc( getpwuid($>) );        # Unix/Cygwin Perl - effective UID
        $grp = join(',', map { lc(getgrgid( $_ )) } split( " ", $( ) );
    } else {                # ActiveState or other Win32 Perl
        $usr = lc( getlogin );
        # Try to use Cygwin's 'id' command - may be on the path, since Cygwin
        # is probably installed to supply ls, egrep, etc - if it isn't, give up.
        # Run command without stderr output, to avoid CGI giving error.
        # Get names of primary and other groups.
        $grp = lc(qx(sh -c '( id -un ; id -gn) 2>/dev/null' 2>nul ));
        if ($?) { 
            $grp = "[Cannott identify groups - no Cygwin 'id' or 'sh' command on path]";
        }
    }

    $block .= _setting('CGI user', 'userid = <strong>'.$usr.'</strong> groups = <strong>'.
              $grp.'</strong>'.
              NOTE('Your CGI scripts are executing as this user.'));

    $block .= _setting("Original PATH", $originalPath.
              NOTE(<<HERE
This is the PATH value passed in from the web server to this
script - it is reset by TWiki scripts to the PATH below, and
is provided here for comparison purposes only.
HERE
                  ));

    my $currentPath = $ENV{PATH} || '';     # As re-set earlier in this routine
    $block .= _setting("Current PATH", $currentPath,
              NOTE(<<HERE
This is the actual PATH setting that will be used by Perl to run
programs. It is normally identical to {SafeEnvPath}, unless
that variable is empty, in which case this will be the webserver users
standard path..
HERE
                  ));

    # Check that GNU patch is found in PATH 
    $block .= _setting(
        "patch", _checkGnuProgram( "patch" ).
          NOTE('It is used by the UpgradeTwiki script to upgrade an existing TWiki installation' ));

    # PERL5SHELL check for non-Cygwin Perl on Windows only
    if( $TWiki::cfg{OS} eq 'WINDOWS' && $perltype ne 'Cygwin' ) {

        # ActiveState or SiePerl/other
        # FIXME: Advice in this section should be reviewed and tested by people
        # using ActivePerl
        my $perl5shell = $ENV{PERL5SHELL} || '';
        $n = $perl5shell.
          NOTE(<<HERE
This environment variable is used by ActiveState and other Win32 Perls to run
commands from TWiki scripts - it determines which shell program is used to run
commands that use 'pipes'.  Examples of shell programs are cmd.exe,
command.com (aka 'DOS Prompt'), and Cygwin's 'bash'
(<strong>recommended</strong> if Cygwin is installed).
<p>
To use 'bash' with ActiveState or other Win32 Perl you should set the
PERL5SHELL environment variable to something like
<tt><strong>c:/YOURCYGWINDIR/bin/bash.exe -c</strong></tt>
This should be set in the System Environment, and ideally set directly in the
web server (e.g. using the Apache <tt>SetEnv</tt> directive).
HERE
              );
        if( $perltype eq 'ActiveState' ) {
            $n .= WARN(<<HERE
ActiveState Perl on IIS does not support safe pipes, which is the mechanism used by TWiki to prevent a range of attacks aimed at arbitrary command execution on the server. You are *highly* recommended not to use this particular configuration on a public server!
HERE
                      );
            if( Win32::BuildNumber() < $ActivePerlRecommendedBuild ) {
                $n .= WARN(<<HERE
ActiveState Perl must be upgraded to build <strong>
$ActivePerlRecommendedBuild
if you are going to use PERL5SHELL, which was broken in earlier builds.
HERE
                             );
            }
        }
        $block .= _setting("PERL5SHELL", $n);
    };
    $output .= _foldableBlock(CGI::em( 'CGI Setup' ), '(read only) ',
                              $block);
    return $output;
};

sub presentEditableInfo {
    # "Parse" TWiki.cfg and LocalSite.cfg
    my $output = '';
    my @blocks;
    my @heads;
    my $depth = 0;
    for my $file ( 'TWiki.cfg', 'LocalSite.cfg' ) {
        my $cfgfile = _findFileOnPath($file);
        next unless $cfgfile;
        open(F, $cfgfile) || next;
        undef $/;
        my $text = <F>;
        close(F);
        $text =~ s/^# //gm;

        my $type = '';
        my $descr;
        my $opts;
        foreach (split(/\r?\n/, $text)) {
            if( m/^\*\*([A-Z]+)(\s*.*?)\*\*/ ) {
                if( $type eq '_HELP' ) {
                    $blocks[$depth] .= _docBlock( $descr );
                }
                $type = $1;
                $opts = $2 || '';
                $opts .= ' '; # to simplify parsing
                $descr = '';
            } elsif ($type && /\$(TWiki::)?cfg(.*?)\s*=/) {
                if( $type eq '_HELP' ) {
                    $blocks[$depth] .= _docBlock( $descr );
                } else {
                    $blocks[$depth] .= _checkAndBuildValueGrabber($type, $opts, $descr, $2);
                }
                $type = '';
                $descr = '';
            } elsif( m/^#---(\++) *(.*?)$/ ) {
                my $ndepth = length($1);
                my $nhead = $2;
                while( $depth >= $ndepth ) {
                    if ($depth <= 1) {
                        $output .= _foldableBlock($heads[$depth], '', $blocks[$depth]);
                    } else {
                        $blocks[$depth - 1] .= _ordinaryBlock($depth, $heads[$depth], '', $blocks[$depth]);
                    }
                    $depth--;
                }
                $depth = $ndepth;
                $heads[$depth] = $nhead;
                $blocks[$depth] = '';
                $type = '_HELP';
            } elsif( m/^\*PLUGINS\*/ ) {
                if( $type eq '_HELP' ) {
                    $blocks[$depth] .= _docBlock( $descr );
                    $descr = '';
                }
                $blocks[$depth] .= _showPlugins();
            } elsif( $type ) {
                $descr .= "$_ ";
            }
        }
    }
    while( $depth && $blocks[$depth]) {
        if ($depth <= 1) {
            $output .= _foldableBlock($heads[$depth], '', $blocks[$depth]);
        } else {
            $blocks[ $depth - 1] .= _ordinaryBlock($depth, $heads[$depth], '', $blocks[$depth]);
        }
        $depth--;
    }
    return $output;
}

######################################################################
################# MAIN PROGRAM #######################################
######################################################################

$| = 1;                  # no buffering - FIXME: mod_perl issue?

eval "use CGI::Carp qw( fatalsToBrowser )";

use vars qw ( $setlibAvail $brokenLocalSiteError $brokenTWikiCfgError
              $js1 $css );

# Set library paths in @INC, read TWiki.cfg and set locale, at compile time
# Try to use setlib.cfg, use default path if missing
if ( -r './setlib.cfg' ) {
    require './setlib.cfg';
    $setlibAvail = 1;
} else {
    unshift @INC, '../lib';
    $setlibAvail = 0;
}

unless( $TWiki::cfg{DetailedOS} ) {
    $TWiki::cfg{DetailedOS} = $^O;
    unless( $TWiki::cfg{DetailedOS} ) {
        require Config;
        $TWiki::cfg{DetailedOS} = $Config::Config{'osname'};
    }
}
unless( $TWiki::cfg{OS} ) {
    if ($TWiki::cfg{DetailedOS} =~ /darwin/i) { # MacOS X
        $TWiki::cfg{OS} = 'UNIX';
    } elsif ($TWiki::cfg{DetailedOS} =~ /Win/i) {
        $TWiki::cfg{OS} = 'WINDOWS';
    } elsif ($TWiki::cfg{DetailedOS} =~ /vms/i) {
        $TWiki::cfg{OS} = 'VMS';
    } elsif ($TWiki::cfg{DetailedOS} =~ /bsdos/i) {
        $TWiki::cfg{OS} = 'UNIX';
    } elsif ($TWiki::cfg{DetailedOS} =~ /dos/i) {
        $TWiki::cfg{OS} = 'DOS';
    } elsif ($TWiki::cfg{DetailedOS} =~ /^MacOS$/i) { # MacOS 9 or earlier
        $TWiki::cfg{OS} = 'MACINTOSH';
    } elsif ($TWiki::cfg{DetailedOS} =~ /os2/i) {
        $TWiki::cfg{OS} = 'OS2';
    } else {
        $TWiki::cfg{OS} = 'UNIX';
    }
}

# if this fails, ignore the problem, but we have to do it
unless( eval 'do "LocalSite.cfg"' ) {
    # Capture the Perl error(s)
    $brokenLocalSiteError = 'require failed: '.
      ( $! ? $! : '') . ( $@ ? $@ : '');
}

# Read the configuration file now in order to set locale;
# includes checking for broken syntax etc.  Need 'require'
# to get the $!/$@ to work.
unless( eval 'require "TWiki.cfg" ' ) {
    # Capture the Perl error(s)
    $brokenTWikiCfgError = 'require failed: '.
      ( $! ? $! : '') . ( $@ ? $@ : '');
}

# and again
eval 'do "LocalSite.cfg"';

# Do a dynamic 'use locale' for this script
if( $TWiki::cfg{UseLocale} ) {
    require locale;
    import locale ();
}

$js1 = <<'HERE';
//<!--

var lastOpenBlock = null;
var lastOpenBlockLink = null;
var allBlocks = null; // array of all foldable blocks
var allBlockLinks = null; // array of all foldable block links (headers)

function foldBlock(id) {
    var shouldClose = false;
    var block = null;
    if (lastOpenBlock == null) {
        block = document.getElementById(id);
        if (block.open) {
            shouldClose = true;
        }
    }
    if (shouldClose) {
        closeBlock(id);
    } else {
        var o = openBlock(id);
        if (lastOpenBlock != null) {
            closeBlockElement(lastOpenBlock, lastOpenBlockLink);
        }
    }
    if (o && o.block) {
		lastOpenBlock = (lastOpenBlock == o.block) ? null : o.block;
	}
	if (o && o.blockLink) {
		lastOpenBlockLink = (lastOpenBlockLink == o.blockLink) ? null : o.blockLink;
	}
}

function openBlock(id) {
    var block = document.getElementById(id);
    var blockLink = document.getElementById('blockLink' + id);
    openBlockElement(block, blockLink);
    return {block:block, blockLink:blockLink};
}

function openBlockElement(block, blockLink) {
    block.className = 'foldableBlock foldableBlockOpen';
    block.open = true;
    blockLink.className = 'blockLink blockLinkOn';
}

function closeBlock(id) {
    var block = document.getElementById(id);
    var blockLink = document.getElementById('blockLink' + id);
    closeBlockElement(block, blockLink);
    return {block:block, blockLink:blockLink};
}

function closeBlockElement(block, blockLink) {
    block.className = 'foldableBlock foldableBlockClosed';
    block.open = false;
    blockLink.className = 'blockLink blockLinkOff';
}

function toggleAllOptions(open) {
    if (allBlocks == null) {
        allBlocks = getElementsByClassName('foldableBlock');
    }
    if (allBlockLinks == null) {
        allBlockLinks = getElementsByClassName('blockLink');
    }
    var i, ilen=allBlocks.length;
    if (open) {
        for (i=0; i<ilen; ++i) {
            openBlockElement(allBlocks[i], allBlockLinks[i]);
        }
    } else {
        for (i=0; i<ilen; ++i) {
            closeBlockElement(allBlocks[i], allBlockLinks[i]);
        }
    }
    lastOpenBlock = null;
    lastOpenBlockLink = null;
}

function getElementsByClassName(class_name)
{
    var all_obj, ret_obj = new Array();
    if (document.all)
        all_obj=document.all;
     else if (document.getElementsByTagName && !document.all)
        all_obj=document.getElementsByTagName("*");
    var len = all_obj.length;
    for (i=0;i<len;++i) {
        var myClass = all_obj[i].className;
         if (myClass == class_name) {
            ret_obj.push(all_obj[i]);
        } else {
            var classElems = myClass.split(" ");
            var elemLen = classElems.length;
            for (ii=0; ii<elemLen; ++ii) {
                if (classElems[ii] == class_name) {
                    ret_obj.push(all_obj[i]);
                }
            }    
        }
    }
    return ret_obj;
}
//-->
HERE

$css = <<'HERE';
html body {
    line-height:1.4em; /*C10*/
    font-family:"Lucida Grande", verdana, lucida, helvetica, sans-serif;
    background-color:#F8F4F1; /*C7*/
    margin:0;
    padding:0;
    font-size:x-small;
    voice-family:"\"}\""; 
    voice-family:inherit;
    font-size:small;
}
html>body {
    font-size:small;    
}

/* be kind to netscape 4 that doesn't understand inheritance */
body, p, li, ul, ol, dl, dt, dd, acronym, h1, h2, h3, h4, h5, h6 {
    background-color:transparent;
}
p {
    margin:1em 0 0 0;
}
table {
    border-collapse:collapse;
}
strong, b {
    font-weight:bold;
}
hr {
    color:#9E9E70;
    background-color:#9E9E70;
    height:1px;
    border:none;
}
pre, code, tt {
    font-size:100%;
    line-height:1.4em;
    color:#7A4707;
}
pre {
    margin-top:1em;
    overflow-x:auto;
    overflow-y:visible;
    width:100%;
}
>pre { /* hide from IE */
    padding-bottom:0.25em;
    /*\*/ overflow:auto !important; /* */ overflow:scroll; /* for Mac Safari */
}

h1, h2, h3, h4, h5, h6 {
    font-family:"Lucida Grande", helvetica, lucida, verdana, sans-serif;
    line-height:104%;
    padding:0;
    margin:1em 0 0.1em 0;
}
h1 {
    font-size:210%;
    color:#e24628;
    margin:0 0 0.5em 0;
}
h2, h3, h4, h5, h6 {color:#900;} /*C5*/
h2 {
    font-size:145%;
    display:block;
    background-color:#F3EDE7; /*C8*/
    padding:0.2em 0.7em;
    margin:1em -0.7em 0.35em -0.7em;
    height:auto;
}
h3 { font-size:140%;}
h4 { font-size:125%;}
h5 { font-size:110%;}
h6 { font-size:95%;}
:link:focus,
:visited:focus,
:link,
:visited,
:link:active,
:visited:active {
    text-decoration:underline;
    color:#0054C0; /*C1*/;
    background-color:transparent;
}
:link:hover,
:visited:hover {
    text-decoration:underline;
    color:#0029A3; /*C3*/
    background-color:#b4d5ff; /*C2*/
}
form { 
    display:inline;
    margin:0;
    padding:0;
}
textarea {
    font-family:monospace;
    font-size:100%;
    margin:0.25em 0;
    width:auto;
}
input, select {
    font-family:verdana,arial,sans-serif;
    padding:1px;
}
select {
    padding:0;
    font-size:100%;
}
.twikiSubmit,
.twikiButton {
	font-size:90%;
	font-weight:normal;
	padding:0;
	margin:0 0.15em;
}
.twikiSubmit {
	color:#041D3A;
	background-color:#f2f2f2;
	border-top:1px solid #fff;
	border-right:1px solid #333;
	border-bottom:1px solid #333;
	border-left:1px solid #fff;
}
.twikiLeft {
    float:left;
    position:relative;
}
.twikiRight {
    position:relative;
    float:right;
    display:inline;
    margin:0;
    text-align:right;
}
.twikiClear {
    /* to clean up floats */
    margin:0;
    padding:0;
    height:1px;
    line-height:1%;
    clear:both;
    display:block;
}
.patternMain {
    margin-left:8em;
    margin-right:8em;
    padding:2em 3em 1em 3em;
    background-color:white;
    border-width:1px;
    border-style:solid;
    border-color:#ddd #ddd #ddd #ddd;
    font-size:95%;
}
.twikiGrayText{
    color:#8490A1; /*C6*/
}
.twikiSmall {
    font-size:85%;
}

/* CONFIGURE SPECIFIC */

.formElem {
    background-color:#F3EDE7;
    margin:0.5em 0;
    padding:0.5em 1em;
}
.blockLinkAttribute {
    margin-left:0.35em;
}
a.blockLink {
    display:block;
    padding:0.25em 1em;
    border-bottom:1px solid #aaa;
}
a:link.blockLink,
a:visited.blockLink {
    text-decoration:none; 
}
a:link:hover.blockLink {
    text-decoration:none;   
}
a:link.blockLinkOff,
a:visited.blockLinkOff {
    background-color:#F3EDE7;
    color:#333;
    font-weight:normal;
}
a:link.blockLinkOn,
a:visited.blockLinkOn {
    background-color:#b4d5ff;
    color:#333;
    font-weight:bold;
}
a.blockLink:hover {
    background-color:#1559B3;
    color:white;
}
div.explanation {
    background-color:#e6e7e8;
    padding:0.5em 1em;
    margin:0.5em 0;
}
div.specialRemark {
    background-color:#fff;
    border:1px solid #ccc;
    margin:0.5em;
    padding:0.5em 1em;
}
div.options {
    margin:1em 0;
}
div.options div.optionHeader {
    padding:0.25em 1em;
    background-color:#666;
    color:white;
    font-weight:bold;
}
div.options div.optionHeader a {
    color:#bbb;
    text-decoration:underline;
}
div.options div.optionHeader a:link:hover,
div.options div.optionHeader a:visited:hover {
    color:#b4d5ff;
    background-color:#666;
    text-decoration:underline;
}
div.options .twikiSmall {
    margin-left:0.5em;
    color:#bbb;
}
div.foldableBlock {
    border-bottom:1px solid #ccc;
    border-left:1px solid #ddd;
    border-right:1px solid #ddd;
    height:auto;
    width:auto;
    overflow:auto;
    padding:0.25em 0 0.5em 0;
}
.foldableBlockOpen {
    display:block;
}
.foldableBlockClosed {
    display:block;
}
div.foldableBlock table {
    margin-bottom:1em;
}
div.foldableBlock td {
    padding:0.15em 1em;
    border-top:1px solid #ddd;
}
.info {
    color:#008000;
    background-color:#EDF6ED;
    margin-bottom:0.25em;
    padding:0.25em 0;
}
.warn {
    color:#f60;
    border-bottom:1px dotted #f60;
}
.error {
    color:red;
    border-bottom:1px solid red;
}
.mandatory,
.mandatory input {
    background-color:#DFEDFD;
    font-weight: bold;
}
.mandatory input {
    font-weight:normal;
}
.docdata {
    padding-top: 1ex;
    vertical-align: top;
}
.keydata {
    font-weight: bold;
    background-color:#FOFOFO;
    vertical-align: top;
}
.subHead {
    font-weight: bold;
    font-style: italic;
}
.firstCol {
    width: 30%;
    font-weight: bold;
    vertical-align: top;
}
.secondCol {
}
.hiddenRow {
    display:none;
}
HERE

my $js2 = <<'HERE';
//<!--

document.write("<style type='text/css'>");
document.write(".foldableBlockClosed {display:none;}");
document.write("<\/style>");
//-->
HERE

my $hdr = CGI::start_html(
    -title => 'TWiki Configuration',
    -head => [
        CGI::meta({ 'http-equiv'=>'Pragma', content=>'no-cache' }),
        CGI::meta({ 'http-equiv'=>'Cache-Control', content=>'no-cache' }),
        CGI::meta({ 'http-equiv'=>'Expires', content=>0 }),
        CGI::meta({ name=>'robots', content=>'noindex' }),
        CGI::Link( { -rel=>'icon', -href=>$ENV{SCRIPT_NAME}.'?action=image;image=configure.ico;type=image/x-icon', -type=>'image/x-icon' } ),
        CGI::Link( { -rel=>'shortcut icon', -href=>$ENV{SCRIPT_NAME}.'?action=image;image=logos/configure.ico;type=image/x-icon', -type=>'image/x-icon' } ),
        CGI::script( { language => 'JavaScript',
                       type => 'text/javascript' }, $js1 ),
        CGI::style( { -type=>'text/css' }, $css),
        CGI::script( { language => 'JavaScript',
                       type => 'text/javascript' }, $js2 ),
       ]);

# XML confuses IE, so strip it out. This is fixed in CGI.pm 3.06.
$hdr =~ s/^<\?xml.*?>//s;
print CGI::header('text/html'), $hdr;

my $body = "";
$body .= CGI::h1( CGI::img({src=>$ENV{SCRIPT_NAME}.'?action=image;image=logo.png;type=image/png', alt=>'TWiki'}).
  '&nbsp;Configuration');

my $update_disabled = 0;
my $path_to_localsite_cfg = _findFileOnPath('LocalSite.cfg');
unless( $path_to_localsite_cfg ) {
    $path_to_localsite_cfg = _findFileOnPath('TWiki.cfg') || '';
    $path_to_localsite_cfg =~ s/TWiki\.cfg/LocalSite.cfg/;
}
my $errs;
if( !$path_to_localsite_cfg ||
    ( $errs = _checkCanCreateFile( $path_to_localsite_cfg ))) {
    $errs ||= 'Cannot locate LocalSite.cfg';
    $body .= CGI::p(WARN('Save is disabled; '.$errs));
    $update_disabled = "Cannot create $path_to_localsite_cfg - are permissions correct?";
}

if( $action eq 'update' ) {
    if( $update_disabled ) {
        die ERROR( "Update is disabled $update_disabled" );
    }
    $body .= handleUpdate( $path_to_localsite_cfg );
} else {
    $body .=  <<HERE;
<p><strong>Use this page to set the configuration options for TWiki. Fill in the settings, and then press 'Next'.</strong></p>
<div class="explanation"><div class="specialRemark">If you are installing TWiki for the first time, and you just want to get up and running, the only section you need to worry about below is
<a rel="nofollow" href="#" onclick="foldBlock('GeneralPathSettings'); return false;">General path settings</a>. You can always come
back and configure other settings later.</div>
<ul>
    <li>If your TWiki site is already working, the front page should be
<a rel="nofollow" href="$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}">right here</a>.</li>
<li>If you are on a public site, you will need to consider carefully
how you are going to manage authentication and access control.</li>
<li>There are a number of documentation topics describing how to
configure TWiki for different platforms, and a lot of support
available on <a href="http://twiki.org/cgi-bin/view/Codev/TWikiIRC">TWikiIRC</a> (irc.freenode.net, channel #twiki) and at <a href="http://twiki.org">TWiki.org</a>.</li>
</ul>
</div>
<div class="remark">Explanation of color codes:
<ul style="margin-top:0;">
<li>Settings marked <span class='mandatory'>like this</span> are required (they must
have a value).</li>
<li>Any <span class='error'>errors</span> in your configuration will be highlighted.</li>
<li><span class='warn'>Warnings</span> are non-fatal, but are often a good indicator that something that is wrong.</li>
</ul></div>
HERE

    $body .= performSanityChecks( $brokenTWikiCfgError, $brokenLocalSiteError );

    my $options = '';
    unless( $update_disabled ) {
        $body .= CGI::start_form({ action=>$ENV{SCRIPT_NAME},method=>"post" });
        # use time to make sure we never allow cacheing
        $options .= CGI::hidden( 'action', 'update' );
        $options .= CGI::hidden( 'time', time() );
    }
    $options .= CGI::div({ class => 'optionHeader'}, 
        CGI::span({ class => 'twikiLeft' }, 'Settings' . 
            CGI::span({ class => 'twikiSmall' },
                      'Click the buttons below to open each section')).
            CGI::span({ class => 'twikiSmall twikiRight' },
                CGI::a({ href => '#', rel => 'nofollow',
                         onclick => 'toggleAllOptions(true); return false;'}, 'Open all options')).
            CGI::br());

    $options .= presentReadOnlyInfo();

    $options .= presentEditableInfo();

    $body .= CGI::div({class=>'options', id=>'options'}, $options);
    my $totwarningsMess = ($totwarnings > 1) ? ' warnings' : ' warning';
    $body .= CGI::div('Total: '.CGI::span(
        {class=>'warn'}, $totwarnings . $totwarningsMess)) if $totwarnings;
    my $toterrorsMess = ($toterrors > 1) ? ' errors' : ' error';
    $body .= CGI::div('Total: ' . CGI::span(
        {class=>'error'}, $toterrors . $toterrorsMess)) if $toterrors;

    if( $update_disabled) {
        $body .= CGI::em("Update is disabled - $update_disabled");
    } else {
        $body .= CGI::p(CGI::submit(-class=>'twikiSubmit', -value=>'Next'));
        $body .= CGI::end_form();
    }

}
print CGI::div({class => 'patternMain'}, $body);
print CGI::end_html();

1;
