Merging in the newly stable Coblynau
authorDan
Sun, 02 Dec 2007 16:00:10 -0500
changeset 304 e2cb5f1432c8
parent 280 dc08c70ca550 (current diff)
parent 303 abefe7181c08 (diff)
child 310 199b9708f4a2
Merging in the newly stable Coblynau
ajax.php
includes/clientside/static/ajax.js
includes/clientside/static/misc.js
includes/common.php
includes/constants.php
includes/dbal.php
includes/functions.php
includes/pageprocess.php
includes/pageutils.php
includes/paths.php
includes/sessions.php
includes/tagcloud.php
includes/template.php
index.php
install.php
language/english/enano.json
language/english/install.json
plugins/SpecialAdmin.php
plugins/SpecialPageFuncs.php
plugins/SpecialSearch.php
plugins/SpecialUpdownload.php
plugins/SpecialUserFuncs.php
plugins/admin/UserManager.php
schema.sql
themes/oxygen/header.tpl
upgrade.php
upgrade.sql
--- a/.hgtags	Sun Dec 02 15:27:21 2007 -0500
+++ b/.hgtags	Sun Dec 02 16:00:10 2007 -0500
@@ -6,3 +6,6 @@
 0b5244001799fa29e83bf06c5f14eb69350f171c rebrand
 42c6c83b8a004163c9cc2d85f3c8eada3b73adf6 rebrand
 d53cc29308f4f4b97fc6d054e9e0855f37137409 rebrand
+90632c09ed7ec816da708df1341d9f4019de9adf feature-freeze
+f948557af0681389165a23419968965fcebcbdd0 devel-freeze
+c36c5034a80052b4dc4710a67513b5b6e3c2fc7b release
--- a/README	Sun Dec 02 15:27:21 2007 -0500
+++ b/README	Sun Dec 02 16:00:10 2007 -0500
@@ -55,7 +55,7 @@
    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."
+     (C) 2006-2007 Dan Fuhry."
 
    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
@@ -82,9 +82,8 @@
 
 This tarball includes an upgrade script that can migrate any previous version
 of Enano to this one. Before you upload the contents of this tarball to your
