#!/usr/bin/perl -w
#
# TWiki Enterprise Collaboration Platform, http://TWiki.org/
#
# Copyright (C) 2000-2007 TWiki Contributors.
#
# 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. For
# more details read LICENSE in the root of this distribution.
#
# 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.
#
# As per the GPL, removal of this notice is prohibited.
#
# 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.
#
# The script works from the top down, by checking features of the
# environment before moving on. The sequence is:
# 1. Check the version of perl
# 2. Check we have the modules to run this script
# 3. Check the environment
# 4. Check we have the modules to load the rest of configure
# ... and so on. At any stage, the script reports any errors in the
# best way it can given the environment established so far.
# When the basic checks are complete, the script moves into the
# real configuration steps; setting configuration variables.
#
# This phase of the configure environment follows a Model-View-
# Controller pattern.
#
# Controller
# This script is the controller; it handles communication with the
# browser (and thus the user). Communication is very simple; this script
# is re-invoked with different 'action' parameters to determine what it does.
#
# Model
# The Model consists of a simple node tree, where each node represents a
# structural element in the *presentation* of the configuration (this may
# not be consistent with the structure of $TWiki:cfg, so beware). Each
# leaf node has an associated Type (in the Types subdirectory) that has
# collected model and view behaviours for the basic types.
# The Model is independent of the language used to represent the
# configuration. There is one parser/generator provided, TWikiCfg, but it
# would be trivial to add others.
#
# The View is a DOM document, generated as HTML by a set of UI classes.
# Because of some convoluted history, there are actually three sets of classes
# that generate views. They are all subclasses of TWiki::Configure::UI
#    UIs - are top-level and pluggable UI components. All the main screens are
#          implemented here.
#    Checkers - are specialised UIs designed to give checking support for
#          variable values. Checkers also include the read-only checking
#          UIs used for checking environment.
#    Types - provide some UI support in the form of type-specific prompters.
#          this is really an abuse of the Model, but it saves creating
#          decorator classes for all the Model types.
# HTML is generated for the model using Visitor pattern. Each node in the tree
# is visited in depth-first order.
#
use strict;
use warnings;

# This is absolutely essential for error reporting. We load it using
# an eval so we can report the problem.
eval "use CGI::Carp qw(fatalsToBrowser)";
if ($@) {
    print <<"REPORT";
Content-type: text/plain

Could not load CGI::Carp. Please install this module before continuing.
It can be downloaded from http://www.cpan.org

The error seen was:
$@
REPORT
    exit 1;
}

###########################################################
# VERY basic stuff required for configure to work. Any errors
# during this phase will throw a die, which will be picked up
# using CGI::Carp fatalsToBrowser


# Warnings are fatal
$SIG{'__WARN__'} = sub { die @_ };

eval 'require 5.00503';
die $@ if $@;

# We warn against running TWiki on an older Perl version then 5.8.0
# but we will not let configure die in this situation. The user
# may have updated many libraries and tweaked TWiki so let us give
# him a chance.
my $perlversion = $];
if ($perlversion < 5.008) {
    print STDERR <<HERE;
Your perl version is older than 5.8.0.
TWiki has only been successfully tested on Perl 5.8.X,
and there has been reports that it does not run on 5.6.and 5.5.
Running TWiki with an older Perl version requires upgrading of modules and
tweaking of the TWiki code.
HERE
}

my $localLibFailure;

sub _loadBasicModule {
    my $module = shift;

    eval "use $module";
    if ($@) {
        my $reason = <<HERE;
Could not load $module. Please ensure this module is installed
and available on the perl \@INC path before continuing. If the
module is already installed, but can't be found, you may need
to adjust your perl path, for example by setting PERL5LIB.

The error seen was:
$@
HERE
        if ($localLibFailure) {
            $reason .= <<HERE;

NOTE that I was unable to load LocalLib.cfg because of the following error:

$localLibFailure
HERE
        }
        die $reason;
    }
}

foreach my $module ('FindBin',
                    'File::Spec',
                    'Config',
                    'CGI qw(:any)'
                   ) {
    _loadBasicModule($module);
}

# Capture DIE for stack *when debugging*
#$SIG{__DIE__} = sub { Carp::confess( $_[0] || '' ) };

###########################################################
# Establish the path to the TWiki library

