Enano's new installable installer, alpha 1. No upgrade and (in some parts) very little localization.
authorDan
Thu, 17 Jan 2008 23:28:43 -0500
changeset 352 9d7225c0db6d
parent 351 8978cb3541ca
child 353 2398420f48e7
Enano's new installable installer, alpha 1. No upgrade and (in some parts) very little localization.
includes/common.php
includes/constants.php
includes/dbal.php
includes/payload.php
install/includes/libenanoinstall.php
install/includes/payload.php
install/includes/stages/database_post.php
install/includes/stages/finish.php
install/includes/stages/install.php
install/install.php
language/english/install.json
language/english/install/mainpage-default.wkt
--- a/includes/common.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/includes/common.php	Thu Jan 17 23:28:43 2008 -0500
@@ -259,36 +259,6 @@
   setConfig('default_language', $row['lang_id']);
 }
 
-// Our list of tables included in Enano
-$system_table_list = Array(
-    table_prefix.'categories',
-    table_prefix.'comments',
-    table_prefix.'config',
-    table_prefix.'logs',
-    table_prefix.'page_text',
-    table_prefix.'session_keys',
-    table_prefix.'pages',
-    table_prefix.'users',
-    table_prefix.'users_extra',
-    table_prefix.'themes',
-    table_prefix.'buddies',
-    table_prefix.'banlist',
-    table_prefix.'files',
-    table_prefix.'privmsgs',
-    table_prefix.'sidebar',
-    table_prefix.'hits',
-    table_prefix.'groups',
-    table_prefix.'group_members',
-    table_prefix.'acl',
-    table_prefix.'page_groups',
-    table_prefix.'page_group_members',
-    table_prefix.'tags',
-    table_prefix.'language',
-    table_prefix.'language_strings',
-    table_prefix.'lockout',
-    table_prefix.'search_index'
-  );
-
 // Load plugin manager
 $plugins = new pluginLoader();
 
--- a/includes/constants.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/includes/constants.php	Thu Jan 17 23:28:43 2008 -0500
@@ -141,6 +141,44 @@
 // significantly increase encryption strength either.
 define('AES_BLOCKSIZE', 128);
 
