<?PHP
#
#   FILE:  installcwis.php
#
#   Part of the Collection Workflow Integration System
#   Copyright 2009 Edward Almasy and Internet Scout
#   http://scout.wisc.edu
#


# ----- CONFIGURATION --------------------------------------------------------

$G_OldestVersionWeCanUpgrade = "1.4.0";

$G_MinimumPhpVersion = "5.1.0";
$G_MinimumMysqlVersion = "5.0";

$G_DirsThatMustBeWritable = array(
        "/",
        "/include",
        "/tmp",
        "/local/data",
        "/local/logs",
        );
$G_DirsToCreate = array(
        "ImageStorage" => "w",
        "FileStorage" => "w",
        "tmp" => "w",
        );
$G_DatabaseSetupFiles = array(
        "install/TestDBPerms.sql" => "Testing database permissions...",
        "lib/AxisPHP/Axis--Session--CreateTables.sql" => "Setting up session tables...",
        "lib/AxisPHP/Axis--User--CreateTables.sql" => "Setting up user tables...",
        "lib/ScoutLib/ApplicationFramework--CreateTables.sql"
                => "Setting up application framework tables...",
        "lib/ScoutLib/SearchEngine--CreateTables.sql"
                => "Setting up search engine tables...",
        "lib/ScoutLib/EventLog--CreateTables.sql"
                => "Setting up event logging tables...",
        "lib/ScoutLib/PluginManager--CreateTables.sql"
                => "Setting up plugin manager tables...",
        "lib/ScoutLib/RSSClient--CreateTables.sql"
                => "Setting up RSS client tables...",
        "install/CreateTables.sql" => "Setting up CWIS tables...",
        );

# SQL errors we can ignore (index = SQL command, value = error message)
$G_SqlErrorsWeCanIgnore = array(
        "/CREATE TABLE /i" => "/Table '[a-z0-9_]+' already exists/i",
        "/ALTER TABLE [a-z]+ ADD /i" => "/Duplicate column name/i",
        "/ALTER TABLE [a-z]+ DROP COLUMN/i" => "/check that column/i",
        "/ALTER TABLE [a-z]+ CHANGE COLUMN/i" => "/Unknown column/i",
        );

$G_VerbosityLevel = 1;


# ----- FORMS AND MESSAGES ---------------------------------------------------

function PrintInstallInfoForm($FVars, $ErrMsgs)
{
    # set up default values
    $Protocol = isset($_SERVER["HTTPS"]) ? "https://" : "http://";
    $SiteUrl = isset($FVars["F_SiteUrl"]) ? $FVars["F_SiteUrl"]
            : $Protocol.$_SERVER["HTTP_HOST"].dirname($_SERVER["SCRIPT_NAME"])."/";
    $DBHost = isset($FVars["F_DBHost"]) ? $FVars["F_DBHost"] : "localhost";
    $DBLogin = isset($FVars["F_DBLogin"]) ? $FVars["F_DBLogin"] : "";
    $DBPassword = isset($FVars["F_DBPassword"]) ? $FVars["F_DBPassword"] : "";
    $DBName = isset($FVars["F_DBName"]) ? $FVars["F_DBName"] : "";
    $AdminLogin = isset($FVars["F_AdminLogin"]) ? $FVars["F_AdminLogin"] : "";
    $AdminPassword = isset($FVars["F_AdminPassword"]) ? $FVars["F_AdminPassword"] : "";
    $AdminEmail = isset($FVars["F_AdminEmail"]) ? $FVars["F_AdminEmail"] : "";
    $AdminEmailAgain = isset($FVars["F_AdminEmailAgain"]) ? $FVars["F_AdminEmailAgain"] : "";

    # display form
    ?>
    <form method="POST" action="installcwis.php">
    <table class="InfoTable">

        <tr><td colspan="2">
        Before beginning installation, we need to gather a few bits of
        information to set up the software and allow it to access the SQL
        database where the resource data will be stored.  It is important
        that this information be entered correctly, so please take the time
        to read the notes accompanying each field to make sure you enter the
        right values, and consult your system administrator if you're not sure.
        </td></tr>

        <tr><td>&nbsp;</td></tr>
        <tr>
            <th>Site URL:</th>
            <td><input type="text" size="40" maxlength="120"
                    name="F_SiteUrl" value="<?PHP  print($SiteUrl);  ?>" /></td>
        </tr>
        <tr><td></td><td>
            This is the URL (web address) where the software will be accessed.
        </td></tr>
        <tr><td>&nbsp;</td></tr>

        <tr>
            <th>Database Host:</th>
            <td><input type="text" size="30" maxlength="60"
                    name="F_DBHost" value="<?PHP  print($DBHost);  ?>" /></td>
        </tr>
        <tr><td></td><td>
            This is the name of the computer on which your SQL database server
            is running.  If your web server and your database server are running
            on the same computer, then you should enter <i>localhost</i> here.
        </td></tr>
        <tr><td>&nbsp;</td></tr>

        <tr>
            <th>Database Login:</th>
            <td><input type="text" size="15" maxlength="40"
                    name="F_DBLogin" value="<?PHP  print($DBLogin);  ?>" /></td>
        </tr>
        <tr>
            <th>Database Password:</th>
            <td><input type="text" size="15" maxlength="40"
                    name="F_DBPassword" value="<?PHP  print($DBPassword);  ?>" /></td>
        </tr>
        <tr><td></td><td>
            This is the login name and password that you need to
            connect to your database server.  Please note that this is a
            <b>database server</b> user name and password, which must have
            already been set up by your database administrator, <b>not</b>
            your Linux or OS X login name and password.<br />
            <br />
            Please Note:  If the database named below does not already exist,
            this database user account must have <i>CREATE</i> privileges.
            If the database <b>does</b> exist, it must not already
            contain tables or data.
        </td></tr>
        <tr><td>&nbsp;</td></tr>

        <tr>
            <th>Database Name:</th>
            <td><input type="text" size="30" maxlength="60"
                    name="F_DBName" value="<?PHP  print($DBName);  ?>" /></td>
        </tr>
        <tr><td></td><td>
            This is the name of the SQL database (the internal database name
            that you yourself choose, like <i>PortalDB</i> or <i>OurDB</i>, not
            the name of the database software package) that we will use to
            store portal information.<br />
            <br />
            Please Note: If this database already exists, it must <b>not</b>
            already contain tables or data.
        </td></tr>
        <tr><td>&nbsp;</td></tr>

        <tr>
            <th>Admin Login:</th>
            <td><input type="text" size="15" maxlength="30"
                    name="F_AdminLogin" value="<?PHP  print($AdminLogin);  ?>" /></td>
        </tr>
        <tr>
            <th>Admin Password:</th>
            <td><input type="text" size="15" maxlength="30"
                    name="F_AdminPassword" value="<?PHP  print($AdminPassword);  ?>" /></td>
        </tr>
        <tr>
            <th>Admin E-Mail:</th>
            <td><input type="text" size="40" maxlength="120"
                    name="F_AdminEmail" value="<?PHP  print($AdminEmail);  ?>" /></td>
        </tr>
        <tr>
            <th>Admin E-Mail:</th>
            <td><input type="text" size="40" maxlength="120"
                    name="F_AdminEmailAgain" value="<?PHP  print($AdminEmailAgain);  ?>" /> <b>(again to confirm)</b></td>
        </tr>
        <tr><td></td><td>
            This is the user name and password that you will initially use
            to log into and configure your portal, and the e-mail address
            where any administrative e-mail will be sent.  The password must
            be at least six characters long.
        </tr>
        <tr><td>&nbsp;</td></tr>

        <tr><td></td><td>
            <input type="submit" name="F_Submit" value="Begin Installation">
            <span style="float: right; color: grey; font-size: 12px;">
                    Verbosity: <input type="checkbox" name="F_Debug" <?PHP
                    if (isset($FVars["F_Debug"])) {  print("checked");  }
                    ?>><input type="checkbox" name="F_MoreDebug" <?PHP
                    if (isset($FVars["F_MoreDebug"])) {  print("checked");  }
                    ?>><input type="checkbox" name="F_EvenMoreDebug" <?PHP
                    if (isset($FVars["F_EvenMoreDebug"])) {  print("checked");  }
                    ?>></span>
        </tr>

    </table>
    </form>
    <?PHP
}