# Set the working dir to the bin dir
no warnings;
$FindBin::Bin =~ /^(.*)$/;
use warnings;
chdir($1);
my @root = File::Spec->splitdir($1);
pop(@root);
my @script = File::Spec->splitdir($0);
my $scriptName = pop(@script);
$scriptName =~ s/.*[\/\\]//;  # Fix for Item3511, on Win XP

# Try to load the LocalLib.cfg optional overload

# Paths from LocalLib.cfg (preferred)
use vars qw( $twikiLibPath @localPerlLibPath );

eval 'require "setlib.cfg"';

if ($@) {
    # No joy. Remember the failure so we can report it later.
    $localLibFailure = $@;
    # Stick the root/lib on the path; there's a high probability we'll be
    # able to find the bits of TWiki::Configure that way. We will report
    # the setlib error later.
    unshift(@INC, File::Spec->catfile(@root, 'lib'));
}

# Load all the bits of the configure module that we use
foreach my $module (
    'TWiki::Configure::Checker',
    'TWiki::Configure::JS',
    'TWiki::Configure::CSS',
    'TWiki::Configure::UI',
    'TWiki::Configure::Root',
    'TWiki::Configure::Valuer',
    'TWiki::Configure::TWikiCfg',
    'TWiki::Configure::Load',
   ) {
    _loadBasicModule($module);
}

$| = 1;                  # no buffering

###########################################################
# From this point on we shouldn't have any more "fatal" (to configure)
# errors, so we can report errors in the browser (i.e. without using die)

# We are configuring $TWiki::cfg, so we need to be in package TWiki from
# now on.
package TWiki;

# We keep the actual config, and the default from TWiki.cfg, separate
use vars qw( %cfg $defaultCfg);

# Declared in TWiki to support checkers
use vars qw( $query );

# 'constants' used in TWiki.cfg
use vars qw( $TRUE $FALSE );
$TRUE = 1;
$FALSE = 0;

# Remember what we detected previously, for use by Checkers
if( $scriptName =~ /(\.\w+)$/ ) {
    $TWiki::cfg{DETECTED}{ScriptExtension} = $1;
}

# very basic tool
sub findFileOnPath {
    my $file = shift;

    $file =~ s(::)(/)g;

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

###########################################################
# Grope the OS. This duplicates a bit of code in TWiki.pm,
# but it has to be duplicated because we don't want to deal
# with loading TWiki just yet.

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';
    }
}

$query = new CGI;

my $url = $query->url();
my $action = $query->param('action') || 'Configure';

# Handle serving an image embedded in the configure page, before generating
# any other output
if( $action eq 'image' ) {
    # SMELL: this call is correct, but causes a perl error
    # on some versions of CGI.pm
    # print $query->header(-type => $query->param('type'));
    # So use this instead:
    print 'Content-type: '.$query->param('type')."\n\n";
    if( open(F, 'logos/'.$query->param('image' ))) {
        local $/ = undef;
        print <F>;
        close(F);
    }
    exit 0;
}

my @meta = (
    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=>$scriptName.'?action=image;image=favicon.ico;type=image/x-icon',
                 -type=>'image/x-icon' } ),
    CGI::Link(
        { -rel=>'shortcut icon',
          -href=>$scriptName.'?action=image;image=logos/favicon.ico;type=image/x-icon',
          -type=>'image/x-icon' } ),
    CGI::script( { language => 'JavaScript',
                   type => 'text/javascript' }, TWiki::Configure::JS::js1() ),
    CGI::style( { -type=>'text/css' }, TWiki::Configure::CSS::css()),
    CGI::script( { language => 'JavaScript',
                   type => 'text/javascript' },
                 TWiki::Configure::JS::js2() ),
   );

# Generate standard page header
my $hdr = CGI::start_html(
    -title => 'TWiki Configuration',
    -head => \@meta,
    -class => 'patternNoViewPage');

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

print <<'HERE';
<div id="patternScreen">
 <div id="patternPageShadow">
  <div id="patternPage">
   <div id="patternOuter">
    <div id="patternFloatWrap">
     <div id="patternMain">
      <div id="patternMainContents">
HERE

# use this script recursively to serve the icon image
print CGI::img({src=>$scriptName.'?action=image;image=T-logo-140x40-t.gif;type=image/gif', class=>'logo', alt=>'TWiki', width=>'140', height=>'40'});

my $stub = new TWiki::Configure::Item();
my $sanityUI = TWiki::Configure::UI::loadChecker('BasicSanity', $stub);
print $sanityUI->ui();

