Merge in new installer framework from stable
authorDan
Tue, 06 Nov 2007 10:53:33 -0500
changeset 238 a78537db2850
parent 231 b11a2f1353c0 (current diff)
parent 237 c26308d81882 (diff)
child 239 0f1b353570a7
Merge in new installer framework from stable
.htaccess
config.php
includes/functions.php
includes/pageutils.php
includes/paths.php
install.php
--- a/README	Sat Nov 03 14:30:53 2007 -0400
+++ b/README	Tue Nov 06 10:53:33 2007 -0500
@@ -1,25 +1,80 @@
 Enano CMS
-Version 1.0.1
+Version 1.0.2
 -----------------------------
 
 Thanks for downloading Enano! If you're looking for an installation guide,
-you can find it at <http://enanocms.org/Help:Installation>.
+you can find it at <http://docs.enanocms.org/Help:2.1>.
 
 COPYRIGHT
 -----------------------------
 
+Enano CMS
+Copyright (C) 2006-2007 Dan Fuhry. All rights except those explicitly granted
+by the included license agreement reserved.
+
+PHILOSOPHY
+-----------------------------
+
 We strongly believe in the idea of Free Software. Enano is released under the
 GNU General Public License; see the file GPL included with this release for
 details.
 
+LICENSING
+-----------------------------
+
+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 details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to:
+
+  Free Software Foundation, Inc.,
+  51 Franklin Street, Fifth Floor
+  Boston, MA 02110-1301, USA
+
+-----------------------------
+
 Most of the PHP code in Enano was written by Dan Fuhry. Some parts were borrowed
 from other projects that are also released under Free licenses; see the various
 files under the licenses/ directory included with this release for details.
 
+If you are planning to make a commercial fork of Enano, all of the software and
+libraries included with Enano are available under licenses that allow you to do
+so; however, in compliance with the GPL, you must:
+
+a) provide attribution to the Enano team in source code files and on the
+   (renamed) Special:About_Enano page,
+b) remove all instances of the word Enano and the Enano logo from your
+   derivative work, with the exception of the following phrase, which must
+   be shown on the (renamed) Special:About_Enano page:
+
+    "The software used on this website was based on Enano CMS. Copyright
+     (C) 2006-2007 Enano Foundation."
+
+   The words "Enano CMS" must link to the page <http://enanocms.org/>. You may
+   (at your option) also include a notice of non-endorsement by the Enano
+   Foundation, unless you're lucky enough to become an official fork
+   maintainer.
+
+c) Provide the complete source code for your modified version of Enano under
+   the terms of the GNU General Public License, and
+d) Include the complete and unmodified licenses/ directory, which contains
+   licensing information for third-party libraries that Enano uses.
+
+As permitted by the GPL, you may charge for the service of downloading Enano
+from your server; however, you may not prevent others from distributing Enano
+or any modified version.
+
 CHANGES IN THIS RELEASE
 -----------------------------
 
-Please see <http://enanocms.org/Release_notes/1.0.1> for a list of changes in
+Please see <http://enanocms.org/Release_notes/1.0.2> for a list of changes in
 this release.
 
 UPGRADING FROM PREVIOUS RELEASES
@@ -44,15 +99,22 @@
 
 You can find more themes for Enano at <http://enanocms.org/Category:Themes>.
 Again, we're still working on packaging up themes and creating pages for them,
-so try to be patient. We have quite a few themes in the works.
+so try to be patient. We have quite a few themes in the works. You can create
+your own themes too; for more information, see Chapter V of the Enano
+Administrator's handbook, at <http://docs.enanocms.org/Help:5>.
 
 GETTING SUPPORT
 -----------------------------
 
+Before contacting support, have a look at the Enano Documentation at
+<http://docs.enanocms.org/>. Most of Enano's features are documented with
+step-by-step guides at this site; if you encounter a problem, then please
+contact the Enano team as instructed below.
+
 Support for Enano is available via the Enano forums at
 <http://forum.enanocms.org>. You can also use our IRC channel