function PrintErrorMessages($ErrMsgs)
{
    if (count($ErrMsgs))
    {
        ?><b>Errors Encountered:</b>
        <ul class="ErrorList"><?PHP
        foreach ($ErrMsgs as $Msg)
        {
            ?><li><?PHP  print($Msg);  ?></li><?PHP
            LogMsg("ERROR: ".$Msg);
        }
        ?></ul><?PHP
    }
}

function BeginHtmlPage($OurVersion)
{
    ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <title>CWIS <?PHP  print($OurVersion);  ?> Installation</title>
        <style type="text/css">
            body {
                background-color: #B3B3B3;
            }
            .MainTable {
                margin: 5% 10% 10% 10%;
                padding: 2em;
                background-color: #FFFFFF;
                border: 2px solid #999999;
            }
            .InfoTable {
                width: 100%;
            }
            .InfoTable tr {
                vertical-align: top;
            }
            .InfoTable th {
                text-align: right;
                white-space: nowrap;
            }
            .InstallInfoSummaryTable {
                border: 1px solid #DDDDDD;
                background-color: #EEEEEE;
                padding: 15px;
            }
            .InstallInfoSummaryTable th {
                text-align: right;
                white-space: nowrap;
                padding-right: 10px;
            }
            .TitleLine {
                font-size: 1.5em;
                font-weight: bold;
                font-family: sans-serif;
            }
            .LogoC
            {
                color: #E79D1B;
                font-weight: bold;
                font-family: sans-serif;
            }
            .LogoWIS
            {
                color: #505B84;
                font-weight: bold;
                font-family: sans-serif;
            }
            .ErrorList
            {
                color: red;
            }
        </style>
    </head>
    <body>
    <table class="MainTable">
        <tr><td colspan="2"><span class="TitleLine"><span class="LogoC">C</span><span class="LogoWIS">WIS</span> <?PHP  print($OurVersion);  ?> Installation</span></td><tr>
        <tr><td colspan="2">
        </td></tr>
        <tr><td>
    <?PHP
}

function EndHtmlPage()
{
    ?>
        </td></tr>
    </table>
    </body>
    </html>
    <?PHP
}