+// Our list of tables included in Enano
+$system_table_list = Array(
+    'categories',
+    'comments',
+    'config',
+    'logs',
+    'page_text',
+    'session_keys',
+    'pages',
+    'users',
+    'users_extra',
+    'themes',
+    'buddies',
+    'banlist',
+    'files',
+    'privmsgs',
+    'sidebar',
+    'hits',
+    'groups',
+    'group_members',
+    'acl',
+    'page_groups',
+    'page_group_members',
+    'tags',
+    'language',
+    'language_strings',
+    'lockout',
+    'search_index'
+  );
+
+if ( defined('table_prefix') )
+{
+  foreach ( $system_table_list as $i => $_ )
+  {
+    $system_table_list[$i] = table_prefix . $system_table_list[$i];
+  }
+}
+
 /*
  * MIMETYPES
  *
--- a/includes/dbal.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/includes/dbal.php	Thu Jan 17 23:28:43 2008 -0500
@@ -199,7 +199,11 @@
     $q = $this->sql_query('USE `'.$dbname.'`;');
     
     if ( !$q )
+    {
+      if ( $manual_credentials )
+        return false;
       $this->_die('The database could not be selected.');
+    }
     
     // We're in!
     $this->disable_errorhandler();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/payload.php	Thu Jan 17 23:28:43 2008 -0500
@@ -0,0 +1,312 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ * Installation package
+ * payload.php - Installer payload (the installation logic)
+ *
+ * This program is Free Software; you can redistribute 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 details.
+ */
+
+if ( !defined('IN_ENANO_INSTALL') )
+  die();
+
+return true;
+
+function stg_sim_good()
+{
+  return true;
+}
+
+function stg_sim_bad()
+{
+  return true;
+}
+
+function stg_password_decode()
+{
+  global $db;
+  static $pass = false;
+  
+  if ( $pass )
+    return $pass;
+  
+  if ( !isset($_POST['crypt_data']) && !empty($_POST['password']) && $_POST['password'] === $_POST['password_confirm'] )
+    $pass = $_POST['password'];
+  
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
+  // retrieve encryption key
+  $q = $db->sql_query('SELECT config_value FROM ' . table_prefix . 'config WHERE config_name=\'install_aes_key\';');
+  if ( !$q )
+    $db->_die();
+  if ( $db->numrows() < 1 )
+    return false;
+  list($aes_key) = $db->fetchrow_num();
+  $aes_key = $aes->hextostring($aes_key);
+  
+  $pass = $aes->decrypt($_POST['crypt_data'], $aes_key, ENC_HEX);
+  if ( !$pass )
+    return false;
+  
+  return $pass; // Will be true if the password isn't crapped
+}
+
+function stg_make_private_key()
+{
+  global $db;
+  static $site_key = false;
+  
+  if ( $site_key )
+    return $site_key;
+  
+  // Is there already a key cached in the database?
+  $q = $db->sql_query('SELECT config_value FROM ' . table_prefix . 'config WHERE config_name=\'site_aes_key\';');
+  if ( !$q )
+    $db->_die();
+  
+  if ( $db->numrows() > 0 )
+  {
+    list($site_key) = $db->fetchrow_num();
+    $db->free_result();
+    return $site_key;
+  }
+  
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
+  // This will use /dev/urandom if possible
+  $site_key = $aes->gen_readymade_key();
+  
+  // Stash it in the database, don't check for errors though because we can always regenerate it
+  $db->sql_query('INSERT INTO ' . table_prefix . 'config ( config_name, config_value ) VALUES ( \'site_aes_key\', \'' . $site_key . '\' );');
+  
+  return $site_key;
+}
+
+function stg_load_schema()
+{
+  global $db, $dbdriver, $installer_version;
+  static $sql_parser = false;
+  
+  if ( is_object($sql_parser) )
+    return $sql_parser->parse();
+  
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
+  
+  $site_key = stg_make_private_key();
+  $site_key = $aes->hextostring($site_key);
+  $admin_pass_clean = stg_password_decode();
+  $admin_pass = $aes->encrypt($admin_pass_clean, $site_key, ENC_HEX);
+  
+  unset($admin_pass_clean); // Security
+  
+  try
+  {
+    $sql_parser = new SQL_Parser( ENANO_ROOT . "/install/schemas/{$dbdriver}_stage2.sql" );
+  }
+  catch ( Exception $e )
+  {
+    echo "<pre>$e</pre>";
+    return false;
+  }
+  
+  $vars = array(
+      'TABLE_PREFIX'         => $_POST['table_prefix'],
+      'SITE_NAME'            => $db->escape($_POST['site_name']),
+      'SITE_DESC'            => $db->escape($_POST['site_desc']),
+      'COPYRIGHT'            => $db->escape($_POST['copyright']),
+      // FIXME: update form
+      'WIKI_MODE'            => ( isset($_POST['wiki_mode']) ? '1' : '0' ),
+      'ENABLE_CACHE'         => ( is_writable( ENANO_ROOT . '/cache/' ) ? '1' : '0' ),
+      'VERSION'              => $installer_version['version'],
+      'ADMIN_USER'           => $db->escape($_POST['username']),
+      'ADMIN_PASS'           => $admin_pass,
+      'ADMIN_EMAIL'          => $db->escape($_POST['email']),
+      'REAL_NAME'            => '', // This has always been stubbed.
+      'ADMIN_EMBED_PHP'      => strval(AUTH_DISALLOW),
+      'UNIX_TIME'            => strval(time())
+    );
+  
+  $sql_parser->assign_vars($vars);
+  return $sql_parser->parse();
+}
+
+function stg_deliver_payload()
+{
+  global $db;
+  $schema = stg_load_schema();
+  foreach ( $schema as $sql )
+  {
+    if ( !$db->sql_query($sql) )
+    {
+      echo $db->get_error();
+      return false;
+    }
+  }
+  return true;
+}
+
+function stg_write_config()
+{
+  global $dbhost, $dbuser, $dbpasswd, $dbname, $dbdriver;
+  $db_data = array(
+      'host' => str_replace("'", "\\'", $dbhost),
+      'user' => str_replace("'", "\\'", $dbuser),
+      'pass' => str_replace("'", "\\'", $dbpasswd),
+      'name' => str_replace("'", "\\'", $dbname),
+      'tp' => table_prefix,
+      'drv' => $dbdriver
+    );
+  
+  // Retrieves the existing key
+  $site_key = stg_make_private_key();
+  
+  // Determine contentPath
+  switch ( @$_POST['url_scheme'] )
+  {
+    case 'standard':
+    default:
+      $sp_append = 'index.php?title=';
+      break;
+    case 'shortened':
+      $sp_append = 'index.php/';
+      break;
+    case 'rewrite':
+      $sp_append = '/';
+      break;
+  }
+  
+  $scriptpath = scriptPath;
+  $contentpath = $scriptpath . $sp_append;
+  
+  $config_file = <<<EOF
+<?php
+
+/**
+ * Enano site configuration
+ * NOTE ON EDITING: You should almost never need to change anything in this
+ * file. The only exceptions are when your DB password/other info is changed
+ * or if you are moving your Enano installation to another directory.
+ */
+
+//
+// DATABASE INFO
+//
+
+// Database type to use, currently mysql and postgresql are supported
+\$dbdriver = '{$db_data['drv']}';
+
+// Hostname of your database server, probably localhost
+\$dbhost = '{$db_data['host']}';
+
+// Username used to connect to the database
+\$dbuser = '{$db_data['user']}';
+// Database password
+\$dbpasswd = '{$db_data['pass']}';
+
+// Name of the database
+\$dbname = '{$db_data['name']}';
+
+//
+// CONSTANTS
+//
+
+// if they're already defined, no use re-defining them
+if ( !defined('ENANO_CONSTANTS') )
+{
+  // The prefix for the tables in the database. Useful for holding more than
+  // one Enano installation in the same database.
+  define('table_prefix', '{$db_data['tp']}');
+  
+  // The path to Enano's files on your server, from the document root. If
+  // Enano is installed in your document root this will be blank; installing
+  // Enano in /enano/ will result in "/enano" here, etc.
+  define('scriptPath', '$scriptpath');
+  
+  // The authoritative prefix for pages. This should be very literal: to
+  // generate a URL on the site, the format is basically
+  // contentPath . \$page_name. This is based off of scriptPath and the URL
+  // scheme selected during installation. Pattern:
+  //
+  //    * Standard URLs:  scriptPath . '/index.php?title='
+  //    * Shortened URLs: scriptPath . '/index.php/'
+  //    * mod_rewrite:    scriptPath . '/'
+  
+  define('contentPath', '$contentpath');
+  
+  // Tell the Enano API that we're installed and that this file is complete
+  define('ENANO_INSTALLED', 'You bet!');
+  
+  define('ENANO_CONSTANTS', '');
+}
+
+// The AES encryption key used to store passwords. We have a very specific
+// reason for doing this; see the rationale at:
+//   http://docs.enanocms.org/Help:Appendix_B
+\$crypto_key = '$site_key';
+
+EOF;
+  
+  // Write config file
+  
+  $ch = @fopen ( ENANO_ROOT . '/config.new.php', 'w' );
+  if ( !$ch )
+    return false;
+  
+  fwrite($ch, $config_file);
+  fclose($ch);
+  
+  // If we are using mod_rewrite, also append any existing .htaccess
+  if ( @$_POST['url_scheme'] === 'rewrite' )
+  {
+    $hh = @fopen ( ENANO_ROOT . '/.htaccess.new', 'w' );
+    if ( !$hh )
+      return false;
+    $hhc = <<<EOF
+#
+# START ENANO RULES
+#
+
+# Enable mod_rewrite
+RewriteEngine on
+
+# Don't rewrite if the user requested a real directory or file
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+
+# Main rule - short and sweet
+RewriteRule (.*) index.php?title=\$1 [L,QSA]
+
+EOF;
+    fwrite($hh, $hhc);
+    fclose($hh);
+  }
+  
+  return true;
+}
+
+function stg_language_setup()
+{
+  global $languages, $db;
+  global $lang_id;
+  $lang_info =& $languages[$lang_id];
+  if ( !is_array($lang_info) )
+    return false;
+  
+  // Install the language
+  // ($lang_code, $lang_name_neutral, $lang_name_local, $lang_file = false)
+  $result = install_language($lang_id, $lang_info['name_eng'], $lang_info['name'], ENANO_ROOT . "/language/{$lang_info['dir']}/core.json");
+  if ( !$result )
+    return false;
+  
+  $lang_local = new Language($lang_id);
+  $lang_local->import( ENANO_ROOT . "/language/{$lang_info['dir']}/user.json" );
+  $lang_local->import( ENANO_ROOT . "/language/{$lang_info['dir']}/tools.json" );
+  $lang_local->import( ENANO_ROOT . "/language/{$lang_info['dir']}/admin.json" );
+  
+  return true;
+}
--- a/install/includes/libenanoinstall.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/install/includes/libenanoinstall.php	Thu Jan 17 23:28:43 2008 -0500
@@ -99,15 +99,18 @@
     $key = htmlspecialchars($key);
     $post_data .= "          <input type=\"hidden\" name=\"$key\" value=\"$value\" />\n";
   }