# This is the dispatcher; $action is the name of the action to perform,
# this is concatenated to _action to determine the name of the procedure.
# Dispatcher methods return a boolean to indicate whether to generate a
# link back to the main page at the end.
unless ($sanityUI->insane() || $query->param('abort')) {

    $action =~ s/\W//g;
    my $method = '_action'.$action;

    die "Undefined action $action" unless defined(&$method);

    no strict 'refs';
    my $reroute = &$method();
    use strict 'refs';

    if ($reroute) {
        print '<div>';
        print CGI::a( { href=>$TWiki::query->url().'?t='.time(),
                        rel => 'nofollow' },
                      'Return to configuration');
        print CGI::br();
        print "</div>\n";
    }
}

print <<'HERE';
</div><!--/patternMainContents-->
</div><!--/patternMain-->
</div><!--/patternFloatWrap-->
<div class="clear">&nbsp;</div>
</div><!--/patternOuter-->
</div><!--/patternPage-->
</div><!--/patternPageShadow-->
</div><!--/patternScreen-->
HERE

print CGI::end_html(),"\n";

###########################################################
# End of the main program; the rest is all subs

sub _checkLoadUI {
    my ($uiname, $root) = @_;
    my $ui = TWiki::Configure::UI::loadUI($uiname, $root);
    unless ($ui) {
        print "Could not load $uiname UI. Error was: <pre>$@</pre>";
        if ($@ =~ /Can't locate (\S+)/) {
            print <<HERE
You may be able to correct this error by installing the missing $1 module.
HERE
        }
    }
    return $ui;
}

# Action invoked by 'Next' button on the main screen
sub _actionNext {
    my $valuer = new TWiki::Configure::Valuer(
        $TWiki::defaultCfg, \%TWiki::cfg);
    my %updated;
    my $modified = $valuer->loadCGIParams($TWiki::query, \%updated);

    # create the root of the UI
    my $root = new TWiki::Configure::Root();
    my $ui;
    if (!TWiki::Configure::UI::authorised()) {
        print CGI::h2('Authorisation is required to save.');
        print CGI::div($modified.' configuration item'.
                         ($modified==1?' was':'s were').' changed');
        if ($modified) {
            print CGI::div(join(' ', keys %updated));
        }
        $ui = _checkLoadUI('AUTH', $root);
        return 1 unless $ui;
        print $ui->ui(1, 'Save');
    } else {
        # Load the specs from the .spec files and generate the UI template
        TWiki::Configure::TWikiCfg::load($root);

        $ui = _checkLoadUI('UPDATE', $root);
        return 1 unless $ui;
        print $ui->ui($root, $valuer, \%updated);
    }
    return 1;
}

# Invoked by "find more extensions" button in the Extensions section
sub _actionFindMoreExtensions {
    print CGI::h1( 'Find TWiki Extensions');
    print '<div class="patternContent"><div class="patternTopic">';

    my $ui = _checkLoadUI('EXTENSIONS');
    return 1 unless $ui;

    print $ui->ui();
    return 1;
}

# Invoked when an extension is to be installed
sub _actionInstallExtension {
    my $root = new TWiki::Configure::Root();
    my $ui;
    if (!TWiki::Configure::UI::authorised()) {
        $ui = _checkLoadUI('AUTH', $root);
        return 1 unless $ui;
        print $ui->ui(0, 'Install '.($query->param('extension')||''));
    } else {
        $ui = _checkLoadUI('EXTEND', $root);
        return 1 unless $ui;
        print $ui->ui();
    }
    return 1;
}