function PrintInstallInfoSummary($FVars, $IsUpgrade)
{
    ?>
    <table class="InstallInfoSummaryTable" width="100%">
        <tr><th>Site Base URL:</th>
                <td><i><?PHP  print($FVars["F_SiteUrl"]);  ?></i></td></tr>
        <tr><th>Database Host:</th>
                <td><i><?PHP  print($FVars["F_DBHost"]);  ?></i></td></tr>
        <tr><th>Database Login:</th>
                <td><i><?PHP  print($FVars["F_DBLogin"]);  ?></i></td></tr>
        <tr><th>Database Name:</th>
                <td><i><?PHP  print($FVars["F_DBName"]);  ?></i></td></tr>
        <?PHP
        if (!$IsUpgrade)
        {
        ?>
        <tr><th>Admin Login:</th>
                <td><i><?PHP  print($FVars["F_AdminLogin"]);  ?></i></td></tr>
        <tr><th>Admin E-Mail:</th>
                <td><i><?PHP  print($FVars["F_AdminEmail"]);  ?></i></td></tr>
        <?PHP
        }
        ?>
    </table>
    <br />
    <?PHP
}

function PrintInstallCompleteInfo($FVars, $IsUpgrade)
{
    ?>
    <br />
    <?PHP
    if ($IsUpgrade)
    {
        ?>
        You may now proceed to your <a href="<?PHP  print($FVars["F_SiteUrl"]);
            ?>index.php" target="_blank">upgraded CWIS site</a>.
            You may also want to sign up for the
            <a href="https://scout.wisc.edu/cwis/support" target="new">CWIS
            forums</a>, in case you have questions about the software or want to
            share your experience with other users.<br />
        <?PHP
        if ($FVars["F_DefaultUI"] == "SPTUI--Default")
        {
            ?>
            <br />
            <b>PLEASE NOTE:</b>  It appears that you have upgraded from SPT to CWIS.
            If some pages on your site appear to load incorrectly, you may
            need to edit the file <code>config.php</code> and change the
            value of <code>$SPT_DefaultUI</code> to <code>"default"</code>.<br />
            <?PHP
        }
    }
    else
    {
        ?>
        You may now proceed to your <a href="<?PHP  print($FVars["F_SiteUrl"]);
            ?>index.php">new CWIS site</a> and log in with the user name <i><?PHP
            print($FVars["F_AdminLogin"]);  ?></i> and the password you supplied.
            You may also want to sign up for the
            <a href="https://scout.wisc.edu/cwis/support" target="new">CWIS
            forums</a>, in case you have questions about the software or want to
            share your experience with other users.<br />
        <?PHP
    }
    ?>
    <br />
    Thank you for using <span class="LogoC">C</span><span class="LogoWIS">WIS</span>!
    <?PHP
}

function PrintHelpPointers()
{
    ?>
    <br />
    Please correct these problems and re-run the installation.<br />
    <br />
    Troubleshooting information can be found online in the
    <a href="http://scout.wisc.edu/Projects/CWIS/faq.php#trouble"
    target="new">CWIS FAQ</a>.  Questions can be posted to the
    <a href="http://scout.wisc.edu/mailman/listinfo/SPT-CWIS-Users"
    target="new">CWIS Users Mailing List</a>.  Past questions and
    answers may be found in the
    <a href="http://www.scout.wisc.edu/pipermail/spt-cwis-users/"
    target="new">CWIS Users Mailing List Archives</a>.
    <?PHP
}


# ----- VALIDATION CHECKS ----------------------------------------------------

function CheckEnvironment($ErrMsgs)
{
    # check PHP version
    global $G_CwisVersion;
    global $G_MinimumPhpVersion;
    if (version_compare(PHP_VERSION, $G_MinimumPhpVersion) == -1)
    {
        $ErrMsgs[] = "Required PHP version not found."
                ." CWIS ".$G_CwisVersion." requires PHP version "
                        .$G_MinimumPhpVersion." or later."
                ."<br />PHP version <i>".PHP_VERSION
                        ."</i> was detected.";
    }

    # Check to make sure that gd is installed
    if (!extension_loaded("gd"))
    {
        $ErrMsgs[] = "The gd extension seems not to be loaded. CWIS uses GD for "
        ."image manipulation. GD has been a standard  part of php since php 4.3, "
        ."however some distributions split it into a separate package."
        ."For Debian/Ubuntu, this package is called php5-gd";
    }

    # check to make sure directories are writable
    $Cwd = getcwd();
    global $G_DirsThatMustBeWritable;
    foreach ($G_DirsThatMustBeWritable as $Dir)
    {
        # the directory must have a forward slash in the beginning since the
        # directory from getcwd() will not have a trailing slash
        $Dir = ($Dir{0} != "/") ? "/".$Dir : $Dir;

        $Dir = $Cwd.$Dir;
        if (!is_dir($Dir))
        {
            @mkdir($Dir, 0777);
            if (!is_dir($Dir))
            {
                $ErrMsgs[] = "Directory <i>".$Dir."</i> not found.";
            }
        }

        if (is_dir($Dir) && !is_writable($Dir))
        {
            @chmod($Dir, 0777);
            if (!is_writable($Dir))
            {
                $ErrMsgs[] = "Directory <i>".$Dir."</i> is not writable.";
            }
        }
    }

    # return updated error message list to caller
    return $ErrMsgs;
}

function NormalizeInstallInfo($FVars)
{
    # return normalized info to caller
    return $FVars;
}