-(irc.freenode.net #enano) or purchase paid support via instant messaging for
-US$20 an hour.
+(irc.freenode.net #enano) or purchase paid one-on-one support via instant
+messaging for US$20 an hour.
 
 Have fun with Enano!
 
--- a/includes/functions.php	Sat Nov 03 14:30:53 2007 -0400
+++ b/includes/functions.php	Tue Nov 06 10:53:33 2007 -0500
@@ -3019,7 +3019,7 @@
  * @return int
  */
 
-function password_score($password, &$debug = false)
+function password_score($password, &$debug)
 {
   if ( !is_string($password) )
   {
--- a/includes/pageutils.php	Sat Nov 03 14:30:53 2007 -0400
+++ b/includes/pageutils.php	Tue Nov 06 10:53:33 2007 -0500
@@ -842,7 +842,7 @@
             return 'The page "' . $name . '" has been undeleted according to the log created at ' . $rb['date_string'] . '.';
             break;
           case "reupload":
-            if ( !$session->get_permissions('history_rollbacks_extra') )
+            if ( !$session->get_permissions('history_rollback_extra') )
             {
               return 'Administrative privileges are required for file rollbacks.';
             }
--- a/includes/paths.php	Sat Nov 03 14:30:53 2007 -0400
+++ b/includes/paths.php	Tue Nov 06 10:53:33 2007 -0500
@@ -54,7 +54,7 @@
     $session->register_acl_type('mod_comments',           AUTH_DISALLOW, 'perm_mod_comments',           Array('edit_comments'),                                   'Article|User|Project|Template|File|Help|System|Category');
     $session->register_acl_type('history_view',           AUTH_WIKIMODE, 'perm_history_view',           Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
     $session->register_acl_type('history_rollback',       AUTH_DISALLOW, 'perm_history_rollback',       Array('history_view'),                                    'Article|User|Project|Template|File|Help|System|Category');
-    $session->register_acl_type('history_rollback_extra', AUTH_DISALLOW, 'perm_history_rollback_extra', Array('history_rollback'),                                'Article|User|Project|Template|File|Help|System|Category');
+    $session->register_acl_type('history_rollback_extra', AUTH_DISALLOW, 'perm_history_rollback_extra', Array('history_rollback'),                                'Article|User|Project|Template|File|Help|System|Category|Special');
     $session->register_acl_type('protect',                AUTH_DISALLOW, 'perm_protect',                Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
     $session->register_acl_type('rename',                 AUTH_WIKIMODE, 'perm_rename',                 Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
     $session->register_acl_type('clear_logs',             AUTH_DISALLOW, 'perm_clear_logs',             Array('read', 'protect', 'even_when_protected'),          'Article|User|Project|Template|File|Help|System|Category');
--- a/install.php	Sat Nov 03 14:30:53 2007 -0400
+++ b/install.php	Tue Nov 06 10:53:33 2007 -0500
@@ -64,6 +64,527 @@
 require('includes/functions.php');
 
 strip_magic_quotes_gpc();
+$neutral_color = 'C';
+
+//
+// INSTALLER LIBRARY
+//
+
+function run_installer_stage($stage_id, $stage_name, $function, $failure_explanation, $allow_skip = true)
+{
+  static $resumed = false;
+  static $resume_stack = array();
+  
+  if ( empty($resume_stack) && isset($_POST['resume_stack']) && preg_match('/[a-z_]+((\|[a-z_]+)+)/', $_POST['resume_stack']) )
+  {
+    $resume_stack = explode('|', $_POST['resume_stack']);
+  }
+  
+  $already_run = false;
+  if ( in_array($stage_id, $resume_stack) )
+  {
+    $already_run = true;
+  }
+  
+  if ( !$resumed )
+  {
+    if ( !isset($_GET['stage']) )
+      $resumed = true;
+    if ( isset($_GET['stage']) && $_GET['stage'] == $stage_id )
+    {
+      $resumed = true;
+    }
+  }
+  if ( !$resumed && $allow_skip )
+  {
+    echo_stage_success($stage_id, "[dbg: skipped] $stage_name");
+    return false;
+  }
+  if ( !function_exists($function) )
+    die('libenanoinstall: CRITICAL: function "' . $function . '" for ' . $stage_id . ' doesn\'t exist');
+  $result = @call_user_func($function, false, $already_run);
+  if ( $result )
+  {
+    echo_stage_success($stage_id, $stage_name);
+    $resume_stack[] = $stage_id;
+    return true;
+  }
+  else
+  {
+    echo_stage_failure($stage_id, $stage_name, $failure_explanation, $resume_stack);
+    return false;
+  }
+}
+
+function start_install_table()
+{
+  echo '<table border="0" cellspacing="0" cellpadding="0">' . "\n";
+}
+
+function close_install_table()
+{
+  echo '</table>' . "\n\n";
+}
+
+function echo_stage_success($stage_id, $stage_name)
+{
+  global $neutral_color;
+  $neutral_color = ( $neutral_color == 'A' ) ? 'C' : 'A';
+  ob_start();
+  echo '<tr><td style="width: 500px; background-color: #' . "{$neutral_color}{$neutral_color}FF{$neutral_color}{$neutral_color}" . '; padding: 0 5px;">' . htmlspecialchars($stage_name) . '</td><td style="padding: 0 5px;"><img alt="Done" src="images/good.gif" /></td></tr>' . "\n";
+  ob_end_flush();
+}
+
+function echo_stage_failure($stage_id, $stage_name, $failure_explanation, $resume_stack)
+{
+  global $neutral_color;
+  
+  $neutral_color = ( $neutral_color == 'A' ) ? 'C' : 'A';
+  ob_start();
+  echo '<tr><td style="width: 500px; background-color: #' . "FF{$neutral_color}{$neutral_color}{$neutral_color}{$neutral_color}" . '; padding: 0 5px;">' . htmlspecialchars($stage_name) . '</td><td style="padding: 0 5px;"><img alt="Failed" src="images/bad.gif" /></td></tr>' . "\n";
+  ob_end_flush();
+  close_install_table();
+  $post_data = '';
+  $mysql_error = mysql_error();
+  foreach ( $_POST as $key => $value )
+  {
+    $value = htmlspecialchars($value);
+    $key = htmlspecialchars($key);
+    $post_data .= "          <input type=\"hidden\" name=\"$key\" value=\"$value\" />\n";
+  }
+  echo '<form action="install.php?mode=install&amp;stage=' . $stage_id . '" method="post">
+          ' . $post_data . '
+          <input type="hidden" name="resume_stack" value="' . htmlspecialchars(implode('|', $resume_stack)) . '" />
+          <h3>Enano installation failed.</h3>
+           <p>' . $failure_explanation . '</p>
+           ' . ( !empty($mysql_error) ? "<p>The error returned from MySQL was: $mysql_error</p>" : '' ) . '
+           <p>When you have corrected the error, click the button below to attempt to continue the installation.</p>
+           <p style="text-align: center;"><input type="submit" value="Retry installation" /></p>
+        </form>';
+  global $template, $template_bak;
+  if ( is_object($template_bak) )
+    $template_bak->footer();
+  else
+    $template->footer();
+  exit;
+}
+
+//
+// INSTALLER STAGES
+//
+
+function stg_mysql_connect($act_get = false)
+{
+  static $conn = false;
+  if ( $act_get )
+    return $conn;
+  
+  $db_user = mysql_real_escape_string($_POST['db_user']);
+  $db_pass = mysql_real_escape_string($_POST['db_pass']);
+  $db_name = mysql_real_escape_string($_POST['db_name']);
+  
+  if ( !preg_match('/^[a-z0-9_]+$/', $db_name) )
+    die("<p>SECURITY: malformed database name</p>");
+  
+  // First, try to connect using the normal credentials
+  $conn = @mysql_connect($_POST['db_host'], $_POST['db_user'], $_POST['db_pass']);
+  if ( !$conn )
+  {
+    // Connection failed. Do we have the root username and password?
+    if ( !empty($_POST['db_root_user']) && !empty($_POST['db_root_pass']) )
+    {
+      $conn_root = @mysql_connect($_POST['db_host'], $_POST['db_root_user'], $_POST['db_root_pass']);
+      if ( !$conn_root )
+      {
+        // Couldn't connect using either set of credentials. Bail out.
+        return false;
+      }
+      // Create the user account
+      $q = @mysql_query("GRANT ALL PRIVILEGES ON test.* TO '{$db_user}'@'localhost' IDENTIFIED BY '$db_pass' WITH GRANT OPTION;", $conn_root);
+      if ( !$q )
+      {
+        return false;
+      }
+      // Revoke privileges from test, we don't need them
+      $q = @mysql_query("REVOKE ALL PRIVILEGES ON test.* FROM '{$db_user}'@'localhost';", $conn_root);
+      if ( !$q )
+      {
+        return false;
+      }
+      if ( $_POST['db_host'] != 'localhost' && $_POST['db_host'] != '127.0.0.1' && $_POST['db_host'] != '::1' )
+      {
+        // If not connecting to a server running on localhost, allow from any host
+        // this is safer than trying to detect the hostname of the webserver, but less secure
+        $q = @mysql_query("GRANT ALL PRIVILEGES ON test.* TO '{$db_user}'@'%' IDENTIFIED BY '$db_pass' WITH GRANT OPTION;", $conn_root);
+        if ( !$q )
+        {
+          return false;
+        }
+        // Revoke privileges from test, we don't need them
+        $q = @mysql_query("REVOKE ALL PRIVILEGES ON test.* FROM '{$db_user}'@'%';", $conn_root);
+        if ( !$q )
+        {
+          return false;
+        }
+      }
+    }
+  }
+  $q = @mysql_query("USE $db_name;", $conn);
+  if ( !$q )
+  {
+    // access denied to the database; try the whole root schenanegan again
+    if ( !empty($_POST['db_root_user']) && !empty($_POST['db_root_pass']) )
+    {
+      $conn_root = @mysql_connect($_POST['db_host'], $_POST['db_root_user'], $_POST['db_root_pass']);
+      if ( !$conn_root )
+      {
+        // Couldn't connect as root; bail out
+        return false;
+      }
+      // create the database, if it doesn't exist
+      $q = @mysql_query("CREATE DATABASE IF NOT EXISTS $db_name;", $conn_root);
+      if ( !$q )
+      {
+        // this really should never fail, so don't give any tolerance to it
+        return false;
+      }
+      // we're in with root rights; grant access to the database
+      $q = @mysql_query("GRANT ALL PRIVILEGES ON $db_name.* TO '{$db_user}'@'localhost';", $conn_root);
+      if ( !$q )
+      {
+        return false;
+      }
+      if ( $_POST['db_host'] != 'localhost' && $_POST['db_host'] != '127.0.0.1' && $_POST['db_host'] != '::1' )
+      {
+        $q = @mysql_query("GRANT ALL PRIVILEGES ON $db_name.* TO '{$db_user}'@'%';", $conn_root);
+        if ( !$q )
+        {
+          return false;
+        }
+      }
+    }
+    else
+    {
+      return false;
+    }
+    // try again
+    $q = @mysql_query("USE $db_name;", $conn);
+    if ( !$q )
+    {
+      // really failed this time; bail out
+      return false;
+    }
+  }
+  // connected and database exists
+  return true;
+}
+
+function stg_drop_tables()
+{
+  $conn = stg_mysql_connect(true);
+  if ( !$conn )
+    return false;
+  // Our list of tables included in Enano
+  $tables = Array( 'categories', 'comments', 'config', 'logs', 'page_text', 'session_keys', 'pages', 'users', 'users_extra', 'themes', 'buddies', 'banlist', 'files', 'privmsgs', 'sidebar', 'hits', 'search_index', 'groups', 'group_members', 'acl', 'search_cache', 'tags', 'page_groups', 'page_group_members' );
+  
+  // Drop each table individually; if it fails, it probably means we're trying to drop a
+  // table that didn't exist in the Enano version we're deleting the database for.
+  foreach ( $tables as $table )
+  {
+    // Remember that table_prefix is sanitized.
+    $table = "{$_POST['table_prefix']}$table";
+    @mysql_query("DROP TABLE $table;", $conn);
+  }
+  return true;
+}
+
+function stg_decrypt_admin_pass($act_get = false)
+{
+  static $decrypted_pass = false;
+  if ( $act_get )
+    return $decrypted_pass;
+  
+  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  
+  if ( !empty($_POST['crypt_data']) )
+  {
+    require('config.new.php');
+    if ( !isset($cryptkey) )
+    {
+      return false;
+    }
+    define('_INSTRESUME_AES_KEYBACKUP', $key);
+    $key = hexdecode($cryptkey);
+    
+    $decrypted_pass = $aes->decrypt($_POST['crypt_data'], $key, ENC_HEX);
+    
+  }
+  else
+  {
+    $decrypted_pass = $_POST['admin_pass'];
+  }
+  if ( empty($decrypted_pass) )
+    return false;
+  return true;
+}
+
+function stg_generate_aes_key($act_get = false)
+{
+  static $key = false;
+  if ( $act_get )
+    return $key;
+  
+  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $key = $aes->gen_readymade_key();
+  return true;
+}
+
+function stg_parse_schema($act_get = false)
+{
+  static $schema;
+  if ( $act_get )
+    return $schema;
+  
+  $admin_pass = stg_decrypt_admin_pass(true);
+  $key = stg_generate_aes_key(true);
+  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $key = $aes->hextostring($key);
+  $admin_pass = $aes->encrypt($admin_pass, $key, ENC_HEX);
+  
+  $cacheonoff = is_writable(ENANO_ROOT.'/cache/') ? '1' : '0';
+  
+  $schema = file_get_contents('schema.sql');
+  $schema = str_replace('{{SITE_NAME}}',    mysql_real_escape_string($_POST['sitename']   ), $schema);
+  $schema = str_replace('{{SITE_DESC}}',    mysql_real_escape_string($_POST['sitedesc']   ), $schema);
+  $schema = str_replace('{{COPYRIGHT}}',    mysql_real_escape_string($_POST['copyright']  ), $schema);
+  $schema = str_replace('{{ADMIN_USER}}',   mysql_real_escape_string($_POST['admin_user'] ), $schema);
+  $schema = str_replace('{{ADMIN_PASS}}',   mysql_real_escape_string($admin_pass          ), $schema);
+  $schema = str_replace('{{ADMIN_EMAIL}}',  mysql_real_escape_string($_POST['admin_email']), $schema);
+  $schema = str_replace('{{ENABLE_CACHE}}', mysql_real_escape_string($cacheonoff          ), $schema);
+  $schema = str_replace('{{REAL_NAME}}',    '',                                              $schema);
+  $schema = str_replace('{{TABLE_PREFIX}}', $_POST['table_prefix'],                          $schema);
+  $schema = str_replace('{{VERSION}}',      ENANO_VERSION,                                   $schema);
+  $schema = str_replace('{{ADMIN_EMBED_PHP}}', $_POST['admin_embed_php'],                    $schema);
+  // Not anymore!! :-D
+  // $schema = str_replace('{{BETA_VERSION}}', ENANO_BETA_VERSION,                              $schema);
+  
+  if(isset($_POST['wiki_mode']))
+  {
+    $schema = str_replace('{{WIKI_MODE}}', '1', $schema);
+  }
+  else
+  {
+    $schema = str_replace('{{WIKI_MODE}}', '0', $schema);
+  }
+  
+  // Build an array of queries      
+  $schema = explode("\n", $schema);
+  
+  foreach ( $schema as $i => $sql )
+  {
+    $query =& $schema[$i];
+    $t = trim($query);
+    if ( empty($t) || preg_match('/^(\#|--)/i', $t) )
+    {
+      unset($schema[$i]);
+      unset($query);
+    }
+  }
+  
+  $schema = array_values($schema);
+  $schema = implode("\n", $schema);
+  $schema = explode(";\n", $schema);
+  
+  foreach ( $schema as $i => $sql )
+  {
+    $query =& $schema[$i];
+    if ( substr($query, ( strlen($query) - 1 ), 1 ) != ';' )
+    {
+      $query .= ';';
+    }
+  }
+  
+  return true;
+}
+
+function stg_install($_unused, $already_run)
+{
+  // This one's pretty easy.
+  $conn = stg_mysql_connect(true);
+  if ( !is_resource($conn) )
+    return false;
+  $schema = stg_parse_schema(true);
+  if ( !is_array($schema) )
+    return false;
+  
+  // If we're resuming installation, the encryption key was regenerated.
+  // This means we'll have to update the encrypted password in the database.
+  if ( $already_run )
+  {
+    $admin_pass = stg_decrypt_admin_pass(true);
+    $key = stg_generate_aes_key(true);
+    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $key = $aes->hextostring($key);
+    $admin_pass = $aes->encrypt($admin_pass, $key, ENC_HEX);
+    $admin_user = mysql_real_escape_string($_POST['admin_user']);
+    
+    $q = @mysql_query("UPDATE {$_POST['table_prefix']}users SET password='$admin_pass' WHERE username='$admin_user';");
+    if ( !$q )
+    {
+      echo '<p><tt>MySQL return: ' . mysql_error() . '</tt></p>';
+      return false;
+    }
+    
+    return true;
+  }
+  
+  // OK, do the loop, baby!!!
+  foreach($schema as $q)
+  {
+    $r = mysql_query($q, $conn);
+    if ( !$r )
+    {
+      echo '<p><tt>MySQL return: ' . mysql_error() . '</tt></p>';
+      return false;
+    }
+  }
+  
+  return true;
+}
+
+function stg_write_config()
+{
+  $privkey = stg_generate_aes_key(true);
+  
+  switch($_POST['urlscheme'])
+  {
+    case "ugly":
+    default:
+      $cp = scriptPath.'/index.php?title=';
+      break;
+    case "short":
+      $cp = scriptPath.'/index.php/';
+      break;
+    case "tiny":
+      $cp = scriptPath.'/';
+      break;
+  }
+  
+  if ( $_POST['urlscheme'] == 'tiny' )
+  {
+    $contents = '# Begin Enano rules
+RewriteEngine on
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteRule ^(.+) '.scriptPath.'/index.php?title=$1 [L,QSA]
+RewriteRule \.(php|html|gif|jpg|png|css|js)$ - [L]
+# End Enano rules
+';
+    if ( file_exists('./.htaccess') )
+      $ht = fopen(ENANO_ROOT.'/.htaccess', 'a+');
+    else
+      $ht = fopen(ENANO_ROOT.'/.htaccess.new', 'w');
+    if ( !$ht )
+      return false;
+    fwrite($ht, $contents);
+    fclose($ht);
+  }
+
+  $config_file = '<?php
+/* Enano auto-generated configuration file - editing not recommended! */
+$dbhost   = \''.addslashes($_POST['db_host']).'\';
+$dbname   = \''.addslashes($_POST['db_name']).'\';
+$dbuser   = \''.addslashes($_POST['db_user']).'\';
+$dbpasswd = \''.addslashes($_POST['db_pass']).'\';
+if ( !defined(\'ENANO_CONSTANTS\') )
+{
+define(\'ENANO_CONSTANTS\', \'\');
+define(\'table_prefix\', \''.addslashes($_POST['table_prefix']).'\');
+define(\'scriptPath\', \''.scriptPath.'\');
+define(\'contentPath\', \''.$cp.'\');
+define(\'ENANO_INSTALLED\', \'true\');
+}
+$crypto_key = \''.$privkey.'\';
+?>';
+
+  $cf_handle = fopen(ENANO_ROOT.'/config.new.php', 'w');
+  if ( !$cf_handle )
+    return false;
+  fwrite($cf_handle, $config_file);
+  
+  fclose($cf_handle);
+  
+  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_rename_config()
+{
+  if ( !@rename('./config.new.php', './config.php') )
+  {
+    echo '<p>Can\'t rename config.php</p>';
+    _stg_rename_config_revert();
+    return false;
+  }
+  
+  if ( $_POST['urlscheme'] == 'tiny' && !file_exists('./.htaccess') )
+  {
+    if ( !@rename('./.htaccess.new', './.htaccess') )
+    {
+      echo '<p>Can\'t rename .htaccess</p>';
+      _stg_rename_config_revert();
+      return false;
+    }
+  }
+  return true;
+}
+
+function stg_start_api_success()
+{
+  return true;
+}
+
+function stg_start_api_failure()
+{
+  return false;
+}
+
+function stg_init_logs()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  $q = $db->sql_query('INSERT INTO ' . table_prefix . 'logs(log_type,action,time_id,date_string,author,page_text,edit_summary) VALUES(\'security\', \'install_enano\', ' . time() . ', \'' . date('d M Y h:i a') . '\', \'' . mysql_real_escape_string($_POST['admin_user']) . '\', \'' . mysql_real_escape_string(ENANO_VERSION) . '\', \'' . mysql_real_escape_string($_SERVER['REMOTE_ADDR']) . '\');');
+  if ( !$q )
+  {
+    echo '<p><tt>MySQL return: ' . mysql_error() . '</tt></p>';
+    return false;
+  }
+  
+  if ( !$session->get_permissions('clear_logs') )
+  {
+    echo '<p><tt>$session: denied clear_logs</tt></p>';
+    return false;
+  }
+  
+  PageUtils::flushlogs('Main_Page', 'Article');
+  
+  return true;
+}
 
 //die('Key size: ' . AES_BITS . '<br />Block size: ' . AES_BLOCKSIZE);
 
@@ -390,7 +911,7 @@
     run_test('return @ini_get(\'file_uploads\');', 'File upload support', 'It seems that your server does not support uploading files. Enano *requires* this functionality in order to work properly. Please ask your server administrator to set the "file_uploads" option in php.ini to "On".');
     run_test('return is_apache();', 'Apache HTTP Server', 'Apparently your server is running a web server other than Apache. Enano will work nontheless, but there are some known bugs with non-Apache servers, and the "fancy" URLs will not work properly. The "Standard URLs" option will be set on the website configuration page, only change it if you are absolutely certain that your server is running Apache.', true);
     //run_test('return function_exists(\'finfo_file\');', 'Fileinfo PECL extension', 'The MIME magic PHP extension is used to determine the type of a file by looking for a certain "magic" string of characters inside it. This functionality is used by Enano to more effectively prevent malicious file uploads. The MIME magic option will be disabled by default.', true);
-    run_test('return is_writable(ENANO_ROOT.\'/config.php\');', 'Configuration file writable', 'It looks like the configuration file, config.php, is not writable. Enano needs to be able to write to this file in order to install.<br /><br /><b>If you are installing Enano on a SourceForge web site:</b><br />SourceForge mounts the web partitions read-only now, so you will need to use the project shell service to symlink config.php to a file in the /tmp/persistent directory.');
+    run_test('return is_writable(ENANO_ROOT.\'/config.new.php\');', 'Configuration file writable', 'It looks like the configuration file, config.new.php, is not writable. Enano needs to be able to write to this file in order to install.<br /><br /><b>If you are installing Enano on a SourceForge web site:</b><br />SourceForge mounts the web partitions read-only now, so you will need to use the project shell service to symlink config.php to a file in the /tmp/persistent directory.');
     run_test('return file_exists(\'/usr/bin/convert\');', 'ImageMagick support', 'Enano uses ImageMagick to scale images into thumbnails. Because ImageMagick was not found on your server, Enano will use the width= and height= attributes on the &lt;img&gt; tag to scale images. This can cause somewhat of a performance increase, but bandwidth usage will be higher, especially if you use high-resolution images on your site.<br /><br />If you are sure that you have ImageMagick, you can set the location of the "convert" program using the administration panel after installation is complete.', true);
     run_test('return is_writable(ENANO_ROOT.\'/cache/\');', 'Cache directory writable', 'Apparently the cache/ directory is not writable. Enano will still work, but you will not be able to cache thumbnails, meaning the server will need to re-render them each time they are requested. In some cases, this can cause a significant slowdown.', true);
     run_test('return is_writable(ENANO_ROOT.\'/files/\');', 'File uploads directory writable', 'It seems that the directory where uploaded files are stored (' . ENANO_ROOT . '/files) cannot be written by the server. Enano will still function, but file uploads will not function, and will be disabled by default.', true);
@@ -740,7 +1261,7 @@
       exit;
     }
     unset($_POST['_cont']);
-    require('config.php');
+    require('config.new.php');
     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
     if ( isset($crypto_key) )
     {
@@ -749,7 +1270,7 @@
     if(!isset($cryptkey) || ( isset($cryptkey) && strlen($cryptkey) != AES_BITS / 4) )
     {
       $cryptkey = $aes->gen_readymade_key();
-      $handle = @fopen(ENANO_ROOT.'/config.php', 'w');
+      $handle = @fopen(ENANO_ROOT.'/config.new.php', 'w');
       if(!$handle)
       {
         echo '<p>ERROR: Cannot open config.php for writing - exiting!</p>';
@@ -759,37 +1280,38 @@
       fwrite($handle, '<?php $cryptkey = \''.$cryptkey.'\'; ?>');
       fclose($handle);
     }
-    ?>
+    // Sorry for the ugly hack, but this f***s up jEdit badly.
+    echo '
     <script type="text/javascript">
       function verify()
       {
         var frm = document.forms.login;
         ret = true;
-        if ( frm.admin_user.value.match(/^([A-z0-9 \-\.]+)$/g) && !frm.admin_user.value.match(/^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/) && frm.admin_user.value.toLowerCase() != 'anonymous' )
+        if ( frm.admin_user.value.match(/^([A-z0-9 \\-\\.]+)$/) && !frm.admin_user.value.match(/^(?:(?:\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$/) && frm.admin_user.value.toLowerCase() != \'anonymous\' )
         {
-          document.getElementById('s_user').src = 'images/good.gif';
+          document.getElementById(\'s_user\').src = \'images/good.gif\';
         }
         else
         {
-          document.getElementById('s_user').src = 'images/bad.gif';
+          document.getElementById(\'s_user\').src = \'images/bad.gif\';
           ret = false;
         }
         if(frm.admin_pass.value.length >= 6 && frm.admin_pass.value == frm.admin_pass_confirm.value)
         {
-          document.getElementById('s_password').src = 'images/good.gif';
+          document.getElementById(\'s_password\').src = \'images/good.gif\';
         }
         else
         {
-          document.getElementById('s_password').src = 'images/bad.gif';
+          document.getElementById(\'s_password\').src = \'images/bad.gif\';
           ret = false;
         }
-        if(frm.admin_email.value.match(/^(?:[\w\d]+\.?)+@(?:(?:[\w\d]\-?)+\.)+\w{2,4}$/))
+        if(frm.admin_email.value.match(/^(?:[\\w\\d]+\\.?)+@(?:(?:[\\w\\d]\\-?)+\\.)+\\w{2,4}$/))
         {
-          document.getElementById('s_email').src = 'images/good.gif';
+          document.getElementById(\'s_email\').src = \'images/good.gif\';
         }
         else
         {
-          document.getElementById('s_email').src = 'images/bad.gif';
+          document.getElementById(\'s_email\').src = \'images/bad.gif\';
           ret = false;
         }
         if(ret) frm._cont.disabled = false;
@@ -803,6 +1325,8 @@
         if(!verify()) return false;
       }
     </script>
+    ';
+    ?>
     <form name="login" action="install.php?mode=confirm" method="post" onsubmit="runEncryption();">
       <?php
         $k = array_keys($_POST);
@@ -847,6 +1371,7 @@
     </form>
     <script type="text/javascript">
     // <![CDATA[
+      var frm = document.forms.login;
       frm.admin_user.focus();
       function runEncryption()
       {
@@ -919,7 +1444,7 @@
     break;
   case "confirm":
     if(!isset($_POST['_cont'])) {
-      echo 'No POST data signature found. Please <a href="install.php?mode=license">restart the installation</a>.';
+      echo 'No POST data signature found. Please <a href="install.php?mode=sysreqs">restart the installation</a>.';
       $template->footer();
       exit;
     }
@@ -988,207 +1513,65 @@
     }
     function err($t) { global $template; echo $t; $template->footer(); exit; }
     
-      echo 'Connecting to MySQL...';
-      if($_POST['db_root_user'] != '')
+    // $stages = array('connect', 'decrypt', 'genkey', 'parse', 'sql', 'writeconfig', 'renameconfig', 'startapi', 'initlogs');
+    
+    if ( !preg_match('/^[a-z0-9_]*$/', $_POST['table_prefix']) )
+      err('Hacking attempt was detected in table_prefix.');
+    
+      start_install_table();
+      // The stages connect, decrypt, genkey, and parse are preprocessing and don't do any actual data modification.
+      // Thus, they need to be run on each retry, e.g. never skipped.
+      run_installer_stage('connect', 'Connect to MySQL', 'stg_mysql_connect', 'MySQL denied our attempt to connect to the database. This is most likely because your login information was incorrect. You will most likely need to <a href="install.php?mode=license">restart the installation</a>.', false);
+      if ( isset($_POST['drop_tables']) )
       {
-        $conn = mysql_connect($_POST['db_host'], $_POST['db_root_user'], $_POST['db_root_pass']);
-        if(!$conn) err('Error connecting to MySQL: '.mysql_error());
-        $q = mysql_query('USE '.$_POST['db_name']);
-        if(!$q)
-        {
-          $q = mysql_query('CREATE DATABASE '.$_POST['db_name']);
-          if(!$q) err('Error initializing database: '.mysql_error());
-        }
-        $q = mysql_query('GRANT ALL PRIVILEGES ON '.$_POST['db_name'].'.* TO \''.$_POST['db_user'].'\'@\'localhost\' IDENTIFIED BY \''.$_POST['db_pass'].'\' WITH GRANT OPTION;');
-        if(!$q) err('Could not create the user account');
-        $q = mysql_query('GRANT ALL PRIVILEGES ON '.$_POST['db_name'].'.* TO \''.$_POST['db_user'].'\'@\'%\' IDENTIFIED BY \''.$_POST['db_pass'].'\' WITH GRANT OPTION;');
-        if(!$q) err('Could not create the user account');
-        mysql_close($conn);
+        // Are we supposed to drop any existing tables? If so, do it now
+        run_installer_stage('drop', 'Drop existing Enano tables', 'stg_drop_tables', 'This step never returns failure');
       }
-      $conn = mysql_connect($_POST['db_host'], $_POST['db_user'], $_POST['db_pass']);
-      if(!$conn) err('Error connecting to MySQL: '.mysql_error());
-      $q = mysql_query('USE '.$_POST['db_name']);
-      if(!$q) err('Error selecting database: '.mysql_error());
-      echo 'done!<br />';
-      
-      // Are we supposed to drop any existing tables? If so, do it now
-      if(isset($_POST['drop_tables']))
-      {
-        echo 'Dropping existing Enano tables...';
-        // Our list of tables included in Enano
-        $tables = Array( 'mdg_categories', 'mdg_comments', 'mdg_config', 'mdg_logs', 'mdg_page_text', 'mdg_session_keys', 'mdg_pages', 'mdg_users', 'mdg_users_extra', 'mdg_themes', 'mdg_buddies', 'mdg_banlist', 'mdg_files', 'mdg_privmsgs', 'mdg_sidebar', 'mdg_hits', 'mdg_search_index', 'mdg_groups', 'mdg_group_members', 'mdg_acl', 'mdg_search_cache', 'mdg_tags', 'mdg_page_groups', 'mdg_page_group_members' );
-        $tables = implode(', ', $tables);
-        $tables = str_replace('mdg_', $_POST['table_prefix'], $tables);
-        $query_of_death = 'DROP TABLE '.$tables.';';
-        mysql_query($query_of_death); // We won't check for errors here because if this operation fails it probably means the tables didn't exist
-        echo 'done!<br />';
-      }
-      
-      $cacheonoff = is_writable(ENANO_ROOT.'/cache/') ? '1' : '0';
-      
-      echo 'Decrypting administration password...';
-      
-      $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+      run_installer_stage('decrypt', 'Decrypt administration password', 'stg_decrypt_admin_pass', 'The administration password you entered couldn\'t be decrypted. It is possible that your server did not properly store the encryption key in the configuration file. Please check the file permissions on config.new.php. You may have to return to the login stage of the installation, clear your browser cache, and then rerun this installation.', false);
+      run_installer_stage('genkey', 'Generate ' . AES_BITS . '-bit AES private key', 'stg_generate_aes_key', 'Enano encountered an internal error while generating the site encryption key. Please contact the Enano team for support.', false);
+      run_installer_stage('parse', 'Prepare to execute schema file', 'stg_parse_schema', 'Enano encountered an internal error while parsing the SQL file that contains the database structure and initial data. Please contact the Enano team for support.', false);
+      run_installer_stage('sql', 'Execute installer schema', 'stg_install', 'The installation failed because an SQL query wasn\'t quite correct. It is possible that you entered malformed data into a form field, or there may be a bug in Enano with your version of MySQL. Please contact the Enano team for support.', false);
+      run_installer_stage('writeconfig', 'Write configuration files', 'stg_write_config', '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.');
+      run_installer_stage('renameconfig', 'Rename configuration files', 'stg_rename_config', 'Enano couldn\'t rename the configuration files to their correct production names. On some UNIX systems, you need to CHMOD the directory with your Enano files to 777 in order for this stage to succeed.');
       
-      if ( !empty($_POST['crypt_data']) )
-      {
-        require('config.php');
-        if ( !isset($cryptkey) )
-        {
-          echo 'failed!<br />Cannot get the key from config.php';
-          break;
-        }
-        $key = hexdecode($cryptkey);
-        
-        $dec = $aes->decrypt($_POST['crypt_data'], $key, ENC_HEX);
-        
-      }
-      else
+      // Mainstream installation complete - Enano should be usable now
+      // The stage of starting the API is special because it has to be called out of function context.
+      // To alleviate this, we have two functions, one that returns success and one that returns failure
+      // If the Enano API load is successful, the success function is called to report the action to the user
+      // If unsuccessful, the failure report is sent
+      
+      $template_bak = $template;
+      
+      $_GET['title'] = 'Main_Page';
+      require('includes/common.php');
+      
+      if ( is_object($db) && is_object($session) )
       {
-        $dec = $_POST['admin_pass'];
-      }
-      echo 'done!<br />Generating '.AES_BITS.'-bit AES private key...';
-      $privkey = $aes->gen_readymade_key();
-      $pkba = hexdecode($privkey);
-      $encpass = $aes->encrypt($dec, $pkba, ENC_HEX);
-      
-      echo 'done!<br />Preparing for schema execution...';
-      $schema = file_get_contents('schema.sql');
-      $schema = str_replace('{{SITE_NAME}}',    mysql_real_escape_string($_POST['sitename']   ), $schema);
-      $schema = str_replace('{{SITE_DESC}}',    mysql_real_escape_string($_POST['sitedesc']   ), $schema);
-      $schema = str_replace('{{COPYRIGHT}}',    mysql_real_escape_string($_POST['copyright']  ), $schema);
-      $schema = str_replace('{{ADMIN_USER}}',   mysql_real_escape_string($_POST['admin_user'] ), $schema);
-      $schema = str_replace('{{ADMIN_PASS}}',   mysql_real_escape_string($encpass             ), $schema);
-      $schema = str_replace('{{ADMIN_EMAIL}}',  mysql_real_escape_string($_POST['admin_email']), $schema);
-      $schema = str_replace('{{ENABLE_CACHE}}', mysql_real_escape_string($cacheonoff          ), $schema);
-      $schema = str_replace('{{REAL_NAME}}',    '',                                              $schema);
-      $schema = str_replace('{{TABLE_PREFIX}}', $_POST['table_prefix'],                          $schema);
-      $schema = str_replace('{{VERSION}}',      ENANO_VERSION,                                   $schema);
-      $schema = str_replace('{{ADMIN_EMBED_PHP}}', $_POST['admin_embed_php'],                    $schema);
-      // Not anymore!! :-D
-      // $schema = str_replace('{{BETA_VERSION}}', ENANO_BETA_VERSION,                              $schema);
-      
-      if(isset($_POST['wiki_mode']))
-      {
-        $schema = str_replace('{{WIKI_MODE}}', '1', $schema);
+        run_installer_stage('startapi', 'Start the Enano API', 'stg_start_api_success', '...', false);
       }
       else
       {
-        $schema = str_replace('{{WIKI_MODE}}', '0', $schema);
-      }
-      
-      // Build an array of queries      
-      $schema = explode("\n", $schema);
-      
-      foreach ( $schema as $i => $sql )
-      {
-        $query =& $schema[$i];
-        $t = trim($query);
-        if ( empty($t) || preg_match('/^(\#|--)/i', $t) )
-        {
-          unset($schema[$i]);
-          unset($query);
-        }
-      }
-      
-      $schema = array_values($schema);
-      $schema = implode("\n", $schema);
-      $schema = explode(";\n", $schema);
-      
-      foreach ( $schema as $i => $sql )
-      {
-        $query =& $schema[$i];
-        if ( substr($query, ( strlen($query) - 1 ), 1 ) != ';' )
-        {
-          $query .= ';';
-        }
-      }
-      
-      // echo '<pre>' . htmlspecialchars(print_r($schema, true)) . '</pre>';
-      // break;
-      
-      echo 'done!<br />Executing schema.sql...';
-      
-      // OK, do the loop, baby!!!
-      foreach($schema as $q)
-      {
-        $r = mysql_query($q, $conn);
-        if(!$r) err('Error during mainstream installation: '.mysql_error());
+        run_installer_stage('startapi', 'Start the Enano API', 'stg_start_api_failure', 'The Enano API could not be started. This is an error that should never occur; please contact the Enano team for support.', false);
       }
       
-      echo 'done!<br />Writing configuration files...';
-      if($_POST['urlscheme']=='tiny')
-      {
-        $ht = fopen(ENANO_ROOT.'/.htaccess', 'a+');
-        if(!$ht) err('Error opening file .htaccess for writing');
-        fwrite($ht, '
-RewriteEngine on
-RewriteCond %{REQUEST_FILENAME} !-d
-RewriteCond %{REQUEST_FILENAME} !-f
-RewriteRule ^(.+) '.scriptPath.'/index.php?title=$1 [L,QSA]
-RewriteRule \.(php|html|gif|jpg|png|css|js)$ - [L]
-');
-        fclose($ht);
-      }
-  
-      $config_file = '<?php
-/* Enano auto-generated configuration file - editing not recommended! */
-$dbhost   = \''.addslashes($_POST['db_host']).'\';
-$dbname   = \''.addslashes($_POST['db_name']).'\';
-$dbuser   = \''.addslashes($_POST['db_user']).'\';
-$dbpasswd = \''.addslashes($_POST['db_pass']).'\';
-if(!defined(\'ENANO_CONSTANTS\')) {
-define(\'ENANO_CONSTANTS\', \'\');
-define(\'table_prefix\', \''.$_POST['table_prefix'].'\');
-define(\'scriptPath\', \''.scriptPath.'\');
-define(\'contentPath\', \''.$cp.'\');
-define(\'ENANO_INSTALLED\', \'true\');
-}
-$crypto_key = \''.$privkey.'\';
-?>';
-
-      $cf_handle = fopen(ENANO_ROOT.'/config.php', 'w');
-      if(!$cf_handle) err('Couldn\'t open file config.php for writing');
-      fwrite($cf_handle, $config_file);
-      fclose($cf_handle);
-      
-      echo 'done!<br />Starting the Enano API...';
-      
-      $template_bak = $template;
-      
-      // Get Enano loaded
-      $_GET['title'] = 'Main_Page';
-      require('includes/common.php');
-      
       // We need to be logged in (with admin rights) before logs can be flushed
-      $session->login_without_crypto($_POST['admin_user'], $dec, false);
+      $admin_password = stg_decrypt_admin_pass(true);
+      $session->login_without_crypto($_POST['admin_user'], $admin_password, false);
       
       // Now that login cookies are set, initialize the session manager and ACLs
       $session->start();
       $paths->init();
       
+      run_installer_stage('initlogs', 'Initialize logs', 'stg_init_logs', '<b>The session manager denied the request to flush logs for the main page.</b><br />
+                           While under most circumstances you can still <a href="install.php?mode=finish">finish the installation</a>, you should be aware that some servers cannot
+                           properly set cookies due to limitations with PHP. These limitations are exposed primarily when this issue is encountered during installation. If you choose
+                           to finish the installation, please be aware that you may be unable to log into your site.');
+      close_install_table();
+      
       unset($template);
       $template =& $template_bak;
-      
-      echo 'done!<br />Initializing logs...';
-      
-      $q = $db->sql_query('INSERT INTO ' . $_POST['table_prefix'] . 'logs(log_type,action,time_id,date_string,author,page_text,edit_summary) VALUES(\'security\', \'install_enano\', ' . time() . ', \'' . date('d M Y h:i a') . '\', \'' . mysql_real_escape_string($_POST['admin_user']) . '\', \'' . mysql_real_escape_string(ENANO_VERSION) . '\', \'' . mysql_real_escape_string($_SERVER['REMOTE_ADDR']) . '\');', $conn);
-      if ( !$q )
-        err('Error setting up logs: '.$db->get_error());
-      
-      if ( !$session->get_permissions('clear_logs') )
-      {
-        echo '<br />Error: session manager won\'t permit flushing logs, these is a bug.';
-        break;
-      }
-      
-      // unset($session);
-      // $session = new sessionManager();
-      // $session->start();
-      
-      PageUtils::flushlogs('Main_Page', 'Article');
-      
-      echo 'done!<h3>Installation of Enano is complete.</h3><p>Review any warnings above, and then <a href="install.php?mode=finish">click here to finish the installation</a>.';
+    
+      echo '<h3>Installation of Enano is complete.</h3><p>Review any warnings above, and then <a href="install.php?mode=finish">click here to finish the installation</a>.';
       
       // echo '<script type="text/javascript">window.location="'.scriptPath.'/install.php?mode=finish";</script>';