-  echo '<form action="install.php?stage=install&amp;sub=' . $stage_id . '" method="post">
-          ' . $post_data . '
-          <input type="hidden" name="resume_stack" value="' . htmlspecialchars(implode('|', $resume_stack)) . '" />
-          <h3>' . $lang->get('meta_msg_err_stagefailed_title') . '</h3>
-           <p>' . $failure_explanation . '</p>
-           ' . ( !empty($mysql_error) ? "<p>" . $lang->get('meta_msg_err_stagefailed_mysqlerror') . " $mysql_error</p>" : '' ) . '
-           <p>' . $lang->get('meta_msg_err_stagefailed_body') . '</p>
-           <p style="text-align: center;"><input type="submit" value="' . $lang->get('meta_btn_retry_installation') . '" /></p>
-        </form>';
+  if ( $stage_id == 'renameconfig' )
+    echo '<p>' . $failure_explanation . '</p>';
+  else
+    echo '<form action="install.php?stage=install&amp;sub=' . $stage_id . '" method="post">
+            ' . $post_data . '
+            <input type="hidden" name="resume_stack" value="' . htmlspecialchars(implode('|', $resume_stack)) . '" />
+            <h3>' . $lang->get('meta_msg_err_stagefailed_title') . '</h3>
+             <p>' . $failure_explanation . '</p>
+             ' . ( !empty($mysql_error) ? "<p>" . $lang->get('meta_msg_err_stagefailed_mysqlerror') . " $mysql_error</p>" : '' ) . '
+             <p>' . $lang->get('meta_msg_err_stagefailed_body') . '</p>
+             <p style="text-align: center;"><input type="submit" value="' . $lang->get('meta_btn_retry_installation') . '" /></p>
+          </form>';
   global $ui;
   $ui->show_footer();
   exit;