function CheckInstallInfo($FVars, $ErrMsgs)
{
    # check MySQL availability and version and that we can create tables in DB
    if (!strlen(trim($FVars["F_DBHost"]))
            || !strlen(trim($FVars["F_DBLogin"]))
            || !strlen(trim($FVars["F_DBPassword"])))
    {
        if (!strlen(trim($FVars["F_DBHost"])))
            {  $ErrMsgs["F_DBHost"] = "No database host was supplied.";  }
        if (!strlen(trim($FVars["F_DBLogin"])))
            {  $ErrMsgs["F_DBLogin"] = "No database login was supplied.";  }
        if (!strlen(trim($FVars["F_DBPassword"])))
            {  $ErrMsgs["F_DBPassword"] = "No database password was supplied.";  }
    }
    else
    {
        $DBHandle = @mysql_connect(
                $FVars["F_DBHost"], $FVars["F_DBLogin"], $FVars["F_DBPassword"]);
        if ($DBHandle === FALSE)
            {  $ErrMsgs[] = "Could not connect to database on ".$FVars["F_DBHost"].".";  }
        else
        {
            global $G_MinimumMysqlVersion;
            global $G_CwisVersion;
            if (version_compare(mysql_get_server_info(), $G_MinimumMysqlVersion) == -1)
            {
                $ErrMsgs[] = "Required MySQL version not found."
                        ." CWIS ".$G_CwisVersion." requires MySQL version "
                                .$G_MinimumMysqlVersion." or later."
                        ."<br />MySQL version <i>".mysql_get_server_info()
                                ."</i> was detected.";
            }
            else
            {
                if (!strlen(trim($FVars["F_DBName"])))
                    {  $ErrMsgs["F_DBName"] = "No database name was supplied.";  }
                elseif (!mysql_select_db($FVars["F_DBName"]))
                {
                    if (mysql_query("CREATE DATABASE `"
                            .addslashes($FVars["F_DBName"])."`") === FALSE)
                        {  $ErrMsgs["F_DBName"] = "Could not create database.";  }
                    else
                    {
                        mysql_query("DROP DATABASE `".addslashes($FVars["F_DBName"])."`");
                    }
                }
            }
            mysql_close($DBHandle);
        }
    }

    # check to make sure that admin info looks valid (if not upgrading)
    if ($FVars["F_Submit"] != "Upgrade Installation")
    {
        if (!strlen(trim($FVars["F_AdminLogin"])))
        {
            $ErrMsgs["F_AdminLogin"] = "No administrative account login was supplied.";
        }
        if (!strlen(trim($FVars["F_AdminPassword"])))
        {
            $ErrMsgs["F_AdminPassword"] = "No administrative account password was supplied.";
        }
        $UnholyEmailAddressRegx = "/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\."
                ."[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+"
                ."(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD";
        if (!strlen(trim($FVars["F_AdminEmail"])))
        {
            $ErrMsgs["F_AdminEmail"] =
                    "No administrative account e-mail address was supplied.";
        }
        elseif (!preg_match($UnholyEmailAddressRegx, trim($FVars["F_AdminEmail"])))
        {
            $ErrMsgs["F_AdminEmail"] =
                    "An invalid administrative account e-mail address was supplied.";
        }
        elseif (trim($FVars["F_AdminEmailAgain"]) != trim($FVars["F_AdminEmail"]))
        {
            $ErrMsgs["F_AdminEmailAgain"] =
                    "The two administrative account e-mail addresses did not match.";
        }
    }

    # return updated error message list to caller
    return $ErrMsgs;
}