# This is the default screen
sub _actionConfigure {

    $TWiki::Configure::UI::toterrors = 0;
    $TWiki::Configure::UI::totwarnings = 0;

    print CGI::h1( 'Configuration');

    # The first three sections go without a root
    my $stub = new TWiki::Configure::Item();
    my $eui = TWiki::Configure::UI::loadChecker('Environment', $stub);

    # See if this platform has special detection or checking requirements
    # (most don't)
    $stub = new TWiki::Configure::Item();
    my $osui = TWiki::Configure::UI::loadChecker(
        $Config::Config{osname}, $stub);

    $stub = new TWiki::Configure::Item();
    my $cgiui = TWiki::Configure::UI::loadChecker('CGISetup', $stub);

    # Use a separate root for the _saveable_ sections
    my $root = new TWiki::Configure::Root();
    my $valuer = new TWiki::Configure::Valuer(
        $TWiki::defaultCfg, \%TWiki::cfg);

    # Load the config structures.
    TWiki::Configure::TWikiCfg::load($root);

    print <<HERE;
<div class="patternContent">
 <div class="patternTopic">
  <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">
    <b>If you are installing TWiki for the first time</b><br />
    If 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><b>If your TWiki site is already working</b>, continue to
     <a rel="nofollow" href="$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}/$TWiki::cfg{SystemWebName}/WebHome">
browse to the TWiki WebHome</a>.
     <ul>
      <li>You will now need to consider how you are going to manage
       authentication and access control. See the reference manual
       sections on <a rel="nofollow" href="$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}/$TWiki::cfg{SystemWebName}/TWikiUserAuthentication">
 authentication</a>
       and
       <a rel="nofollow" href="$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}/$TWiki::cfg{SystemWebName}/TWikiAccessControl">
        access control</a>, and the
       <a rel="nofollow" href="#" onclick="foldBlock('SecuritySetup'); return false;">Security Setup</a>
       section below.
      </li>
     </ul>
    </li>
    <li>
     <b>If you are on a non-standard platform or environment</b> there are a lot of
     <a href="http://twiki.org/cgi-bin/view/TWiki/SupplementalDocument">supplemental documents</a>
     on TWiki.org describing how to
     <a href="http://twiki.org/cgi-bin/view/TWiki/InstallingTWiki">install</a>, 
     <a href="http://twiki.org/cgi-bin/view/TWiki/UpgradingTWiki">upgrade</a>, 
     <a href="http://twiki.org/cgi-bin/view/TWiki/InternationalizationSupplement">internationalize</a> and 
     <a href="http://twiki.org/cgi-bin/view/TWiki/SecuringTWikiSite">secure</a>
     your installation.
    </li>
    <li>
      <b>If you get stuck</b> there is a lot of support available at the
      <a href="http://twiki.org/cgi-bin/view/Support/WebHome">TWiki:Support</a>
      forum and on
      <a href="http://twiki.org/cgi-bin/view/Codev/TWikiIRC">TWikiIRC</a>
      (irc.freenode.net, channel #twiki).
     </li>
    </ul>
   </div>
   <div class="remark">Explanation of colours and symbols:
    <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>
     <li>The little <span class='twikiAlert' title="Not here, stupid. In the table below!">&delta;</span> after an entry means that the current value is <b>not</b> the same as the default value. If you hover the cursor over the <span class='twikiAlert' title="Not here, stupid. In the table below!">&delta;</span>, a popup will show you what the default value is.</li>
     <li><b><font color="#f00">EXPERT</font></b> means a setting is for expert use only. You should not fiddle with it unless you know what you are doing, or at least have read all the documentation!</lib>
    </ul>
   </div><!-- remark-->
  </div><!--explanation-->
HERE

    print CGI::start_form({ name=>'update',
                            action=>$scriptName,
                            method=>"post" });
    # use time to make sure we never allow cacheing
    print CGI::hidden( 'time', time() );

    print '<div class="options" id="options">';
    print 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());

    print $eui->ui();
    print $osui->ui() if $osui;
    print $cgiui->ui() if $cgiui;

    # Load the UI for the configuration and whack it out
    my $ui = _checkLoadUI('Root', $root);
    return 1 unless $ui;

    print $ui->ui($root, $valuer);

    print "</div><!-- options -->\n";

    if ($TWiki::Configure::UI::toterrors || $TWiki::Configure::UI::totwarnings) {
        my $mess = 'Total: '.$TWiki::Configure::UI::toterrors.' error'.
          ($TWiki::Configure::UI::toterrors==1?'':'s').', '.
            $TWiki::Configure::UI::totwarnings.' warning'.
              ($TWiki::Configure::UI::totwarnings==1?'':'s');
        print CGI::div($mess);
    }

    print CGI::p(CGI::submit(-class=>'twikiSubmit',
                             -name=>'action',
                             -value=>'Next',
                             -accesskey=>'N'));

    print "<a href=\"$TWiki::cfg{ScriptUrlPath}/view$TWiki::cfg{ScriptSuffix}/$TWiki::cfg{SystemWebName}/WebHome\">Cancel and return to TWiki WebHome</a>";

    print CGI::end_form();

    print <<DIVS;
 </div><!--/patternTopic-->
</div><!--/patternContent-->
DIVS
    return 0;
}

1;