--- a/install/includes/payload.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/install/includes/payload.php	Thu Jan 17 23:28:43 2008 -0500
@@ -339,3 +339,76 @@
   return true;
 }
 
+function stg_aes_cleanup()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  $q = $db->sql_query('DELETE FROM ' . table_prefix . 'config WHERE config_name = \'install_aes_key\' OR config_name = \'site_aes_key\';');
+  if ( !$q )
+    $db->_die();
+  return true;
+}
+
+function _stg_rename_config_revert()
+{
+  if ( file_exists('./config.php') )
+  {
+    @rename('./config.php', './config.new.php');
+  }
+  
+  $handle = @fopen('./config.php.new', 'w');
+  if ( !$handle )
+    return false;
+  $contents = '<?php $cryptkey = \'' . _INSTRESUME_AES_KEYBACKUP . '\'; ?>';
+  fwrite($handle, $contents);
+  fclose($handle);
+  return true;
+}
+
+function stg_build_index()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  if ( $paths->rebuild_search_index() )
+    return true;
+  return false;
+}
+
+function stg_rename_config()
+{
+  if ( !@rename(ENANO_ROOT . '/config.new.php', ENANO_ROOT . '/config.php') )
+  {
+    echo '<p>Can\'t rename config.php</p>';
+    _stg_rename_config_revert();
+    return false;
+  }
+  
+  if ( filesize(ENANO_ROOT . '/.htaccess.new') > 1 )
+  {
+    // rename/possibly concatenate .htaccess.new
+    $htaccess_base = '';
+    if ( file_exists(ENANO_ROOT . '/.htaccess') )
+      $htaccess_base .= @file_get_contents(ENANO_ROOT . '/.htaccess');
+    if ( strlen($htaccess_base) > 0 && !preg_match("/\n$/", $htaccess_base) )
+      $htaccess_base .= "\n\n";
+    $htaccess_base .= @file_get_contents(ENANO_ROOT . '/.htaccess.new');
+    if ( file_exists(ENANO_ROOT . '/.htaccess') )
+    {
+      $hh = @fopen(ENANO_ROOT . '/.htaccess', 'w');
+      if ( !$hh )
+        return false;
+      fwrite($hh, $htaccess_base);
+      fclose($hh);
+      @unlink(ENANO_ROOT . '/.htaccess.new');
+      return true;
+    }
+    else
+    {
+      return @rename(ENANO_ROOT . '/.htaccess.new', ENANO_ROOT . '/.htaccess');
+    }
+  }
+  else
+  {
+    @unlink(ENANO_ROOT . '/.htaccess.new');
+  }
+  return true;
+}
+
--- a/install/includes/stages/database_post.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/install/includes/stages/database_post.php	Thu Jan 17 23:28:43 2008 -0500
@@ -26,14 +26,68 @@
 $db_pass =& $_POST['db_pass'];
 $db_name =& $_POST['db_name'];
 $db_prefix =& $_POST['table_prefix'];