-server, be sure to delete the config.php and .htaccess files included with this
-tarball, and back up these two files from your server. (.htaccess only needs to
-be backed up if you're using the Tiny URLs option and/or you have Apache.)
+server, you might want to delete the files config.new.php and .htaccess.new,
+neither of which are needed if you are performing an upgrade.
 
 EXPANDING YOUR SITE'S CAPABILITIES
 -----------------------------
--- a/ajax.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/ajax.php	Sun Dec 02 16:00:10 2007 -0500
@@ -21,6 +21,11 @@
     function dc_watch($n)    { return false; }
     function dc_start_timer($u) { return false; }
     function dc_stop_timer($m) { return false; }
+    function microtime_float()
+    {
+      list($usec, $sec) = explode(" ", microtime());
+      return ((float)$usec + (float)$sec);
+    }
     // Determine directory (special case for development servers)
     if ( strpos(__FILE__, '/repo/') && file_exists('.enanodev') )
     {
Binary file images/enano-artwork/installer-greeting-blue.png has changed
Binary file images/enano-artwork/installer-greeting-green.png has changed
--- a/includes/clientside/static/ajax.js	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/clientside/static/ajax.js	Sun Dec 02 16:00:10 2007 -0500
@@ -233,7 +233,7 @@
   document.getElementById('protbtn_1').style.textDecoration = 'none';
   document.getElementById('protbtn_2').style.textDecoration = 'none';
   document.getElementById('protbtn_'+l).style.textDecoration = 'underline';
-  ajaxPost(stdAjaxPrefix+'&_mode=protect', 'reason='+escape(r)+'&level='+l, function() {
+  ajaxPost(stdAjaxPrefix+'&_mode=protect', 'reason='+ajaxEscape(r)+'&level='+l, function() {
     if(ajax.readyState == 4) {
       unsetAjaxLoading();
       if(ajax.responseText != 'good')
@@ -250,7 +250,7 @@
   r = prompt($lang.get('ajax_rename_prompt'));
   if(!r || r=='') return;
   setAjaxLoading();
-  ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+escape(r), function() {
+  ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+ajaxEscape(r), function() {
     if(ajax.readyState == 4) {
       unsetAjaxLoading();
       alert(ajax.responseText);
@@ -288,7 +288,7 @@
     return;
   }
   setAjaxLoading();
-  ajaxPost(stdAjaxPrefix+'&_mode=deletepage', 'reason=' + escape(reason), function() {
+  ajaxPost(stdAjaxPrefix+'&_mode=deletepage', 'reason=' + ajaxEscape(reason), function() {
     if(ajax.readyState == 4) {
       unsetAjaxLoading();
       alert(ajax.responseText);
@@ -660,7 +660,7 @@
     return true;
   }
   
-  ajaxPost(stdAjaxPrefix + '&_mode=change_theme', 'theme_id=' + escape(theme_id) + '&style_id=' + escape(style_id), function()
+  ajaxPost(stdAjaxPrefix + '&_mode=change_theme', 'theme_id=' + ajaxEscape(theme_id) + '&style_id=' + ajaxEscape(style_id), function()
     {
       if ( ajax.readyState == 4 )
       {
--- a/includes/clientside/static/misc.js	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/clientside/static/misc.js	Sun Dec 02 16:00:10 2007 -0500
@@ -392,6 +392,10 @@
 
 function ajaxAuthLoginInnerSetup()
 {
+  // let's hope this gets the image cached
+  var _ = new Image(32, 32); 
+  _.src = scriptPath + "/images/good.gif";
+  
   ajaxGet(makeUrlNS('Special', 'Login', 'act=getkey'), function() {
       if ( ajax.readyState == 4 )
       {
@@ -619,6 +623,11 @@
         switch(response.result)
         {
           case 'success':
+            var success_win = '<div align="center" style="text-align: center;"> \
+                  <p>' + $lang.get('user_login_success_short') + '</p> \
+                  <p><img alt=" " src="'+scriptPath+'/images/good.gif" /></p> \
+                </div>';
+            ajax_auth_mb_cache.updateContent(success_win);
             if ( typeof(ajax_auth_prompt_cache) == 'function' )
             {
               ajax_auth_prompt_cache(response.key);
@@ -758,6 +767,6 @@
 
 function validateEmail(email)
 {
-  return ( email.match(/^(?:[\w\d]+\.?)+@((?:(?:[\w\d]\-?)+\.)+\w{2,4}|localhost)$/) ) ? true : false;
+  return ( email.match(/^(?:[\w\d_-]+\.?)+@((?:(?:[\w\d_-]\-?)+\.)+\w{2,4}|localhost)$/) ) ? true : false;
 }
 
--- a/includes/common.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/common.php	Sun Dec 02 16:00:10 2007 -0500
@@ -54,16 +54,18 @@
   die(__FILE__.':'.__LINE__.': The debugConsole requires PHP 5.x.x or greater. Please comment out the ENANO_DEBUG constant in your index.php.');
 }
 
+/*
 if(defined('ENANO_DEBUG'))
 {
   require_once(ENANO_ROOT.'/includes/debugger/debugConsole.php');
 } else {
+*/
   function dc_here($m)     { return false; }
   function dc_dump($a, $g) { return false; }
   function dc_watch($n)    { return false; }
   function dc_start_timer($u) { return false; }
   function dc_stop_timer($m) { return false; }
-}
+//}
 
 if ( file_exists( ENANO_ROOT . '/_nightly.php') )
   require(ENANO_ROOT.'/_nightly.php');
--- a/includes/constants.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/constants.php	Sun Dec 02 16:00:10 2007 -0500
@@ -296,6 +296,22 @@
   'xwd'     => 'image/x-xwindowdump',
   'xyz'     => 'chemical/x-xyz',
   'zip'     => 'application/zip',
+  'odt' => 'application/vnd.oasis.opendocument.text',
+  'ott' => 'application/vnd.oasis.opendocument.text-template',
+  'odg' => 'application/vnd.oasis.opendocument.graphics',
+  'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+  'odp' => 'application/vnd.oasis.opendocument.presentation',
+  'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+  'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+  'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+  'odc' => 'application/vnd.oasis.opendocument.chart',
+  'otc' => 'application/vnd.oasis.opendocument.chart-template',
+  'odi' => 'application/vnd.oasis.opendocument.image',
+  'oti' => 'application/vnd.oasis.opendocument.image-template',
+  'odf' => 'application/vnd.oasis.opendocument.formula',
+  'otf' => 'application/vnd.oasis.opendocument.formula-template',
+  'odm' => 'application/vnd.oasis.opendocument.text-master',
+  'oth' => 'application/vnd.oasis.opendocument.text-web'
 );
 
 $mimetype_extlist = Array(
@@ -418,6 +434,22 @@
   'x-conference/x-cooltalk'=>'ice',
   // Added for Enano
   'image/xcf' => 'xcf xcfbz2 xcf.bz2',
+  'application/vnd.oasis.opendocument.text' => 'odt',
+  'application/vnd.oasis.opendocument.text-template' => 'ott',
+  'application/vnd.oasis.opendocument.graphics' => 'odg',
+  'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+  'application/vnd.oasis.opendocument.presentation' => 'odp',
+  'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+  'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+  'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+  'application/vnd.oasis.opendocument.chart' => 'odc',
+  'application/vnd.oasis.opendocument.chart-template' => 'otc',
+  'application/vnd.oasis.opendocument.image' => 'odi',
+  'application/vnd.oasis.opendocument.image-template' => 'oti',
+  'application/vnd.oasis.opendocument.formula' => 'odf',
+  'application/vnd.oasis.opendocument.formula-template' => 'otf',
+  'application/vnd.oasis.opendocument.text-master' => 'odm',
+  'application/vnd.oasis.opendocument.text-web' => 'oth'
 );
 
 $k = array_keys($mime_types);
--- a/includes/dbal.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/dbal.php	Sun Dec 02 16:00:10 2007 -0500
@@ -27,18 +27,21 @@
     case E_WARNING: case E_USER_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: $errtype = 'Warning'; break;
   }
   $debug = debug_backtrace();
-  $debug = $debug[2]['file'] . ', line ' . $debug[2]['line'];
+  if ( !isset($debug[0]['file']) )
+    return false;
+  $debug = $debug[0]['file'] . ', line ' . $debug[0]['line'];
   echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>";
 }
  
 class mysql {
-  var $num_queries, $query_backtrace, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values;
+  var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
   var $row = array();
 	var $rowset = array();
   var $errhandler;
   
   function enable_errorhandler()
   {
+    // echo "DBAL: enabling error handler<br />";
     if ( function_exists('debug_backtrace') )
     {
       $this->errhandler = set_error_handler('db_error_handler');
@@ -47,6 +50,7 @@
   
   function disable_errorhandler()
   {
+    // echo "DBAL: disabling error handler<br />";
     if ( $this->errhandler )
     {
       set_error_handler($this->errhandler);
@@ -57,14 +61,9 @@
     }
   }
   
-  function sql_backtrace() {
-    $qb = explode("\n", $this->query_backtrace);
-    $bt = '';
-    //for($i=sizeof($qb)-1;$i>=0;$i--) {
-    for($i=0;$i<sizeof($qb);$i++) {
-      $bt .= $qb[$i]."\n";
-    }
-    return $bt;
+  function sql_backtrace()
+  {
+    return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
   }
   
   function ensure_connection()
@@ -127,7 +126,7 @@
     
     dc_here('dbal: trying to connect....');
     
-    if ( defined('IN_ENANO_INSTALL') )
+    if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
     {
       @include(ENANO_ROOT.'/config.new.php');
     }
@@ -149,6 +148,11 @@
         {
           $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
         }
+        if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
+        {
+          // user requested http://foo/enano as opposed to http://foo/enano/index.php
+          $_SERVER['REQUEST_URI'] .= '/index.php';
+        }
         $sp = dirname($_SERVER['REQUEST_URI']);
         if($sp == '/' || $sp == '\\') $sp = '';
         define('scriptPath', $sp);
@@ -170,9 +174,13 @@
     }
     
     // Reset some variables
-    $this->query_backtrace = '';
+    $this->query_backtrace = array();
+    $this->query_times = array();
+    $this->query_sources = array();
     $this->num_queries = 0;
     
+    $this->debug = ( defined('ENANO_DEBUG') );
+    
     dc_here('dbal: we\'re in, selecting database...');
     $q = $this->sql_query('USE `'.$dbname.'`;');
     
@@ -189,8 +197,27 @@
   function sql_query($q)
   {
     $this->enable_errorhandler();
+    
+    if ( $this->debug && function_exists('debug_backtrace') )
+    {
+      $backtrace = @debug_backtrace();
+      if ( is_array($backtrace) )
+      {
+        $bt = $backtrace[0];
+        if ( isset($backtrace[1]['class']) )
+        {
+          if ( $backtrace[1]['class'] == 'sessionManager' )
+          {
+            $bt = $backtrace[1];
+          }
+        }
+        $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
+      }
+      unset($backtrace);
+    }
+    
     $this->num_queries++;
-    $this->query_backtrace .= $q . "\n";
+    $this->query_backtrace[] = $q;
     $this->latest_query = $q;
     dc_here('dbal: making SQL query:<br /><tt>'.$q.'</tt>');
     // First make sure we have a connection
@@ -205,7 +232,9 @@
       grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>');
     }
     
+    $time_start = microtime_float();
     $r = mysql_query($q, $this->_conn);
+    $this->query_times[$q] = microtime_float() - $time_start;
     $this->latest_result = $r;
     $this->disable_errorhandler();
     return $r;
@@ -214,8 +243,9 @@
   function sql_unbuffered_query($q)
   {
     $this->enable_errorhandler();
+    
     $this->num_queries++;
-    $this->query_backtrace .= '(UNBUFFERED) ' . $q."\n";
+    $this->query_backtrace[] = '(UNBUFFERED) ' . $q;
     $this->latest_query = $q;
     dc_here('dbal: making SQL query:<br /><tt>'.$q.'</tt>');
     // First make sure we have a connection
@@ -230,7 +260,9 @@
       grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>');
     }
     
+    $time_start = microtime_float();
     $r = mysql_unbuffered_query($q, $this->_conn);
+    $this->query_times[$q] = microtime_float() - $time_start;
     $this->latest_result = $r;
     $this->disable_errorhandler();
     return $r;
@@ -681,6 +713,79 @@
 			return false;
 		}
 	}
+  /**
+   * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
+   */
+  
+  function sql_report()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    if ( !$session->get_permissions('mod_misc') )
+    {
+      die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
+    }
+    // Create copies of variables that may be changed after header is called
+    $backtrace = $this->query_backtrace;
+    $times = $this->query_times;
+    $template->header();
+    echo '<h3>SQL query log and timetable</h3>';
+    echo '<div class="tblholder">
+            <table border="0" cellspacing="1" cellpadding="4">';
+    $i = 0;
+    foreach ( $backtrace as $query )
+    {
+      $i++;
+      $unbuffered = false;
+      if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
+      {
+        $query = substr($query, 13);
+        $unbuffered = true;
+      }
+      if ( $i == 1 )
+      {
+        echo '<tr>
+                <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
+              </tr>';
+      }
+      else
+      {
+        echo '<tr>
+                <th class="subhead" colspan="2">&nbsp;</th>
+              </tr>';
+      }
+      echo '<tr>
+              <td class="row2">Query:</td>
+              <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
+            </tr>
+            <tr>
+              <td class="row2">Time:</td>
+              <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
+            </tr>
+            <tr>
+              <td class="row2">Unbuffered:</td>
+              <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
+            </tr>';
+      if ( isset($this->query_sources[$query]) )
+      {
+        echo '<tr>
+                <td class="row2">Called from:</td>
+                <td class="row1">' . $this->query_sources[$query] . '</td>
+              </tr>';
+      }
+    }
+    if ( function_exists('array_sum') )
+    {
+      $query_time_total = array_sum($this->query_times);
+      echo '<tr>
+              <th class="subhead" colspan="2">
+                Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
+              </th>
+            </tr>';
+    }
+    echo '  </table>
+          </div>';
+    $template->footer();
+  }
 }
 
 ?>
--- a/includes/functions.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/functions.php	Sun Dec 02 16:00:10 2007 -0500
@@ -560,94 +560,6 @@
  * Prints out the categorization box found on most regular pages. Doesn't take or return anything, but assumes that the page information is already set in $paths.
  */
 
- /*
-function show_category_info()
-{
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  dc_here('functions: showing category info');
-  // if($template->no_headers && !strpos($_SERVER['REQUEST_URI'], 'ajax.php')) return '';
-  if ( $paths->namespace == 'Category' )
-  {
-    $q = $db->sql_query('SELECT page_id,namespace FROM '.table_prefix.'categories WHERE category_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\'Category\' ORDER BY page_id;');
-    if(!$q) $db->_die('The category information could not be selected.');
-    $ticker = -1;
-    echo '<h3>Subcategories</h3>';
-    if($db->numrows() < 1) echo '<p>There are no subcategories in this category.</p>';
-    echo '<table border="0" cellspacing="1" cellpadding="4">';
-    while($row = $db->fetchrow())
-    {
-      $ticker++;
-      if ( $ticker == 3 )
-      {
-        $ticker = 0;
-      }
-      if ( $ticker == 0 )
-      {
-        echo '<tr>';
-      }
-      echo '<td style="width: 200px;"><a href="' . makeUrlNS($row['namespace'], $row['page_id']) . '">' . htmlspecialchars($paths->pages[$paths->nslist[$row['namespace']].$row['page_id']]['name']) . '</a></td>';
-      if ( $ticker == 2 )
-      {
-        echo '</tr>';
-      }
-    }
-    $db->free_result();
-    if($ticker) echo '</tr>';
-    echo '</table>';
-
-    $q = $db->sql_query('SELECT page_id,namespace FROM '.table_prefix.'categories WHERE category_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace!=\'Category\' ORDER BY page_id;');
-    if ( !$q )
-    {
-      $db->_die('The category information could not be selected.');
-    }
-    $ticker = -1;
-    echo '<h3>Pages</h3>';
-    if ( $db->numrows() < 1 )
-    {
-      echo '<p>There are no pages in this category.</p>';
-    }
-    echo '<table border="0" cellspacing="1" cellpadding="4">';
-    while($row = $db->fetchrow())
-    {
-      $ticker += ( $ticker == 3 ) ? -3 : 1;
-      if ( $ticker == 0 )
-      {
-        echo '<tr>';
-      }
-      echo '<td style="width: 200px;"><a href="'.makeUrlNS($row['namespace'], $row['page_id']).'">'.htmlspecialchars($paths->pages[$paths->nslist[$row['namespace']].$row['page_id']]['name']).'</a></td>';
-      if ( $ticker == 2 )
-      {
-        echo '</tr>';
-      }
-    }
-    $db->free_result();
-    if($ticker) echo '</tr>';
-    echo '</table><br /><br />';
-  }
-  $q = $db->sql_query('SELECT category_id FROM '.table_prefix.'categories WHERE page_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\''.$paths->namespace.'\'');
-  if(!$q) $db->_die('The error seems to have occurred during selection of category data.');
-  if($db->numrows() > 0) {
-    echo '<div class="mdg-comment" style="margin-left: 0;">Categories: ';
-    $i=0;
-    while($r = $db->fetchrow())
-    {
-      if($i>0) echo ', ';
-      $i++;
-      echo '<a href="'.makeUrlNS('Category', $r['category_id']).'">'.$paths->pages[$paths->nslist['Category'].$r['category_id']]['name'].'</a>';
-    }
-    if( ( $paths->wiki_mode && !$paths->page_protected ) || ( $session->get_permissions('edit_cat') && $session->get_permissions('even_when_protected') ) ) echo ' [ <a href="'.makeUrl($paths->page, 'do=catedit', true).'" onclick="ajaxCatEdit(); return false;">edit categorization</a> ]</div>';
-  } 
-  else
-  {
-    echo '<div class="mdg-comment" style="margin-left: 0;">Categories: ';
-    echo '(Uncategorized)';
-    if( ( $paths->wiki_mode && !$paths->page_protected ) || ( $session->get_permissions('edit_cat') && $session->get_permissions('even_when_protected') ) ) echo ' [ <a href="'.makeUrl($paths->page, 'do=catedit', true).'" onclick="ajaxCatEdit(); return false;">edit categorization</a> ]</div>';
-    else echo '</div>';
-  }
-  $db->free_result();
-}
-*/
-
 function show_category_info()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -2801,7 +2713,7 @@
 /**
  * Aggressively and hopefully non-destructively optimizes a blob of HTML.
  * @param string HTML to process
- * @return string much snaller HTML
+ * @return string much smaller HTML
  */
 
 function aggressive_optimize_html($html)
--- a/includes/pageprocess.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/pageprocess.php	Sun Dec 02 16:00:10 2007 -0500
@@ -166,13 +166,16 @@
         $this->send_headers = false;
         $strict_no_headers = true;
       }
-      if ( $paths->pages[$pathskey]['password'] != '' && $paths->pages[$pathskey]['password'] != sha1('') )
+      if ( isset($paths->pages[$pathskey]['password']) )
       {
-        $password =& $paths->pages[$pathskey]['password'];
-        if ( $this->password != $password )
+        if ( $paths->pages[$pathskey]['password'] != '' && $paths->pages[$pathskey]['password'] != sha1('') )
         {
-          $this->err_wrong_password();
-          return false;
+          $password =& $paths->pages[$pathskey]['password'];
+          if ( $this->password != $password )
+          {
+            $this->err_wrong_password();
+            return false;
+          }
         }
       }
     }
@@ -617,6 +620,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $email;
     
+    $page_urlname = dirtify_page_id($this->page_id);
     if ( $this->page_id == $paths->cpage['urlname_nons'] && $this->namespace == $paths->namespace )
     {
       $page_name = ( isset($paths->cpage['name']) ) ? $paths->cpage['name'] : $this->page_id;
@@ -626,7 +630,7 @@
       $page_name = ( isset($paths->pages[$this->page_id]) ) ? $paths->pages[$this->page_id]['name'] : $this->page_id;
     }
     
-    $target_username = strtr($page_name, 
+    $target_username = strtr($page_urlname, 
       Array(
         '_' => ' ',
         '<' => '&lt;',
--- a/includes/pageutils.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/pageutils.php	Sun Dec 02 16:00:10 2007 -0500
@@ -629,8 +629,8 @@
         {
           $rc = '';
         }
-        echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', $r['author']).'" ';
-        if ( !isPage($paths->nslist['User'] . $r['author']) )
+        echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', sanitize_page_id($r['author'])).'" ';
+        if ( !isPage($paths->nslist['User'] . sanitize_page_id($r['author'])) )
         {
           echo 'class="wikilink-nonexistent"';
         }
@@ -697,8 +697,8 @@
         echo '<td class="' . $cls . '">' . $r['date_string'] . '</td class="' . $cls . '">';
         
         // User
-        echo '<td class="' . $cls . '"><a href="'.makeUrlNS('User', $r['author']).'" ';
-        if(!isPage($paths->nslist['User'] . $r['author'])) echo 'class="wikilink-nonexistent"';
+        echo '<td class="' . $cls . '"><a href="'.makeUrlNS('User', sanitize_page_id($r['author'])).'" ';
+        if(!isPage($paths->nslist['User'] . sanitize_page_id($r['author']))) echo 'class="wikilink-nonexistent"';
         echo '>' . $r['author'] . '</a></td class="' . $cls . '">';
         
         
@@ -1051,7 +1051,7 @@
         
         // Determine the name, and whether to link to the user page or not
         $name = '';
-        if($row['user_id'] > 1) $name .= '<a href="'.makeUrlNS('User', str_replace(' ', '_', $row['name'])).'">';
+        if($row['user_id'] > 1) $name .= '<a href="'.makeUrlNS('User', sanitize_page_id(' ', '_', $row['name'])).'">';
         $name .= $row['name'];
         if($row['user_id'] > 1) $name .= '</a>';
         $strings['NAME'] = $name; unset($name);
--- a/includes/paths.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/paths.php	Sun Dec 02 16:00:10 2007 -0500
@@ -118,7 +118,8 @@
       eval($cmd);
     }
     
-    $e = $db->sql_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,delvote_ips,wiki_mode,password FROM '.table_prefix.'pages ORDER BY name;');
+    $e = $db->sql_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
+                        . '  delvote_ips,wiki_mode,password FROM '.table_prefix.'pages ORDER BY name;');
     if( !$e )
     {
       $db->_die('The error seems to have occured while selecting the page information. File: includes/paths.php; line: '.__LINE__);
@@ -681,17 +682,30 @@
   
   /**
    * Rebuilds the search index
+   * @param bool If true, prints out status messages
    */
    
-  function rebuild_search_index()
+  function rebuild_search_index($verbose = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     $search = new Searcher();
+    if ( $verbose )
+    {
+      echo '<p>';
+    }
     $texts = Array();
     $textq = $db->sql_unbuffered_query($this->fetch_page_search_resource());
     if(!$textq) $db->_die('');
     while($row = $db->fetchrow())
     {
+      if ( $verbose )
+      {
+        ob_start();
+        echo "Indexing page " . $this->nslist[$row['namespace']] . sanitize_page_id($row['page_id']) . "<br />";
+        ob_flush();
+        while (@ob_end_flush());
+        flush();
+      }
       if ( isset($this->nslist[$row['namespace']]) )
       {
         $idstring = $this->nslist[$row['namespace']] . sanitize_page_id($row['page_id']);
@@ -710,7 +724,19 @@
       }
       $texts[(string)$row['page_idstring']] = $row['page_text'] . ' ' . $page['name'];
     }
+    if ( $verbose )
+    {
+      ob_start();
+      echo "Calculating word list...";
+      ob_flush();
+      while (@ob_end_flush());
+      flush();
+    }
     $search->buildIndex($texts);
+    if ( $verbose )
+    {
+      echo '</p>';
+    }
     // echo '<pre>'.print_r($search->index, true).'</pre>';
     // return;
     $q = $db->sql_query('DELETE FROM '.table_prefix.'search_index');
--- a/includes/rijndael.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/rijndael.php	Sun Dec 02 16:00:10 2007 -0500
@@ -16,10 +16,13 @@
 define ('ENC_BASE64', 202);
 define ('ENC_BINARY', 203);
 
+$_aes_objcache = array();
+
 class AESCrypt {
   
   var $debug = false;
   var $mcrypt = false;
+  var $decrypt_cache = array();
 
   // Rijndael parameters --  Valid values are 128, 192, or 256
   
@@ -128,6 +131,18 @@
     $this->debug = $debug;
   }
   
+  function singleton($key_size, $block_size)
+  {
+    global $_aes_objcache;
+    if ( isset($_aes_objcache["$key_size,$block_size"]) )
+    {
+      return $_aes_objcache["$key_size,$block_size"];
+    }
+    
+    $_aes_objcache["$key_size,$block_size"] = new AESCrypt($key_size, $block_size);
+    return $_aes_objcache["$key_size,$block_size"];
+  }
+  
   // Error handler
   
   function trigger_error($text, $level = E_USER_NOTICE)
@@ -804,6 +819,14 @@
   {
     if ( $text == '' )
       return '';
+    $text_orig = $text;
+    if ( isset($this->decrypt_cache[$key]) && is_array($this->decrypt_cache[$key]) )
+    {
+      if ( isset($this->decrypt_cache[$key][$text]) )
+      {
+        return $this->decrypt_cache[$key][$text];
+      }
+    }
     switch($input_encoding)
     {
       case ENC_BINARY:
@@ -838,6 +861,11 @@
       }
       $dypt = $this->byteArrayToString($dypt);
     }
+    if ( !isset($this->decrypt_cache[$key]) )
+      $this->decrypt_cache[$key] = array();
+    
+    $this->decrypt_cache[$key][$text_orig] = $dypt;
+    
     return $dypt;
   }
   
--- a/includes/search.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/search.php	Sun Dec 02 16:00:10 2007 -0500
@@ -19,7 +19,7 @@
  * @param array $arr2
  * @return array
  */
- 
+
 function enano_safe_array_merge($arr1, $arr2)
 {
   $arr3 = $arr1;
@@ -34,7 +34,7 @@
  * In Enano versions prior to 1.0.2, this class provided a search function that was keyword-based and allowed boolean searches. It was
  * cut from Coblynau and replaced with perform_search(), later in this file, because of speed issues. Now mostly deprecated. The only
  * thing remaining is the buildIndex function, which is still used by the path manager and the new search framework.
- * 
+ *
  * @package Enano
  * @subpackage Page management frontend
  * @license GNU General Public License <http://enanocms.org/Special:GNU_General_Public_License>
@@ -42,17 +42,17 @@
 
 class Searcher
 {
-  
+
   var $results;
   var $index;
   var $warnings;
   var $match_case = false;
-  
+
   function buildIndex($texts)
   {
     $this->index = Array();
     $stopwords = get_stopwords();
-    
+
     foreach($texts as $i => $l)
     {
       $seed = md5(microtime(true) . mt_rand());
@@ -119,15 +119,15 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   $warnings = array();
-  
+
   $query = parse_search_query($query, $warnings);
-  
+
   // Segregate search terms containing spaces
   $query_phrase = array(
     'any' => array(),
     'req' => array()
     );
-  
+
   foreach ( $query['any'] as $i => $_ )
   {
     $term =& $query['any'][$i];
@@ -141,7 +141,7 @@
   }
   unset($term);
   $query['any'] = array_values($query['any']);
-  
+
   foreach ( $query['req'] as $i => $_ )
   {
     $term =& $query['req'][$i];
@@ -154,12 +154,13 @@
   }
   unset($term);
   $query['req'] = array_values($query['req']);
-  
+
   $results = array();
   $scores = array();
-  
+  $ns_list = '(' . implode('|', array_keys($paths->nslist)) . ')';
+
   // FIXME: Update to use FULLTEXT algo when available.
-  
+
   // Build an SQL query to load from the index table
   if ( count($query['any']) < 1 && count($query['req']) < 1 && count($query_phrase['any']) < 1 && count($query_phrase['req']) < 1 )
   {
@@ -167,14 +168,14 @@
     $warnings[] = 'You need to have at least one keyword in your search query. Searching only for pages not containing a term is not allowed.';
     return array();
   }
-  
+
   //
   // STAGE 1
   // Get all possible result pages from the search index. Tally which pages have the most words, and later sort them by boolean relevance
   //
-  
+
   // Skip this if no indexable words are included
-  
+
   if ( count($query['any']) > 0 || count($query['req']) > 0 )
   {
     $where_any = array();
@@ -192,18 +193,18 @@
         $term = strtolower($term);
       $where_any[] = $term;
     }
-    
+
     $col_word = ( $case_sensitive ) ? 'word' : 'lcase(word)';
     $where_any = ( count($where_any) > 0 ) ? '( ' . $col_word . ' = \'' . implode('\' OR ' . $col_word . ' = \'', $where_any) . '\' )' : '';
-    
+
     // generate query
     // using a GROUP BY here ensures that the same word with a different case isn't counted as 2 words - it's all melted back
     // into one later in the processing stages
-    $group_by = ( $case_sensitive ) ? '' : ' GROUP BY lcase(word);';
-    $sql = "SELECT word, page_names FROM " . table_prefix . "search_index WHERE {$where_any}{$group_by}";
+    // $group_by = ( $case_sensitive ) ? '' : ' GROUP BY lcase(word);';
+    $sql = "SELECT word, page_names FROM " . table_prefix . "search_index WHERE {$where_any}";
     if ( !($q = $db->sql_unbuffered_query($sql)) )
       $db->_die('Error is in perform_search(), includes/search.php, query 1');
-    
+
     $word_tracking = array();
     if ( $row = $db->fetchrow() )
     {
@@ -211,11 +212,10 @@
       {
         // get page list
         $pages =& $row['page_names'];
-        $ns_list = '(' . implode('|', array_keys($paths->nslist)) . ')';
         if ( strpos($pages, ',') )
         {
           // the term occurs in more than one page
-          
+
           // Find page IDs that contain commas
           // This should never happen because commas are escaped by sanitize_page_id(). Nevertheless for compatibility with older
           // databases, and to alleviate the concerns of hackers, we'll accommodate for page IDs with commas here by checking for
@@ -235,62 +235,104 @@
             $prev = $i;
           }
           unset($match);
-          
+
           // Iterate through each of the results, assigning scores based on how many times the page has shown up.
           // This works because this phase of the search is strongly word-based not page-based. If a page shows up
           // multiple times while fetching the result rows from the search_index table, it simply means that page
           // contains more than one of the terms the user searched for.
-          
+
           foreach ( $matches as $match )
           {
-            if ( isset($scores[$match]) )
+            $word_cs = (( $case_sensitive ) ? $row['word'] : strtolower($row['word']));
+            if ( isset($word_tracking[$match]) && in_array($word_cs, $word_tracking[$match]) )
             {
-              $scores[$match]++;
+              continue;
+            }
+            if ( isset($word_tracking[$match]) )
+            {
+              if ( isset($word_tracking[$match]) )
+              {
+                $word_tracking[$match][] = ($word_cs);
+              }
             }
             else
             {
-              $scores[$match] = 1;
+              $word_tracking[$match] = array($word_cs);
             }
-            if ( isset($word_tracking[$match]) )
+            $inc = 1;
+
+            // Is this search term present in the page's title? If so, give extra points
+            preg_match("/^ns=$ns_list;pid=(.+)$/", $match, $piecesparts);
+            $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
+            if ( isset($paths->pages[$pathskey]) )
             {
-              $word_tracking[$match][] = $row['word'];
+              $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
+              if ( $test_func($paths->pages[$pathskey]['name'], $row['word']) || $test_func($paths->pages[$pathskey]['urlname_nons'], $row['word']) )
+              {
+                $inc = 1.5;
+              }
+            }
+            if ( isset($scores[$match]) )
+            {
+              $scores[$match] = $scores[$match] + $inc;
             }
             else
             {
-              $word_tracking[$match] = array($row['word']);
+              $scores[$match] = $inc;
             }
           }
         }
         else
         {
           // the term only occurs in one page
-          if ( isset($scores[$pages]) )
+          $word_cs = (( $case_sensitive ) ? $row['word'] : strtolower($row['word']));
+          if ( isset($word_tracking[$pages]) && in_array($word_cs, $word_tracking[$pages]) )
           {
-            $scores[$pages]++;
+            continue;
+          }
+          if ( isset($word_tracking[$pages]) )
+          {
+            if ( isset($word_tracking[$pages]) )
+            {
+              $word_tracking[$pages][] = ($word_cs);
+            }
           }
           else
           {
-            $scores[$pages] = 1;
+            $word_tracking[$pages] = array($word_cs);
           }
-          if ( isset($word_tracking[$pages]) )
+          $inc = 1;
+
+          // Is this search term present in the page's title? If so, give extra points
+          preg_match("/^ns=$ns_list;pid=(.+)$/", $pages, $piecesparts);
+          $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
+          if ( isset($paths->pages[$pathskey]) )
           {
-            $word_tracking[$pages][] = $row['word'];
+            $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
+            if ( $test_func($paths->pages[$pathskey]['name'], $row['word']) || $test_func($paths->pages[$pathskey]['urlname_nons'], $row['word']) )
+            {
+              $inc = 1.5;
+            }
+          }
+          if ( isset($scores[$pages]) )
+          {
+            $scores[$pages] = $scores[$pages] + $inc;
           }
           else
           {
-            $word_tracking[$pages] = array($row['word']);
+            $scores[$pages] = $inc;
           }
         }
       }
       while ( $row = $db->fetchrow() );
     }
     $db->free_result();
-  
+
     //
     // STAGE 2: FIRST ELIMINATION ROUND
     // Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
     //
-    
+
     foreach ( $query['req'] as $term )
     {
       foreach ( $word_tracking as $i => $page )
@@ -302,85 +344,108 @@
       }
     }
   }
-  
+
   //
   // STAGE 3: PHRASE SEARCHING
   // Use LIKE to find pages with specified phrases. We can do a super-picky single query without another elimination round because
   // at this stage we can search the full page_text column instead of relying on a word list.
   //
-  
+
   // We can skip this stage if none of these special terms apply
-  
+
   $text_col = ( $case_sensitive ) ? 'page_text' : 'lcase(page_text)';
-  
+  $name_col = ( $case_sensitive ) ? 'name' : 'lcase(name)';
+  $text_col_join = ( $case_sensitive ) ? 't.page_text' : 'lcase(t.page_text)';
+  $name_col_join = ( $case_sensitive ) ? 'p.name' : 'lcase(p.name)';
+
   if ( count($query_phrase['any']) > 0 || count($query_phrase['req']) > 0 )
   {
-  
+
     $where_any = array();
     foreach ( $query_phrase['any'] as $term )
     {
       $term = escape_string_like($term);
       if ( !$case_sensitive )
         $term = strtolower($term);
-      $where_any[] = $term;
+      $where_any[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
     }
-    
-    $where_any = ( count($where_any) > 0 ) ? "( $text_col LIKE '%" . implode("%' OR $text_col LIKE '%", $where_any) . "%' )" : '';
-    
-    // Also do required columns, but use AND to ensure that all required terms are included
+
+    $where_any = ( count($where_any) > 0 ) ? implode(" OR\n  ", $where_any) : '';
+
+    // Also do required terms, but use AND to ensure that all required terms are included
     $where_req = array();
     foreach ( $query_phrase['req'] as $term )
     {
       $term = escape_string_like($term);
       if ( !$case_sensitive )
         $term = strtolower($term);
-      $where_req[] = $term;
+      $where_req[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
     }
     $and_clause = ( $where_any != '' ) ? 'AND ' : '';
-    $where_req = ( count($where_req) > 0 ) ? "{$and_clause}$text_col LIKE '%" . implode("%' AND $text_col LIKE '%", $where_req) . "%'" : '';
-    
-    $sql = 'SELECT CONCAT("ns=",namespace,";pid=",page_id) AS id FROM ' . table_prefix . "page_text WHERE $where_any $where_req;";
+    $where_req = ( count($where_req) > 0 ) ? "{$and_clause}" . implode(" AND\n  ", $where_req) : '';
+
+    $sql = 'SELECT CONCAT("ns=",t.namespace,";pid=",t.page_id) AS id, p.name FROM ' . table_prefix . "page_text AS t\n"
+            . "  LEFT JOIN " . table_prefix . "pages AS p\n"
+            . "    ON ( p.urlname = t.page_id AND p.namespace = t.namespace )\n"
+            . "  WHERE\n  $where_any\n  $where_req;";
     if ( !($q = $db->sql_unbuffered_query($sql)) )
       $db->_die('Error is in perform_search(), includes/search.php, query 2. Parsed query dump follows:<pre>(indexable) ' . htmlspecialchars(print_r($query, true)) . '(non-indexable) ' . htmlspecialchars(print_r($query_phrase, true)) . '</pre>');
-    
+
     if ( $row = $db->fetchrow() )
     {
       do
       {
         $id =& $row['id'];
+        $inc = 1;
+
+        // Is this search term present in the page's title? If so, give extra points
+        preg_match("/^ns=$ns_list;pid=(.+)$/", $id, $piecesparts);
+        $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
+        if ( isset($paths->pages[$pathskey]) )
+        {
+          $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
+          foreach ( array_merge($query_phrase['any'], $query_phrase['req']) as $term )
+          {
+            if ( $test_func($paths->pages[$pathskey]['name'], $term) || $test_func($paths->pages[$pathskey]['urlname_nons'], $term) )
+            {
+              $inc = 1.5;
+              break;
+            }
+          }
+        }
         if ( isset($scores[$id]) )
         {
-          $scores[$id]++;
+          $scores[$id] = $scores[$id] + $inc;
         }
         else
         {
-          $scores[$id] = 1;
+          $scores[$id] = $inc;
         }
       }
       while ( $row = $db->fetchrow() );
     }
     $db->free_result();
   }
-  
+
   //
   // STAGE 4 - SELECT PAGE TEXT AND ELIMINATE NOTS
   // At this point, we have a complete list of all the possible pages. Now we want to obtain the page text, and within the same query
   // eliminate any terms that shouldn't be in there.
   //
-  
+
   // Generate master word list for the highlighter
   $word_list = array_values(array_merge($query['any'], $query['req'], $query_phrase['any'], $query_phrase['req']));
-  
+
   $text_where = array();
   foreach ( $scores as $page_id => $_ )
   {
     $text_where[] = $db->escape($page_id);
   }
   $text_where = '( CONCAT("ns=",t.namespace,";pid=",t.page_id) = \'' . implode('\' OR CONCAT("ns=",t.namespace,";pid=",t.page_id) = \'', $text_where) . '\' )';
-  
+
   if ( count($query['not']) > 0 )
     $text_where .= ' AND';
-  
+
   $where_not = array();
   foreach ( $query['not'] as $term )
   {
@@ -390,14 +455,14 @@
     $where_not[] = $term;
   }
   $where_not = ( count($where_not) > 0 ) ? "$text_col NOT LIKE '%" . implode("%' AND $text_col NOT LIKE '%", $where_not) . "%'" : '';
-  
+
   $sql = 'SELECT CONCAT("ns=",t.namespace,";pid=",t.page_id) AS id, t.page_id, t.namespace, CHAR_LENGTH(t.page_text) AS page_length, t.page_text, p.name AS page_name FROM ' . table_prefix . "page_text AS t
             LEFT JOIN " . table_prefix . "pages AS p
               ON ( p.urlname = t.page_id AND p.namespace = t.namespace )
             WHERE $text_where $where_not;";
   if ( !($q = $db->sql_unbuffered_query($sql)) )
     $db->_die('Error is in perform_search(), includes/search.php, query 3');
-  
+
   $page_data = array();
   if ( $row = $db->fetchrow() )
   {
@@ -405,7 +470,7 @@
     {
       $row['page_text'] = htmlspecialchars($row['page_text']);
       $row['page_name'] = htmlspecialchars($row['page_name']);
-      
+
       // Highlight results (this is wonderfully automated)
       $row['page_text'] = highlight_and_clip_search_result($row['page_text'], $word_list, $case_sensitive);
       if ( strlen($row['page_text']) > 250 && !preg_match('/^\.\.\.(.+)\.\.\.$/', $row['page_text']) )
@@ -413,7 +478,7 @@
         $row['page_text'] = substr($row['page_text'], 0, 150) . '...';
       }
       $row['page_name'] = highlight_search_result($row['page_name'], $word_list, $case_sensitive);
-      
+
       $page_data[$row['id']] = $row;
     }
     while ( $row = $db->fetchrow() );
@@ -424,27 +489,29 @@
   // STAGE 5 - SPECIAL PAGE TITLE SEARCH
   // Iterate through $paths->pages and check the titles for search terms. Score accordingly.
   //
-  
-  foreach ( $paths->pages as $page )
+
+  foreach ( $paths->pages as $id => $page )
   {
     if ( $page['namespace'] != 'Special' )
       continue;
+    if ( !is_int($id) )
+      continue;
     $idstring = 'ns=' . $page['namespace'] . ';pid=' . $page['urlname_nons'];
-    $any = array_merge($query['any'], $query_phrase['any']);
+    $any = array_values(array_unique(array_merge($query['any'], $query_phrase['any'])));
     foreach ( $any as $term )
     {
       if ( $case_sensitive )
       {
         if ( strstr($page['name'], $term) || strstr($page['urlname_nons'], $term) )
         {
-          ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
+          ( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
         }
       }
       else
       {
-        if ( strstr(strtolower($page['name']), strtolower($term)) || strstr(strtolower($page['urlname_nons']), strtolower($term)) )
+        if ( stristr($page['name'], $term) || stristr($page['urlname_nons'], $term) )
         {
-          ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
+          ( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
         }
       }
     }
@@ -466,7 +533,7 @@
   // STAGE 6 - SECOND ELIMINATION ROUND
   // Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
   //
-  
+
   $required = array_merge($query['req'], $query_phrase['req']);
   foreach ( $required as $term )
   {
@@ -478,54 +545,54 @@
       }
     }
   }
-  
+
   // At this point, all of our normal results are in. However, we can also allow plugins to hook into the system and score their own
   // pages and add text, etc. as necessary.
   // Plugins are COMPLETELY responsible for using the search terms and handling Boolean logic properly
-  
+
   $code = $plugins->setHook('search_global_inner');
   foreach ( $code as $cmd )
   {
     eval($cmd);
   }
-  
+
   // a marvelous debugging aid :-)
   // die('<pre>' . htmlspecialchars(print_r($page_data, true)) . '</pre>');
-  
+
   //
   // STAGE 7 - HIGHLIGHT, TRIM, AND SCORE RESULTS
   // We now have the complete results of the search. We need to trim text down to show only portions of the page containing search
   // terms, highlight any search terms within the page, and sort the final results array in descending order of score.
   //
-  
+
   // Sort scores array
   arsort($scores);
-  
+
   // Divisor for calculating relevance scores
-  $divisor = count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query_phrase['not']);
-  
+  $divisor = ( count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query_phrase['not']) ) * 1.5;
+
   foreach ( $scores as $page_id => $score )
   {
     if ( !isset($page_data[$page_id]) )
       // It's possible that $scores contains a score for a page that was later eliminated because it contained a disallowed term
       continue;
-      
+
     // Make a copy of the datum, then delete the original (it frees up a LOT of RAM)
     $datum = $page_data[$page_id];
     unset($page_data[$page_id]);
-    
+
     // This is an internal value used for sorting - it's no longer needed.
     unset($datum['id']);
-    
+
     // Calculate score
-    if ( $score > $divisor )
-      $score = $divisor;
+    // if ( $score > $divisor )
+    //   $score = $divisor;
     $datum['score'] = round($score / $divisor, 2) * 100;
-    
+
     // Store it in our until-now-unused results array
     $results[] = $datum;
   }
-  
+
   // Our work here is done. :-D
   return $results;
 }
@@ -558,7 +625,7 @@
     $chr = $query{$i};
     $prev = ( $i > 0 ) ? $query{ $i - 1 } : '';
     $next = ( ( $i + 1 ) < strlen($query) ) ? $query{ $i + 1 } : '';
-    
+
     if ( ( $chr == ' ' && !$in_quote ) || ( $i + 1 == strlen ( $query ) ) )
     {
       $len = ( $next == '' ) ? $i + 1 : $i - $start_term;
@@ -566,36 +633,36 @@
       $terms[] = $word;
       $start_term = $i + 1;
     }
-    
+
     elseif ( $chr == '"' && $in_quote && $prev != '\\' )
     {
       $word = substr ( $query, $start_term, $i - $start_term + 1 );
       $start_pos = ( $next == ' ' ) ? $i + 2 : $i + 1;
       $in_quote = false;
     }
-    
+
     elseif ( $chr == '"' && !$in_quote )
     {
       $in_quote = true;
       $start_pos = $i;
     }
-    
+
   }
-  
+
   $ticker = 0;
-  
+
   foreach ( $terms as $element => $__unused )
   {
     $atom =& $terms[$element];
-    
+
     $ticker++;
-    
+
     if ( $ticker == 20 )
     {
       $warnings[] = 'Some of your search terms were excluded because searches are limited to 20 terms to prevent excessive server load.';
       break;
     }
-    
+
     if ( substr ( $atom, 0, 2 ) == '+"' && substr ( $atom, ( strlen ( $atom ) - 1 ), 1 ) == '"' )
     {
       $word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
@@ -732,11 +799,11 @@
     if(!empty($words[$i]))
       $words2[] = preg_quote($words[$i]);
   }
-  
+
   $flag = ( $case_sensitive ) ? '' : 'i';
   $regex = '/(' . implode('|', $words2) . ')/' . $flag;
   $pt = preg_replace($regex, '<highlight>\\1</highlight>', $pt);
-  
+
   return $pt;
 }
 
@@ -752,11 +819,11 @@
 function highlight_and_clip_search_result($pt, $words, $case_sensitive = false)
 {
   $cut_off = false;
-  
+
   $space_chars = Array("\t", "\n", "\r", " ");
-  
+
   $pt = highlight_search_result($pt, $words, $case_sensitive);
-  
+
   foreach ( $words as $word )
   {
     // Boldface searched words
@@ -789,9 +856,9 @@
               }
             }
             $mid_chunk = substr($pt, ( $i - 75 ), 75);
-            
+
             $clipped = '...' . $final_chunk . $mid_chunk . $chunk2;
-            
+
             $chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
             $final_chunk = $chunk;
             for ( $j = 0; $j < strlen($chunk); $j++ )
@@ -802,19 +869,19 @@
                 break;
               }
             }
-            
+
             $end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
-            
+
             $clipped .= $end_chunk . $final_chunk . '...';
-            
+
             $pt = $clipped;
           }
           else if ( strlen($pt) > 200 )
           {
             $mid_chunk = substr($pt, ( $i - 75 ), 75);
-            
+
             $clipped = $chunk1 . $chunk2;
-            
+
             $chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
             $final_chunk = $chunk;
             for ( $j = 0; $j < strlen($chunk); $j++ )
@@ -825,13 +892,13 @@
                 break;
               }
             }
-            
+
             $end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
-            
+
             $clipped .= $end_chunk . $final_chunk . '...';
-            
+
             $pt = $clipped;
-            
+
           }
           break 2;
         }
@@ -853,7 +920,7 @@
   static $stopwords;
   if ( is_array($stopwords) )
     return $stopwords;
-  
+
   $stopwords = array('a\'s', 'able', 'after', 'afterwards', 'again',
                      'against', 'ain\'t', 'all', 'almost', 'alone', 'along', 'already', 'also', 'although', 'always',
                      'am', 'among', 'amongst', 'an', 'and', 'another', 'any', 'anybody', 'anyhow', 'anyone', 'anything', 'anyway',
@@ -889,18 +956,17 @@
                      'shouldn\'t', 'since', 'six', 'so', 'some', 'somebody', 'somehow', 'someone', 'something', 'sometime', 'sometimes',
                      'somewhat', 'somewhere', 'soon', 'sorry', 'specified', 'specify', 'specifying', 'still', 'sub', 'such', 'sup',
                      'sure', 't\'s', 'take', 'taken', 'tell', 'tends', 'th', 'than', 'thank', 'thanks', 'thanx', 'that', 'that\'s',
-                     'thats', 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'thence', 'there', 'there\'s', 'thereafter',
+                     'thats', 'the', 'their', 'theirs', 'them', 'then', 'thence', 'there', 'there\'s', 'thereafter',
                      'thereby', 'therefore', 'therein', 'theres', 'thereupon', 'these', 'they', 'they\'d', 'they\'ll', 'they\'re',
                      'they\'ve', 'think', 'third', 'this', 'thorough', 'thoroughly', 'those', 'though', 'three', 'through', 'throughout',
                      'thru', 'thus', 'to', 'together', 'too', 'took', 'toward', 'towards', 'tried', 'tries', 'truly', 'try', 'trying',
-                     'twice', 'two', 'un', 'under', 'unfortunately', 'unless', 'unlikely', 'until', 'unto', 'up', 'upon', 'us', 'use',
-                     'used', 'useful', 'uses', 'using', 'usually', 'value', 'various', 'very', 'via', 'viz', 'vs', 'want', 'wants',
+                     'twice', 'two', 'un', 'under', 'unfortunately', 'unless', 'unlikely', 'until', 'unto', 'upon', 'use',
+                     'used', 'useful', 'uses', 'using', 'usually', 'value', 'various', 'very',
                      'was', 'wasn\'t', 'way', 'we', 'we\'d', 'we\'ll', 'we\'re', 'we\'ve', 'welcome', 'well', 'went', 'were', 'weren\'t',
                      'what', 'what\'s', 'whatever', 'when', 'whence', 'whenever', 'where', 'where\'s', 'whereafter', 'whereas',
-                     'whereby', 'wherein', 'whereupon', 'wherever', 'whether', 'which', 'while', 'whither', 'who', 'who\'s', 'whoever',
-                     'whole', 'whom', 'whose', 'why', 'will', 'willing', 'wish', 'with', 'within', 'without', 'won\'t', 'wonder',
-                     'would', 'would', 'wouldn\'t', 'yes', 'yet', 'you', 'you\'d', 'you\'ll', 'you\'re', 'you\'ve', 'your', 'yours',
-                     'yourself', 'yourselves', 'zero');
+                     'which', 'while', 'who', 'who\'s', 'whole', 'whom', 'whose', 'why', 'will', 'willing', 'wish', 'with', 'within',
+                     'without', 'won\'t', 'wonder', 'would', 'would', 'wouldn\'t', 'yes', 'yet', 'you', 'you\'d', 'you\'ll', 'you\'re',
+                     'you\'ve', 'your', 'yours', 'zero');
   return $stopwords;
 }
 
--- a/includes/sessions.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/sessions.php	Sun Dec 02 16:00:10 2007 -0500
@@ -150,7 +150,6 @@
    * @var string
    */
    
-  //var $valid_username = '([A-Za-z0-9 \!\@\(\)-]+)';
   var $valid_username = '([^<>&\?\'"%\n\r\t\a\/]+)';
    
   /**
@@ -261,7 +260,7 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
-    if ( defined('IN_ENANO_INSTALL') )
+    if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
     {
       @include(ENANO_ROOT.'/config.new.php');
     }
@@ -282,7 +281,7 @@
       {
         // Generate and stash a private key
         // This should only happen during an automated silent gradual migration to the new encryption platform.
-        $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+        $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
         $this->private_key = $aes->gen_readymade_key();
         
         $config = file_get_contents(ENANO_ROOT.'/config.php');
@@ -511,7 +510,10 @@
           else
           {
             $key = strrev($_REQUEST['auth']);
-            $super = $this->validate_session($key);
+            if ( !empty($key) && ( strlen($key) / 2 ) % 4 == 0 )
+            {
+              $super = $this->validate_session($key);
+            }
           }
           if(is_array($super))
           {
@@ -529,13 +531,13 @@
     if(!$this->compat)
     {
       // init groups
-      $q = $this->sql('SELECT g.group_name,g.group_id,m.is_mod FROM '.table_prefix.'groups AS g
-          LEFT JOIN '.table_prefix.'group_members AS m
-            ON g.group_id=m.group_id
-          WHERE ( m.user_id='.$this->user_id.' 
-            OR g.group_name=\'Everyone\')
-            ' . ( enano_version() == '1.0RC1' ? '' : 'AND ( m.pending != 1 OR m.pending IS NULL )' ) . '
-          ORDER BY group_id ASC;'); // Make sure "Everyone" comes first so the permissions can be overridden
+      $q = $this->sql('SELECT g.group_name,g.group_id,m.is_mod FROM '.table_prefix.'groups AS g' . "\n"
+        . '  LEFT JOIN '.table_prefix.'group_members AS m' . "\n"
+        . '    ON g.group_id=m.group_id' . "\n"
+        . '  WHERE ( m.user_id='.$this->user_id.'' . "\n" 
+        . '    OR g.group_name=\'Everyone\')' . "\n"
+        . '    ' . ( enano_version() == '1.0RC1' ? '' : 'AND ( m.pending != 1 OR m.pending IS NULL )' ) . '' . "\n"
+        . '  ORDER BY group_id ASC;'); // Make sure "Everyone" comes first so the permissions can be overridden
       if($row = $db->fetchrow())
       {
         do {
@@ -618,16 +620,27 @@
     }
     
     // Instanciate the Rijndael encryption object
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     
     // Fetch our decryption key
     
     $aes_key = $this->fetch_public_key($aes_key_id);
-    if(!$aes_key)
+    if ( !$aes_key )
+    {
+      // It could be that our key cache is full. If it seems larger than 65KB, clear it
+      if ( strlen(getConfig('login_key_cache')) > 65000 )
+      {
+        setConfig('login_key_cache', '');
+        return array(
+          'success' => false,
+          'error' => 'key_not_found_cleared',
+          );
+      }
       return array(
         'success' => false,
         'error' => 'key_not_found'
         );
+    }
     
     // Convert the key to a binary string
     $bin_key = hexdecode($aes_key);
@@ -862,7 +875,7 @@
     }
     
     // Instanciate the Rijndael encryption object
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     
     // Initialize our success switch
     $success = false;
@@ -1059,7 +1072,7 @@
     $session_key = "u=$username;p=$passha1;s=$salt";
     
     // Encrypt the key
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     $session_key = $aes->encrypt($session_key, $this->private_key, ENC_HEX);
     
     // If we're registering an elevated-privilege key, it needs to be on GET
@@ -1172,7 +1185,8 @@
     
     if ( !$decrypted_key )
     {
-      die_semicritical('AES encryption error', '<p>Something went wrong during the AES decryption process.</p><pre>'.print_r($decrypted_key, true).'</pre>');
+      // die_semicritical('AES encryption error', '<p>Something went wrong during the AES decryption process.</p><pre>'.print_r($decrypted_key, true).'</pre>');
+      return false;
     }
     
     $n = preg_match('/^u='.$this->valid_username.';p=([A-Fa-f0-9]+?);s=([A-Fa-f0-9]+?)$/', $decrypted_key, $keydata);
@@ -1183,16 +1197,19 @@
     }
     $keyhash = md5($key);
     $salt = $db->escape($keydata[3]);
-    $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,u.user_lang,x.* FROM '.table_prefix.'session_keys AS k
-                               LEFT JOIN '.table_prefix.'users AS u
-                                 ON ( u.user_id=k.user_id )
-                               LEFT JOIN '.table_prefix.'users_extra AS x
-                                 ON ( u.user_id=x.user_id OR x.user_id IS NULL )
-                               LEFT JOIN '.table_prefix.'privmsgs AS p
-                                 ON ( p.message_to=u.username AND p.message_read=0 )
-                               WHERE k.session_key=\''.$keyhash.'\'
-                                 AND k.salt=\''.$salt.'\'
-                               GROUP BY u.user_id;');
+    // using a normal call to $db->sql_query to avoid failing on errors here
+    $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,' . "\n"
+                             . '    u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,' . "\n"
+                             . '    x.* FROM '.table_prefix.'session_keys AS k' . "\n"
+                             . '  LEFT JOIN '.table_prefix.'users AS u' . "\n"
+                             . '    ON ( u.user_id=k.user_id )' . "\n"
+                             . '  LEFT JOIN '.table_prefix.'users_extra AS x' . "\n"
+                             . '    ON ( u.user_id=x.user_id OR x.user_id IS NULL )' . "\n"
+                             . '  LEFT JOIN '.table_prefix.'privmsgs AS p' . "\n"
+                             . '    ON ( p.message_to=u.username AND p.message_read=0 )' . "\n"
+                             . '  WHERE k.session_key=\''.$keyhash.'\'' . "\n"
+                             . '    AND k.salt=\''.$salt.'\'' . "\n"
+                             . '  GROUP BY u.user_id;');
     if ( !$query )
     {
       $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms FROM '.table_prefix.'session_keys AS k
@@ -1337,7 +1354,7 @@
     $oid = $this->user_id;
     if($level > USER_LEVEL_CHPREF)
     {
-      $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+      $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
       if(!$this->user_logged_in || $this->auth_level < USER_LEVEL_MOD)
       {
         return 'success';
@@ -1416,7 +1433,7 @@
    
   function rijndael_genkey()
   {
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     $key = $aes->gen_readymade_key();
     $keys = getConfig('login_key_cache');
     if(is_string($keys))
@@ -1434,7 +1451,7 @@
    
   function dss_rand()
   {
-    $aes = new AESCrypt();
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     $random = $aes->randkey(128);
     unset($aes);
     return md5(microtime() . $random);
@@ -1551,7 +1568,7 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     $col_reason = ( $this->compat ) ? '"No reason entered (session manager is in compatibility mode)" AS reason' : 'reason';
-    $is_banned = false;
+    $banned = false;
     if ( $this->user_logged_in )
     {
       // check by IP, email, and username
@@ -1646,7 +1663,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     // Initialize AES
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     
     if(!preg_match('#^'.$this->valid_username.'$#', $username)) return 'The username you chose contains invalid characters.';
     $username = str_replace('_', ' ', $username);
@@ -2002,7 +2019,7 @@
    
   function register_temp_password($user_id, $password)
   {
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     $temp_pass = $aes->encrypt($password, $this->private_key, ENC_HEX);
     $this->sql('UPDATE '.table_prefix.'users SET temp_password=\'' . $temp_pass . '\',temp_password_time='.time().' WHERE user_id='.intval($user_id).';');
   }
@@ -2113,7 +2130,7 @@
     if(intval($user_id) < 1) $errors[] = 'SQL injection attempt';
     
     // Instanciate the AES encryption class
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     
     // If all of our input vars are false, then we've effectively done our job so get out of here
     if($username === false && $password === false && $email === false && $realname === false && $signature === false && $user_level === false)
@@ -2407,7 +2424,9 @@
     $this->acl_defaults_used = $this->perms;
     
     // Fetch sitewide defaults from the permissions table
-    $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE page_id IS NULL AND namespace IS NULL AND ( ';
+    $bs = 'SELECT rules, target_type, target_id FROM '.table_prefix.'acl' . "\n"
+             . '  WHERE page_id IS NULL AND namespace IS NULL AND' . "\n"
+             . '  ( ';
     
     $q = Array();
     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
@@ -2418,7 +2437,7 @@
         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       }
     }
-    $bs .= implode(' OR ', $q) . ' ) ORDER BY target_type ASC, target_id ASC;';
+    $bs .= implode(" OR \n    ", $q) . " ) \n  ORDER BY target_type ASC, target_id ASC;";
     $q = $this->sql($bs);
     if ( $row = $db->fetchrow() )
     {
@@ -2462,7 +2481,7 @@
     }
     // The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
     // permissions to override group permissions.
-    $bs .= implode(' OR ', $q) . ' ) AND (' . $pg_info . ' ( page_id=\''.$db->escape($paths->cpage['urlname_nons']).'\' AND namespace=\''.$db->escape($paths->namespace).'\' ) )     
+    $bs .= implode(" OR\n    ", $q) . " )\n  AND (" . $pg_info . ' ( page_id=\''.$db->escape($paths->cpage['urlname_nons']).'\' AND namespace=\''.$db->escape($paths->namespace).'\' ) )     
       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
     $q = $this->sql($bs);
     if ( $row = $db->fetchrow() )
@@ -2904,7 +2923,8 @@
     }
     
     // Build a query to grab ACL info
-    $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE ( ';
+    $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE ' . "\n"
+          . '  ( ';
     $q = Array();
     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$session->user_id.' )';
     if(count($session->groups) > 0)
@@ -2916,7 +2936,7 @@
     }
     // The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
     // permissions to override group permissions.
-    $bs .= implode(' OR ', $q) . ' ) AND (' . $pg_info . ' page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' )     
+    $bs .= implode(" OR\n    ", $q) . ' ) AND (' . $pg_info . ' page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' )     
       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
     $q = $session->sql($bs);
     if ( $row = $db->fetchrow() )
--- a/includes/tagcloud.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/tagcloud.php	Sun Dec 02 16:00:10 2007 -0500
@@ -35,7 +35,7 @@
    * @param array Optional. An initial list of words, just a plain old array.
    */
   
-  function __construct($words)
+  function __construct($words = array())
   {
     if ( count($words) > 0 )
     {
--- a/includes/template.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/includes/template.php	Sun Dec 02 16:00:10 2007 -0500
@@ -775,7 +775,8 @@
       'STYLE_ID'=>$this->style,
       'JS_DYNAMIC_VARS'=>$js_dynamic,
       'UNREAD_PMS'=>$session->unread_pms,
-      'URL_ABOUT_ENANO' => makeUrlNS('Special', 'About_Enano', '', true)
+      'URL_ABOUT_ENANO' => makeUrlNS('Special', 'About_Enano', '', true),
+      'REPORT_URI' => makeUrl($paths->page, 'do=sql_report', true)
       );
     
     foreach ( $paths->nslist as $ns_id => $ns_prefix )
@@ -1076,7 +1077,7 @@
           continue;
         
         $tag_complete = <<<TPLCODE
-        ';
+';
         /* START OF CONDITION: $type ($test) */
         if ( $cond )
         {
@@ -1666,7 +1667,8 @@
     if ( !$this->fetch_block('Links') )
       $this->initLinksWidget();
     
-    $q = $db->sql_query('SELECT item_id,sidebar_id,block_name,block_type,block_content FROM '.table_prefix.'sidebar WHERE item_enabled=1 ORDER BY sidebar_id ASC, item_order ASC;');
+    $q = $db->sql_query('SELECT item_id,sidebar_id,block_name,block_type,block_content FROM '.table_prefix.'sidebar' . "\n"
+                           . '  WHERE item_enabled=1 ORDER BY sidebar_id ASC, item_order ASC;');
     if(!$q) $db->_die('The sidebar text data could not be selected.');
     
     $vars = $this->extract_vars('elements.tpl');
@@ -1855,7 +1857,8 @@
  * "critical error" messages. ** REQUIRES ** the Oxygen theme.
  */
 
-class template_nodb {
+class template_nodb
+{
   var $fading_button, $tpl_strings, $tpl_bool, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list;
   function __construct() {
     
@@ -1967,6 +1970,7 @@
       'STYLE_ID'=>$this->style,
       'JS_DYNAMIC_VARS'=>$js_dynamic,
       'SIDEBAR_RIGHT'=>'',
+      'REPORT_URI' => ''
       );
     $this->tpl_strings = array_merge($tpl_strings, $this->tpl_strings);
     
--- a/index.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/index.php	Sun Dec 02 16:00:10 2007 -0500
@@ -54,7 +54,7 @@
   switch($_GET['do'])
   {
     default:
-      die_friendly('Invalid action', '<p>The action "'.$_GET['do'].'" is not defined. Return to <a href="'.makeUrl($paths->page).'">viewing this page\'s text</a>.</p>');
+      die_friendly('Invalid action', '<p>The action "'.htmlspecialchars($_GET['do']).'" is not defined. Return to <a href="'.makeUrl($paths->page).'">viewing this page\'s text</a>.</p>');
       break;
     case 'view':
       // echo PageUtils::getpage($paths->page, true, ( (isset($_GET['oldid'])) ? $_GET['oldid'] : false ));
@@ -118,10 +118,18 @@
       $template->footer();
       break;
     case 'edit':
-      if(isset($_POST['_cancel'])) { header('Location: '.makeUrl($paths->page)); echo '<html><head><title>Redirecting...</title></head><body>If you haven\'t been redirected yet, <a href="'.makeUrl($paths->page).'">click here</a>.'; break; }
-      if(isset($_POST['_save'])) {
+      if(isset($_POST['_cancel']))
+      {
+        redirect(makeUrl($paths->page), '', '', 0);
+        break;
+      }
+      if(isset($_POST['_save']))
+      {
         $e = PageUtils::savepage($paths->cpage['urlname_nons'], $paths->namespace, $_POST['page_text'], $_POST['edit_summary'], isset($_POST['minor']));
-        header('Location: '.makeUrl($paths->page)); echo '<html><head><title>Redirecting...</title></head><body>If you haven\'t been redirected yet, <a href="'.makeUrl($paths->page).'">click here</a>.'; break;
+        if ( $e == 'good' )
+        {
+          redirect(makeUrl($paths->page), $lang->get('editor_msg_save_success_title'), $lang->get('editor_msg_save_success_body'), 3);
+        }
       }
       $template->header();
       if(isset($_POST['_preview']))
@@ -250,7 +258,7 @@
       if(!empty($_POST['newname']))
       {
         $r = PageUtils::rename($paths->cpage['urlname_nons'], $paths->namespace, $_POST['newname']);
-        die_friendly('Page renamed', '<p>'.nl2br($r).' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>');
+        die_friendly($lang->get('page_rename_success_title'), '<p>'.nl2br($r).' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>');
       }
       $template->header();
       ?>
@@ -434,6 +442,17 @@
       $data = ( isset($_POST['data']) ) ? $_POST['data'] : Array('mode' => 'listgroups');
       PageUtils::aclmanager($data);
       break;
+    case 'sql_report':
+      $rev_id = ( (isset($_GET['oldid'])) ? intval($_GET['oldid']) : 0 );
+      $page = new PageProcessor( $paths->cpage['urlname_nons'], $paths->namespace, $rev_id );
+      $page->send_headers = true;
+      $pagepass = ( isset($_REQUEST['pagepass']) ) ? sha1($_REQUEST['pagepass']) : '';
+      $page->password = $pagepass;
+      $page->send(true);
+      ob_end_clean();
+      ob_start();
+      $db->sql_report();
+      break;
   }
   
   //
@@ -443,7 +462,7 @@
   {
     // Load up the HTML
     $html = ob_get_contents();
-    ob_end_clean();
+    @ob_end_clean();
     
     $html = aggressive_optimize_html($html);
     
--- a/install.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/install.php	Sun Dec 02 16:00:10 2007 -0500
@@ -338,7 +338,7 @@
   if ( $act_get )
     return $decrypted_pass;
   
-  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   
   if ( !empty($_POST['crypt_data']) )
   {
@@ -368,7 +368,7 @@
   if ( $act_get )
     return $key;
   
-  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   $key = $aes->gen_readymade_key();
   return true;
 }
@@ -381,7 +381,7 @@
   
   $admin_pass = stg_decrypt_admin_pass(true);
   $key = stg_generate_aes_key(true);
-  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   $key = $aes->hextostring($key);
   $admin_pass = $aes->encrypt($admin_pass, $key, ENC_HEX);
   
@@ -461,7 +461,7 @@
   {
     $admin_pass = stg_decrypt_admin_pass(true);
     $key = stg_generate_aes_key(true);
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(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']);
@@ -826,6 +826,10 @@
         $title = $lang->get('pophelp_admin_embed_php_title');
         $content = $lang->get('pophelp_admin_embed_php_body');
         break;
+      case 'url_schemes':
+        $title = $lang->get('pophelp_url_schemes_title');
+        $content = $lang->get('pophelp_url_schemes_body');
+        break;
       default:
         $title = 'Invalid topic';
         $content = 'Invalid help topic.';
@@ -1021,6 +1025,7 @@
     <table border="0" cellspacing="0" cellpadding="0">
     <?php
     run_test('return version_compare(\'4.3.0\', PHP_VERSION, \'<\');', $lang->get('sysreqs_req_php'), $lang->get('sysreqs_req_desc_php') );
+    run_test('return version_compare(\'5.2.0\', PHP_VERSION, \'<\');', $lang->get('sysreqs_req_php5'), $lang->get('sysreqs_req_desc_php5'), true);
     run_test('return function_exists(\'mysql_connect\');', $lang->get('sysreqs_req_mysql'), $lang->get('sysreqs_req_desc_mysql') );
     run_test('return @ini_get(\'file_uploads\');', $lang->get('sysreqs_req_uploads'), $lang->get('sysreqs_req_desc_uploads') );
     run_test('return is_apache();', $lang->get('sysreqs_req_apache'), $lang->get('sysreqs_req_desc_apache'), true);
@@ -1524,7 +1529,8 @@
           <td colspan="2">
             <input type="radio" <?php if(!is_apache()) echo 'checked="checked" '; ?>name="urlscheme" value="ugly" id="ugly"  />  <label for="ugly"><?php echo $lang->get('website_field_urlscheme_ugly'); ?></label><br />
             <input type="radio" <?php if(is_apache()) echo 'checked="checked" '; ?>name="urlscheme" value="short" id="short" />  <label for="short"><?php echo $lang->get('website_field_urlscheme_short'); ?></label><br />
-            <input type="radio" name="urlscheme" value="tiny" id="petite">  <label for="petite"><?php echo $lang->get('website_field_urlscheme_tiny'); ?></label>
+            <input type="radio" name="urlscheme" value="tiny" id="petite">  <label for="petite"><?php echo $lang->get('website_field_urlscheme_tiny'); ?></label><br />
+            <small><a href="install.php?mode=pophelp&amp;topic=url_schemes" onclick="window.open(this.href, 'pophelpwin', 'width=550,height=400,status=no,toolbars=no,toolbar=no,address=no,scroll=yes'); return false;"><?php echo $lang->get('website_field_urlscheme_helplink'); ?></a></small>
           </td>
         </tr>
       </table>
@@ -1554,7 +1560,7 @@
     }
     unset($_POST['_cont']);
     require('config.new.php');
-    $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+    $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
     if ( isset($crypto_key) )
     {
       $cryptkey = $crypto_key;
@@ -1579,7 +1585,7 @@
       {
         var frm = document.forms.login;
         ret = true;
-        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\' )
+        if ( frm.admin_user.value.match(/^([^<>&\?\'"%\/]+)$/) && !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\';
         }
@@ -1597,7 +1603,7 @@
           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\';
         }
--- a/language/english/enano.json	Sun Dec 02 15:27:21 2007 -0500
+++ b/language/english/enano.json	Sun Dec 02 16:00:10 2007 -0500
@@ -66,6 +66,7 @@
       login_success_title: 'Login successful',
       login_success_body: 'You have successfully logged into the %config.site_name% site as "%username%". Redirecting to %redir_target%...',
       login_success_body_mainpage: 'the main page',
+      login_success_short: 'Success.',
       
       login_ajax_fetching_key: 'Fetching an encryption key...',
       login_ajax_prompt_title: 'Please enter your username and password to continue.',
@@ -76,6 +77,7 @@
       login_ajax_loggingin: 'Logging in...',
       
       err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.',
+      err_key_not_found_cleared: 'It seems that the list of encryption keys used for login information has reached its maximum length, thus preventing new keys from being inserted. The list has been automatically cleared. Please try logging in again; if you are still unable to log in, please contact the site administration.',
       err_key_wrong_length: 'The encryption key was the wrong length.',
       err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.',
       err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.',
@@ -303,6 +305,8 @@
       btn_cancel: 'Cancel and return to page',
       btn_closeviewer: 'Close viewer',
       preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
+      msg_save_success_title: 'Changes saved',
+      msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
     },
     history: {
       summary_clearlogs: 'Automatic backup created when logs were purged',
@@ -349,6 +353,7 @@
       rename_err_need_name: 'Error: you must enter a new name for this page.',
       rename_lbl: 'Please enter a new name for this page:',
       rename_btn_submit: 'Rename page',
+      rename_success_title: 'Page renamed',
       
       flushlogs_warning_stern: '<h3>You are about to <span style="color: red;">destroy</span> all logged edits and actions on this page.</h3><p>Unlike deleting or editing this page, this action is <u>not reversible</u>! You should only do this if you are desparate for database space.</p><p>Do you really want to continue?</p>',
       flushlogs_btn_submit: 'Flush logs',
--- a/language/english/install.json	Sun Dec 02 15:27:21 2007 -0500
+++ b/language/english/install.json	Sun Dec 02 16:00:10 2007 -0500
@@ -61,6 +61,7 @@
       heading: 'Checking your server',
       blurb: 'Enano has several requirements that must be met before it can be installed. If all is good then note any warnings and click Continue below.',
       req_php: 'PHP Version >=4.3.0',
+      req_php5: 'PHP 5.2.0 or later',
       req_mysql: 'MySQL extension for PHP',
       req_uploads: 'File upload support',
       req_apache: 'Apache HTTP Server',
@@ -70,6 +71,7 @@
       req_fileswriteable: 'File uploads directory writable',
       
       req_desc_php: 'It seems that the version of PHP that your server is running is too old to support Enano properly. If this is your server, please upgrade to the most recent version of PHP, remembering to use the --with-mysql configure option if you compile it yourself. If this is not your server, please contact your webhost and ask them if it would be possible to upgrade PHP. If this is not possible, you will need to switch to a different webhost in order to use Enano.',
+      req_desc_php5: 'Your server does not have support for PHP 5.2.0. While you may continue installing Enano, please be warned that as of December 31, 2007, all support for Enano on PHP 4 servers is discontinued. If you have at least PHP 5.0.0, support will still be available, but there are many security problems in PHP versions under 5.2.0 that Enano cannot effectively prevent.',
       req_desc_mysql: 'It seems that your PHP installation does not have the MySQL extension enabled. If this is your own server, you may need to just enable the "libmysql.so" extension in php.ini. If you do not have the MySQL extension installed, you will need to either use your distribution\'s package manager to install it, or you will have to compile PHP from source. If you compile PHP from source, please remember to use the "--with-mysql" configure option, and you will have to have the MySQL development files installed (they usually are). If this is not your server, please contact your hosting company and ask them to install the PHP MySQL extension.',
       req_desc_uploads: '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".',
       req_desc_apache: '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.',
@@ -151,6 +153,7 @@
       field_urlscheme_ugly: 'Standard URLs - compatible with any web server (www.example.com/index.php?title=Page_name)',
       field_urlscheme_short: 'Short URLs - requires Apache with a PHP module (www.example.com/index.php/Page_name)',
       field_urlscheme_tiny: 'Tiny URLs - requires Apache on Linux/Unix/BSD with PHP module and mod_rewrite enabled (www.example.com/Page_name)',
+      field_urlscheme_helplink: 'Which URL scheme should I choose?',
       
       objective_verify: 'Verify that your site information is correct. Again, all of the above settings can be changed from the administration panel.',
     },
@@ -253,6 +256,23 @@
                                 embed Javascript and arbitrary HTML and CSS.</p>
                              <p>If you don\'t have experience coding in PHP, you can safely disable this option. You may change this at any time
                                 using the ACL editor by selecting the Administrators group and This Entire Website under the scope selection.</p>',
+      url_scheme_title: 'URL schemes',
+      url_scheme_body: '<p>The URL scheme allows you to decide how the URLs to your Enano pages will look.</p>
+                        <p>The first option (Standard URLs) works on any web server. You should select it if your server doesn\'t run Apache, or
+                           if you are at all unsure of your server\'s configuration. With this scheme, URLs at your site will look like <tt>
+                           http://yoursite.com/path-to-enano/index.php/Main_Page</tt>.</p>
+                        <p>The second option, Small URLs, will be selected by default if Enano detects Apache. Small URLs are more friendly towards
+                           search engines, but they don\'t work on very many non-Apache servers, or if PHP is set up through CGI on your server. Many
+                           free and low-cost web hosts will configure PHP through CGI in order to keep your user account as the owner of any files that
+                           Enano generates. With this scheme, URLs at your site will look like <tt>http://yoursite.com/path-to-enano/index.php/Main_Page</tt>.
+                           </p>
+                        <p>The last option, Tiny URLs, is the most friendly URL scheme for search engines, because your URLs won\'t have any special characters
+                           at all in them. However, this only works if your webhost has configured Apache with support for mod_rewrite. Most of the time if your
+                           host supports this you will see a listing for it in their feature matrix. None of the popular Linux distributions (such as Ubuntu,
+                           Debian, Red Hat Enterprise Linux&trade;, Fedora, openSUSE&trade;, or CentOS) come with mod_rewrite enabled, so if you run a
+                           home-brew server, you should consult your distribution\'s documentation for enabling mod_rewrite before selecting this option.
+                           With this scheme, URLs at your site will look like <tt>http://yoursite.com/path-to-enano/Main_Page</tt>.</p>
+                           </p>',
       btn_close_window: 'Close window',
     }
   }
--- a/plugins/SpecialAdmin.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialAdmin.php	Sun Dec 02 16:00:10 2007 -0500
@@ -818,6 +818,7 @@
     $t = -1;
     $cl = 'row1';
     echo "\n".'    <div class="tblholder">'."\n".'      <table cellspacing="1" cellpadding="2" style="margin: 0; padding: 0;" border="0">'."\n".'        <tr>'."\n        ";
+    ksort($mime_types);
     foreach($mime_types as $e => $m)
     {
       $c++;
--- a/plugins/SpecialPageFuncs.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialPageFuncs.php	Sun Dec 02 16:00:10 2007 -0500
@@ -172,6 +172,7 @@
     exit;
   }
   $template->header();
+  /*
   if ( !$session->get_permissions('create_page') )
   {
     echo 'Wiki mode is disabled, only admins can create pages.';
@@ -181,6 +182,7 @@
     
     exit;
   }
+  */
   echo RenderMan::render('Using the form below you can create a page.');
   ?>
   <form action="" method="post">
--- a/plugins/SpecialSearch.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialSearch.php	Sun Dec 02 16:00:10 2007 -0500
@@ -42,7 +42,8 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
   if(!$session->get_permissions('mod_misc')) die_friendly('Unauthorized', '<p>You need to be an administrator to rebuild the search index</p>');
   $template->header();
-  if($paths->rebuild_search_index())
+  @set_time_limit(0);
+  if($paths->rebuild_search_index(true))
     echo '<p>Index rebuilt!</p>';
   else
     echo '<p>Index was not rebuilt due to an error.';
@@ -94,7 +95,7 @@
   
   $qin = ( isset($q) ) ? str_replace('"', '\"', htmlspecialchars($q)) : '';
   $search_form = '<form action="' . makeUrlNS('Special', 'Search') . '">
-  <input type="text" tabindex="1" name="q" size="50" value="' . $qin . '" />&nbsp;<input tabindex="2" type="submit" value="Search" />
+  <input type="text" tabindex="1" name="q" size="50" value="' . $qin . '" />&nbsp;<input tabindex="2" type="submit" value="Search" />&nbsp;<a href="' . makeUrlNS('Special', 'Search') . '">Advanced search</a>
   ' . ( $session->auth_level > USER_LEVEL_MEMBER ? '<input type="hidden" name="auth" value="' . $session->sid_super . '" />' : '' ) . '
   </form>';
   
@@ -133,10 +134,10 @@
     foreach ( $results as $i => $_ )
     {
       $result =& $results[$i];
-      $result['page_text'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="highlight">', '</span>'), $result['page_text']);
+      $result['page_text'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="search-term">', '</span>'), $result['page_text']);
       if ( !empty($result['page_text']) )
         $result['page_text'] .= '<br />';
-      $result['page_name'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="highlight">', '</span>'), $result['page_name']);
+      $result['page_name'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="title-search-term">', '</span>'), $result['page_name']);
       if ( $result['page_length'] >= 1048576 )
       {
         $result['page_length'] = round($result['page_length'] / 1048576, 1);
--- a/plugins/SpecialUpdownload.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialUpdownload.php	Sun Dec 02 16:00:10 2007 -0500
@@ -218,12 +218,25 @@
   global $do_gzip;
   $filename = rawurldecode($paths->getParam(0));
   $timeid = $paths->getParam(1);
-  if($timeid && preg_match('#^([0-9]+)$#', (string)$timeid)) $tid = ' AND time_id='.$timeid;
-  else $tid = '';
+  if ( $timeid && preg_match('#^([0-9]+)$#', (string)$timeid) )
+  {
+    $tid = ' AND time_id='.$timeid;
+  }
+  else
+  {
+    $tid = '';
+  }
   $filename = $db->escape($filename);
   $q = $db->sql_query('SELECT page_id,size,mimetype,time_id,file_extension,file_key FROM '.table_prefix.'files WHERE filename=\''.$filename.'\''.$tid.' ORDER BY time_id DESC;');
-  if(!$q) $db->_die('The file data could not be selected.');
-  if($db->numrows() < 1) { header('HTTP/1.1 404 Not Found'); die_friendly('File not found', '<p>The file "'.$filename.'" cannot be found.</p>'); }
+  if ( !$q )
+  {
+    $db->_die('The file data could not be selected.');
+  }
+  if ( $db->numrows() < 1 )
+  {
+    header('HTTP/1.1 404 Not Found');
+    die_friendly('File not found', '<p>The file "'.$filename.'" cannot be found.</p>');
+  }
   $row = $db->fetchrow();
   $db->free_result();
   
--- a/plugins/SpecialUserFuncs.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialUserFuncs.php	Sun Dec 02 16:00:10 2007 -0500
@@ -525,7 +525,7 @@
         
         if ( $_POST['use_crypt'] == 'yes' )
         {
-          $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+          $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
           $crypt_key = $session->fetch_public_key($_POST['crypt_key']);
           if ( !$crypt_key )
           {
@@ -1235,7 +1235,7 @@
     
     if ( isset($_POST['do_stage2']) )
     {
-      $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+      $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
       if($_POST['use_crypt'] == 'yes')
       {
         $crypt_key = $session->fetch_public_key($_POST['crypt_key']);
--- a/plugins/SpecialUserPrefs.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/SpecialUserPrefs.php	Sun Dec 02 16:00:10 2007 -0500
@@ -25,7 +25,7 @@
 function userprefs_menu_add($section, $text, $link)
 {
   global $userprefs_menu;
-  if ( is_array($userprefs_menu[$section]) )
+  if ( isset($userprefs_menu[$section]) && is_array($userprefs_menu[$section]) )
   {
     $userprefs_menu[$section][] = Array(
       'text' => $text,
@@ -133,7 +133,7 @@
   $uid = intval($session->user_id);
   
   // Instanciate the AES encryptor
-  $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+  $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   
   // Basic user info
   $q = $db->sql_query('SELECT username, password, email, real_name, signature, theme, style FROM '.table_prefix.'users WHERE user_id='.$uid.';');
--- a/plugins/admin/UserManager.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/plugins/admin/UserManager.php	Sun Dec 02 16:00:10 2007 -0500
@@ -68,7 +68,7 @@
         $password = false;
         if ( $_POST['changing_pw'] == 'yes' )
         {
-          $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+          $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
           $key_hex_md5 = $_POST['crypt_key'];
           $key_hex = $session->fetch_public_key($key_hex_md5);
           if ( $key_hex )
@@ -348,7 +348,7 @@
     else
     {
       // Get the current session information so the user doesn't get logged out
-      $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
+      $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
       $sk = md5(strrev($session->sid_super));
       $qb = $db->sql_query('SELECT session_key,salt,auth_level,source_ip,time FROM '.table_prefix.'session_keys WHERE session_key=\''.$sk.'\' AND user_id='.$session->user_id.' AND auth_level='.USER_LEVEL_ADMIN);
       if ( !$qb )
@@ -544,21 +544,12 @@
    * Constructor.
    */
   
-  function __construct()
+  function Admin_UserManager_SmartForm()
   {
     $this->uuid = md5( mt_rand() . microtime() );
   }
   
   /**
-   * PHP4 constructor.
-   */
-  
-  function Admin_UserManager_SmartForm()
-  {
-    $this->__construct();
-  }
-  
-  /**
    * Renders and returns the finished form.
    * @return string
    */
--- a/schema.sql	Sun Dec 02 15:27:21 2007 -0500
+++ b/schema.sql	Sun Dec 02 16:00:10 2007 -0500
@@ -49,7 +49,7 @@
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
 CREATE TABLE {{TABLE_PREFIX}}page_text(
-  page_id varchar(63),
+  page_id varchar(255),
   namespace varchar(16) NOT NULL default 'Article',
   page_text text,
   char_tag varchar(63),
@@ -58,8 +58,8 @@
 
 CREATE TABLE {{TABLE_PREFIX}}pages(
   page_order int(8),
-  name varchar(127),
-  urlname varchar(63),
+  name varchar(255),
+  urlname varchar(255),
   namespace varchar(16) NOT NULL default 'Article',
   special tinyint(1) default '0',
   visible tinyint(1) default '1',
@@ -304,7 +304,7 @@
   ('copyright_notice', '{{COPYRIGHT}}'),
   ('wiki_edit_notice_text', '== Why can I edit this page? ==\n\nEveryone can edit almost any page in this website. This concept is called a wiki. It gives everyone the opportunity to make a change for the best. While some spam and vandalism may occur, it is believed that most contributions will be legitimate and helpful.\n\nFor security purposes, a history of all page edits is kept, and administrators are able to restore vandalized or spammed pages with just a few clicks.'),
   ('cache_thumbs', '{{ENABLE_CACHE}}'),
-  ('max_file_size', '256000'),('enano_version', '{{VERSION}}'),( 'allowed_mime_types', 'cbf:len=168;crc=c3dcad3f;data=0[1],1[4],0[3],1[1],0[2],1[1],0[11],1[1],0[7],1[1],0[9],1[1],0[6],1[3],0[10],1[1],0[2],1[2],0[1],1[1],0[1],1[2],0[6],1[3],0[1],1[1],0[2],1[4],0[1],1[2],0[3],1[1],0[4],1[2],0[26],1[5],0[6],1[2],0[2],1[1],0[4],1[1],0[10],1[2],0[1],1[1],0[6]|end' ),
+  ('max_file_size', '256000'),('enano_version', '{{VERSION}}'),( 'allowed_mime_types', 'cbf:len=185;crc=55fb6f14;data=0[1],1[4],0[3],1[1],0[22],1[1],0[16],1[3],0[16],1[1],0[1],1[2],0[6],1[1],0[1],1[1],0[4],1[2],0[3],1[1],0[48],1[2],0[2],1[1],0[4],1[1],0[37]|end' ),
   ('contact_email', '{{ADMIN_EMAIL}}'),
   ('powered_btn', '1');
 
--- a/themes/oxygen/footer.tpl	Sun Dec 02 15:27:21 2007 -0500
+++ b/themes/oxygen/footer.tpl	Sun Dec 02 16:00:10 2007 -0500
@@ -12,7 +12,7 @@
                  -->
             <div id="credits">
               <b>{COPYRIGHT}</b><br />
-              Website engine powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>&nbsp;&nbsp;|&nbsp;&nbsp;Generated in [[GenTime]]sec
+              Website engine powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>&nbsp;&nbsp;|&nbsp;&nbsp;Generated in [[GenTime]]sec<!-- BEGIN auth_admin -->&nbsp;&nbsp;|&nbsp;&nbsp;<a href="{REPORT_URI}">[[NumQueries]] SQL</a><!-- END auth_admin -->
               <!-- Do not remove this line or scheduled tasks will not run. -->
               <img alt=" " src="{SCRIPTPATH}/cron.php" width="1" height="1" />
             </div>
--- a/themes/oxygen/header.tpl	Sun Dec 02 15:27:21 2007 -0500
+++ b/themes/oxygen/header.tpl	Sun Dec 02 16:00:10 2007 -0500
@@ -94,7 +94,7 @@
         elem1.appendChild(document.createTextNode(value));
         elem1.style.display = 'block';
         if(!value || value=='') return;
-        ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+escape(value), function() {
+        ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+ajaxEscape(value), function() {
           if(ajax.readyState == 4) {
             alert(ajax.responseText);
           }
--- a/themes/stpatty/footer.tpl	Sun Dec 02 15:27:21 2007 -0500
+++ b/themes/stpatty/footer.tpl	Sun Dec 02 16:00:10 2007 -0500
@@ -10,7 +10,7 @@
                  -Dan
                  -->
           <b>{COPYRIGHT}</b><br />
-          Powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>  |  <a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>  |  <a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>  |  [[Stats]]
+          Powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>&nbsp;&nbsp;|&nbsp;&nbsp;Time: [[GenTime]]s<!-- BEGIN auth_admin -->&nbsp;&nbsp;|&nbsp;&nbsp;<a href="{REPORT_URI}">[[NumQueries]] SQL</a><!-- END auth_admin -->
           <!-- Do not remove this line or scheduled tasks will not run. -->
           <img alt=" " src="{SCRIPTPATH}/cron.php" width="1" height="1" />
         </div>
--- a/themes/stpatty/header.tpl	Sun Dec 02 15:27:21 2007 -0500
+++ b/themes/stpatty/header.tpl	Sun Dec 02 16:00:10 2007 -0500
@@ -45,7 +45,7 @@
         elem1.appendChild(document.createTextNode(value));
         elem1.style.display = 'block';
         if(!value || value=='') return;
-        ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+escape(value), function() {
+        ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+ajaxEscape(value), function() {
           if(ajax.readyState == 4) {
             alert(ajax.responseText);
           }
--- a/upgrade.php	Sun Dec 02 15:27:21 2007 -0500
+++ b/upgrade.php	Sun Dec 02 16:00:10 2007 -0500
@@ -13,7 +13,8 @@
  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
  */
 
-define('IN_ENANO_INSTALL', 'true');   
+define('IN_ENANO_INSTALL', 'true');
+define('IN_ENANO_UPGRADE', 'true');
 
 if(!defined('scriptPath')) {
   $sp = dirname($_SERVER['REQUEST_URI']);
@@ -27,8 +28,16 @@
   define('contentPath', $sp);
 }
 
-global $_starttime, $this_page, $sideinfo;
-$_starttime = microtime(true);
+global $this_page, $sideinfo;
+
+function microtime_float()
+{
+  list($usec, $sec) = explode(" ", microtime());
+  return ((float)$usec + (float)$sec);
+}
+
+global $_starttime;
+$_starttime = microtime_float();
 
 // Determine directory (special case for development servers)
 if ( strpos(__FILE__, '/repo/') && file_exists('.enanodev') )
@@ -85,7 +94,8 @@
     '1.0' => Array('u_1_0_1_update_del_votes'),
     '1.0b4' => Array('u_1_0_RC1_update_user_ids', 'u_1_0_RC1_add_admins_to_group', 'u_1_0_RC1_alter_files_table', 'u_1_0_RC1_destroy_session_cookie', 'u_1_0_RC1_set_contact_email', 'u_1_0_RC1_update_page_text'), // ,
     // '1.0RC2' => Array('u_1_0_populate_userpage_comments')
-    '1.0RC3' => Array('u_1_0_RC3_make_users_extra')
+    '1.0RC3' => Array('u_1_0_RC3_make_users_extra'),
+    '1.0.2b1' => Array('u_1_0_2_nuke_template_cache', 'u_1_0_2_rebuild_search_index')
   );
 
 if(!isset($_GET['mode'])) 
@@ -433,6 +443,29 @@
     $db->_die();
 }
 
+function u_1_0_2_nuke_template_cache()
+{
+  $dir = @opendir(ENANO_ROOT . '/cache');
+  if ( !$dir )
+  {
+    return false;
+  }
+  while ( ($fname = @readdir($dir)) )
+  {
+    if ( preg_match('/\.tpl\.php$/', $fname) )
+    {
+      unlink( ENANO_ROOT . '/cache/' . $fname );
+    }
+  }
+}
+
+function u_1_0_2_rebuild_search_index()
+{
+  global $paths;
+  @set_time_limit(0);
+  $paths->rebuild_search_index();
+}
+
 switch($_GET['mode'])
 {
   case "login":
--- a/upgrade.sql	Sun Dec 02 15:27:21 2007 -0500
+++ b/upgrade.sql	Sun Dec 02 16:00:10 2007 -0500
@@ -20,11 +20,16 @@
 ALTER TABLE {{TABLE_PREFIX}}page_groups MODIFY COLUMN pg_target text DEFAULT NULL;
 -- I have no idea how or why, but the f'ing index didn't get created for who-knows-how-many releases.
 -- We'll attempt to create it here, but don't die if it fails
-@ALTER TABLE {{TABLE_PREFIX}}page_text ENGINE = MYISAM COLLATE = utf8_bin;
+@ALTER TABLE {{TABLE_PREFIX}}page_text ENGINE = MYISAM, COLLATE = utf8_bin, CHARSET = utf8;
+ALTER TABLE {{TABLE_PREFIX}}search_index CHARSET = utf8, COLLATE = utf8_bin, MODIFY COLUMN word varchar(64) NOT NULL;
+-- The search cache is no longer needed because of the new unified search engine (it's too f'ing fast to need a cache :-D)
+@DROP TABLE {{TABLE_PREFIX}}search_cache;
+-- Yes, it appears we need pages with names this long after all
+ALTER TABLE {{TABLE_PREFIX}}pages MODIFY COLUMN urlname varchar(255), MODIFY COLUMN name varchar(255);
+ALTER TABLE {{TABLE_PREFIX}}page_text MODIFY COLUMN page_id varchar(255), MODIFY COLUMN namespace varchar(63), MODIFY COLUMN page_text longtext;
 @CREATE FULLTEXT INDEX {{TABLE_PREFIX}}page_search_idx ON {{TABLE_PREFIX}}page_text(page_id, namespace, page_text);
-ALTER TABLE {{TABLE_PREFIX}}search_index COLLATE = utf8_bin, MODIFY COLUMN word varchar(64) NOT NULL;
--- The search cache is no longer needed because of the new unified search engine
-@DROP TABLE {{TABLE_PREFIX}}search_cache;
+UPDATE {{TABLE_PREFIX}}config SET config_value='cbf:len=185;crc=55fb6f14;data=0[1],1[4],0[3],1[1],0[22],1[1],0[16],1[3],0[16],1[1],0[1],1[2],0[6],1[1],0[1],1[1],0[4],1[2],0[3],1[1],0[48],1[2],0[2],1[1],0[4],1[1],0[37]|end' WHERE config_name = 'allowed_mime_types' AND config_value='cbf:len=168;crc=c3dcad3f;data=0[1],1[4],0[3],1[1],0[2],1[1],0[11],1[1],0[7],1[1],0[9],1[1],0[6],1[3],0[10],1[1],0[2],1[2],0[1],1[1],0[1],1[2],0[6],1[3],0[1],1[1],0[2],1[4],0[1],1[2],0[3],1[1],0[4],1[2],0[26],1[5],0[6],1[2],0[2],1[1],0[4],1[1],0[10],1[2],0[1],1[1],0[6]|end';
+@UPDATE {{TABLE_PREFIX}}users SET theme='oxygen',style='bleu' WHERE user_id = 2;
 ---END 1.0.2b1---
 ---BEGIN 1.0.1.1---
 ---END 1.0.1.1---