function CheckDistributionFiles($ErrMsgs, $NewVersion)
{
    # error out if checksum file not found or not readable
    if (!file_exists("install/CHECKSUMS"))
    {
        $ErrMsgs[] = "Checksum file <i>install/CHECKSUMS</i> not found.";
        return $ErrMsgs;
    }
    elseif (!is_readable("install/CHECKSUMS"))
    {
        $ErrMsgs[] = "Checksum file <i>install/CHECKSUMS</i> not readable.";
        return $ErrMsgs;
    }

    # load in MD5 checksums
    $Lines = file("install/CHECKSUMS", FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
    $Checksums = array();
    foreach ($Lines as $Line)
    {
        $Pieces = preg_split("/\s+/", $Line);
        $Checksums[$Pieces[1]] = $Pieces[0];
    }

    # for each checksum
    $ErrorCount = 0;
    foreach ($Checksums as $FileName => $Chksum)
    {
        # if file exists
        if (file_exists($FileName))
        {
            # generate checksum for file
            $CalcChksum = md5_file($FileName);

            # if checksums do not match
            if (strtolower($CalcChksum) != strtolower($Chksum))
            {
                # record error message about checksum error
                $ErrMsgs[] = "Checksum does not match for CWIS ".$NewVersion
                        ." distribution file <i>".$FileName."</i>.";
                $ErrorCount++;
            }
        }
        else
        {
            # record error message about missing file
            $ErrMsgs[] = "Distribution file <i>".$FileName."</i> not found.";
            $ErrorCount++;
        }

        # quit if too many errors encountered
        if ($ErrorCount > 20)
        {
            $ErrMsgs[] = "More than 20 errors encountered when checking"
                    ." distribution file integrity.  If files were uploaded"
                    ." to the web server via FTP, please make sure they"
                    ." were transferred using \"Binary\" mode, rather than"
                    ." \"ASCII\" or \"Automatic\" mode.";
            break;
        }
    }

    # return updated error message list to caller
    return $ErrMsgs;
}


# ----- INSTALLATION ---------------------------------------------------------

function InstallFiles($FVars, $IsUpgrade)
{
    # create needed subdirectories and check they are writable
    Msg(1, ($IsUpgrade ? "Checking" : "Creating")." subdirectories...");
    $ErrMsgs = array();
    global $G_DirsToCreate;
    foreach ($G_DirsToCreate as $Dir => $Permissions)
    {
        Msg(2, "Dir: <i>".$Dir."</i>");
        if (file_exists($Dir))
        {
            if (!is_dir($Dir))
            {
                $ErrMsgs[] = "File found in place of subdirectory <i>".$Dir."</i>.";
            }
            elseif (($Permissions == "w") && !is_writable($Dir))
            {
                $ErrMsgs[] = "Subdirectory <i>".$Dir."</i> was not writable.";
            }
        }
        else
        {
            mkdir($Dir);
            if (!file_exists($Dir))
            {
                $ErrMsgs[] = "Unable to create subdirectory <i>".$Dir."</i>.";
            }
            else
            {
                if ($Permissions == "w")
                {
                    # make the new directory writable by us and readable by everyone
                    chmod($Dir, 0755);
                    if (!is_writable($Dir))
                    {
                        $ErrMsgs[] = "Unable to make Subdirectory <i>"
                                .$Dir."</i> writable.";
                    }
                }
            }
        }
    }

    # set up config file with DB info if not present
    if (!file_exists("config.php"))
    {
        Msg(1, "Creating configuration file...");
        $ConfigReplacements = array(
                "X-DBUSER-X" => addslashes($FVars["F_DBLogin"]),
                "X-DBPASSWORD-X" => addslashes($FVars["F_DBPassword"]),
                "X-DBHOST-X" => addslashes($FVars["F_DBHost"]),
                "X-DBNAME-X" => addslashes($FVars["F_DBName"]),
                );
        $ErrMsg = FilterFile(
                "install/config.php.DIST", "config.php",
                $ConfigReplacements);
        if ($ErrMsg) {  $ErrMsgs[] = $ErrMsg;  }
    }

    # set up .htaccess file with correct rewrite base
    if (!file_exists(".htaccess"))
    {
        Msg(1, "Creating .htaccess file...");
        $Pieces = parse_url($FVars["F_SiteUrl"]);
        $BasePath = trim($Pieces["path"]);
        $BasePath = preg_match("%/$%", $BasePath) ? $BasePath : dirname($BasePath);
        $ConfigReplacements = array(
                "X-REWRITEBASE-X" => $BasePath,
                );
        $ErrMsg = FilterFile(
                "install/htaccess.DIST", ".htaccess",
                $ConfigReplacements);
        if ($ErrMsg) {  $ErrMsgs[] = $ErrMsg;  }
    }

    # return any error messages to caller
    return $ErrMsgs;
}

function SetUpNewDatabase($FVars, $ErrMsgs)
{
    # create database if necessary
    $DBHandle = mysql_connect(
            $FVars["F_DBHost"], $FVars["F_DBLogin"], $FVars["F_DBPassword"]);
    $Result = mysql_select_db($FVars["F_DBName"]);
    if ($Result === FALSE)
    {
        $Result = mysql_query("CREATE DATABASE `".$FVars["F_DBName"]."`");
        if ($Result === FALSE)
        {
            $ErrMsgs[] = "Could not create database <i>".$FVars["F_DBName"]."</i>.";
            return $ErrMsgs;
        }
        $Result = mysql_select_db($FVars["F_DBName"]);
    }

    # set default storage engine (need MyISAM for full-text indexing)
    mysql_query("SET default_storage_engine=MYISAM");

    # set up database tables
    global $G_SqlErrorsWeCanIgnore;
    global $G_DatabaseSetupFiles;
    foreach ($G_DatabaseSetupFiles as $SqlFile => $Msg)
    {
        Msg(1, $Msg);
        $ErrMsg = ExecuteSqlFile($DBHandle, $SqlFile, $G_SqlErrorsWeCanIgnore, 3);
        if ($ErrMsg)
        {
            $ErrMsgs[] = $ErrMsg;
            return $ErrMsgs;
        }
    }

    # load default schema
    $QHandle = mysql_query("SELECT * FROM SystemConfiguration");
    if (($QHandle === FALSE) || (mysql_num_rows($QHandle) == 0))
    {
        Msg(1, "Loading default schema...");
        $ErrMsg = ExecuteSqlFile($DBHandle,
                "install/LoadDefaultSchema.sql", array(), 4);
        if ($ErrMsg)
        {
            $ErrMsgs[] = $ErrMsg;
            return $ErrMsgs;
        }
    }

    # load sample records
    $QHandle = mysql_query("SELECT * FROM Resources");
    if (($QHandle === FALSE) || (mysql_num_rows($QHandle) == 0))
    {
        Msg(1, "Loading sample records...");
        $ErrMsg = ExecuteSqlFile($DBHandle,
                "install/LoadSampleResources.sql", array(), 4);
        if ($ErrMsg)
        {
            $ErrMsgs[] = $ErrMsg;
            return $ErrMsgs;
        }
    }

    # create administrative account
    $QHandle = mysql_query("SELECT * FROM APUsers WHERE EMail != ''");
    if (($QHandle === FALSE) || (mysql_num_rows($QHandle) == 0))
    {
        Msg(1, "Adding administrator account...");
        mysql_query("UPDATE APUsers SET UserPassword = "
                ." '".addslashes(crypt($FVars["F_AdminPassword"]))."'"
                ." WHERE UserName = 'sptadmin'");
        mysql_query("UPDATE APUsers SET EMail = "
                ." '".addslashes($FVars["F_AdminEmail"])."'"
                ." WHERE UserName = 'sptadmin'");
        mysql_query("UPDATE APUsers SET CreationDate = NOW()"
                ." WHERE UserName = 'sptadmin'");
        mysql_query("UPDATE APUsers SET UserName = "
                ." '".addslashes($FVars["F_AdminLogin"])."'"
                ." WHERE UserName = 'sptadmin'");
        mysql_query("UPDATE APUsers SET RegistrationConfirmed=1");
        mysql_query("UPDATE SystemConfiguration SET AdminEmail ="
                ." '".addslashes($FVars["F_AdminEmail"])."'");
    }

    # return (possibly updated) error message list to caller
    mysql_close($DBHandle);
    return $ErrMsgs;
}

function UpgradeExistingDatabase($FVars, $ErrMsgs, $OldVersion)
{
    # open connection to database
    $DBHandle = mysql_connect(
            $FVars["F_DBHost"], $FVars["F_DBLogin"], $FVars["F_DBPassword"]);
    $Result = mysql_select_db($FVars["F_DBName"]);
    if ($Result === FALSE)
    {
        $ErrMsgs[] = "Could not connect to database <i>"
                .$FVars["F_DBName"]."</i> to upgrade.";
        return $ErrMsgs;
    }

    # for each available database upgrade file
    $SqlFileNames = ReadDirectory("install/DBUpgrades/.", "/DBUpgrade--.*\.sql/");
    $PhpFileNames = ReadDirectory("install/DBUpgrades/.", "/DBUpgrade--.*\.php/");
    $FileNames = array_merge($SqlFileNames, $PhpFileNames);
    # (use custom sort function so that SQL files execute before PHP files)
    function CompareDBUpgradeFileNames($A, $B)
    {
        if ($A == $B) {  return 0;  }
        list($ABase1, $ABase2, $ABase3, $AType) = explode(".", $A);
        $ABase = $ABase1.".".$ABase2.".".$ABase3;
        list($BBase1, $BBase2, $BBase3, $BType) = explode(".", $B);
        $BBase = $BBase1.".".$BBase2.".".$BBase3;
        if ($ABase == $BBase)
        {
            return ($AType < $BType) ? 1 : -1;
        }
        else
        {
            return ($ABase < $BBase) ? -1 : 1;
        }
    }
    usort($FileNames, "CompareDBUpgradeFileNames");
    foreach ($FileNames as $FileName)
    {
        # parse out version number of upgrade file
        $UpgradeVersion = str_replace("DBUpgrade--", "", $FileName);
        $UpgradeVersion = str_replace(".sql", "", $UpgradeVersion);
        $UpgradeVersion = str_replace(".php", "", $UpgradeVersion);

        # if upgrade file version is greater than old version
        if (strcmp($UpgradeVersion, $OldVersion) > 0)
        {
            # add file to list of those to be run
            $FilesToRun["install/DBUpgrades/".$FileName] =
                    "Upgrading database to version ".$UpgradeVersion
                    .(preg_match("/.php/", $FileName) ? " (PHP)" : "")
                    ."...";
        }
    }

    # if there were upgrades to be done
    if (isset($FilesToRun))
    {
        # add entry to test database permissions at start of upgrade
        $Test = array("install/TestDBPerms.sql" => "Testing database permissions...");
        $FilesToRun = $Test + $FilesToRun;

        foreach ($FilesToRun as $FileName => $Msg)
        {
            Msg(1, $Msg);
            if (preg_match("/.php/", $FileName))
            {
                include($FileName);
                $ErrMsg = isset($GLOBALS["G_ErrMsg"]) ? $GLOBALS["G_ErrMsg"] : NULL;
                unset($GLOBALS["G_ErrMsg"]);
            }
            else
            {
                $ErrMsg = ExecuteSqlFile($DBHandle, $FileName,
                        $GLOBALS["G_SqlErrorsWeCanIgnore"], 3);
            }
            if ($ErrMsg)
            {
                $ErrMsgs[] = $ErrMsg;
                return $ErrMsgs;
            }
        }
    }

    # return any error messages to caller
    return $ErrMsgs;
}

# (returns NULL if not upgrading)
function CheckForUpgrade()
{
    # if both old and new version files are present
    $OldVersion = NULL;
    if (file_exists("VERSION") && file_exists("NEWVERSION"))
    {
        # read in old version
        $InputFile = fopen("VERSION", "r");
        $OldVersion = trim(fgets($InputFile, 256));
        fclose($InputFile);

        # read in new version
        $InputFile = fopen("NEWVERSION", "r");
        $NewVersion = trim(fgets($InputFile, 256));
        fclose($InputFile);

        # if new version is the same as old version
        if ($NewVersion == $OldVersion)
        {
            # clear old version
            $OldVersion = NULL;
        }
        # else if new version is older than old version
        elseif ($NewVersion < $OldVersion)
        {
            # ???
        }
    }

    # return old version number to caller
    return $OldVersion;
}

function LoadOldInstallInfo(&$FVars, $ErrMsgs)
{
    # load values from existing configuration file
    if (file_exists("config.php"))
    {
        include("config.php");
    }
    elseif (file_exists("include/SPT--Config.php"))
    {
        include("include/SPT--Config.php");
    }

    $Protocol = isset($_SERVER["HTTPS"]) ? "https://" : "http://";

    $FVars["F_DBHost"] = $SPT_DBHost;
    $FVars["F_DBLogin"] = $SPT_DBUserName;
    $FVars["F_DBPassword"] = $SPT_DBPassword;
    $FVars["F_DBName"] = $SPT_DBName;
    $FVars["F_Submit"] = "Upgrade Installation";
    $FVars["F_SiteUrl"] = $Protocol.$_SERVER["HTTP_HOST"]
            .dirname($_SERVER["SCRIPT_NAME"])."/";
    $FVars["F_DefaultUI"] = $SPT_DefaultUI;

    # return any error messages to caller
    return $ErrMsgs;
}

function QueueFollowUpWork($FVars, $IsUpgrade, $OldVersion)
{
    if ($IsUpgrade)
    {
        $Callback = "PerformUpgradeFollowUp";
        $Parameters = array($OldVersion);
        $Description = "Upgrade Follow-Up";
    }
    else
    {
        $Callback = "PerformNewInstallFollowUp";
        $Parameters = NULL;
        $Description = "New Installation Follow-Up";
    }
    $DBHandle = @mysql_connect(
            $FVars["F_DBHost"], $FVars["F_DBLogin"], $FVars["F_DBPassword"]);
    mysql_select_db($FVars["F_DBName"]);
    $QHandle = mysql_query("SELECT * FROM TaskQueue"
            ." WHERE Callback = '".addslashes(serialize($Callback))."'"
            .($Parameters ? " AND Parameters = '"
                    .addslashes(serialize($Parameters))."'" : ""));
    $FoundCount = ($QHandle === FALSE) ? 0 : mysql_num_rows($QHandle);
    $QHandle = mysql_query("SELECT * FROM RunningTasks"
            ." WHERE Callback = '".addslashes(serialize($Callback))."'"
            .($Parameters ? " AND Parameters = '"
                    .addslashes(serialize($Parameters))."'" : ""));
    $FoundCount += ($QHandle === FALSE) ? 0 : mysql_num_rows($QHandle);
    if ($FoundCount < 1)
    {
        mysql_query("INSERT INTO TaskQueue"
                ." (Callback, Parameters, Priority, Description)"
                ." VALUES ('".addslashes(serialize($Callback))."',"
                ." '".addslashes(serialize($Parameters))."', "
                ." 1, '".addslashes($Description)."')");
    }
    mysql_close($DBHandle);
}


# ----- UTILITY FUNCTIONS ----------------------------------------------------

/**
* Replace specified strings in file.
* @param SrcFile Name of original file.
* @param DstFile Name of new file with strings replaced.
* @param Replacements Array of replacement strings with what is to be replaced as index.
* @return NULL if successful or error message if failed.
*/
function FilterFile($SrcFile, $DstFile, $Replacements)
{
    # read source file contents
    $Text = file_get_contents($SrcFile);
    if ($Text === FALSE) {  return "Unable to open file <i>".$SrcFile."</i>.";  }

    # make substitutions
    $Text = str_replace(array_keys($Replacements), $Replacements, $Text);

    # write out destination file
    $Result = file_put_contents($DstFile, $Text);
    if ($Result === FALSE) {  return "Unable to write file <i>".$SrcFile."</i>.";  }

    # return NULL to caller to indicate success
    return NULL;
}

function ExecuteSqlFile($DBHandle, $FileName, $ErrorsToIgnore, $DebugLevel)
{
    # open input file
    $FHandle = fopen($FileName, "r");
    if ($FHandle === FALSE)
    {
        return "Unable to open SQL file <i>".$FileName."</i>";
    }

    # while lines left in DB setup file and everything is going okay
    $CurrentCommand = "";
    $CurrentDisplay = array();
    while (!feof($FHandle))
    {
        # read in line from DB setup file
        $Line = fgets($FHandle, 8192);

        # trim whitespace from line
        $TLine = trim($Line);

        # if line is not a comment
        if ((preg_match("/^#/", $TLine) == FALSE)
                && (preg_match("/^--/", $TLine) == FALSE)
                && strlen($TLine))
        {
            # add line to current command
            $CurrentCommand .= " ".$TLine;
            $CurrentDisplay[] = rtrim($Line);

            # if line ends command
            if (preg_match("/;$/", $TLine) == TRUE)
            {
                # execute command
                foreach ($CurrentDisplay as $DLine)
                    {  Msg($DebugLevel, preg_replace("/^ /", "&nbsp;&nbsp;", $DLine));  }
                $CurrentCommand = preg_replace("/;$/", "", $CurrentCommand);
                $Result = mysql_query($CurrentCommand, $DBHandle);

                # if command failed
                if ($Result === FALSE)
                {
                    # if error was one we should not ignore
                    $IgnoreError = FALSE;
                    foreach ($ErrorsToIgnore as $SqlPattern => $ErrMsgPattern)
                    {
                        if (preg_match($SqlPattern, $CurrentCommand)
                                && preg_match($ErrMsgPattern, mysql_error()))
                        {
                            $IgnoreError = TRUE;
                        }
                    }
                    if ($IgnoreError === FALSE)
                    {
                        # bail out with error message
                        $ErrMsg = "Database command failed:\n";
                        foreach ($CurrentDisplay as $DLine)
                            {  $ErrMsg .= "<pre>".$DLine."</pre>\n";  }
                        $ErrMsg .= "(SQL error: <i>".mysql_error()."</i>)";
                        return $ErrMsg;
                    }
                }

                # clear current command
                $CurrentCommand = "";
                $CurrentDisplay = array();
            }
        }
    }

    # close input file
    fclose($FHandle);

    # report back to caller that everything worked
    return NULL;
}

function ReadDirectory($Path, $PerlExp = "")
{
    # while file names left to read from directory
    $FileNames = array();
    $Dir = opendir($Path);
    while ($FileName = readdir($Dir))
    {
        # if name matches mask
        if (($PerlExp != "") && preg_match($PerlExp, $FileName))
        {
            # store file name in array
            $FileNames[count($FileNames)] = $FileName;
        }
    }
    closedir($Dir);

    # return sorted array of file names to caller
    sort($FileNames);
    return $FileNames;
}

function Msg($VerbLvl, $Message)
{
    global $G_VerbosityLevel;
    if ($VerbLvl <= $G_VerbosityLevel)
    {
        for ($Index = $VerbLvl;  $Index > 1;  $Index--) {  print("&nbsp;&nbsp;");  }
        print($Message."<br />\n");
        LogMsg($Message);
    }
}

function LogMsg($Message)
{
    static $FHandle = FALSE;
    if ($FHandle == FALSE)
    {
        $InstallLogFile = "local/data/install.log";
        if (!is_dir(dirname($InstallLogFile))
                && is_writable(dirname(dirname($InstallLogFile))))
        {
            mkdir(dirname(dirname($InstallLogFile)));
        }
        if ((file_exists($InstallLogFile) && is_writable($InstallLogFile))
                || (!file_exists($InstallLogFile)
                        && is_writable(dirname($InstallLogFile))))
        {
            $FHandle = fopen($InstallLogFile, "a");
        }
    }
    if ($FHandle)
    {
        $LogMsg = date("Y-m-d H:i:s")."  ".strip_tags($Message)."\n";
        fwrite($FHandle, $LogMsg);
        fflush($FHandle);
    }
}


# ----- MAIN -----------------------------------------------------------------

# have all output write out immediately
ob_implicit_flush(TRUE);

# initialize starting values
$ErrMsgs = array();
$FVars = $_POST;

# set debug output level
if (isset($_POST["F_Debug"])) {  $G_VerbosityLevel = 2;  }
if (isset($_POST["F_MoreDebug"])) {  $G_VerbosityLevel = 3;  }
if (isset($_POST["F_EvenMoreDebug"])) {  $G_VerbosityLevel = 4;  }
if (isset($_GET["VB"])) {  $G_VerbosityLevel = $_GET["VB"];  }

# grab our version number
$NewVersion = file_exists("NEWVERSION") ? rtrim(file_get_contents("NEWVERSION")) : "";

# begin page
BeginHtmlPage($NewVersion);

# check environment to make sure we can run
$ErrMsgs = CheckEnvironment($ErrMsgs);

# check distribution files
if (!array_key_exists("NOCHKSUMS", $_GET))
{
    $ErrMsgs = CheckDistributionFiles($ErrMsgs, $NewVersion);
}

# if problems were found with environment
if (count($ErrMsgs))
{
    # display error messages
    PrintErrorMessages($ErrMsgs);
}
else
{
    # if we are upgrading
    $OldVersion = CheckForUpgrade();
    $IsUpgrade = $OldVersion ? TRUE : FALSE;
    if ($IsUpgrade)
    {
        # if existing version is too old to upgrade
        if ($OldVersion < $G_OldestVersionWeCanUpgrade)
        {
            $ErrMsgs[] = "Your currently installed version (<i>".$OldVersion
                    ."</i>) is too old to be upgraded with this package. "
                    ."You must first upgrade to version ".$G_OldestVersionWeCanUpgrade
                    ." and then use this package.";
        }
        else
        {
            # load install information from existing files
            $ErrMsgs = LoadOldInstallInfo($FVars, $ErrMsgs);
        }
    }

    # if we have install information
    if (isset($FVars["F_Submit"]) && !count($ErrMsgs))
    {
        # normalize install info
        $FVars = NormalizeInstallInfo($FVars);

        # check installation information
        $ErrMsgs = CheckInstallInfo($FVars, $ErrMsgs);
    }

    # if install information has not been collected or we encountered errors
    if (!isset($FVars["F_Submit"]) || count($ErrMsgs))
    {
        # display any error messages
        PrintErrorMessages($ErrMsgs);

        # if this is an upgrade
        if ($IsUpgrade)
        {
            # display pointer to helpful info
            PrintHelpPointers();
        }
        else
        {
            # display install information form
            PrintInstallInfoForm($FVars, $ErrMsgs);
        }
    }
    else
    {
        # print install parameter summary
        PrintInstallInfoSummary($FVars, $IsUpgrade);

        # set up files
        Msg(1, "<b>Beginning ".($IsUpgrade ? "Upgrade" : "Installation")
                ." Process...</b>");
        $ErrMsgs = InstallFiles($FVars, $IsUpgrade);

        # if we are upgrading
        if ($IsUpgrade)
        {
            # upgrade existing database
            $ErrMsgs = UpgradeExistingDatabase($FVars, $ErrMsgs, $OldVersion);
        }
        else
        {
            # set up database
            $ErrMsgs = SetUpNewDatabase($FVars, $ErrMsgs);
        }

        # if errors encountered
        if (count($ErrMsgs))
        {
            # display any error messages
            PrintErrorMessages($ErrMsgs);

            # display pointer to helpful info
            PrintHelpPointers();
        }
        else
        {
            # queue new install or upgrade follow-up work as appropriate
            QueueFollowUpWork($FVars, $IsUpgrade, $OldVersion);

            # declare victory
            Msg(1, "<b>".($IsUpgrade ? "Upgrade" : "Installation")
                    ." Process Completed</b>");

            # print install complete message
            PrintInstallCompleteInfo($FVars, $IsUpgrade);

            # set version
            if ($IsUpgrade)
            {
                if (file_exists("OLDVERSION")) {  unlink("OLDVERSION");  }
                rename("VERSION", "OLDVERSION");
            }
            rename("NEWVERSION", "VERSION");

            # clean up after ourselves
            if (file_exists("installcwis.php.SAVE")) {  unlink("installcwis.php.SAVE");  }
            rename("installcwis.php", "installcwis.php.SAVE");
        }
    }
}

# end page
EndHtmlPage();

?>