+$db_root_user =& $_POST['db_root_user'];
+$db_root_pass =& $_POST['db_root_pass'];
+
+if ( !preg_match('/^[a-z0-9_]*$/', $db_prefix) )
+{
+  $ui->show_header();
+  echo '<p>That table prefix isn\'t going to work.</p>';
+  return true;
+}
 
 $result = $dbal->connect(true, $db_host, $db_user, $db_pass, $db_name);
 
+// If connection failed, we have the root login, AND we're on MySQL, try to force our way in
+if ( !$result && !empty($_POST['db_root_user']) && !empty($_POST['db_root_pass']) && $driver == 'mysql' )
+{
+  // Allow a jump / breakout
+  switch ( 'foo' ) { case 'foo':
+      
+    // Try to connect to the DB as root
+    $result_root = $dbal->connect(true, $db_host, $db_root_user, $db_root_pass, 'mysql');
+    if ( !$result_root )
+      break;
+    
+    $q = $dbal->sql_query('CREATE DATABASE IF NOT EXISTS `' . $dbal->escape($db_name) . '`;');
+    if ( !$q )
+      break;
+    
+    if ( $db_host == 'localhost' || $db_host == '127.0.0.1' )
+    {
+      $q = $dbal->sql_query('GRANT ALL PRIVILEGES ON `' . $dbal->escape($db_name) . '`.* TO \'' . $dbal->escape($db_user) . '\'@\'localhost\'' . "\n" .
+                            '  IDENTIFIED BY \'' . $dbal->escape($db_pass) . '\' WITH GRANT OPTION');
+    }
+    else
+    {
+      $q = $dbal->sql_query('GRANT ALL PRIVILEGES ON `' . $dbal->escape($db_name) . '`.* TO \'' . $dbal->escape($db_user) . '\'@\'%\'' . "\n" .
+                            '  IDENTIFIED BY \'' . $dbal->escape($db_pass) . '\' WITH GRANT OPTION');
+    }
+    
+    if ( !$q )
+      break;
+    
+    $dbal->close();
+    $result = $dbal->connect(true, $db_host, $db_user, $db_pass, $db_name);
+      
+    break;
+  }
+}
+
 $ui->show_header();
 
 if ( $result )
 {
-  // We're good, write out a config file
+  // We're good, do table drop if requested
+  if ( isset($_POST['drop_tables']) )
+  {
+    global $system_table_list;
+    foreach ( $system_table_list as $table )
+    {
+      $dbal->sql_query("DROP TABLE {$db_prefix}$table");
+    }
+  }
+  // Write out a config file
   $ch = @fopen( ENANO_ROOT . '/config.new.php', 'w' );
   if ( !$ch )
   {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install/includes/stages/finish.php	Thu Jan 17 23:28:43 2008 -0500
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ * Installation package
+ * finish.php - Installer finalization stage
+ *
+ * This program is Free Software; you can redistribute 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 details.
+ */
+
+if ( !defined('IN_ENANO_INSTALL') )
+  die();
+
+require ( ENANO_ROOT . '/install/includes/libenanoinstall.php' );
+require ( ENANO_ROOT . '/install/includes/sql_parse.php' );
+require ( ENANO_ROOT . '/includes/common.php' );
+
+if ( !in_array($dbdriver, $supported_drivers) )
+{
+  $ui->show_header();
+  echo '<h3>Installation error</h3>
+         <p>ERROR: That database driver is not supported.</p>';
+  return true;
+}
+
+$db = new $dbdriver();
+$db->connect();
+
+$ui->show_header();
+flush();
+
+?>
+<h3>Performing final installation steps</h3>
+<p>Enano is cleaning up and performing some final installation tasks. Please wait...</p>
+
+<?php
+
+@set_time_limit(0);
+
+function stg_load_files()
+{
+  global $dbdriver;
+  if ( !@include( ENANO_ROOT . "/install/includes/payload.php" ) )
+    return false;
+  
+  return true;
+}
+
+// FIXME: l10n
+start_install_table();
+
+run_installer_stage('load', 'Load installer files', 'stg_load_files', 'One of the files needed for installation couldn\'t be loaded. Please check your Enano directory.', false);
+run_installer_stage('cleanup', 'Clean up encryption keys', 'stg_aes_cleanup', 'There was a database error while removing the temporary encryption keys from the database. For maximum site security you should delete the config entries install_aes_key and site_aes_key manually.', false);
+run_installer_stage('buildindex', $lang->get('install_stg_buildindex_title'), 'stg_build_index', $lang->get('install_stg_buildindex_body'));
+run_installer_stage('renameconfig', $lang->get('install_stg_rename_title'), 'stg_rename_config', $lang->get('install_stg_rename_body', array('mainpage_link' => scriptPath . '/index.php')));
+
+close_install_table();
+
+?>
+<h3>Congratulations! You've finished installing Enano.</h3>
+<p>Enano has finished setting up on your server. Now you can go to your <a href="<?php echo makeUrlNS('Article', 'Main_Page');?>">new
+   website</a> and start creating content!</p>
+<?php 
+  echo $lang->get('finish_body');
+  echo '<p>' . $lang->get('finish_link_mainpage', array('mainpage_link' => scriptPath . '/index.php')) . '</p>';
+?>
+<?php
+
+$db->close();
+
--- a/install/includes/stages/install.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/install/includes/stages/install.php	Thu Jan 17 23:28:43 2008 -0500
@@ -112,5 +112,14 @@
 
 close_install_table();
 
+?>
+<form action="install.php?stage=finish" method="post">
+  <input type="hidden" name="language" value="<?php echo $lang_id; ?>" />
+  <div style="text-align: center;">
+    <input type="submit" name="_cont" value="<?php echo $lang->get('meta_btn_continue'); ?>" tabindex="1" />
+  </div>
+</form>
+<?php
+
 $db->close();
 
--- a/install/install.php	Thu Jan 17 19:49:14 2008 -0500
+++ b/install/install.php	Thu Jan 17 23:28:43 2008 -0500
@@ -19,6 +19,7 @@
 define('ENANO_DANGEROUS', 1);
 
 require_once('includes/common.php');
+@ini_set('display_errors', 'on');
 
 $stages = array('language', 'license', 'sysreqs', 'database', 'website', 'login', 'confirm', 'install', 'finish');
 $stage_ids = array();
@@ -268,6 +269,9 @@
   case 'install':
     require( ENANO_ROOT . '/install/includes/stages/install.php' );
     break;
+  case 'finish':
+    require( ENANO_ROOT . '/install/includes/stages/finish.php' );
+    break;
 }
 
 $ui->show_footer();
--- a/language/english/install.json	Thu Jan 17 19:49:14 2008 -0500
+++ b/language/english/install.json	Thu Jan 17 23:28:43 2008 -0500
@@ -273,7 +273,13 @@
       stg_writeconfig_title: 'Write configuration files',
       stg_writeconfig_body: 'Enano was unable to write the configuration file with your site\'s database credentials. This is almost always because your configuration file does not have the correct permissions. On Windows servers, you may see this message even if the check on the System Requirements page passed. Temporarily running IIS as the Administrator user may help.',
       stg_rename_title: 'Rename configuration files',
-      stg_rename_body: 'Enano couldn\'t rename the configuration files to their correct production names. Please CHMOD the folder where your Enano files are to 777 and click the retry button below, <b><u>or</u></b> perform the following rename operations and then <a href="install.php?mode=finish">finish the installation</a>.<ul><li>Rename config.new.php to config.php</li><li>Rename .htaccess.new to .htaccess (only if you selected Tiny URLs)</li></ul>',
+      stg_rename_body: 'Enano couldn\'t rename the configuration files to their correct production names. <span style="font-weight: bold; color: red;">Please perform the following rename operations and then follow the instructions to finish the installation below.</span>
+                          <ul>
+                            <li>Rename config.new.php to config.php</li>
+                            <li>Rename .htaccess.new to .htaccess (only if you selected the Rewrite URL scheme)</li>
+                          </ul>
+                        %this.finish_body%
+                        %this.finish_link_mainpage%',
       stg_startapi_title: 'Start the Enano API',
       stg_startapi_body: 'The Enano API could not be started. This is an error that should never occur; please contact the Enano team for support.',
       stg_importlang_title: 'Import default language',
@@ -292,8 +298,7 @@
       modetitle: 'Finish',
       modetitle_long: 'Complete installation',
       msg_congratulations: 'Congratulations!',
-      body: '<p>You have finished installing Enano on this server.</p>
-            <h3>Now what?</h3>
+      body: '<h3>Wait... Now what?</h3>
              <p>Click the link below to see the main page for your website. Where to go from here:</p>
              <ul>
                <li>The first thing you should do is log into your site using the Log in link on the sidebar.</li>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/language/english/install/mainpage-default.wkt	Thu Jan 17 23:28:43 2008 -0500
@@ -0,0 +1,11 @@
+=== Enano has been successfully installed and is working. ===
+
+If you can see this message, it means that you've finished the Enano setup process and are ready to start building your website. Congratulations!
+
+To edit this front page, click the Log In button to the left, enter the credentials you provided during the installation, and click the Edit This Page button that appears on the blue toolbar just above this text. You can also [http://docs.enanocms.org/Help:2.4 learn more] about editing pages.
+
+To create more pages, use the Create a Page button to the left. If you enabled wiki mode, you don't have to log in first, however your IP address will be shown in the page history.
+
+Visit the [http://docs.enanocms.org/Help:Contents Enano documentation project website] to learn more about administering your site effectively and keeping things secure.
+
+'''NOTE:''' You have just installed an unstable version of Enano. This release is completely unsupported and may contain security issues or serious usability bugs. You should not use this release on a production website. The Enano team will not provide any type of support at all for this experimental release.