Redid merge, the previous one had a few problems
authorDan
Wed, 19 Dec 2007 22:55:40 -0500
changeset 326 ab66d6d1f1f4
parent 314 474f8be55943 (current diff)
parent 325 e17cc42d77cf (diff)
child 327 c2f4c900c507
Redid merge, the previous one had a few problems
ajax.php
includes/clientside/static/acl.js
includes/clientside/static/ajax.js
includes/clientside/static/faders.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/render.php
includes/sessions.php
includes/tagcloud.php
includes/template.php
index.php
install.php
plugins/PrivateMessages.php
plugins/SpecialAdmin.php
plugins/SpecialPageFuncs.php
plugins/SpecialUpdownload.php
plugins/SpecialUserFuncs.php
plugins/admin/PageGroups.php
plugins/admin/UserManager.php
upgrade.php
upgrade.sql
--- a/.hgtags	Fri Dec 07 18:47:37 2007 -0500
+++ b/.hgtags	Wed Dec 19 22:55:40 2007 -0500
@@ -9,3 +9,5 @@
 90632c09ed7ec816da708df1341d9f4019de9adf feature-freeze
 f948557af0681389165a23419968965fcebcbdd0 devel-freeze
 95dc632bf0846b5a7d7d841a4fe9518964a1cc9a release
+f8356d9c3481b7ebb88dbf70637d8f5d2cca4528 rebrand
+d409d7fcf6f8bc638e0282101fb823ccf3e29e59 release
--- a/README	Fri Dec 07 18:47:37 2007 -0500
+++ b/README	Wed Dec 19 22:55:40 2007 -0500
@@ -1,5 +1,5 @@
 Enano CMS
-Version 1.0.2
+Version 1.0.3
 -----------------------------
 
 Thanks for downloading Enano! If you're looking for an installation guide,
@@ -74,7 +74,7 @@
 CHANGES IN THIS RELEASE
 -----------------------------
 
-Please see <http://enanocms.org/Release_notes/1.0.2> for a list of changes in
+Please see <http://enanocms.org/Release_notes/1.0.3> for a list of changes in
 this release.
 
 UPGRADING FROM PREVIOUS RELEASES
--- a/ajax.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/ajax.php	Wed Dec 19 22:55:40 2007 -0500
@@ -18,11 +18,6 @@
   if ( isset($_GET['_mode']) && $_GET['_mode'] == 'fillusername' )
   {
     // setup and load a very basic, specialized instance of the Enano API
-    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; }
     function microtime_float()
     {
       list($usec, $sec) = explode(" ", microtime());
@@ -41,7 +36,14 @@
     require(ENANO_ROOT.'/includes/functions.php');
     require(ENANO_ROOT.'/includes/dbal.php');
     require(ENANO_ROOT.'/includes/json.php');
-    $db = new mysql();
+    
+    require(ENANO_ROOT . '/config.php');
+    unset($dbuser, $dbpasswd);
+    if ( !isset($dbdriver) )
+      $dbdriver = 'mysql';
+    
+    $db = new $dbdriver();
+    
     $db->connect();
     
     // result is sent using JSON
@@ -62,14 +64,10 @@
       die( $json->encode($return) );
     }
     $allowanon = ( isset($_GET['allowanon']) && $_GET['allowanon'] == '1' ) ? '' : ' AND user_id > 1';
-    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE lcase(username) LIKE lcase(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
+    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username) LIKE ' . ENANO_SQLFUNC_LOWERCASE . '(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
     if ( !$q )
     {
-      $return = array(
-        'mode' => 'error',
-        'error' => 'MySQL error selecting username data: '.addslashes(mysql_error())
-      );
-      die( $json->encode($return) );
+      $db->die_json();
     }
     $i = 0;
     while($r = $db->fetchrow())
@@ -99,13 +97,26 @@
       echo PageUtils::checkusername($_GET['name']);
       break;
     case "getsource":
-      $p = ( isset($_GET['pagepass']) ) ? $_GET['pagepass'] : false;
-      echo PageUtils::getsource($paths->page, $p);
+      $password = ( isset($_GET['pagepass']) ) ? $_GET['pagepass'] : false;
+      $page = new PageProcessor($paths->page_id, $paths->namespace);
+      $page->password = $password;
+      if ( $src = $page->fetch_source() )
+      {
+        echo $src;
+      }
+      else if ( $src !== false )
+      {
+        echo '';
+      }
+      else
+      {
+        echo 'err_access_denied';
+      }
       break;
     case "getpage":
       // echo PageUtils::getpage($paths->page, false, ( (isset($_GET['oldid'])) ? $_GET['oldid'] : false ));
       $revision_id = ( (isset($_GET['oldid'])) ? intval($_GET['oldid']) : 0 );
-      $page = new PageProcessor( $paths->cpage['urlname_nons'], $paths->namespace, $revision_id );
+      $page = new PageProcessor( $paths->page_id, $paths->namespace, $revision_id );
       
       $pagepass = ( isset($_REQUEST['pagepass']) ) ? $_REQUEST['pagepass'] : '';
       $page->password = $pagepass;
@@ -115,10 +126,10 @@
     case "savepage":
       $summ = ( isset($_POST['summary']) ) ? $_POST['summary'] : '';
       $minor = isset($_POST['minor']);
-      $e = PageUtils::savepage($paths->cpage['urlname_nons'], $paths->namespace, $_POST['text'], $summ, $minor);
+      $e = PageUtils::savepage($paths->page_id, $paths->namespace, $_POST['text'], $summ, $minor);
       if($e=='good')
       {
-        $page = new PageProcessor($paths->cpage['urlname_nons'], $paths->namespace);
+        $page = new PageProcessor($paths->page_id, $paths->namespace);
         $page->send();
       }
       else
@@ -127,16 +138,16 @@
       }
       break;
     case "protect":
-      echo PageUtils::protect($paths->cpage['urlname_nons'], $paths->namespace, (int)$_POST['level'], $_POST['reason']);
+      echo PageUtils::protect($paths->page_id, $paths->namespace, (int)$_POST['level'], $_POST['reason']);
       break;
     case "histlist":
-      echo PageUtils::histlist($paths->cpage['urlname_nons'], $paths->namespace);
+      echo PageUtils::histlist($paths->page_id, $paths->namespace);
       break;
     case "rollback":
       echo PageUtils::rollback( (int)$_GET['id'] );
       break;
     case "comments":
-      $comments = new Comments($paths->cpage['urlname_nons'], $paths->namespace);
+      $comments = new Comments($paths->page_id, $paths->namespace);
       if ( isset($_POST['data']) )
       {
         $comments->process_json($_POST['data']);
@@ -147,37 +158,37 @@
       }
       break;
     case "rename":
-      echo PageUtils::rename($paths->cpage['urlname_nons'], $paths->namespace, $_POST['newtitle']);
+      echo PageUtils::rename($paths->page_id, $paths->namespace, $_POST['newtitle']);
       break;
     case "flushlogs":
-      echo PageUtils::flushlogs($paths->cpage['urlname_nons'], $paths->namespace);
+      echo PageUtils::flushlogs($paths->page_id, $paths->namespace);
       break;
     case "deletepage":
       $reason = ( isset($_POST['reason']) ) ? $_POST['reason'] : false;
       if ( empty($reason) )
         die('Please enter a reason for deleting this page.');
-      echo PageUtils::deletepage($paths->cpage['urlname_nons'], $paths->namespace, $reason);
+      echo PageUtils::deletepage($paths->page_id, $paths->namespace, $reason);
       break;
     case "delvote":
-      echo PageUtils::delvote($paths->cpage['urlname_nons'], $paths->namespace);
+      echo PageUtils::delvote($paths->page_id, $paths->namespace);
       break;
     case "resetdelvotes":
-      echo PageUtils::resetdelvotes($paths->cpage['urlname_nons'], $paths->namespace);
+      echo PageUtils::resetdelvotes($paths->page_id, $paths->namespace);
       break;
     case "getstyles":
       echo PageUtils::getstyles($_GET['id']);
       break;
     case "catedit":
-      echo PageUtils::catedit($paths->cpage['urlname_nons'], $paths->namespace);
+      echo PageUtils::catedit($paths->page_id, $paths->namespace);
       break;
     case "catsave":
-      echo PageUtils::catsave($paths->cpage['urlname_nons'], $paths->namespace, $_POST);
+      echo PageUtils::catsave($paths->page_id, $paths->namespace, $_POST);
       break;
     case "setwikimode":
-      echo PageUtils::setwikimode($paths->cpage['urlname_nons'], $paths->namespace, (int)$_GET['mode']);
+      echo PageUtils::setwikimode($paths->page_id, $paths->namespace, (int)$_GET['mode']);
       break;
     case "setpass":
-      echo PageUtils::setpass($paths->cpage['urlname_nons'], $paths->namespace, $_POST['password']);
+      echo PageUtils::setpass($paths->page_id, $paths->namespace, $_POST['password']);
       break;
     case "fillusername":
       break;
@@ -232,7 +243,7 @@
       if(!$id1 || !$id2) { echo '<p>Invalid request.</p>'; $template->footer(); break; }
       if(!preg_match('#^([0-9]+)$#', (string)$_GET['diff1']) ||
          !preg_match('#^([0-9]+)$#', (string)$_GET['diff2']  )) { echo '<p>SQL injection attempt</p>'; $template->footer(); break; }
-      echo PageUtils::pagediff($paths->cpage['urlname_nons'], $paths->namespace, $id1, $id2);
+      echo PageUtils::pagediff($paths->page_id, $paths->namespace, $id1, $id2);
       break;
     case "jsres":
       die('// ERROR: this section is deprecated and has moved to includes/clientside/static/enano-lib-basic.js.');
@@ -277,10 +288,10 @@
       $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       
       $ret = array('tags' => array(), 'user_level' => $session->user_level, 'can_add' => $session->get_permissions('tag_create'));
-      $q = $db->sql_query('SELECT t.tag_id, t.tag_name, pg.pg_target IS NOT NULL AS used_in_acl, t.user FROM '.table_prefix.'tags AS t
+      $q = $db->sql_query('SELECT t.tag_id, t.tag_name, pg.pg_target IS NOT NULL AS used_in_acl, t.user_id FROM '.table_prefix.'tags AS t
         LEFT JOIN '.table_prefix.'page_groups AS pg
           ON ( ( pg.pg_type = ' . PAGE_GRP_TAGGED . ' AND pg.pg_target=t.tag_name ) OR ( pg.pg_type IS NULL AND pg.pg_target IS NULL ) )
-        WHERE t.page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND t.namespace=\'' . $db->escape($paths->namespace) . '\';');
+        WHERE t.page_id=\'' . $db->escape($paths->page_id) . '\' AND t.namespace=\'' . $db->escape($paths->namespace) . '\';');
       if ( !$q )
         $db->_die();
       
@@ -288,11 +299,11 @@
       {
         $can_del = true;
         
-        $perm = ( $row['user'] != $session->user_id ) ?
+        $perm = ( $row['user_id'] != $session->user_id ) ?
                 'tag_delete_other' :
                 'tag_delete_own';
         
-        if ( $row['user'] == 1 && !$session->user_logged_in )
+        if ( $row['user_id'] == 1 && !$session->user_logged_in )
           // anonymous user trying to delete tag (hardcode blacklisted)
           $can_del = false;
           
@@ -340,7 +351,7 @@
       }
       
       // check if tag is already on page
-      $q = $db->sql_query('SELECT 1 FROM '.table_prefix.'tags WHERE page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND namespace=\'' . $db->escape($paths->namespace) . '\' AND tag_name=\'' . $tag . '\';');
+      $q = $db->sql_query('SELECT 1 FROM '.table_prefix.'tags WHERE page_id=\'' . $db->escape($paths->page_id) . '\' AND namespace=\'' . $db->escape($paths->namespace) . '\' AND tag_name=\'' . $tag . '\';');
       if ( !$q )
         $db->_die();
       if ( $db->numrows() > 0 )
@@ -364,7 +375,7 @@
       $db->free_result();
       
       // we're good
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'tags(tag_name,page_id,namespace,user) VALUES(\'' . $tag . '\', \'' . $db->escape($paths->cpage['urlname_nons']) . '\', \'' . $db->escape($paths->namespace) . '\', ' . $session->user_id . ');');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'tags(tag_name,page_id,namespace,user_id) VALUES(\'' . $tag . '\', \'' . $db->escape($paths->page_id) . '\', \'' . $db->escape($paths->namespace) . '\', ' . $session->user_id . ');');
       if ( !$q )
         $db->_die();
       
@@ -380,7 +391,7 @@
       if ( empty($tag_id) )
         die('Invalid tag ID');
       
-      $q = $db->sql_query('SELECT t.tag_id, t.user, t.page_id, t.namespace, pg.pg_target IS NOT NULL AS used_in_acl FROM '.table_prefix.'tags AS t
+      $q = $db->sql_query('SELECT t.tag_id, t.user_id, t.page_id, t.namespace, pg.pg_target IS NOT NULL AS used_in_acl FROM '.table_prefix.'tags AS t
   LEFT JOIN '.table_prefix.'page_groups AS pg
     ON ( pg.pg_id IS NULL OR ( pg.pg_target = t.tag_name AND pg.pg_type = ' . PAGE_GRP_TAGGED . ' ) )
   WHERE t.tag_id=' . $tag_id . ';');
@@ -394,16 +405,16 @@
       $row = $db->fetchrow();
       $db->free_result();
       
-      if ( $row['page_id'] == $paths->cpage['urlname_nons'] && $row['namespace'] == $paths->namespace )
+      if ( $row['page_id'] == $paths->page_id && $row['namespace'] == $paths->namespace )
         $perms =& $session;
       else
         $perms = $session->fetch_page_acl($row['page_id'], $row['namespace']);
         
-      $perm = ( $row['user'] != $session->user_id ) ?
+      $perm = ( $row['user_id'] != $session->user_id ) ?
                 'tag_delete_other' :
                 'tag_delete_own';
       
-      if ( $row['user'] == 1 && !$session->user_logged_in )
+      if ( $row['user_id'] == 1 && !$session->user_logged_in )
         // anonymous user trying to delete tag (hardcode blacklisted)
         die('You are not authorized to delete this tag.');
         
--- a/cron.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/cron.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/captcha.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/captcha.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * captcha.php - visual confirmation system used during registration
  *
--- a/includes/clientside/jsres.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/clientside/jsres.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * jsres.php - the Enano client-side runtime, a.k.a. AJAX on steroids
  *
@@ -40,7 +40,7 @@
 {
   echo "/*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * [Aggressively compressed] Javascript client code
  * Copyright (C) 2006-2007 Dan Fuhry
  * Enano is Free Software, licensed under the GNU General Public License; see http://enanocms.org/ for details.
--- a/includes/clientside/static/ajax.js	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/clientside/static/ajax.js	Wed Dec 19 22:55:40 2007 -0500
@@ -42,6 +42,81 @@
   ajax.send(parms);
 }
 
+/**
+ * Show a friendly error message depicting an AJAX response that is not valid JSON
+ * @param string Response text
+ * @param string Custom error message. If omitted, the default will be shown.
+ */
+
+function handle_invalid_json(response, customerror)
+{
+  var mainwin = $('ajaxEditContainer').object;
+  mainwin.innerHTML = '';
+  
+  // Title
+  var h3 = document.createElement('h3');
+  h3.appendChild(document.createTextNode('The site encountered an error while processing your request.'));
+  mainwin.appendChild(h3);
+  
+  if ( typeof(customerror) == 'string' )
+  {
+    var el = document.createElement('p');
+    el.appendChild(document.createTextNode(customerror));
+    mainwin.appendChild(el);
+  }
+  else
+  {
+    customerror  = 'We unexpectedly received the following response from the server. The response should have been in the JSON ';
+    customerror += 'serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers';
+    customerror += 'for this problem:';
+    var el = document.createElement('p');
+    el.appendChild(document.createTextNode(customerror));
+    mainwin.appendChild(el);
+    var ul = document.createElement('ul');
+    var li1 = document.createElement('li');
+    var li2 = document.createElement('li');
+    var li3 = document.createElement('li');
+    li1.appendChild(document.createTextNode('The server sent back a bad HTTP response code and thus sent an error page instead of running Enano. This indicates a possible problem with your server, and is not likely to be a bug with Enano.'));
+    var osc_exception = ( window.location.hostname == 'demo.opensourcecms.com' ) ? ' This is KNOWN to be the case with the OpenSourceCMS.com demo version of Enano.' : '';
+    li2.appendChild(document.createTextNode('The server sent back the expected JSON response, but also injected some code into the response that should not be there. Typically this consists of advertisement code. In this case, the administrator of this site will have to contact their web host to have advertisements disabled.' + osc_exception));
+    li3.appendChild(document.createTextNode('It\'s possible that Enano triggered a PHP error or warning. In this case, you may be looking at a bug in Enano.'));
+      
+    ul.appendChild(li1);
+    ul.appendChild(li2);
+    ul.appendChild(li3);
+    mainwin.appendChild(ul);
+  }
+  
+  var p2 = document.createElement('p');
+  p2.appendChild(document.createTextNode('The response received from the server is as follows:'));
+  mainwin.appendChild(p2);
+  
+  var pre = document.createElement('pre');
+  pre.appendChild(document.createTextNode(response));
+  mainwin.appendChild(pre);
+  
+  var p3 = document.createElement('p');
+  p3.appendChild(document.createTextNode('You may also choose to view the response as HTML. '));
+  var a = document.createElement('a');
+  a.appendChild(document.createTextNode('View as HTML...'));
+  a._resp = response;
+  a.id = 'invalidjson_link';
+  a.onclick = function()
+  {
+    var mb = new messagebox(MB_YESNO | MB_ICONEXCLAMATION, 'Do you really want to view this response as HTML?', 'If the response was changed during transmission to include malicious code, you may be allowing that malicious code to run by viewing the response as HTML. Only do this if you have reviewed the response text and have found no suspicious code in it.');
+    mb.onclick['Yes'] = function()
+    {
+      var html = $('invalidjson_link').object._resp;
+      var win = window.open('about:blank', 'invalidjson_htmlwin', 'width=550,height=400,status=no,toolbars=no,toolbar=no,address=no,scroll=yes');
+      win.document.write(html);
+    }
+    return false;
+  }
+  a.href = '#';
+  p3.appendChild(a);
+  mainwin.appendChild(p3);
+}
+
 function ajaxEscape(text)
 {
   /*
@@ -925,7 +1000,7 @@
         resptext = resptext.substr(0, resptext.length-1);
         if ( resptext.substr(0, 1) != '{' )
         {
-          alert('Invalid JSON response from server:\n' + resptext);
+          handle_invalid_json(resptext);
           return false;
         }
         var json = parseJSON(resptext);
@@ -1025,7 +1100,7 @@
         resptext = resptext.substr(0, resptext.length-1);
         if ( resptext.substr(0, 1) != '{' )
         {
-          alert('Invalid JSON response from server:\n' + resptext);
+          handle_invalid_json(resptext);
           return false;
         }
         var json = parseJSON(resptext);
--- a/includes/clientside/static/faders.js	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/clientside/static/faders.js	Wed Dec 19 22:55:40 2007 -0500
@@ -74,6 +74,8 @@
  *   onclick - an array of functions to be called on button click events
  *             NOTE: key names are to be strings, and they must be the value of the input, CaSe-SeNsItIvE
  *   onbeforeclick - same as onclick but called before the messagebox div is destroyed
+ * Methods:
+ *   destroy: kills the running message box
  * Example:
  *   var my_message = new messagebox(MB_OK|MB_ICONSTOP, 'Error logging in', 'The username and/or password is incorrect. Please check the username and retype your password');
  *   my_message.onclick['OK'] = function() {
@@ -275,6 +277,14 @@
     {
       this.text_area.innerHTML = text;
     };
+    
+  this.destroy = function()
+    {
+      var mbdiv = document.getElementById('messageBox');
+      mbdiv.parentNode.removeChild(mbdiv.nextSibling);
+      mbdiv.parentNode.removeChild(mbdiv);
+      enlighten(true);
+    };
   
   //domObjChangeOpac(0, mydiv);
   //domObjChangeOpac(0, master_div);
--- a/includes/clientside/static/misc.js	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/clientside/static/misc.js	Wed Dec 19 22:55:40 2007 -0500
@@ -402,7 +402,8 @@
         var response = String(ajax.responseText);
         if ( response.substr(0,1) != '{' )
         {
-          alert('Invalid JSON response from server: ' + response);
+          handle_invalid_json(response);
+          ajax_auth_mb_cache.destroy();
           return false;
         }
         response = parseJSON(response);
@@ -447,56 +448,77 @@
          }
          var disableme = ( disable_controls ) ? 'disabled="disabled" ' : '';
         form_html += ' \
-          <table border="0" align="center"> \
-            <tr> \
-              <td>' + $lang.get('user_login_field_username') + ':</td><td><input tabindex="1" id="ajaxlogin_user" type="text"     ' + disableme + 'size="25" /> \
-            </tr> \
-            <tr> \
-              <td>' + $lang.get('user_login_field_password') + ':</td><td><input tabindex="2" id="ajaxlogin_pass" type="password" ' + disableme + 'size="25" /> \
-            </tr> \
-            ' + captcha_html + ' \
-            <tr> \
-              <td colspan="2" style="text-align: center;"> \
+          <form action="#" onsubmit="ajaxValidateLogin(); return false;" name="ajax_login_form"> \
+            <table border="0" align="center"> \
+              <tr> \
+                <td>' + $lang.get('user_login_field_username') + ':</td><td><input tabindex="1" id="ajaxlogin_user" type="text"     ' + disableme + 'size="25" /> \
+              </tr> \
+              <tr> \
+                <td>' + $lang.get('user_login_field_password') + ':</td><td><input tabindex="2" id="ajaxlogin_pass" type="password" ' + disableme + 'size="25" /> \
+              </tr> \
+              ' + captcha_html + ' \
+              <tr> \
+                <td colspan="2" style="text-align: center;"> \
                 <small>' + $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title, 'level=' + level) }) + '<br />';
        if ( level <= USER_LEVEL_MEMBER )
        {
          form_html += ' \
-                ' + $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '<br /> \
-                ' + $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') });
+                  ' + $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '<br /> \
+                  ' + $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') });
        }
        form_html += '</small> \
-              </td> \
-            </tr> \
-          </table> \
-          <input type="hidden" id="ajaxlogin_crypt_key"       value="' + response.key + '" /> \
-          <input type="hidden" id="ajaxlogin_crypt_challenge" value="' + response.challenge + '" /> \
-        </form>';
+                </td> \
+              </tr> \
+            </table> \
+            <input type="hidden" id="ajaxlogin_crypt_key"       value="' + response.key + '" /> \
+            <input type="hidden" id="ajaxlogin_crypt_challenge" value="' + response.challenge + '" /> \
+          </form>';
         ajax_auth_mb_cache.updateContent(form_html);
         $('messageBox').object.nextSibling.firstChild.tabindex = '3';
         if ( typeof(response.username) == 'string' )
         {
           $('ajaxlogin_user').object.value = response.username;
-          $('ajaxlogin_pass').object.focus();
+          if ( IE )
+          {
+            setTimeout("document.forms['ajax_login_form'].password.focus();", 200);
+          }
+          else
+          {
+            $('ajaxlogin_pass').object.focus();
+          }
         }
         else
         {
-          $('ajaxlogin_user').object.focus();
+          if ( IE )
+          {
+            setTimeout("document.forms['ajax_login_form'].username.focus();", 200);
+          }
+          else
+          {
+            $('ajaxlogin_user').object.focus();
+          }
         }
-        if ( ajax_auth_show_captcha )
-        {
-          $('ajaxlogin_captcha_code').object.onblur = function(e) { if ( !shift ) $('messageBox').object.nextSibling.firstChild.focus(); };
-          $('ajaxlogin_captcha_code').object.onkeypress = function(e) { if ( !e && IE ) return true; if ( e.keyCode == 13 ) $('messageBox').object.nextSibling.firstChild.click(); };
-        }
-        else
+        var enter_obj = ( ajax_auth_show_captcha ) ? 'ajaxlogin_captcha_code' : 'ajaxlogin_pass';
+        $(enter_obj).object.onblur = function(e) { if ( !shift ) $('messageBox').object.nextSibling.firstChild.focus(); };
+        $(enter_obj).object.onkeypress = function(e)
         {
-          $('ajaxlogin_pass').object.onblur = function(e) { if ( !shift ) $('messageBox').object.nextSibling.firstChild.focus(); };
-          $('ajaxlogin_pass').object.onkeypress = function(e) { if ( !e && IE ) return true; if ( e.keyCode == 13 ) $('messageBox').object.nextSibling.firstChild.click(); };
-        }
-        if ( disable_controls )
-        {
-          var panel = document.getElementById('messageBoxButtons');
-          panel.firstChild.disabled = true;
-        }
+          // Trigger a form submit when the password field is focused and the user presses enter
+          
+          // IE doesn't give us an event object when it should - check window.event. If that
+          // still fails, give up.
+          if ( !e )
+          {
+            e = window.event;
+          }
+          if ( !e && IE )
+          {
+            return true;
+          }
+          if ( e.keyCode == 13 )
+          {
+            ajaxValidateLogin();
+          }
+        };
         /*
         ## This causes the background image to disappear under Fx 2
         if ( shown_error )
--- a/includes/comment.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/comment.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -54,7 +54,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     // Initialize permissions
-    if ( $page_id == $paths->cpage['urlname_nons'] && $namespace == $paths->namespace )
+    if ( $page_id == $paths->page_id && $namespace == $paths->namespace )
       $this->perms =& $GLOBALS['session'];
     else
       $this->perms = $session->fetch_page_acl($page_id, $namespace);
@@ -113,7 +113,7 @@
                                  ON ( ( b.user_id=' . $session->user_id.' AND b.buddy_user_id=c.user_id ) OR b.user_id IS NULL)
                                WHERE page_id=\'' . $this->page_id . '\'
                                  AND namespace=\'' . $this->namespace . '\'
-                               GROUP BY c.comment_id
+                               GROUP BY c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature,b.buddy_id,b.is_friend
                                ORDER BY c.time ASC;');
         $count_appr = 0;
         $count_total = 0;
--- a/includes/common.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/common.php	Wed Dec 19 22:55:40 2007 -0500
@@ -155,7 +155,13 @@
 // The first thing we need to do is start the database connection. At this point, for all we know, Enano might not
 // even be installed. If this connection attempt fails and it's because of a missing or corrupt config file, the
 // user will be redirected (intelligently) to install.php.
-$db = new mysql();
+
+@include(ENANO_ROOT . '/config.php');
+unset($dbuser, $dbpasswd);
+if ( !isset($dbdriver) )
+  $dbdriver = 'mysql';
+
+$db = new $dbdriver();
 $db->connect();
 
 // The URL separator is the character appended to contentPath + url_title type strings.
@@ -347,7 +353,7 @@
   // If the site is disabled, bail out, unless we're trying to log in or administer the site
   if(getConfig('site_disabled') == '1' && $session->user_level < USER_LEVEL_ADMIN)
   {
-    if ( $paths->namespace == 'Admin' || ( $paths->namespace == 'Special' && ( $paths->cpage['urlname_nons'] == 'CSS' || $paths->cpage['urlname_nons'] == 'Administration' || $paths->cpage['urlname_nons'] == 'Login' ) ) )
+    if ( $paths->namespace == 'Admin' || ( $paths->namespace == 'Special' && ( $paths->page_id == 'CSS' || $paths->page_id == 'Administration' || $paths->page_id == 'Login' ) ) )
     {
       // do nothing; allow execution to continue
     }
--- a/includes/constants.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/constants.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * constants.php - important defines used Enano-wide
  *
--- a/includes/dbal.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/dbal.php	Wed Dec 19 22:55:40 2007 -0500
@@ -120,46 +120,56 @@
     return $internal_text;
   }
   
-  function connect()
+  function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false)
   {
     $this->enable_errorhandler();
     
-    if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
-    {
-      @include(ENANO_ROOT.'/config.new.php');
-    }
-    else
+    define('ENANO_DBLAYER', 'MYSQL');
+    define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
+    define('ENANO_SQL_MULTISTRING_PRFIX', '');
+    define('ENANO_SQL_BOOLEAN_TRUE', 'true');
+    define('ENANO_SQL_BOOLEAN_FALSE', 'false');
+    
+    if ( !$manual_credentials )
     {
-      @include(ENANO_ROOT.'/config.php');
-    }
+      if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
+      {
+        @include(ENANO_ROOT.'/config.new.php');
+      }
+      else
+      {
+        @include(ENANO_ROOT.'/config.php');
+      }
+        
+      if ( isset($crypto_key) )
+        unset($crypto_key); // Get this sucker out of memory fast
       
-    if ( isset($crypto_key) )
-      unset($crypto_key); // Get this sucker out of memory fast
-    
-    if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
-    {
-      // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
-      if ( !defined('scriptPath') )
+      if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
       {
-        if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
+        // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
+        if ( !defined('scriptPath') )
         {
-          $_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';
+          if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
+          {
+            $_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);
+          define('contentPath', "$sp/index.php?title=");
         }
-        $sp = dirname($_SERVER['REQUEST_URI']);
-        if($sp == '/' || $sp == '\\') $sp = '';
-        define('scriptPath', $sp);
-        define('contentPath', "$sp/index.php?title=");
+        $loc = scriptPath . '/install.php';
+        // header("Location: $loc");
+        redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3);
+        exit;
       }
-      $loc = scriptPath . '/install.php';
-      // header("Location: $loc");
-      redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3);
-      exit;
     }
+    
     $this->_conn = @mysql_connect($dbhost, $dbuser, $dbpasswd);
     unset($dbuser);
     unset($dbpasswd); // Security
@@ -778,4 +788,750 @@
   }
 }
 
+class postgresql {
+  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');
+    }
+  }
+  
+  function disable_errorhandler()
+  {
+    // echo "DBAL: disabling error handler<br />";
+    if ( $this->errhandler )
+    {
+      set_error_handler($this->errhandler);
+    }
+    else
+    {
+      restore_error_handler();
+    }
+  }
+  
+  function sql_backtrace()
+  {
+    return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
+  }
+  
+  function ensure_connection()
+  {
+    if(!$this->_conn)
+    {
+      $this->connect();
+    }
+  }
+  
+  function _die($t = '') {
+    if(defined('ENANO_HEADERS_SENT')) {
+      ob_clean();
+    }
+    header('HTTP/1.1 500 Internal Server Error');
+    $bt = $this->latest_query; // $this->sql_backtrace();
+    $e = htmlspecialchars(pg_last_error());
+    if($e=='') $e='&lt;none&gt;';
+    $t = ( !empty($t) ) ? $t : '&lt;No error description provided&gt;';
+    global $email;
+    $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;' : '';
+    $internal_text = '<h3>The site was unable to finish serving your request.</h3>
+                      <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p>
+                      <p>Description or location of error: '.$t.'<br />
+                      Error returned by PostgreSQL extension: ' . $e . '<br />
+                      Most recent SQL query:</p>
+                      <pre>'.$bt.'</pre>';
+    if(defined('ENANO_CONFIG_FETCHED')) die_semicritical('Database error', $internal_text);
+    else                                   grinding_halt('Database error', $internal_text);
+    exit;
+  }
+  
+  function die_json()
+  {
+    $e = addslashes(htmlspecialchars(pg_last_error()));
+    $q = addslashes($this->latest_query);
+    $t = "{'mode':'error','error':'An error occurred during database query.\nQuery was:\n  $q\n\nError returned by PostgreSQL: $e'}";
+    die($t);
+  }
+  
+  function get_error($t = '') {
+    header('HTTP/1.1 500 Internal Server Error');
+    $bt = $this->sql_backtrace();
+    $e = htmlspecialchars(pg_last_error());
+    if($e=='') $e='&lt;none&gt;';
+    global $email;
+    $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;' : '';
+    $internal_text = '<h3>The site was unable to finish serving your request.</h3>
+                      <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p>
+                      <p>Description or location of error: '.$t.'<br />
+                      Error returned by MySQL extension: ' . $e . '<br />
+                      Most recent SQL query:</p>
+                      <pre>'.$bt.'</pre>';
+    return $internal_text;
+  }
+  
+  function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false)
+  {
+    $this->enable_errorhandler();
+    
+    define('ENANO_DBLAYER', 'PGSQL');
+    define('ENANO_SQLFUNC_LOWERCASE', 'lower');
+    define('ENANO_SQL_MULTISTRING_PRFIX', 'E');
+    define('ENANO_SQL_BOOLEAN_TRUE', '1');
+    define('ENANO_SQL_BOOLEAN_FALSE', '0');
+    
+    if ( !$manual_credentials )
+    {
+      if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
+      {
+        @include(ENANO_ROOT.'/config.new.php');
+      }
+      else
+      {
+        @include(ENANO_ROOT.'/config.php');
+      }
+        
+      if ( isset($crypto_key) )
+        unset($crypto_key); // Get this sucker out of memory fast
+      
+      if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
+      {
+        // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
+        if ( !defined('scriptPath') )
+        {
+          if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
+          {
+            $_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);
+          define('contentPath', "$sp/index.php?title=");
+        }
+        $loc = scriptPath . '/install.php';
+        // header("Location: $loc");
+        redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3);
+        exit;
+      }
+    }
+    $this->_conn = @pg_connect("host=$dbhost port=5432 dbname=$dbname user=$dbuser password=$dbpasswd");
+    unset($dbuser);
+    unset($dbpasswd); // Security
+    
+    if ( !$this->_conn )
+    {
+      grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>');
+    }
+    
+    // Reset some variables
+    $this->query_backtrace = array();
+    $this->query_times = array();
+    $this->query_sources = array();
+    $this->num_queries = 0;
+    
+    $this->debug = ( defined('ENANO_DEBUG') );
+    
+    // We're in!
+    $this->disable_errorhandler();
+    return true;
+  }
+  
+  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;
+    $this->latest_query = $q;
+    // First make sure we have a connection
+    if ( !$this->_conn )
+    {
+      $this->_die('A database connection has not yet been established.');
+    }
+    // Does this query look malicious?
+    if ( !$this->check_query($q) )
+    {
+      $this->report_query($q);
+      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 = pg_query($q);
+    $this->query_times[$q] = microtime_float() - $time_start;
+    $this->latest_result = $r;
+    $this->disable_errorhandler();
+    return $r;
+  }
+  
+  function sql_unbuffered_query($q)
+  {
+    $this->enable_errorhandler();
+    
+    $this->num_queries++;
+    $this->query_backtrace[] = '(UNBUFFERED) ' . $q;
+    $this->latest_query = $q;
+    // First make sure we have a connection
+    if ( !$this->_conn )
+    {
+      $this->_die('A database connection has not yet been established.');
+    }
+    // Does this query look malicious?
+    if ( !$this->check_query($q) )
+    {
+      $this->report_query($q);
+      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 = pg_query($q);
+    $this->query_times[$q] = microtime_float() - $time_start;
+    $this->latest_result = $r;
+    $this->disable_errorhandler();
+    return $r;
+  }
+  
+  /**
+   * Checks a SQL query for possible signs of injection attempts
+   * @param string $q the query to check
+   * @return bool true if query passed check, otherwise false
+   */
+  
+  function check_query($q, $debug = false)
+  {
+    if($debug) echo "\$db-&gt;check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n";
+    $sz = strlen($q);
+    $quotechar = false;
+    $quotepos  = 0;
+    $prev_is_quote = false;
+    $just_started = false;
+    for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) )
+    {
+      $next = substr($q, $i+1, 1);
+      $next2 = substr($q, $i+2, 1);
+      $prev = substr($q, $i-1, 1);
+      $prev2 = substr($q, $i-2, 1);
+      if(isset($c) && in_array($c, Array('"', "'", '`')))
+      {
+        if($quotechar)
+        {
+          if (
+              ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') ||
+              ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c )
+            )
+          {
+            $quotechar = false;
+            if($debug) echo('$db-&gt;check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />');
+            $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q));
+            if($debug) echo('$db-&gt;check_query(): Filtered query: '.$q.'<br />');
+            $i = $quotepos;
+          }
+        }
+        else
+        {
+          $quotechar = $c;
+          $quotepos  = $i;
+          $just_started = true;
+        }
+        if($debug) echo '$db-&gt;check_query(): found quote char as pos: '.$i.'<br />';
+        continue;
+      }
+      $just_started = false;
+    }
+    if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1);
+    for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1))
+    {
+      if ( 
+           ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' )
+        || ( in_array($c, Array('"', "'", '`')) )
+         ) // Don't permit semicolons in mid-query, and never allow comments
+      {
+        // Injection attempt!
+        if($debug)
+        {
+          $e = '';
+          for($j=$i-5;$j<$i+5;$j++)
+          {
+            if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>';
+            else $e .= $c;
+          }
+          echo 'Injection attempt caught at pos: '.$i.'<br />';
+        }
+        return false;
+      }
+    }
+    if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
+    {
+      if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
+      return false;
+    }
+    return true;
+  }
+  
+  /**
+   * Set the internal result pointer to X
+   * @param int $pos The number of the row
+   * @param resource $result The MySQL result resource - if not given, the latest cached query is assumed
+   * @return true on success, false on failure
+   */
+   
+  function sql_data_seek($pos, $result = false)
+  {
+    $this->enable_errorhandler();
+    if(!$result)
+      $result = $this->latest_result;
+    if(!$result)
+    {
+      $this->disable_errorhandler();
+      return false;
+    }
+    if(pg_result_seek($result, $pos))
+    {
+      $this->disable_errorhandler();
+      return true;
+    }
+    else
+    {
+      $this->disable_errorhandler();
+      return false;
+    }
+  }
+  
+  /**
+   * Reports a bad query to the admin
+   * @param string $query the naughty query
+   * @access private
+   */
+   
+  function report_query($query)
+  {
+    global $session;
+    if(is_object($session) && defined('ENANO_MAINSTREAM'))
+      $username = $session->username;
+    else
+      $username = 'Unavailable';
+    $query = $this->escape($query);
+    $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            edit_summary)
+                                                     VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', \''.$_SERVER['REMOTE_ADDR'].'\');');
+  }
+  
+  /**
+   * Returns the ID of the row last inserted.
+   * @return int
+   */
+  
+  function insert_id()
+  {
+    return @pg_last_oid();
+  }
+  
+  function fetchrow($r = false) {
+    $this->enable_errorhandler();
+    if(!$this->_conn) return false;
+    if(!$r) $r = $this->latest_result;
+    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    $row = pg_fetch_assoc($r);
+    $this->disable_errorhandler();
+    return $row;
+  }
+  
+  function fetchrow_num($r = false) {
+    $this->enable_errorhandler();
+    if(!$r) $r = $this->latest_result;
+    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    $row = pg_fetch_row($r);
+    $this->disable_errorhandler();
+    return $row;
+  }
+  
+  function numrows($r = false) {
+    $this->enable_errorhandler();
+    if(!$r) $r = $this->latest_result;
+    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    $n = pg_num_rows($r);
+    $this->disable_errorhandler();
+    return $n;
+  }
+  
+  function escape($str)
+  {
+    $this->enable_errorhandler();
+    $str = pg_escape_string($str);
+    $this->disable_errorhandler();
+    return $str;
+  }
+  
+  function free_result($result = false)
+  {
+    $this->enable_errorhandler();
+    if(!$result)
+      $result = $this->latest_result;
+    if(!$result)
+    {
+      $this->disable_errorhandler();
+      return null;
+    }
+    pg_free_result($result);
+    $this->disable_errorhandler();
+    return null;
+  }
+  
+  function close() {
+    pg_close($this->_conn);
+    unset($this->_conn);
+  }
+  
+  // phpBB DBAL compatibility
+  function sql_fetchrow($r = false)
+  {
+    return $this->fetchrow($r);
+  }
+  function sql_freeresult($r = false)
+  {
+    if(!$this->_conn) return false;
+    if(!$r) $r = $this->latest_result;
+    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    $this->free_result($r);
+  }
+  function sql_numrows($r = false)
+  {
+    return $this->numrows();
+  }
+  function sql_affectedrows($r = false, $f, $n)
+  {
+    if(!$this->_conn) return false;
+    if(!$r) $r = $this->latest_result;
+    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    return pg_affected_rows();
+  }
+  
+  function sql_type_cast(&$value)
+	{
+		if ( is_float($value) )
+		{
+			return doubleval($value);
+		}
+		if ( is_integer($value) || is_bool($value) )
+		{
+			return intval($value);
+		}
+		if ( is_string($value) || empty($value) )
+		{
+			return '\'' . $this->sql_escape_string($value) . '\'';
+		}
+		// uncastable var : let's do a basic protection on it to prevent sql injection attempt
+		return '\'' . $this->sql_escape_string(htmlspecialchars($value)) . '\'';
+	}
+
+	function sql_statement(&$fields, $fields_inc='')
+	{
+		// init result
+		$this->sql_fields = $this->sql_values = $this->sql_update = '';
+		if ( empty($fields) && empty($fields_inc) )
+		{
+			return;
+		}
+
+		// process
+		if ( !empty($fields) )
+		{
+			$first = true;
+			foreach ( $fields as $field => $value )
+			{
+				// field must contain a field name
+				if ( !empty($field) && is_string($field) )
+				{
+					$value = $this->sql_type_cast($value);
+					$this->sql_fields .= ( $first ? '' : ', ' ) . $field;
+					$this->sql_values .= ( $first ? '' : ', ' ) . $value;
+					$this->sql_update .= ( $first ? '' : ', ' ) . $field . ' = ' . $value;
+					$first = false;
+				}
+			}
+		}
+		if ( !empty($fields_inc) )
+		{
+			foreach ( $fields_inc as $field => $indent )
+			{
+				if ( $indent != 0 )
+				{
+					$this->sql_update .= (empty($this->sql_update) ? '' : ', ') . $field . ' = ' . $field . ($indent < 0 ? ' - ' : ' + ') . abs($indent);
+				}
+			}
+		}
+	}
+
+	function sql_stack_reset($id='')
+	{
+		if ( empty($id) )
+		{
+			$this->sql_stack_fields = array();
+			$this->sql_stack_values = array();
+		}
+		else
+		{
+			$this->sql_stack_fields[$id] = array();
+			$this->sql_stack_values[$id] = array();
+		}
+	}
+
+	function sql_stack_statement(&$fields, $id='')
+	{
+		$this->sql_statement($fields);
+		if ( empty($id) )
+		{
+			$this->sql_stack_fields = $this->sql_fields;
+			$this->sql_stack_values[] = '(' . $this->sql_values . ')';
+		}
+		else
+		{
+			$this->sql_stack_fields[$id] = $this->sql_fields;
+			$this->sql_stack_values[$id][] = '(' . $this->sql_values . ')';
+		}
+	}
+
+	function sql_stack_insert($table, $transaction=false, $line='', $file='', $break_on_error=true, $id='')
+	{
+		if ( (empty($id) && empty($this->sql_stack_values)) || (!empty($id) && empty($this->sql_stack_values[$id])) )
+		{
+			return false;
+		}
+		switch( SQL_LAYER )
+		{
+			case 'mysql':
+			case 'mysql4':
+				if ( empty($id) )
+				{
+					$sql = 'INSERT INTO ' . $table . '
+								(' . $this->sql_stack_fields . ') VALUES ' . implode(",\n", $this->sql_stack_values);
+				}
+				else
+				{
+					$sql = 'INSERT INTO ' . $table . '
+								(' . $this->sql_stack_fields[$id] . ') VALUES ' . implode(",\n", $this->sql_stack_values[$id]);
+				}
+				$this->sql_stack_reset($id);
+				return $this->sql_query($sql, $transaction, $line, $file, $break_on_error);
+				break;
+			default:
+				$count_sql_stack_values = empty($id) ? count($this->sql_stack_values) : count($this->sql_stack_values[$id]);
+				$result = !empty($count_sql_stack_values);
+				for ( $i = 0; $i < $count_sql_stack_values; $i++ )
+				{
+					if ( empty($id) )
+					{
+						$sql = 'INSERT INTO ' . $table . '
+									(' . $this->sql_stack_fields . ') VALUES ' . $this->sql_stack_values[$i];
+					}
+					else
+					{
+						$sql = 'INSERT INTO ' . $table . '
+									(' . $this->sql_stack_fields[$id] . ') VALUES ' . $this->sql_stack_values[$id][$i];
+					}
+					$result &= $this->sql_query($sql, $transaction, $line, $file, $break_on_error);
+				}
+				$this->sql_stack_reset($id);
+				return $result;
+				break;
+		}
+	}
+
+	function sql_subquery($field, $sql, $line='', $file='', $break_on_error=true, $type=TYPE_INT)
+	{
+		// sub-queries doable
+		$this->sql_get_version();
+		if ( !in_array(SQL_LAYER, array('mysql', 'mysql4')) || (($this->sql_version[0] + ($this->sql_version[1] / 100)) >= 4.01) )
+		{
+			return $sql;
+		}
+
+		// no sub-queries
+		$ids = array();
+		$result = $this->sql_query(trim($sql), false, $line, $file, $break_on_error);
+		while ( $row = $this->sql_fetchrow($result) )
+		{
+			$ids[] = $type == TYPE_INT ? intval($row[$field]) : '\'' . $this->sql_escape_string($row[$field]) . '\'';
+		}
+		$this->sql_freeresult($result);
+		return empty($ids) ? 'NULL' : implode(', ', $ids);
+	}
+
+	function sql_col_id($expr, $alias)
+	{
+		$this->sql_get_version();
+		return in_array(SQL_LAYER, array('mysql', 'mysql4')) && (($this->sql_version[0] + ($this->sql_version[1] / 100)) <= 4.01) ? $alias : $expr;
+	}
+
+	function sql_get_version()
+	{
+		if ( empty($this->sql_version) )
+		{
+			$this->sql_version = array(0, 0, 0);
+			switch ( SQL_LAYER )
+			{
+				case 'mysql':
+				case 'mysql4':
+					if ( function_exists('mysql_get_server_info') )
+					{
+						$lo_version = explode('-', mysql_get_server_info());
+						$this->sql_version = explode('.', $lo_version[0]);
+						$this->sql_version = array(intval($this->sql_version[0]), intval($this->sql_version[1]), intval($this->sql_version[2]), $lo_version[1]);
+					}
+					break;
+
+				case 'postgresql':
+				case 'mssql':
+				case 'mssql-odbc':
+				default:
+					break;
+			}
+		}
+		return $this->sql_version;
+	}
+
+	function sql_error()
+	{
+		if ( $this->_conn )
+		{
+			return mysql_error();
+		}
+		else
+		{
+			return array();
+		}
+	}
+  function sql_escape_string($t) 
+  {
+    return mysql_real_escape_string($t);
+  }
+  function sql_close()
+  {
+    $this->close();
+  }
+  function sql_fetchrowset($query_id = 0)
+	{
+		if( !$query_id )
+		{
+			$query_id = $this->query_result;
+		}
+
+		if( $query_id )
+		{
+			unset($this->rowset[$query_id]);
+			unset($this->row[$query_id]);
+
+			while($this->rowset[$query_id] = mysql_fetch_array($query_id, MYSQL_ASSOC))
+			{
+				$result[] = $this->rowset[$query_id];
+			}
+
+			return $result;
+		}
+		else
+		{
+			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/email.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/email.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/functions.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/functions.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -260,7 +260,14 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
 
   $page_id_key = $paths->nslist[ $namespace ] . $page_id;
-  $page_data = $paths->pages[$page_id_key];
+  if ( isset($paths->pages[$page_id_key]) )
+  {
+    $page_data = $paths->pages[$page_id_key];
+  }
+  else
+  {
+    $page_data = array();
+  }
   $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$namespace] . str_replace('_', ' ', dirtify_page_id( $page_id ) );
   return $title;
 }
@@ -567,7 +574,7 @@
     $q = $db->sql_query('SELECT p.urlname, p.namespace, p.name, p.namespace=\'Category\' AS is_category FROM '.table_prefix.'categories AS c
                            LEFT JOIN '.table_prefix.'pages AS p
                              ON ( p.urlname = c.page_id AND p.namespace = c.namespace )
-                           WHERE c.category_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\'
+                           WHERE c.category_id=\'' . $db->escape($paths->page_id) . '\'
                            ORDER BY is_category DESC, p.name ASC;');
     if ( !$q )
     {
@@ -679,7 +686,7 @@
     echo '</div>';
     echo '<div id="mdgCatBox">' . $lang->get('catedit_catbox_lbl_categories') . ' ';
     
-    $where = '( c.page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND c.namespace=\'' . $db->escape($paths->namespace) . '\' )';
+    $where = '( c.page_id=\'' . $db->escape($paths->page_id) . '\' AND c.namespace=\'' . $db->escape($paths->namespace) . '\' )';
     $prefix = table_prefix;
     $sql = <<<EOF
 SELECT c.category_id FROM {$prefix}categories AS c
@@ -731,11 +738,11 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   if($paths->namespace != 'File') return null; // Prevent unnecessary work
-  $selfn = $paths->cpage['urlname_nons']; // substr($paths->page, strlen($paths->nslist['File']), strlen($paths->cpage));
-  if(substr($paths->cpage['name'], 0, strlen($paths->nslist['File']))==$paths->nslist['File']) $selfn = substr($paths->cpage['urlname_nons'], strlen($paths->nslist['File']), strlen($paths->cpage['urlname_nons']));
+  $selfn = $paths->page_id; // substr($paths->page, strlen($paths->nslist['File']), strlen($paths->cpage));
+  if(substr($paths->cpage['name'], 0, strlen($paths->nslist['File']))==$paths->nslist['File']) $selfn = substr($paths->page_id, strlen($paths->nslist['File']), strlen($paths->page_id));
   $q = $db->sql_query('SELECT mimetype,time_id,size FROM '.table_prefix.'files WHERE page_id=\''.$selfn.'\' ORDER BY time_id DESC;');
   if(!$q) $db->_die('The file type could not be fetched.');
-  if($db->numrows() < 1) { echo '<div class="mdg-comment" style="margin-left: 0;"><h3>Uploaded file</h3><p>There are no files uploaded with this name yet. <a href="'.makeUrlNS('Special', 'UploadFile/'.$paths->cpage['urlname_nons']).'">Upload a file...</a></p></div><br />'; return; }
+  if($db->numrows() < 1) { echo '<div class="mdg-comment" style="margin-left: 0;"><h3>Uploaded file</h3><p>There are no files uploaded with this name yet. <a href="'.makeUrlNS('Special', 'UploadFile/'.$paths->page_id).'">Upload a file...</a></p></div><br />'; return; }
   $r = $db->fetchrow();
   $mimetype = $r['mimetype'];
   $datestring = date('F d, Y h:i a', (int)$r['time_id']);
@@ -1134,7 +1141,8 @@
       '1.0.1'  => 'Loch Ness',
       '1.0.1.1'=> 'Loch Ness internal bugfix build',
       '1.0.2b1'=> 'Coblynau unstable',
-      '1.0.2'  => 'Coblynau'
+      '1.0.2'  => 'Coblynau',
+      '1.0.3'  => 'Dyrad'
     );
   $version = enano_version();
   if ( isset($names[$version]) )
@@ -2228,7 +2236,6 @@
 function paginate_array($q, $num_results, $result_url, $start = 0, $perpage = 10, $header = '', $footer = '')
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  $parser = $template->makeParserText($tpl_text);
   $num_pages = ceil ( $num_results / $perpage );
   $out = '';
   $i = 0;
@@ -2415,7 +2422,20 @@
 
 function sanitize_page_id($page_id)
 {
-
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( isset($paths->nslist['User']) )
+  {
+    if ( preg_match('/^' . preg_quote($paths->nslist['User']) . '/', $page_id) )
+    {
+      $ip = preg_replace('/^' . preg_quote($paths->nslist['User']) . '/', '', $page_id);
+      if ( is_valid_ip($ip) )
+      {
+        return $page_id;
+      }
+    }
+  }
+  
   // Remove character escapes
   $page_id = dirtify_page_id($page_id);
 
@@ -2446,7 +2466,7 @@
     else
       $page_id_cleaned .= $pid_dirty[$id];
   }
-
+  
   // global $mime_types;
 
   // $exts = array_keys($mime_types);
@@ -2470,11 +2490,9 @@
   $page_id = str_replace(' ', '_', $page_id);
 
   // Exception for userpages for IP addresses
-  if ( preg_match('/^' . preg_quote($paths->nslist['User']) . '/', $page_id) )
+  if ( is_valid_ip($page_id) )
   {
-    $ip = preg_replace('/^' . preg_quote($paths->nslist['User']) . '/', '', $page_id);
-    if ( is_valid_ip($ip) )
-      return $page_id;
+    return $page_id;
   }
 
   preg_match_all('/\.[A-Fa-f0-9][A-Fa-f0-9]/', $page_id, $matches);
@@ -2487,7 +2505,7 @@
     $char = chr($char);
     $page_id = str_replace($matches[0][$id], $char, $page_id);
   }
-
+  
   return $page_id;
 }
 
@@ -2672,7 +2690,8 @@
 function sanitize_tag($tag)
 {
   $tag = strtolower($tag);
-  $tag = preg_replace('/[^\w _@\$%\^&-]+/', '', $tag);
+  $tag = preg_replace('/[^\w @\$%\^&-]+/', '', $tag);
+  $tag = str_replace('_', ' ', $tag);
   $tag = trim($tag);
   return $tag;
 }
--- a/includes/graphs.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/graphs.php	Wed Dec 19 22:55:40 2007 -0500
@@ -740,198 +740,310 @@
 // Graph Generator for PHP
 // Originally located at http://szewo.com/php/graph, but link was broken, so this file was retrieved from:
 // http://web.archive.org/web/20030130065944/szewo.com/php/graph/graph.class.php3.txt
-// License unknown
+// License unknown, however sources on the web have shown this to be either GPL or public domain.
 
 class GraphMaker_compat {
- var $_values;
- var $_ShowLabels;
- var $_ShowCounts;
- var $_ShowCountsMode;
+  var $_values;
+  var $_ShowLabels;
+  var $_ShowCounts;
+  var $_ShowCountsMode;
 
- var $_BarWidth;
- var $_GraphWidth;
- var $_BarImg;
- var $_BarBorderWidth;
- var $_BarBorderColor;
- var $_RowSortMode;
- var $_TDClassHead;
- var $_TDClassLabel;
- var $_TDClassCount;
- var $_GraphTitle;
+  var $_BarWidth;
+  var $_GraphWidth;
+  var $_BarImg;
+  var $_BarBorderWidth;
+  var $_BarBorderColor;
+  var $_BarBackgroundColor;
+  var $_RowSortMode;
+  var $_TDClassHead;
+  var $_TDClassLabel;
+  var $_TDClassCount;
+  var $_GraphTitle;
 
- function __construct() {
-  $this->_values = array();
-  $this->_ShowLabels = true;
-  $this->_BarWidth = 16;
-  $this->_GraphWidth = 360;
-  $this->_BarImg = "NULL";
-  $this->_BarBorderWidth = 0;
-  $this->_BarBorderColor = "red";
-  $this->_ShowCountsMode = 2;
-  $this->_RowSortMode = 1;
-  $this->_TDClassHead = "grphh";
-  $this->_TDClassLabel = "grph";
-  $this->_TDClassCount = "grphc";
-  $this->_GraphTitle="Graph title";
- }
- 
- function GraphMaker_compat() {
-  $this->__construct();
- }
+  function __construct() {
+    $this->_values = array();
+    $this->_ShowLabels = true;
+    $this->_BarWidth = 32;
+    $this->_GraphWidth = 360;
+    $this->_BarImg = scriptPath . "/images/graphbit.png";
+    $this->_BarBorderWidth = 0;
+    $this->_BarBorderColor = "red";
+    $this->_ShowCountsMode = 2;
+    $this->_RowSortMode = 1;
+    $this->_TDClassHead = "graph-title";
+    $this->_TDClassLabel = "graph-label";
+    $this->_TDClassCount = "graph-count";
+    $this->_GraphTitle="Graph title";
+    $this->_BarBackgroundColor = "#456798";
+  }
 
- function SetBarBorderWidth($width) {
-  $this->_BarBorderWidth = $width;
- }
- function SetBorderColor($color) {
-  $this->_BarBorderColor = $color;
- }
+  function GraphMaker_compat() {
+    $this->__construct();
+  }
+
+  function SetBarBorderWidth($width) {
+    $this->_BarBorderWidth = $width;
+  }
+  function SetBorderColor($color) {
+    $this->_BarBorderColor = $color;
+  }
+  
+  function SetBarBackgroundColor($color)
+  {
+    $this->_BarBackgroundColor = $color;
+  }
 
 //  mode = 1 labels asc, 2 label desc
- function SetSortMode($mode) {
-  switch ($mode) {
-   case 1:
-    asort($this->_values);
-    break;
-   case 2:
-    arsort($this->_values);
-    break;
-   default:
-    break;
-   }
+  function SetSortMode($mode) {
+    switch ($mode) {
+      case 1:
+        asort($this->_values);
+        break;
+      case 2:
+        arsort($this->_values);
+        break;
+      default:
+        break;
+      }
 
- }
+  }
 
- function AddValue($labelName, $theValue) {
-  array_push($this->_values, array("label" => $labelName, "value" => $theValue));
+  function AddValue($labelName, $theValue) {
+    array_push($this->_values, array("label" => $labelName, "value" => $theValue));
+  }
 
- }
- function SetBarWidth($width) {
-  $this->_BarWidth = $width;
- }
- function SetBarImg($img) {
-  $this->_BarImg = $img;
- }
- function SetShowLabels($lables) {
-  $this->_ShowLabels = $labels;
- }
- function SetGraphWidth($width) {
-  $this->_GraphWidth = $width;
- }
- function SetGraphTitle($title) {
-  $this->_GraphTitle = $title;
- }
- //mode = percentage or counts
- function SetShowCountsMode($mode) {
-  $this->_ShowCountsMode = $mode;
- }
- //mode = none(0) label(1) or count(2)
- function SetRowSortMode($sortmode) {
-  $this->_RowSortMode = $sortmode;
- }
+  function SetBarData($data)
+  {
+      foreach ( $data as $name => $value )
+      {
+          $this->AddValue($name, $value);
+      }
+  }
+  function DrawGraph()
+  {
+      $this->BarGraphVert();
+  }
+  function SetBarWidth($width)
+  {
+    $this->_BarWidth = $width;
+  }
+  function SetBarImg($img)
+  {
+    $this->_BarImg = $img;
+  }
+  function SetShowLabels($lables)
+  {
+    $this->_ShowLabels = $labels;
+  }
+  function SetGraphWidth($width)
+  {
+    $this->_GraphWidth = $width;
+  }
+  function SetGraphTitle($title)
+  {
+    $this->_GraphTitle = $title;
+  }
+  //mode = percentage or counts
+  function SetShowCountsMode($mode)
+  {
+    $this->_ShowCountsMode = $mode;
+  }
+  //mode = none(0) label(1) or count(2)
+  function SetRowSortMode($sortmode)
+  {
+    $this->_RowSortMode = $sortmode;
+  }
+
+  function SetTDClassHead($class)
+  {
+    $this->_TDClassHead = $class;
+  }
+  function SetTDClassLabel($class)
+  {
+    $this->_TDClassLabel = $class;
+  }
+  function SetTDClassCount($class)
+  {
+    $this->_TDClassCount = $class;
+  }
+  function GetMaxVal()
+  {
+    $maxval = 0;
+    foreach ( $this->_values as $value )
+    {
+      if ( $maxval < $value["value"] )
+      {
+        $maxval = $value["value"];
+      }
+    }
+    return $maxval;
+  }
+  function BarGraphVert()
+  {
+    $maxval = $this->GetMaxVal();
+    foreach($this->_values as $value)
+    {
+      $sumval += $value["value"];
+    }
+    
+    $this->SetSortMode($this->_RowSortMode);
+    
+    echo "\n<!-- ----------------------------------------- -->\n<div class=\"tblholder\" style=\"width: 100%; clip: rect(0px,auto,auto,0px); overflow: auto;\">\n<table border=\"0\" cellspacing=\"1\" cellpadding=\"4\">\n  ";
+    
+    if ( strlen($this->_GraphTitle) > 0 )
+    {
+      echo "<tr>\n    <th colspan=\"".count($this->_values)."\" class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."</th>\n  </tr>\n  ";
+    }
+    
+    echo "<tr>\n  ";
+    $css_class = 'row1';
+    
+    foreach($this->_values as $value)
+    {
+      $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1';
+      echo "  <td valign=\"bottom\" align=\"center\" class=\"$css_class\">\n      ";
+      $width = $this->_BarWidth;
+      $height = ceil( $value["value"] * $this->_GraphWidth / $maxval );
+
+      echo "<div style=\"width: {$width}px; height: {$height}px; background-color: {$this->_BarBackgroundColor}; border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\">\n      ";
+      echo "</div>\n    ";
+      
+      // echo "<img src=\"".$this->_BarImg."\" height=\"$width\" width=\"$height\" ";
+      // echo "  style=\"border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\"";
+      // echo ">";
 
- function SetTDClassHead($class) {
-  $this->_TDClassHead = $class;
- }
- function SetTDClassLabel($class) {
-  $this->_TDClassLabel = $class;
- }
- function SetTDClassCount($class) {
-  $this->_TDClassCount = $class;
- }
- function GetMaxVal() {
-  $maxval = 0;
-  foreach($this->_values as $value) if($maxval<$value["value"]) $maxval = $value["value"];
-  return $maxval;
- }
- function BarGraphVert() {
-  $maxval = $this->GetMaxVal();
-  foreach($this->_values as $value) $sumval += $value["value"];
-  $this->SetSortMode($this->_RowSortMode);
-  echo "<table>";
-  if (strlen($this->_GraphTitle)>0)  echo "<tr><td colspan=".count($this->_values)." class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."</td></tr>";
-   echo "<tr>";
-   foreach($this->_values as $value) {
-    echo "<td valign=bottom align=center>";
-    $height = $this->_BarWidth;
-    $width=ceil($value["value"]*$this->_GraphWidth/$maxval);
-    echo "<div ";
-    echo "  style=\"background-color: #666666; border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\"";
-    echo ">";
-    echo "</td>";
-   }
-   echo "</tr>";
-   if ($this->_ShowCountsMode>0) {
-   	echo "<tr>";
-    foreach($this->_values as $value) {
-     switch ($this->_ShowCountsMode) {
-     case 1:
-      $count = round(100*$value["value"]/$sumval)."%";
-      break;
-     case 2:
-      $count = $value["value"];
-      break;  /* Exit the switch and the while. */
-     default:
-      break;
-     }
-     echo "<td align=center class=".$this->_TDClassCount.">$count</td>";
-   	}
-	echo "</tr>";
-   }
+      echo "</td>\n  ";
+    }
+    echo "</tr>\n  ";
+    if ( $this->_ShowCountsMode > 0 )
+    {
+      $css_class = 'row1';
+      echo "<tr>\n  ";
+      foreach($this->_values as $value)
+      {
+        $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1';
+        switch ($this->_ShowCountsMode)
+        {
+          case 1:
+            $count = round ( 100 * $value["value"] / $sumval ) . "%";
+            break;
+          case 2:
+            $count = $value["value"];
+            break;
+          default:
+            break;
+        }
+        echo "  <td align=\"center\" class=\"$css_class ".$this->_TDClassCount."\">$count</td>\n  ";
+      }
+      echo "</tr>\n";
+    }
+
+    if ($this->_ShowLabels)
+    {
+      $css_class = 'row1';
+      echo "  <tr>\n  ";
+      foreach($this->_values as $value)
+      {
+        $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1';
+        echo "  <td align=\"center\" class=\"$css_class ".$this->_TDClassLabel."\"";
+        echo ">".$value["label"]."</td>\n  ";
+      }
+      echo "</tr>\n";
+    }
+
+    echo "</table>";
+  }
 
-   if ($this->_ShowLabels) {
-    echo "<tr>";
-    foreach($this->_values as $value) {
-     echo "<td align=center class=".$this->_TDClassLabel;
-	 echo ">".$value["label"]."</td>";
-	}
-	echo "</tr>";
-   }	
-
-  echo "</table>";
- }
+  function BarGraphHoriz()
+  {
+    $maxval = $this->GetMaxVal();
+    
+    foreach($this->_values as $value)
+    {
+      $sumval += $value["value"];
+    }
+    
+    $this->SetSortMode($this->_RowSortMode);
+    
+    echo "<table border=\"0\">";
+    
+    if ( strlen($this->_GraphTitle) > 0 )
+    {
+      echo "<tr><td ";
+      if ( $this->_ShowCountsMode > 0 )
+      {
+        echo " colspan=\"2\"";
+      }
+      echo " class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."</td></tr>";
+    }
+    foreach($this->_values as $value)
+    {
+      if ($this->_ShowLabels)
+      {
+        echo "<tr>";
+        echo "<td class=\"".$this->_TDClassLabel."\"";
+        if ( $this->_ShowCountsMode > 0 )
+        {
+          echo " colspan=\"2\"";
+        }
+        echo ">".$value["label"]."</td></tr>";
+      }
+      echo "<tr>";
+      if ( $this->_ShowCountsMode > 0 )
+      {
+        switch ($this->_ShowCountsMode)
+        {
+          case 1:
+            $count = round(100 * $value["value"] / $sumval )."%";
+            break;
+          case 2:
+            $count = $value["value"];
+            break;  /* Exit the switch and the while. */
+          default:
+            break;
+        }
+        echo "<td class=\"".$this->_TDClassCount."\">$count</TD>";
+      }
+      echo "<td>";
+      $height = $this->_BarWidth;
+      $width = ceil( $value["value"] * $this->_GraphWidth / $maxval );
+      echo "<div style=\"width: {$width}px; height: {$height}px; background-color: #456798; border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\">\n      ";
+      echo "</div>\n    ";
+      //echo "<img SRC=\"".$this->_BarImg."\" height=$height width=$width ";
+      //echo "  style=\"border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\"";
+      //echo ">";
+      echo "</td></tr>";
+    }
+    echo "</table>";
+  }
+  /**
+   * Dummy functions for compatibility with the GD version of the class
+   */
+  
+  function SetGraphPadding($a, $b, $c, $d)
+  {
+    return true;
+  }
+  function SetBarPadding($a)
+  {
+    return true;
+  }
+  function SetAxisStep($a)
+  {
+    return true;
+  }
+  function SetGraphBackgroundTransparent($r, $g, $b, $a)
+  {
+    return true;
+  }
+  function SetGraphTransparency($a)
+  {
+    return true;
+  }
+  function SetGraphAreaHeight($a)
+  {
+    return true;
+  }
+}
 
 
-
- function BarGraphHoriz() {
-  $maxval = $this->GetMaxVal();
-  foreach($this->_values as $value) $sumval += $value["value"];
-  $this->SetSortMode($this->_RowSortMode);
-  echo "<table border=0>";
-  if (strlen($this->_GraphTitle)>0)  {
-    echo "<tr><td ";
-   if ($this->_ShowCountsMode>0) echo " colspan=2";
-    echo " class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."</TD></TR>";
-  }
-  foreach($this->_values as $value) {
-   if ($this->_ShowLabels) {
-    echo "<tr>";
-    echo "<td class=".$this->_TDClassLabel;
-    if ($this->_ShowCountsMode>0) echo " colspan=2";
-	echo ">".$value["label"]."</TD></TR>";
-   }	
-   echo "<tr>";
-   if ($this->_ShowCountsMode>0) {
-    switch ($this->_ShowCountsMode) {
-    case 1:
-     $count = round(100*$value["value"]/$sumval)."%";
-     break;
-    case 2:
-     $count = $value["value"];
-     break;  /* Exit the switch and the while. */
-    default:
-     break;
-    }
-   echo "<td class=".$this->_TDClassCount.">$count</TD>";
-   }
-   echo "<td>";
-   $height = $this->_BarWidth;
-    $width=ceil($value["value"]*$this->_GraphWidth/$maxval);
-   echo "<img SRC=\"".$this->_BarImg."\" height=$height width=$width ";
-   echo "  style=\"border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\"";
-   echo ">";
-   echo "</TD></TR>";
-  }
-  echo "</TABLE>";
- }
-}
-
--- a/includes/js-compressor.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/js-compressor.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * Javascript compression library - used to compact the client-side Javascript code (all 72KB of it!) to save some bandwidth
  *
--- a/includes/pageprocess.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/pageprocess.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * pageprocess.php - intelligent retrieval of pages
  * Copyright (C) 2006-2007 Dan Fuhry
  *
@@ -15,6 +15,7 @@
 
 /**
  * Class to handle fetching page text (possibly from a cache) and formatting it.
+ * As of 1.0.4, this also handles the fetching and editing of certain data for pages.
  * @package Enano
  * @subpackage UI
  * @copyright 2007 Dan Fuhry
@@ -113,6 +114,13 @@
     );
   
   /**
+   * The list of errors raised in the class.
+   * @var array
+   */
+  
+  var $_errors = array();
+  
+  /**
    * Constructor.
    * @param string The page ID (urlname) of the page
    * @param string The namespace of the page
@@ -313,6 +321,177 @@
   }
   
   /**
+   * Fetches the wikitext or HTML source for the page.
+   * @return string
+   */
+  
+  function fetch_source()
+  {
+    if ( !$this->perms->get_permissions('view_source') )
+    {
+      return false;
+    }
+    if ( !$this->page_exists )
+    {
+      return '';
+    }
+    return $this->fetch_text();
+  }
+  
+  /**
+   * Updates the content of the page.
+   * @param string The new text for the page
+   * @param string A summary of edits made to the page.
+   * @return bool True on success, false on failure
+   */
+  
+  function update_page($text, $edit_summary = false)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // Create the page if it doesn't exist
+    if ( !$this->page_exists )
+    {
+      if ( !$this->create_page() )
+      {
+        return false;
+      }
+    }
+      
+    //
+    // Validation
+    //
+    
+    $page_id = $db->escape($this->page_id);
+    $namespace = $db->escape($this->namespace);
+    
+    $q = $db->sql_query('SELECT protected FROM ' . table_prefix . "pages WHERE urlname='$page_id' AND namespace='$namespace';");
+    if ( !$q )
+      $db->_die('PageProcess updating page content');
+    if ( $db->numrows() < 1 )
+    {
+      $this->raise_error('Page doesn\'t exist in the database');
+      return false;
+    }
+    
+    // Do we have permission to edit the page?
+    if ( !$this->perms->get_permissions('edit_page') )
+    {
+      $this->raise_error('You do not have permission to edit this page.');
+      return false;
+    }
+    
+    list($protection) = $db->fetchrow_num();
+    $db->free_result();
+    
+    if ( $protection == 1 )
+    {
+      // The page is protected - do we have permission to edit protected pages?
+      if ( !$this->perms->get_permissions('even_when_protected') )
+      {
+        $this->raise_error('This page is protected, and you do not have permission to edit protected pages.');
+        return false;
+      }
+    }
+    else if ( $protection == 2 )
+    {
+      // The page is semi-protected.
+      if (
+           ( !$session->user_logged_in || // Is the user logged in?
+             ( $session->user_logged_in && $session->reg_time + ( 4 * 86400 ) >= time() ) ) // If so, have they been registered for 4 days?
+           && !$this->perms->get_permissions('even_when_protected') ) // And of course, is there an ACL that overrides semi-protection?
+      {
+        $this->raise_error('This page is protected, and you do not have permission to edit protected pages.');
+        return false;
+      }
+    }
+    
+    // Protection validated
+    
+  }
+  
+  /**
+   * Creates the page if it doesn't already exist.
+   * @return bool True on success, false on failure.
+   */
+  
+  function create_page()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // Do we have permission to create the page?
+    if ( !$this->perms->get_permissions('create_page') )
+    {
+      $this->raise_error('You do not have permission to create this page.');
+      return false;
+    }
+    
+    // Does it already exist?
+    if ( $this->page_exists )
+    {
+      $this->raise_error('The page already exists.');
+      return false;
+    }
+    
+    // It's not in there. Perform validation.
+    
+    // We can't create special, admin, or external pages.
+    if ( $this->namespace == 'Special' || $this->namespace == 'Admin' || $this->namespace == 'Anonymous' )
+    {
+      $this->raise_error('You cannot create Special or Admin pages - they can\'t be stored in the database.');
+      return false;
+    }
+    
+    // Guess the proper title
+    $name = dirtify_page_id($this->page_id);
+    
+    // Check for the restricted Project: prefix
+    if ( substr($this->page_id, 0, 8) == 'Project:' )
+    {
+      $this->raise_error('The prefix "Project:" is reserved for internal links and can\'t be used on a page name.');
+      return false;
+    }
+    
+    // Validation successful - insert the page
+    
+    $metadata = array(
+        'urlname' => $this->page_id,
+        'namespace' => $this->namespace,
+        'name' => $name,
+        'special' => 0,
+        'visible' => 1,
+        'comments_on' => 1,
+        'protected' => ( $this->namespace == 'System' ? 1 : 0 ),
+        'delvotes' => 0,
+        'delvote_ips' => serialize(array()),
+        'wiki_mode' => 2
+      );
+    
+    $paths->add_page($metadata);
+    
+    $page_id = $db->escape($this->page_id);
+    $namespace = $db->escape($this->namespace);
+    $name = $db->escape($name);
+    $protect = ( $this->namespace == 'System' ) ? '1' : '0';
+    $blank_array = $db->escape(serialize(array()));
+    
+    // Query 1: Metadata entry
+    $q = $db->sql_query('INSERT INTO ' . table_prefix . "pages(name, urlname, namespace, protected, delvotes, delvote_ips, wiki_mode)\n"
+                        . "VALUES ( '$name', '$page_id', '$namespace', $protect, 0, '$blank_array', 2 );");
+    if ( !$q )
+      $db->_die('PageProcessor page creation - metadata stage');
+    
+    // Query 2: Text insertion
+    $q = $db->sql_query('INSERT INTO ' . table_prefix . "page_text(page_id, namespace, page_text)\n"
+                        . "VALUES ( '$page_id', '$namespace', '' );");
+    if ( !$q )
+      $db->_die('PageProcessor page creation - text stage');
+    
+    // Page created. We're good!
+    return true;
+  }
+  
+  /**
    * Sets internal variables.
    * @access private
    */
@@ -337,7 +516,7 @@
     }
     
     // Does the page "exist"?
-    if ( $paths->cpage['urlname_nons'] == $page_id && $paths->namespace == $namespace && !$paths->page_exists && ( $this->namespace != 'Admin' || ($this->namespace == 'Admin' && !function_exists($fname) ) ) )
+    if ( $paths->page_id == $page_id && $paths->namespace == $namespace && !$paths->page_exists && ( $this->namespace != 'Admin' || ($this->namespace == 'Admin' && !function_exists($fname) ) ) )
     {
       $this->page_exists = false;
     }
@@ -355,7 +534,7 @@
     {
       $page_id = str_replace('.2e', '.', $page_id);
       
-      if ( $paths->cpage['urlname_nons'] == $page_id && $paths->namespace == $namespace && !$paths->page_exists && ( $this->namespace != 'Admin' || ($this->namespace == 'Admin' && !function_exists($fname) ) ) )
+      if ( $paths->page_id == $page_id && $paths->namespace == $namespace && !$paths->page_exists && ( $this->namespace != 'Admin' || ($this->namespace == 'Admin' && !function_exists($fname) ) ) )
       {
         $this->page_exists = false;
       }
@@ -457,7 +636,7 @@
     
     if ( $this->revision_id )
     {
-      echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;"><b>Notice:</b><br />The page you are viewing was archived on '.date('F d, Y \a\t h:i a', $this->revision_id).'.<br /><a href="'.makeUrlNS($this->namespace, $this->page_id).'" onclick="ajaxReset(); return false;">View current version</a>  |  <a href="'.makeUrlNS($this->namespace, $this->pageid, 'do=rollback&amp;id='.$this->revision_id).'" onclick="ajaxRollback(\''.$this->revision_id.'\')">Restore this version</a></div><br />';
+      echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;"><b>Notice:</b><br />The page you are viewing was archived on '.date('F d, Y \a\t h:i a', $this->revision_id).'.<br /><a href="'.makeUrlNS($this->namespace, $this->page_id).'" onclick="ajaxReset(); return false;">View current version</a>  |  <a href="'.makeUrlNS($this->namespace, $this->page_id, 'do=rollback&amp;id='.$this->revision_id).'" onclick="ajaxRollback(\''.$this->revision_id.'\')">Restore this version</a></div><br />';
     }
     
     if ( $redir_enabled )
@@ -628,7 +807,7 @@
     global $email;
     
     $page_urlname = dirtify_page_id($this->page_id);
-    if ( $this->page_id == $paths->cpage['urlname_nons'] && $this->namespace == $paths->namespace )
+    if ( $this->page_id == $paths->page_id && $this->namespace == $paths->namespace )
     {
       $page_name = ( isset($paths->cpage['name']) ) ? $paths->cpage['name'] : $this->page_id;
     }
@@ -667,7 +846,7 @@
                            LEFT JOIN '.table_prefix.'comments AS c
                              ON ( ( c.user_id=u.user_id AND c.name=u.username AND c.approved=1 ) OR ( c.comment_id IS NULL AND c.approved IS NULL ) )
                            WHERE u.username=\'' . $db->escape($target_username) . '\'
-                           GROUP BY u.user_id;');
+                           GROUP BY u.username, u.user_id, u.real_name, u.email, u.reg_time,x.user_id, x.user_aim, x.user_yahoo, x.user_msn, x.user_xmpp, x.user_homepage, x.user_location, x.user_job, x.user_hobbies, x.email_public;');
     if ( !$q )
       $db->_die();
     
@@ -805,7 +984,13 @@
     
     echo '</div>';
     echo '</td></tr>';
-            
+    
+    $code = $plugins->setHook('userpage_sidebar_left');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
     echo '  </table>
           </div>';
     
@@ -921,6 +1106,12 @@
       echo '<tr><td class="'.$class.'">' . htmlspecialchars($target_username) . ' hasn\'t posted any real-life contact information.</td></tr>';
     }
     
+    $code = $plugins->setHook('userpage_sidebar_right');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
     echo '  </table>
           </div>';
           
@@ -1247,6 +1438,30 @@
     
   }
   
+  /**
+   * Raises an error.
+   * @param string Error string
+   */
+   
+  function raise_error($string)
+  {
+    if ( !is_string($string) )
+      return false;
+    $this->_errors[] = $string;
+  }
+  
+  /**
+   * Retrieves the latest error from the error stack and returns it ('pops' the error stack)
+   * @return string
+   */
+  
+  function pop_error()
+  {
+    if ( count($this->_errors) < 1 )
+      return false;
+    return array_pop($this->_errors);
+  }
+  
 } // class PageProcessor
 
 ?>
--- a/includes/pageutils.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/pageutils.php	Wed Dec 19 22:55:40 2007 -0500
@@ -203,7 +203,7 @@
         else echo ' Return to the <a href="'.makeUrl(getConfig('main_page')).'">homepage</a>.</p>';
         if ( $session->get_permissions('history_rollback') )
         {
-          $e = $db->sql_query('SELECT * FROM ' . table_prefix.'logs WHERE action=\'delete\' AND page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $pid[1] . '\' ORDER BY time_id DESC;');
+          $e = $db->sql_query('SELECT * FROM ' . table_prefix.'logs WHERE action=\'delete\' AND page_id=\'' . $paths->page_id . '\' AND namespace=\'' . $pid[1] . '\' ORDER BY time_id DESC;');
           if ( !$e )
           {
             $db->_die('The deletion log could not be selected.');
@@ -379,11 +379,11 @@
     
     $msg = $db->escape($message);
     
-    $minor = $minor ? 'true' : 'false';
-    $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $paths->cpage['urlname_nons'] . '\', \'' . $paths->namespace . '\', \'' . $msg . '\', \'' . $uid . '\', \'' . $session->username . '\', \'' . $db->escape(htmlspecialchars($summary)) . '\', ' . $minor . ');';
+    $minor = $minor ? ENANO_SQL_BOOLEAN_TRUE : ENANO_SQL_BOOLEAN_FALSE;
+    $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $paths->page_id . '\', \'' . $paths->namespace . '\', ' . ENANO_SQL_MULTISTRING_PRFIX . '\'' . $msg . '\', \'' . $uid . '\', \'' . $session->username . '\', \'' . $db->escape(htmlspecialchars($summary)) . '\', ' . $minor . ');';
     if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
     
-    $q = 'UPDATE ' . table_prefix.'page_text SET page_text=\'' . $msg . '\',char_tag=\'' . $uid . '\' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';';
+    $q = 'UPDATE ' . table_prefix.'page_text SET page_text=' . ENANO_SQL_MULTISTRING_PRFIX . '\'' . $msg . '\',char_tag=\'' . $uid . '\' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';';
     $e = $db->sql_query($q);
     if(!$e) $db->_die('Enano was unable to save the page contents. Your changes have been lost <tt>:\'(</tt>.');
       
@@ -444,7 +444,7 @@
     if ( !$name )
       $name = str_replace('_', ' ', $page_id);
     $regex = '#^([A-z0-9 _\-\.\/\!\@\(\)]*)$#is';
-    if(!preg_match($regex, $page))
+    if(!preg_match($regex, $name))
     {
       //echo '<b>Notice:</b> PageUtils::createPage: Name contains invalid characters<br />';
       return 'Name contains invalid characters';
@@ -664,7 +664,7 @@
     }
     $db->free_result();
     echo '<h3>' . $lang->get('history_heading_other') . '</h3>';
-    $q = 'SELECT time_id,action,date_string,page_id,namespace,author,edit_summary,minor_edit FROM ' . table_prefix.'logs WHERE log_type=\'page\' AND action!=\'edit\' AND page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $paths->namespace . '\' ORDER BY time_id DESC;';
+    $q = 'SELECT time_id,action,date_string,page_id,namespace,author,edit_summary,minor_edit FROM ' . table_prefix.'logs WHERE log_type=\'page\' AND action!=\'edit\' AND page_id=\'' . $paths->page_id . '\' AND namespace=\'' . $paths->namespace . '\' ORDER BY time_id DESC;';
     if ( !$db->sql_query($q) )
     {
       $db->_die('The history data for the page "' . htmlspecialchars($paths->cpage['name']) . '" could not be selected.');
@@ -1403,7 +1403,7 @@
     }
     if( ( $session->get_permissions('rename') && ( ( $prot && $session->get_permissions('even_when_protected') ) || !$prot ) ) && ( $paths->namespace != 'Special' && $paths->namespace != 'Admin' ))
     {
-      $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'rename\', \'' . $db->escape($paths->cpage['urlname_nons']) . '\', \'' . $paths->namespace . '\', \'' . $db->escape($session->username) . '\', \'' . $db->escape($paths->cpage['name']) . '\')');
+      $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'rename\', \'' . $db->escape($paths->page_id) . '\', \'' . $paths->namespace . '\', \'' . $db->escape($session->username) . '\', \'' . $db->escape($paths->cpage['name']) . '\')');
       if ( !$e )
       {
         $db->_die('The page title could not be updated.');
@@ -1459,7 +1459,8 @@
       if(!$e) $db->_die('The current page text could not be selected; as a result, creating the backup of the page failed. Please make a backup copy of the page by clicking Edit this page and then clicking Save Changes.');
       $row = $db->fetchrow();
       $db->free_result();
-      $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape($row['page_text']) . '\', \'' . $row['char_tag'] . '\', \'' . $session->username . '\', \''."Automatic backup created when logs were purged".'\', '.'false'.');';
+      $minor_edit = ( ENANO_DBLAYER == 'MYSQL' ) ? 'false' : '0';
+      $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape($row['page_text']) . '\', \'' . $row['char_tag'] . '\', \'' . $session->username . '\', \''."Automatic backup created when logs were purged".'\', '.$minor_edit.');';
       if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
     }
     return $lang->get('ajax_clearlogs_success');
@@ -1652,7 +1653,7 @@
     
     ob_start();
     $_ob = '';
-    $e = $db->sql_query('SELECT category_id FROM ' . table_prefix.'categories WHERE page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $paths->namespace . '\'');
+    $e = $db->sql_query('SELECT category_id FROM ' . table_prefix.'categories WHERE page_id=\'' . $paths->page_id . '\' AND namespace=\'' . $paths->namespace . '\'');
     if(!$e) jsdie('Error selecting category information for current page: '.mysql_error());
     $cat_current = Array();
     while($r = $db->fetchrow())
@@ -2246,7 +2247,7 @@
               <p><label><input name="data[scope]" value="entire_site" type="radio" /> ' . $lang->get('acl_radio_scope_wholesite') . '</p>
               <div style="margin: 0 auto 0 0; text-align: right;">
                 <input name="data[mode]" value="seltarget" type="hidden" />
-                <input type="hidden" name="data[page_id]" value="' . $paths->cpage['urlname_nons'] . '" />
+                <input type="hidden" name="data[page_id]" value="' . $paths->page_id . '" />
                 <input type="hidden" name="data[namespace]" value="' . $paths->namespace . '" />
                 <input type="submit" value="' . htmlspecialchars($lang->get('etc_wizard_next')) . '" />
               </div>';
--- a/includes/paths.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/paths.php	Wed Dec 19 22:55:40 2007 -0500
@@ -18,7 +18,7 @@
  */
  
 class pathManager {
-  var $pages, $custom_page, $cpage, $page, $fullpage, $page_exists, $namespace, $nslist, $admin_tree, $wiki_mode, $page_protected, $template_cache, $anonymous_page;
+  var $pages, $custom_page, $cpage, $page, $fullpage, $page_exists, $page_id, $namespace, $nslist, $admin_tree, $wiki_mode, $page_protected, $template_cache, $anonymous_page;
   function __construct()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
@@ -293,6 +293,7 @@
     {
       $this->page_exists = true;
       $this->cpage = $this->pages[$this->page];
+      $this->page_id =& $this->cpage['urlname_nons'];
       $this->namespace = $this->cpage['namespace'];
       if(!isset($this->cpage['wiki_mode'])) $this->cpage['wiki_mode'] = 2;
       
@@ -341,7 +342,7 @@
       $pid_cleaned = sanitize_page_id($this->page);
       if ( $pid_cleaned != $this->page )
       {
-        redirect($pid_cleaned, 'Sanitizer message', 'page id sanitized', 0);
+        redirect(makeUrl($pid_cleaned), 'Sanitizer message', 'page id sanitized', 0);
       }
       
       if ( !is_array($this->cpage) )
@@ -375,6 +376,7 @@
         }
       }
       $this->namespace = $this->cpage['namespace'];
+      $this->page_id =& $this->cpage['urlname_nons'];
       
       if($this->namespace=='System') 
       {
@@ -701,7 +703,12 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     // sha1('') returns "da39a3ee5e6b4b0d3255bfef95601890afd80709"
-    $texts = 'SELECT t.page_text, CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id) AS page_idstring, t.page_id, t.namespace FROM '.table_prefix.'page_text AS t
+    
+    $concat_column = ( ENANO_DBLAYER == 'MYSQL' ) ?
+      'CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id)' :
+      "'ns=' || t.namespace || ';pid=' || t.page_id";
+    
+    $texts = 'SELECT t.page_text, ' . $concat_column . ' AS page_idstring, t.page_id, t.namespace FROM '.table_prefix.'page_text AS t
                            LEFT JOIN '.table_prefix.'pages AS p
                              ON ( t.page_id=p.urlname AND t.namespace=p.namespace )
                            WHERE p.namespace=t.namespace
@@ -815,13 +822,26 @@
     $search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>$row['page_text'] . ' ' . $this->pages[$idstring]['name']));
     $new_index = $search->index;
     
-    $keys = array_keys($search->index);
-    foreach($keys as $i => $k)
+    if ( ENANO_DBLAYER == 'MYSQL' )
     {
-      $c =& $keys[$i];
-      $c = hexencode($c, '', '');
+      $keys = array_keys($search->index);
+      foreach($keys as $i => $k)
+      {
+        $c =& $keys[$i];
+        $c = hexencode($c, '', '');
+      }
+      $keys = "word=0x" . implode ( " OR word=0x", $keys ) . "";
     }
-    $keys = "word=0x" . implode ( " OR word=0x", $keys ) . "";
+    else
+    {
+      $keys = array_keys($search->index);
+      foreach($keys as $i => $k)
+      {
+        $c =& $keys[$i];
+        $c = $db->escape($c);
+      }
+      $keys = "word='" . implode ( "' OR word='", $keys ) . "'";
+    }
     
     $query = $db->sql_query('SELECT word,page_names FROM '.table_prefix.'search_index WHERE '.$keys.';');
     
@@ -995,5 +1015,5 @@
   }
   
 }
-  
+
 ?>
--- a/includes/plugins.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/plugins.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/render.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/render.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * render.php - handles fetching pages and parsing them into HTML
  *
@@ -44,7 +44,7 @@
     
     $perms =& $session;
     
-    if ( $page_id != $paths->cpage['urlname_nons'] || $namespace != $paths->namespace )
+    if ( $page_id != $paths->page_id || $namespace != $paths->namespace )
     {
       unset($perms);
       unset($perms); // PHP <5.1.5 Zend bug
@@ -889,7 +889,7 @@
         {
           $side = ( $clear == '|left' ) ? 'left' : 'right';
           $opposite = ( $clear == '|left' ) ? 'right' : 'left';
-          $clear_text .= "float: $side; margin-$opposite: 20px;";
+          $clear_text .= "float: $side; margin-$opposite: 20px; width: {$r_width}px;";
           $complete_tag .= 'style="' . $clear_text . '" ';
         }
         $complete_tag .= '>';
--- a/includes/search.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/search.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * search.php - algorithm used to search pages
  *
@@ -194,7 +194,7 @@
       $where_any[] = $term;
     }
 
-    $col_word = ( $case_sensitive ) ? 'word' : 'lcase(word)';
+    $col_word = ( $case_sensitive ) ? 'word' : ENANO_SQLFUNC_LOWERCASE . '(word)';
     $where_any = ( count($where_any) > 0 ) ? '( ' . $col_word . ' = \'' . implode('\' OR ' . $col_word . ' = \'', $where_any) . '\' )' : '';
 
     // generate query
@@ -353,10 +353,14 @@
 
   // 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)';
+  $text_col = ( $case_sensitive ) ? 'page_text' : ENANO_SQLFUNC_LOWERCASE . '(page_text)';
+  $name_col = ( $case_sensitive ) ? 'name' : ENANO_SQLFUNC_LOWERCASE . '(name)';
+  $text_col_join = ( $case_sensitive ) ? 't.page_text' : ENANO_SQLFUNC_LOWERCASE . '(t.page_text)';
+  $name_col_join = ( $case_sensitive ) ? 'p.name' : ENANO_SQLFUNC_LOWERCASE . '(p.name)';
+    
+  $concat_column = ( ENANO_DBLAYER == 'MYSQL' ) ?
+    'CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id)' :
+    "'ns=' || t.namespace || ';pid=' || t.page_id";
 
   if ( count($query_phrase['any']) > 0 || count($query_phrase['req']) > 0 )
   {
@@ -384,7 +388,7 @@
     $and_clause = ( $where_any != '' ) ? 'AND ' : '';
     $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"
+    $sql = 'SELECT ' . $concat_column . ' 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;";
@@ -441,7 +445,7 @@
   {
     $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) . '\' )';
+  $text_where = '( ' . $concat_column . ' = \'' . implode('\' OR ' . $concat_column . ' = \'', $text_where) . '\' )';
 
   if ( count($query['not']) > 0 )
     $text_where .= ' AND';
@@ -456,7 +460,7 @@
   }
   $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
+  $sql = 'SELECT ' . $concat_column . ' 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;";
@@ -569,7 +573,7 @@
   arsort($scores);
 
   // Divisor for calculating relevance scores
-  $divisor = ( count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query_phrase['not']) ) * 1.5;
+  $divisor = ( count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query['not']) ) * 1.5;
 
   foreach ( $scores as $page_id => $score )
   {
@@ -847,7 +851,7 @@
             // Navigate backwards until a space character is found
             $chunk = substr($pt, 0, ( $i - 75 ));
             $final_chunk = $chunk;
-            for ( $j = strlen($chunk); $j > 0; $j = $j - 1 )
+            for ( $j = strlen($chunk) - 1; $j > 0; $j = $j - 1 )
             {
               if ( in_array($chunk{$j}, $space_chars) )
               {
--- a/includes/sessions.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/sessions.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * sessions.php - everything related to security and user management
  *
@@ -663,7 +663,7 @@
     $db_username       = $this->prepare_text($username);
     
     // Select the user data from the table, and decrypt that so we can verify the password
-    $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';');
+    $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';');
     if($db->numrows() < 1)
     {
       // This wasn't logged in <1.0.2, dunno how it slipped through
@@ -881,7 +881,7 @@
     $success = false;
     
     // Retrieve the real password from the database
-    $this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';');
+    $this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username)=\''.$this->prepare_text(strtolower($username)).'\';');
     if($db->numrows() < 1)
     {
       // This wasn't logged in <1.0.2, dunno how it slipped through
@@ -1209,7 +1209,8 @@
                              . '    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;');
+                             . '  GROUP BY u.user_id,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,x.user_id, x.user_aim, x.user_yahoo, x.user_msn, x.user_xmpp, x.user_homepage, x.user_location, x.user_job, x.user_hobbies, x.email_public;');
+    
     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
@@ -1219,7 +1220,7 @@
                                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;');
+                             GROUP BY u.user_id,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;');
     }
     if($db->numrows() < 1)
     {
@@ -1572,14 +1573,28 @@
     if ( $this->user_logged_in )
     {
       // check by IP, email, and username
-      $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE \n"
-            . "    ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR \n"
-            . "    ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' REGEXP ban_value ) OR \n"
-            . "    ( ban_type = " . BAN_USER  . " AND is_regex = 0 AND ban_value = '{$this->username}' ) OR \n"
-            . "    ( ban_type = " . BAN_USER  . " AND is_regex = 1 AND '{$this->username}' REGEXP ban_value ) OR \n"
-            . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 0 AND ban_value = '{$this->email}' ) OR \n"
-            . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 1 AND '{$this->email}' REGEXP ban_value ) \n"
-            . "  ORDER BY ban_type ASC;";
+      if ( ENANO_DBLAYER == 'MYSQL' )
+      {
+        $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE \n"
+              . "    ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR \n"
+              . "    ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' REGEXP ban_value ) OR \n"
+              . "    ( ban_type = " . BAN_USER  . " AND is_regex = 0 AND ban_value = '{$this->username}' ) OR \n"
+              . "    ( ban_type = " . BAN_USER  . " AND is_regex = 1 AND '{$this->username}' REGEXP ban_value ) OR \n"
+              . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 0 AND ban_value = '{$this->email}' ) OR \n"
+              . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 1 AND '{$this->email}' REGEXP ban_value ) \n"
+              . "  ORDER BY ban_type ASC;";
+      }
+      else if ( ENANO_DBLAYER == 'PGSQL' )
+      {
+        $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE \n"
+              . "    ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR \n"
+              . "    ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' ~ ban_value ) OR \n"
+              . "    ( ban_type = " . BAN_USER  . " AND is_regex = 0 AND ban_value = '{$this->username}' ) OR \n"
+              . "    ( ban_type = " . BAN_USER  . " AND is_regex = 1 AND '{$this->username}' ~ ban_value ) OR \n"
+              . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 0 AND ban_value = '{$this->email}' ) OR \n"
+              . "    ( ban_type = " . BAN_EMAIL . " AND is_regex = 1 AND '{$this->email}' ~ ban_value ) \n"
+              . "  ORDER BY ban_type ASC;";
+      }
       $q = $this->sql($sql);
       if ( $db->numrows() > 0 )
       {
@@ -1610,10 +1625,20 @@
     else
     {
       // check by IP only
-      $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE
-                ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR
-                ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' REGEXP ban_value )
-              ORDER BY ban_type ASC;";
+      if ( ENANO_DBLAYER == 'MYSQL' )
+      {
+        $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE
+                  ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR
+                  ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' REGEXP ban_value )
+                ORDER BY ban_type ASC;";
+      }
+      else if ( ENANO_DBLAYER == 'PGSQL' )
+      {
+        $sql = "SELECT $col_reason, ban_value, ban_type, is_regex FROM " . table_prefix . "banlist WHERE
+                  ( ban_type = " . BAN_IP    . " AND is_regex = 0 ) OR
+                  ( ban_type = " . BAN_IP    . " AND is_regex = 1 AND '{$_SERVER['REMOTE_ADDR']}' ~ ban_value )
+                ORDER BY ban_type ASC;";
+      }
       $q = $this->sql($sql);
       if ( $db->numrows() > 0 )
       {
@@ -1673,7 +1698,7 @@
     $real_name = $this->prepare_text($real_name);
     
     $nameclause = ( $real_name != '' ) ? ' OR real_name=\''.$real_name.'\'' : '';
-    $q = $this->sql('SELECT * FROM '.table_prefix.'users WHERE lcase(username)=\''.strtolower($username).'\' OR email=\''.$email.'\''.$nameclause.';');
+    $q = $this->sql('SELECT * FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username)=\''.strtolower($username).'\' OR email=\''.$email.'\''.$nameclause.';');
     if($db->numrows() > 0)
     {
       $r = 'The ';
@@ -1958,7 +1983,7 @@
     }
     elseif(is_string($user))
     {
-      $q = $this->sql('SELECT user_id,username,email FROM '.table_prefix.'users WHERE lcase(username)=lcase(\''.$db->escape($user).'\');');
+      $q = $this->sql('SELECT user_id,username,email FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username)=' . ENANO_SQLFUNC_LOWERCASE . '(\''.$db->escape($user).'\');');
     }
     else
     {
@@ -2459,7 +2484,7 @@
     }
     
     // PAGE group info
-    $pg_list = $paths->get_page_groups($paths->cpage['urlname_nons'], $paths->namespace);
+    $pg_list = $paths->get_page_groups($paths->page_id, $paths->namespace);
     $pg_info = '';
     foreach ( $pg_list as $g_id )
     {
@@ -2479,7 +2504,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\n    ", $q) . " )\n  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->page_id).'\' AND namespace=\''.$db->escape($paths->namespace).'\' ) )     
       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
     $q = $this->sql($bs);
     if ( $row = $db->fetchrow() )
@@ -2686,7 +2711,7 @@
   {
     $code = $this->generate_captcha_code($len);
     $hash = md5(microtime() . mt_rand());
-    $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key,salt,auth_level,source_ip,user_id) VALUES(\''.$hash.'\', \''.$s.'\', -1, \''.ip2hex($_SERVER['REMOTE_ADDR']).'\', -2);');
+    $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key,salt,auth_level,source_ip,user_id) VALUES(\''.$hash.'\', \'\', -1, \''.ip2hex($_SERVER['REMOTE_ADDR']).'\', -2);');
     return $hash;
   }
   
@@ -2921,7 +2946,7 @@
     }
     
     // Build a query to grab ACL info
-    $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE ' . "\n"
+    $bs = 'SELECT rules,target_type,target_id FROM '.table_prefix.'acl WHERE ' . "\n"
           . '  ( ';
     $q = Array();
     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$session->user_id.' )';
--- a/includes/stats.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/stats.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  * stats.php - handles statistics for pages (disablable in the admin CP)
  *
@@ -22,7 +22,7 @@
   {
     if(!$page_id || !$namespace)
     {
-      $page_id = $paths->cpage['urlname_nons'];
+      $page_id = $paths->page_id;
       $namespace = $paths->namespace;
     }
     if($namespace == 'Special' || $namespace == 'Admin') 
--- a/includes/tagcloud.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/tagcloud.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/template.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/template.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -261,7 +261,7 @@
     if ( $session->get_permissions('read') && getConfig('enable_comments')=='1' && $paths->namespace != 'Special' && $paths->namespace != 'Admin' && $paths->cpage['comments_on'] == 1 )
     {
       
-      $e = $db->sql_query('SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\''.$paths->namespace.'\';');
+      $e = $db->sql_query('SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$paths->page_id.'\' AND namespace=\''.$paths->namespace.'\';');
       if ( !$e )
       {
         $db->_die();
@@ -646,7 +646,7 @@
     
     $this->tpl_bool['stupid_mode'] = false;
     
-    $this->tpl_bool['in_admin'] = ( ( $paths->cpage['urlname_nons'] == 'Administration' && $paths->namespace == 'Special' ) || $paths->namespace == 'Admin' );
+    $this->tpl_bool['in_admin'] = ( ( $paths->page_id == 'Administration' && $paths->namespace == 'Special' ) || $paths->namespace == 'Admin' );
     
     $p = ( isset($_GET['printable']) ) ? '/printable' : '';
     
@@ -768,7 +768,7 @@
       'ADMIN_LINK'=>$admin_link,
       'THEME_LINK'=>$theme_link,
       'SEARCH_ACTION'=>makeUrlNS('Special', 'Search'),
-      'INPUT_TITLE'=>( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->nslist[$paths->namespace] . $paths->cpage['urlname_nons'] ) . '" />' : ''),
+      'INPUT_TITLE'=>( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->nslist[$paths->namespace] . $paths->page_id ) . '" />' : ''),
       'INPUT_AUTH'=>( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : ''),
       'TEMPLATE_DIR'=>scriptPath.'/themes/'.$this->theme,
       'THEME_ID'=>$this->theme,
@@ -776,7 +776,7 @@
       'JS_DYNAMIC_VARS'=>$js_dynamic,
       'UNREAD_PMS'=>$session->unread_pms,
       'URL_ABOUT_ENANO' => makeUrlNS('Special', 'About_Enano', '', true),
-      'REPORT_URI' => makeUrl($paths->page, 'do=sql_report', true)
+      'REPORT_URI' => makeUrl($paths->fullpage, 'do=sql_report', true)
       );
     
     foreach ( $paths->nslist as $ns_id => $ns_prefix )
@@ -1101,7 +1101,7 @@
     //
     
     // System messages
-    $text = preg_replace('/<!-- SYSMSG ([A-z0-9\._-]+?) -->/is', '\' . $this->tplWikiFormat($pages->sysMsg(\'\\1\')) . \'', $text);
+    $text = preg_replace('/<!-- SYSMSG ([A-z0-9\._-]+?) -->/is', '\' . $this->tplWikiFormat($paths->sysMsg(\'\\1\')) . \'', $text);
     
     // Template variables
     $text = preg_replace('/\{([A-z0-9_-]+?)\}/is', '\' . $this->tpl_strings[\'\\1\'] . \'', $text);
@@ -1764,7 +1764,7 @@
   function notify_unread_pms()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if ( ( $paths->cpage['urlname_nons'] == 'PrivateMessages' || $paths->cpage['urlname_nons'] == 'Preferences' ) && $paths->namespace == 'Special' )
+    if ( ( $paths->page_id == 'PrivateMessages' || $paths->page_id == 'Preferences' ) && $paths->namespace == 'Special' )
     {
       return '';
     }
--- a/includes/wikiengine/Tables.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/includes/wikiengine/Tables.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/index.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/index.php	Wed Dec 19 22:55:40 2007 -0500
@@ -23,7 +23,7 @@
   $aggressive_optimize_html = false;
   
   global $do_gzip;
-  $do_gzip = true;
+  $do_gzip = false;
   
   if(isset($_SERVER['PATH_INFO'])) $v = $_SERVER['PATH_INFO'];
   elseif(isset($_GET['title'])) $v = $_GET['title'];
@@ -64,7 +64,7 @@
     case 'view':
       // echo PageUtils::getpage($paths->page, true, ( (isset($_GET['oldid'])) ? $_GET['oldid'] : false ));
       $rev_id = ( (isset($_GET['oldid'])) ? intval($_GET['oldid']) : 0 );
-      $page = new PageProcessor( $paths->cpage['urlname_nons'], $paths->namespace, $rev_id );
+      $page = new PageProcessor( $paths->page_id, $paths->namespace, $rev_id );
       $page->send_headers = true;
       $pagepass = ( isset($_REQUEST['pagepass']) ) ? sha1($_REQUEST['pagepass']) : '';
       $page->password = $pagepass;
@@ -79,7 +79,7 @@
         default:
           $act = ( isset ($_GET['action']) ) ? $_GET['action'] : false;
           $id = ( isset ($_GET['id']) ) ? intval($_GET['id']) : -1;
-          echo PageUtils::comments_html($paths->cpage['urlname_nons'], $paths->namespace, $act, Array('id'=>$id));
+          echo PageUtils::comments_html($paths->page_id, $paths->namespace, $act, Array('id'=>$id));
           break;
         case 'postcomment':
           if(empty($_POST['name']) ||
@@ -88,8 +88,8 @@
              ) { echo 'Invalid request'; break; }
           $cid = ( isset($_POST['captcha_id']) ) ? $_POST['captcha_id'] : false;
           $cin = ( isset($_POST['captcha_input']) ) ? $_POST['captcha_input'] : false;
-          PageUtils::addcomment($paths->cpage['urlname_nons'], $paths->namespace, $_POST['name'], $_POST['subj'], $_POST['text'], $cin, $cid); // All filtering, etc. is handled inside this method
-          echo PageUtils::comments_html($paths->cpage['urlname_nons'], $paths->namespace);
+          PageUtils::addcomment($paths->page_id, $paths->namespace, $_POST['name'], $_POST['subj'], $_POST['text'], $cin, $cid); // All filtering, etc. is handled inside this method
+          echo PageUtils::comments_html($paths->page_id, $paths->namespace);
           break;
         case 'editcomment':
           if(!isset($_GET['id']) || ( isset($_GET['id']) && !preg_match('#^([0-9]+)$#', $_GET['id']) )) { echo '<p>Invalid comment ID</p>'; break; }
@@ -108,16 +108,16 @@
           break;
         case 'savecomment':
           if(empty($_POST['subj']) || empty($_POST['text'])) { echo '<p>Invalid request</p>'; break; }
-          $r = PageUtils::savecomment_neater($paths->cpage['urlname_nons'], $paths->namespace, $_POST['subj'], $_POST['text'], (int)$_POST['id']);
+          $r = PageUtils::savecomment_neater($paths->page_id, $paths->namespace, $_POST['subj'], $_POST['text'], (int)$_POST['id']);
           if($r != 'good') { echo "<pre>$r</pre>"; break; }
-          echo PageUtils::comments_html($paths->cpage['urlname_nons'], $paths->namespace);
+          echo PageUtils::comments_html($paths->page_id, $paths->namespace);
           break;
         case 'deletecomment':
           if(!empty($_GET['id']))
           {
-            PageUtils::deletecomment_neater($paths->cpage['urlname_nons'], $paths->namespace, (int)$_GET['id']);
+            PageUtils::deletecomment_neater($paths->page_id, $paths->namespace, (int)$_GET['id']);
           }
-          echo PageUtils::comments_html($paths->cpage['urlname_nons'], $paths->namespace);
+          echo PageUtils::comments_html($paths->page_id, $paths->namespace);
           break;
       }
       $template->footer();
@@ -130,7 +130,7 @@
       }
       if(isset($_POST['_save']))
       {
-        $e = PageUtils::savepage($paths->cpage['urlname_nons'], $paths->namespace, $_POST['page_text'], $_POST['edit_summary'], isset($_POST['minor']));
+        $e = PageUtils::savepage($paths->page_id, $paths->namespace, $_POST['page_text'], $_POST['edit_summary'], isset($_POST['minor']));
         if ( $e == 'good' )
         {
           redirect(makeUrl($paths->page), $lang->get('editor_msg_save_success_title'), $lang->get('editor_msg_save_success_body'), 3);
@@ -171,7 +171,7 @@
       break;
     case 'viewsource':
       $template->header();
-      $text = RenderMan::getPage($paths->cpage['urlname_nons'], $paths->namespace, 0, false, false, false, false);
+      $text = RenderMan::getPage($paths->page_id, $paths->namespace, 0, false, false, false, false);
       echo '
         <form action="'.makeUrl($paths->page, 'do=edit').'" method="post">
         <br />
@@ -183,7 +183,7 @@
       $template->footer();
       break;
     case 'history':
-      $hist = PageUtils::histlist($paths->cpage['urlname_nons'], $paths->namespace);
+      $hist = PageUtils::histlist($paths->page_id, $paths->namespace);
       $template->header();
       echo $hist;
       $template->footer();
@@ -200,7 +200,7 @@
       if(isset($_POST['__enanoSaveButton']))
       {
         unset($_POST['__enanoSaveButton']);
-        $val = PageUtils::catsave($paths->cpage['urlname_nons'], $paths->namespace, $_POST);
+        $val = PageUtils::catsave($paths->page_id, $paths->namespace, $_POST);
         if($val == 'GOOD')
         {
           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;
@@ -213,7 +213,7 @@
         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;
       }
       $template->header();
-      $c = PageUtils::catedit_raw($paths->cpage['urlname_nons'], $paths->namespace);
+      $c = PageUtils::catedit_raw($paths->page_id, $paths->namespace);
       echo $c[1];
       $template->footer();
       break;
@@ -227,7 +227,7 @@
       if(!empty($_POST['reason']))
       {
         if(!preg_match('#^([0-2]*){1}$#', $_POST['level'])) die_friendly('Error protecting page', '<p>Request validation failed</p>');
-        PageUtils::protect($paths->cpage['urlname_nons'], $paths->namespace, intval($_POST['level']), $_POST['reason']);
+        PageUtils::protect($paths->page_id, $paths->namespace, intval($_POST['level']), $_POST['reason']);
         
         die_friendly($lang->get('page_protect_lbl_success_title'), '<p>' . $lang->get('page_protect_lbl_success_body', array( 'page_link' => makeUrl($paths->page) )) . '</p>');
       }
@@ -262,7 +262,7 @@
     case 'rename':
       if(!empty($_POST['newname']))
       {
-        $r = PageUtils::rename($paths->cpage['urlname_nons'], $paths->namespace, $_POST['newname']);
+        $r = PageUtils::rename($paths->page_id, $paths->namespace, $_POST['newname']);
         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();
@@ -284,7 +284,7 @@
       if(isset($_POST['_downthejohn']))
       {
         $template->header();
-          $result = PageUtils::flushlogs($paths->cpage['urlname_nons'], $paths->namespace);
+          $result = PageUtils::flushlogs($paths->page_id, $paths->namespace);
           echo '<p>'.$result.' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>';
         $template->footer();
         break;
@@ -302,7 +302,7 @@
       if(isset($_POST['_ballotbox']))
       {
         $template->header();
-        $result = PageUtils::delvote($paths->cpage['urlname_nons'], $paths->namespace);
+        $result = PageUtils::delvote($paths->page_id, $paths->namespace);
         echo '<p>'.$result.' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>';
         $template->footer();
         break;
@@ -334,7 +334,7 @@
       if(isset($_POST['_youmaylivealittlelonger']))
       {
         $template->header();
-          $result = PageUtils::resetdelvotes($paths->cpage['urlname_nons'], $paths->namespace);
+          $result = PageUtils::resetdelvotes($paths->page_id, $paths->namespace);
           echo '<p>'.$result.' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>';
         $template->footer();
         break;
@@ -361,7 +361,7 @@
         else
         {
           $template->header();
-            $result = PageUtils::deletepage($paths->cpage['urlname_nons'], $paths->namespace, $reason);
+            $result = PageUtils::deletepage($paths->page_id, $paths->namespace, $reason);
             echo '<p>'.$result.' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a>.</p>';
           $template->footer();
           break;
@@ -390,7 +390,7 @@
         {
           die_friendly('Invalid request', '<p>Level not specified</p>');
         }
-        $q = $db->sql_query('UPDATE '.table_prefix.'pages SET wiki_mode=' . $level . ' WHERE urlname=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND namespace=\'' . $paths->namespace . '\';');
+        $q = $db->sql_query('UPDATE '.table_prefix.'pages SET wiki_mode=' . $level . ' WHERE urlname=\'' . $db->escape($paths->page_id) . '\' AND namespace=\'' . $paths->namespace . '\';');
         if ( !$q )
           $db->_die();
         redirect(makeUrl($paths->page), htmlspecialchars($paths->cpage['name']), $lang->get('page_wikimode_success_redirect'), 2);
@@ -426,7 +426,7 @@
       if(!$id1 || !$id2) { echo '<p>Invalid request.</p>'; $template->footer(); break; }
       if(!preg_match('#^([0-9]+)$#', (string)$_GET['diff1']) ||
          !preg_match('#^([0-9]+)$#', (string)$_GET['diff2']  )) { echo '<p>SQL injection attempt</p>'; $template->footer(); break; }
-      echo PageUtils::pagediff($paths->cpage['urlname_nons'], $paths->namespace, $id1, $id2);
+      echo PageUtils::pagediff($paths->page_id, $paths->namespace, $id1, $id2);
       $template->footer();
       break;
     case 'detag':
@@ -438,7 +438,7 @@
       {
         die_friendly($lang->get('etc_invalid_request_short'), '<p>' . $lang->get('page_detag_err_page_exists') . '</p>');
       }
-      $q = $db->sql_query('DELETE FROM '.table_prefix.'tags WHERE page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND namespace=\'' . $paths->namespace . '\';');
+      $q = $db->sql_query('DELETE FROM '.table_prefix.'tags WHERE page_id=\'' . $db->escape($paths->page_id) . '\' AND namespace=\'' . $paths->namespace . '\';');
       if ( !$q )
         $db->_die('Detag query, index.php:'.__LINE__);
       die_friendly($lang->get('page_detag_success_title'), '<p>' . $lang->get('page_detag_success_body') . '</p>');
@@ -449,7 +449,7 @@
       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 = new PageProcessor( $paths->page_id, $paths->namespace, $rev_id );
       $page->send_headers = true;
       $pagepass = ( isset($_REQUEST['pagepass']) ) ? sha1($_REQUEST['pagepass']) : '';
       $page->password = $pagepass;
--- a/install.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/install.php	Wed Dec 19 22:55:40 2007 -0500
@@ -45,6 +45,8 @@
 global $_starttime, $this_page, $sideinfo;
 $_starttime = microtime(true);
 
+global $db;
+
 // Determine directory (special case for development servers)
 if ( strpos(__FILE__, '/repo/') && file_exists('.enanodev') )
 {
@@ -185,6 +187,9 @@
 
 function stg_mysql_connect($act_get = false)
 {
+  global $db;
+  $db = new mysql();
+  
   static $conn = false;
   if ( $act_get )
     return $conn;
@@ -309,15 +314,114 @@
       return false;
     }
   }
+  // initialize DBAL
+  $db->connect(true, $_POST['db_host'], $db_user, $db_pass, $db_name);
+  // connected and database exists
+  return true;
+}
+
+function stg_pgsql_connect($act_get = false)
+{
+  global $db;
+  $db = new postgresql();
+  
+  static $conn = false;
+  if ( $act_get )
+    return $conn;
+  
+  $db_user =& $_POST['db_user'];
+  $db_pass =& $_POST['db_pass'];
+  $db_name =& $_POST['db_name'];
+  
+  if ( !preg_match('/^[a-z0-9_-]+$/', $db_name) )
+  {
+    $db_name = htmlspecialchars($db_name);
+    die("<p>SECURITY: malformed database name \"$db_name\"</p>");
+  }
+  
+  // First, try to connect using the normal credentials
+  $conn = @pg_connect("host={$_POST['db_host']} port=5432 user={$_POST['db_user']} password={$_POST['db_pass']}");
+  if ( !$conn )
+  {
+    // Connection failed. Do we have the root username and password?
+    if ( !empty($_POST['db_root_user']) && !empty($_POST['db_root_pass']) )
+    {
+      $conn_root = @pg_connect("host={$_POST['db_host']} port=5432 user={$_POST['db_root_user']} password={$_POST['db_root_pass']}");
+      if ( !$conn_root )
+      {
+        // Couldn't connect using either set of credentials. Bail out.
+        return false;
+      }
+      unset($db_user, $db_pass);
+      $db_user = pg_escape_string($_POST['db_user']);
+      $db_pass = pg_escape_string($_POST['db_pass']);
+      // Create the user account
+      $q = @pg_query("CREATE ROLE '$db_user' WITH NOSUPERUSER UNENCRYPTED PASSWORD '$db_pass';", $conn_root);
+      if ( !$q )
+      {
+        return false;
+      }
+      pg_close($conn_root);
+      $conn = @pg_connect("host={$_POST['db_host']} port=5432 user={$_POST['db_user']} password={$_POST['db_pass']}");
+      if ( !$conn )
+      {
+        // This should honestly never happen.
+        return false;
+      }
+    }
+  }
+  if ( !$q )
+  {
+    // access denied to the database; try the whole root schenanegan again
+    if ( !empty($_POST['db_root_user']) && !empty($_POST['db_root_pass']) )
+    {
+      $conn_root = @pg_connect("host={$_POST['db_host']} port=5432 user={$_POST['db_root_user']} password={$_POST['db_root_pass']}");
+      if ( !$conn_root )
+      {
+        // Couldn't connect as root; bail out
+        return false;
+      }
+      unset($db_user, $db_pass);
+      $db_user = pg_escape_string($_POST['db_user']);
+      $db_pass = pg_escape_string($_POST['db_pass']);
+      // create the database, if it doesn't exist
+      $q = @mysql_query("CREATE DATABASE $db_name WITH OWNER $db_user;", $conn_root);
+      if ( !$q )
+      {
+        // this really should never fail, so don't give any tolerance to it
+        return false;
+      }
+      // Setting the owner to $db_user should grant all the rights we need
+      pg_close($conn_root);
+      // grant tables have hopefully been flushed, kill and reconnect our regular user connection
+      pg_close($conn);
+      $conn = @pg_connect("host={$_POST['db_host']} port=5432 user={$_POST['db_user']} password={$_POST['db_pass']}");
+      if ( !$conn )
+      {
+        return false;
+      }
+    }
+    else
+    {
+      return false;
+    }
+    // try again
+    $q = @mysql_query("USE `$db_name`;", $conn);
+    if ( !$q )
+    {
+      // really failed this time; bail out
+      return false;
+    }
+  }
+  // initialize DBAL
+  $db->connect(true, $_POST['db_host'], $db_user, $db_pass, $db_name);
   // connected and database exists
   return true;
 }
 
 function stg_drop_tables()
 {
-  $conn = stg_mysql_connect(true);
-  if ( !$conn )
-    return false;
+  global $db;
   // Our list of tables included in Enano
   $tables = Array( 'categories', 'comments', 'config', 'logs', 'page_text', 'session_keys', 'pages', 'users', 'users_extra', 'themes', 'buddies', 'banlist', 'files', 'privmsgs', 'sidebar', 'hits', 'search_index', 'groups', 'group_members', 'acl', 'tags', 'page_groups', 'page_group_members' );
   
@@ -327,7 +431,7 @@
   {
     // Remember that table_prefix is sanitized.
     $table = "{$_POST['table_prefix']}$table";
-    @mysql_query("DROP TABLE $table;", $conn);
+    $db->sql_query("DROP TABLE $table;", $conn);
   }
   return true;
 }
@@ -379,6 +483,8 @@
   if ( $act_get )
     return $schema;
   
+  global $db;
+  
   $admin_pass = stg_decrypt_admin_pass(true);
   $key = stg_generate_aes_key(true);
   $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
@@ -389,16 +495,29 @@
   
   $admin_user = $_POST['admin_user'];
   $admin_user = str_replace('_', ' ', $admin_user);
-  $admin_user = mysql_real_escape_string($admin_user);
+  $admin_user = $db->escape($admin_user);
+  
+  switch ( $_POST['db_driver'] )
+  {
+    case 'mysql':
+      $schema_file = 'schema.sql';
+      break;
+    case 'postgresql':
+      $schema_file = 'schema-pg.sql';
+      break;
+  }
   
-  $schema = file_get_contents('schema.sql');
-  $schema = str_replace('{{SITE_NAME}}',    mysql_real_escape_string($_POST['sitename']   ), $schema);
-  $schema = str_replace('{{SITE_DESC}}',    mysql_real_escape_string($_POST['sitedesc']   ), $schema);
-  $schema = str_replace('{{COPYRIGHT}}',    mysql_real_escape_string($_POST['copyright']  ), $schema);
+  if ( !isset($schema_file) )
+    die('insanity');
+  
+  $schema = file_get_contents($schema_file);
+  $schema = str_replace('{{SITE_NAME}}',    $db->escape($_POST['sitename']   ), $schema);
+  $schema = str_replace('{{SITE_DESC}}',    $db->escape($_POST['sitedesc']   ), $schema);
+  $schema = str_replace('{{COPYRIGHT}}',    $db->escape($_POST['copyright']  ), $schema);
   $schema = str_replace('{{ADMIN_USER}}',   $admin_user                                    , $schema);
-  $schema = str_replace('{{ADMIN_PASS}}',   mysql_real_escape_string($admin_pass          ), $schema);
-  $schema = str_replace('{{ADMIN_EMAIL}}',  mysql_real_escape_string($_POST['admin_email']), $schema);
-  $schema = str_replace('{{ENABLE_CACHE}}', mysql_real_escape_string($cacheonoff          ), $schema);
+  $schema = str_replace('{{ADMIN_PASS}}',   $db->escape($admin_pass          ), $schema);
+  $schema = str_replace('{{ADMIN_EMAIL}}',  $db->escape($_POST['admin_email']), $schema);
+  $schema = str_replace('{{ENABLE_CACHE}}', $db->escape($cacheonoff          ), $schema);
   $schema = str_replace('{{REAL_NAME}}',    '',                                              $schema);
   $schema = str_replace('{{TABLE_PREFIX}}', $_POST['table_prefix'],                          $schema);
   $schema = str_replace('{{VERSION}}',      ENANO_VERSION,                                   $schema);
@@ -818,6 +937,78 @@
     mysql_close($conn);
     die('good');
     break;
+  case 'pgsql_test':
+    error_reporting(0);
+    $dbhost     = rawurldecode($_POST['host']);
+    $dbname     = rawurldecode($_POST['name']);
+    $dbuser     = rawurldecode($_POST['user']);
+    $dbpass     = rawurldecode($_POST['pass']);
+    $dbrootuser = rawurldecode($_POST['root_user']);
+    $dbrootpass = rawurldecode($_POST['root_pass']);
+    if($dbrootuser != '')
+    {
+      $conn = @pg_connect("host=$dbhost port=5432 user=$dbuser password=$dbpass dbname=$dbname");
+      if(!$conn)
+      {
+        $e = pg_last_error();
+        if(strstr($e, "Lost connection"))
+          die('host'.$e);
+        else
+          die('root'.$e);
+      }
+      $rsp = 'good';
+      $q = mysql_query('USE `' . mysql_real_escape_string($dbname) . '`;', $conn);
+      if(!$q)
+      {
+        $e = mysql_error();
+        if(strstr($e, 'Unknown database'))
+        {
+          $rsp .= '_creating_db';
+        }
+      }
+      mysql_close($conn);
+      $conn = mysql_connect($dbhost, $dbuser, $dbpass);
+      if(!$conn)
+      {
+        $e = mysql_error();
+        if(strstr($e, "Lost connection"))
+          die('host'.$e);
+        else
+          $rsp .= '_creating_user';
+      }
+      mysql_close($conn);
+      die($rsp);
+    }
+    else
+    {
+      $conn = mysql_connect($dbhost, $dbuser, $dbpass);
+      if(!$conn)
+      {
+        $e = mysql_error();
+        if(strstr($e, "Lost connection"))
+          die('host'.$e);
+        else
+          die('auth'.$e);
+      }
+      $q = mysql_query('USE `' . mysql_real_escape_string($dbname) . '`;', $conn);
+      if(!$q)
+      {
+        $e = mysql_error();
+        if(strstr($e, 'Unknown database'))
+        {
+          die('name'.$e);
+        }
+        else
+        {
+          die('perm'.$e);
+        }
+      }
+    }
+    $v = mysql_get_server_info();
+    if(version_compare($v, '4.1.17', '<')) die('vers'.$v);
+    mysql_close($conn);
+    die('good');
+    break;  
   case 'pophelp':
     $topic = ( isset($_GET['topic']) ) ? $_GET['topic'] : 'invalid';
     switch($topic)
@@ -892,8 +1083,10 @@
               'welcome' => $lang->get('welcome_modetitle'),
               'license' => $lang->get('license_modetitle'),
               'sysreqs' => $lang->get('sysreqs_modetitle'),
-              'database'=> $lang->get('database_modetitle'),
-              'website' => $lang->get('website_modetitle'),
+              'database' => $lang->get('database_modetitle'),
+              'database_mysql'=> $lang->get('database_mysql_modetitle'),
+              'database_pgsql'=> $lang->get('database_pgsql_modetitle'),
+              'website' => $lang->get('website_modetitle'), 
               'login'   => $lang->get('login_modetitle'),
               'confirm' => $lang->get('confirm_modetitle'),
               'install' => $lang->get('install_modetitle'),
@@ -1027,12 +1220,17 @@
     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 function_exists(\'pg_connect\');', 'PostgreSQL extension for PHP', 'It seems that your PHP installation does not have the PostgreSQL extension enabled. Because of this, you won\'t be able to use the PostgreSQL database driver. This is OK in the majority of cases. If you want to use PostgreSQL support, you\'ll need to either compile the PHP extension for Postgres or install the extension with your distribution\'s package manager. Windows administrators will need enable php_pgsql.dll in their php.ini.', true);
     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);
     run_test('return is_writable(ENANO_ROOT.\'/config.new.php\');', $lang->get('sysreqs_req_config'), $lang->get('sysreqs_req_desc_config') );
     run_test('return file_exists(\'/usr/bin/convert\');', $lang->get('sysreqs_req_magick'), $lang->get('sysreqs_req_desc_magick'), true);
     run_test('return is_writable(ENANO_ROOT.\'/cache/\');', $lang->get('sysreqs_req_cachewriteable'), $lang->get('sysreqs_req_desc_cachewriteable'), true);
     run_test('return is_writable(ENANO_ROOT.\'/files/\');', $lang->get('sysreqs_req_fileswriteable'), $lang->get('sysreqs_req_desc_fileswriteable'), true);
+    if ( !function_exists('mysql_connect') && !function_exists('pg_connect') )
+    {
+      run_test('return false;', 'No database drivers are available.', 'You need to have at least one database driver working to install Enano. See the warnings on MySQL and PostgreSQL above for more information on installing these database drivers.', false);
+    }
     echo '</table>';
     if(!$failed)
     {
@@ -1082,6 +1280,76 @@
     <?php
     break;
   case "database":
+    echo '<h3>Choose a database driver</h3>';
+    echo '<p>The next step is to choose the database driver that Enano will use. In most cases this is MySQL, but there are certain
+             advantages to PostgreSQL, which is made available only experimentally.</p>';
+    if ( @file_exists('/etc/enano-is-virt-appliance') )
+    {
+      echo '<p><b>You\'re using the Enano virtual appliance.</b><br />Unless you configured the appliance manually, PostgreSQL support is not available. In 99% of cases you\'ll want to click MySQL below.</p>';
+    }
+    
+    $mysql_disable_reason = '';
+    $pgsql_disable_reason = '';
+    $mysql_disable = '';
+    $pgsql_disable = '';
+    if ( !function_exists('mysql_connect') )
+    {
+      $mysql_disable = ' disabled="disabled"';
+      $mysql_disable_reason = 'You don\'t have the MySQL PHP extension installed.';
+    }
+    if ( !function_exists('pg_connect') )
+    {
+      $pgsql_disable = ' disabled="disabled"';
+      $pgsql_disable_reason = 'You don\'t have the PostgreSQL PHP extensnion installed.';
+    }
+    if ( function_exists('pg_connect') && version_compare(PHP_VERSION, '5.0.0', '<') )
+    {
+      $pgsql_disable = ' disabled="disabled"';
+      $pgsql_disable_reason = 'You need to have at least PHP 5 to use the PostgreSQL database driver.';
+    }
+    
+    echo '<form action="install.php" method="get">';
+    ?>
+    <table border="0" cellspacing="5">
+      <tr>
+        <td>
+          <input type="image" name="mode" value="database_mysql" src="images/about-powered-mysql.png"<?php echo $mysql_disable; ?>/>
+        </td>
+        <td<?php if ( $mysql_disable ) echo ' style="opacity: 0.5; filter: alpha(opacity=50);"'; ?>>
+          <b>MySQL</b><br />
+          Click this button to use MySQL as the database backend for your site. Most web hosts support MySQL, and if you have
+          administrative access to your MySQL server, you can create a new database and user during this installation process if you
+          haven't done so already.
+          <?php
+          if ( $mysql_disable )
+          {
+            echo "<br /><br /><b>$mysql_disable_reason</b>";
+          }
+          ?>
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input type="image" name="mode" value="database_pgsql" src="images/about-powered-pgsql.png"<?php echo $pgsql_disable; ?> />
+        </td>
+        <td<?php if ( $pgsql_disable ) echo ' style="opacity: 0.5; filter: alpha(opacity=50);"'; ?>>
+          <b>PostgreSQL</b><br />
+          Click this button to use PostgreSQL as the database backend for your site. While not as widely supported, PostgreSQL has more
+          liberal licensing conditions and when properly configured is faster than MySQL. Some plugins may not work with the PostgreSQL
+          driver.
+          <?php
+          if ( $pgsql_disable )
+          {
+            echo "<br /><br /><b>$pgsql_disable_reason</b>";
+          }
+          ?>
+        </td>
+      </tr>
+    </table>
+    <?php
+    echo '</form>';
+    break;
+  case "database_mysql":
     ?>
     <script type="text/javascript">
       function ajaxGet(uri, f) {
@@ -1287,6 +1555,7 @@
     }
     ?>
     <form name="dbinfo" action="install.php?mode=website" method="post">
+      <input type="hidden" name="db_driver" value="mysql" />
       <table border="0">
         <tr>
           <td colspan="3" style="text-align: center">
@@ -1414,6 +1683,222 @@
           </tr>
         </table>
       </div>
+        } else {
+          if (window.ActiveXObject) {           
+            ajax = new ActiveXObject("Microsoft.XMLHTTP");
+          } else {
+            alert('Enano client-side runtime error: No AJAX support, unable to continue');
+            return;
+          }
+        }
+        ajax.onreadystatechange = f;
+        ajax.open('GET', uri, true);
+        ajax.send(null);
+      }
+      
+      function ajaxPost(uri, parms, f) {
+        if (window.XMLHttpRequest) {
+          ajax = new XMLHttpRequest();
+        } else {
+          if (window.ActiveXObject) {           
+            ajax = new ActiveXObject("Microsoft.XMLHTTP");
+          } else {
+            alert('Enano client-side runtime error: No AJAX support, unable to continue');
+            return;
+          }
+        }
+        ajax.onreadystatechange = f;
+        ajax.open('POST', uri, true);
+        ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        ajax.setRequestHeader("Content-length", parms.length);
+        ajax.setRequestHeader("Connection", "close");
+        ajax.send(parms);
+      }
+      function ajaxTestConnection()
+      {
+        v = verify();
+        if(!v)
+        {
+          alert('One or more of the form fields is incorrect. Please correct any information in the form that has an "X" next to it.');
+          return false;
+        }
+        var frm = document.forms.dbinfo;
+        db_host      = escape(frm.db_host.value.replace('+', '%2B'));
+        db_name      = escape(frm.db_name.value.replace('+', '%2B'));
+        db_user      = escape(frm.db_user.value.replace('+', '%2B'));
+        db_pass      = escape(frm.db_pass.value.replace('+', '%2B'));
+        db_root_user = escape(frm.db_root_user.value.replace('+', '%2B'));
+        db_root_pass = escape(frm.db_root_pass.value.replace('+', '%2B'));
+        
+        parms = 'host='+db_host+'&name='+db_name+'&user='+db_user+'&pass='+db_pass+'&root_user='+db_root_user+'&root_pass='+db_root_pass;
+        ajaxPost('<?php echo scriptPath; ?>/install.php?mode=pgsql_test', parms, function() {
+            if(ajax.readyState==4)
+            {
+              s = ajax.responseText.substr(0, 4);
+              t = ajax.responseText.substr(4, ajax.responseText.length);
+              if(s.substr(0, 4)=='good')
+              {
+                document.getElementById('s_db_host').src='images/good.gif';
+                document.getElementById('s_db_name').src='images/good.gif';
+                document.getElementById('s_db_auth').src='images/good.gif';
+                document.getElementById('s_db_root').src='images/good.gif';
+                if(t.match(/_creating_db/)) document.getElementById('e_db_name').innerHTML = '<b>Warning:<\/b> The database you specified does not exist. It will be created during installation.';
+                if(t.match(/_creating_user/)) document.getElementById('e_db_auth').innerHTML = '<b>Warning:<\/b> The specified regular user does not exist or the password is incorrect. The user will be created during installation. If the user already exists, the password will be reset.';
+                document.getElementById('s_mysql_version').src='images/good.gif';
+                document.getElementById('e_mysql_version').innerHTML = 'Your version of PostgreSQL meets Enano requirements.';
+              }
+              else
+              {
+                switch(s)
+                {
+                case 'host':
+                  document.getElementById('s_db_host').src='images/bad.gif';
+                  document.getElementById('s_db_name').src='images/unknown.gif';
+                  document.getElementById('s_db_auth').src='images/unknown.gif';
+                  document.getElementById('s_db_root').src='images/unknown.gif';
+                  document.getElementById('e_db_host').innerHTML = '<b>Error:<\/b> The database server "'+document.forms.dbinfo.db_host.value+'" couldn\'t be contacted.<br \/>'+t;
+                  document.getElementById('e_mysql_version').innerHTML = 'The MySQL version that your server is running could not be determined.';
+                  break;
+                case 'auth':
+                  document.getElementById('s_db_host').src='images/good.gif';
+                  document.getElementById('s_db_name').src='images/unknown.gif';
+                  document.getElementById('s_db_auth').src='images/bad.gif';
+                  document.getElementById('s_db_root').src='images/unknown.gif';
+                  document.getElementById('e_db_auth').innerHTML = '<b>Error:<\/b> Access to MySQL under the specified credentials was denied.<br \/>'+t;
+                  document.getElementById('e_mysql_version').innerHTML = 'The MySQL version that your server is running could not be determined.';
+                  break;
+                case 'perm':
+                  document.getElementById('s_db_host').src='images/good.gif';
+                  document.getElementById('s_db_name').src='images/bad.gif';
+                  document.getElementById('s_db_auth').src='images/good.gif';
+                  document.getElementById('s_db_root').src='images/unknown.gif';
+                  document.getElementById('e_db_name').innerHTML = '<b>Error:<\/b> Access to the specified database using those login credentials was denied.<br \/>'+t;
+                  document.getElementById('e_mysql_version').innerHTML = 'The MySQL version that your server is running could not be determined.';
+                  break;
+                case 'name':
+                  document.getElementById('s_db_host').src='images/good.gif';
+                  document.getElementById('s_db_name').src='images/bad.gif';
+                  document.getElementById('s_db_auth').src='images/good.gif';
+                  document.getElementById('s_db_root').src='images/unknown.gif';
+                  document.getElementById('e_db_name').innerHTML = '<b>Error:<\/b> The specified database does not exist<br \/>'+t;
+                  document.getElementById('e_mysql_version').innerHTML = 'The MySQL version that your server is running could not be determined.';
+                  break;
+                case 'root':
+                  document.getElementById('s_db_host').src='images/good.gif';
+                  document.getElementById('s_db_name').src='images/unknown.gif';
+                  document.getElementById('s_db_auth').src='images/unknown.gif';
+                  document.getElementById('s_db_root').src='images/bad.gif';
+                  document.getElementById('e_db_root').innerHTML = '<b>Error:<\/b> Access to MySQL under the specified credentials was denied.<br \/>'+t;
+                  document.getElementById('e_mysql_version').innerHTML = 'The MySQL version that your server is running could not be determined.';
+                  break;
+                case 'vers':
+                  document.getElementById('s_db_host').src='images/good.gif';
+                  document.getElementById('s_db_name').src='images/good.gif';
+                  document.getElementById('s_db_auth').src='images/good.gif';
+                  document.getElementById('s_db_root').src='images/good.gif';
+                  if(t.match(/_creating_db/)) document.getElementById('e_db_name').innerHTML = '<b>Warning:<\/b> The database you specified does not exist. It will be created during installation.';
+                  if(t.match(/_creating_user/)) document.getElementById('e_db_auth').innerHTML = '<b>Warning:<\/b> The specified regular user does not exist or the password is incorrect. The user will be created during installation. If the user already exists, the password will be reset.';
+                  
+                  document.getElementById('e_mysql_version').innerHTML = '<b>Error:<\/b> Your version of MySQL ('+t+') is older than 4.1.17. Enano will still work, but there is a known bug with the comment system and MySQL 4.1.11 that involves some comments not being displayed, due to an issue with the PHP function mysql_fetch_row().';
+                  document.getElementById('s_mysql_version').src='images/bad.gif';
+                default:
+                  alert(t);
+                  break;
+                }
+              }
+            }
+          });
+      }
+      function verify()
+      {
+        document.getElementById('e_db_host').innerHTML = '';
+        document.getElementById('e_db_auth').innerHTML = '';
+        document.getElementById('e_db_name').innerHTML = '';
+        document.getElementById('e_db_root').innerHTML = '';
+        var frm = document.forms.dbinfo;
+        ret = true;
+        if(frm.db_host.value != '')
+        {
+          document.getElementById('s_db_host').src='images/unknown.gif';
+        }
+        else
+        {
+          document.getElementById('s_db_host').src='images/bad.gif';
+          ret = false;
+        }
+        if(frm.db_name.value.match(/^([a-z0-9_-]+)$/g))
+        {
+          document.getElementById('s_db_name').src='images/unknown.gif';
+        }
+        else
+        {
+          document.getElementById('s_db_name').src='images/bad.gif';
+          ret = false;
+        }
+        if(frm.db_user.value != '')
+        {
+          document.getElementById('s_db_auth').src='images/unknown.gif';
+        }
+        else
+        {
+          document.getElementById('s_db_auth').src='images/bad.gif';
+          ret = false;
+        }
+        if(frm.table_prefix.value.match(/^([a-z0-9_]*)$/g))
+        {
+          document.getElementById('s_table_prefix').src='images/good.gif';
+        }
+        else
+        {
+          document.getElementById('s_table_prefix').src='images/bad.gif';
+          ret = false;
+        }
+        if(frm.db_root_user.value == '')
+        {
+          document.getElementById('s_db_root').src='images/good.gif';
+        }
+        else if(frm.db_root_user.value != '' && frm.db_root_pass.value == '')
+        {
+          document.getElementById('s_db_root').src='images/bad.gif';
+          ret = false;
+        }
+        else
+        {
+          document.getElementById('s_db_root').src='images/unknown.gif';
+        }
+        if(ret) frm._cont.disabled = false;
+        else    frm._cont.disabled = true;
+        return ret;
+      }
+      window.onload = verify;
+    </script>
+    <p>Now we need some information that will allow Enano to contact your database server. Enano uses PostgreSQL as a data storage backend,
+       and we need to have access to a PostgreSQL server in order to continue.</p>
+    <p>If you do not have access to a PostgreSQL server, and you are using your own server, you can download PostgreSQL for free from
+       <a href="http://www.postgresql.org/">PostgreSQL.org</a>.</p>
+    <form name="dbinfo" action="install.php?mode=website" method="post">
+      <input type="hidden" name="db_driver" value="postgresql" />
+      <table border="0">
+        <tr><td colspan="3" style="text-align: center"><h3>Database information</h3></td></tr>
+        <tr><td><b>Database hostname</b><br />This is the hostname (or sometimes the IP address) of your Postgres server. In many cases, this is "localhost".<br /><span style="color: #993300" id="e_db_host"></span></td><td><input onkeyup="verify();" name="db_host" size="30" type="text" /></td><td><img id="s_db_host" alt="Good/bad icon" src="images/bad.gif" /></td></tr>
+        <tr><td><b>Database name</b><br />The name of the actual database. If you don't already have a database, you can create one here, if you have the username and password of a PostgreSQL superuser.<br /><span style="color: #993300" id="e_db_name"></span></td><td><input onkeyup="verify();" name="db_name" size="30" type="text" /></td><td><img id="s_db_name" alt="Good/bad icon" src="images/bad.gif" /></td></tr>
+        <tr><td rowspan="2"><b>Database login</b><br />These fields should be the username and password for a role that has permission to create and alter tables, select data, insert data, update data, and delete data. You may or may not choose to allow dropping tables.<br /><span style="color: #993300" id="e_db_auth"></span></td><td><input onkeyup="verify();" name="db_user" size="30" type="text" /></td><td rowspan="2"><img id="s_db_auth" alt="Good/bad icon" src="images/bad.gif" /></td></tr>
+        <tr><td><input name="db_pass" size="30" type="password" /></td></tr>
+        <tr><td colspan="3" style="text-align: center"><h3>Optional information</h3></td></tr>
+        <tr><td><b>Table prefix</b><br />The value that you enter here will be added to the beginning of the name of each Enano table. You may use lowercase letters (a-z), numbers (0-9), and underscores (_).</td><td><input onkeyup="verify();" name="table_prefix" size="30" type="text" /></td><td><img id="s_table_prefix" alt="Good/bad icon" src="images/good.gif" /></td></tr>
+        <tr><td rowspan="2"><b>Database administrative login</b><br />If the Postgres database or role that you entered above does not exist yet, you can create them here, assuming that you have the login information for a PostgreSQL superuser. Leave these fields blank unless you need to use them.<br /><span style="color: #993300" id="e_db_root"></span></td><td><input onkeyup="verify();" name="db_root_user" size="30" type="text" /></td><td rowspan="2"><img id="s_db_root" alt="Good/bad icon" src="images/good.gif" /></td></tr>
+        <tr><td><input onkeyup="verify();" name="db_root_pass" size="30" type="password" /></td></tr>
+        <tr><td><b>PostgreSQL version</b></td><td id="e_mysql_version">PostgreSQL version information will<br />be checked when you click "Test<br />Connection". You need to have at<br />least PostgreSQL 8.2.0 to install Enano.</td><td><img id="s_mysql_version" alt="Good/bad icon" src="images/unknown.gif" /></td></tr>
+        <tr><td><b>Delete existing tables?</b><br />If this option is checked, all the tables that will be used by Enano will be dropped (deleted) before the schema is executed. Do NOT use this option unless specifically instructed to.</td><td><input type="checkbox" name="drop_tables" id="dtcheck" />  <label for="dtcheck">Drop existing tables</label></td></tr>
+        <tr><td colspan="3" style="text-align: center"><input type="button" value="Test connection" onclick="ajaxTestConnection();" /></td></tr>
+      </table>
+      <div class="pagenav">
+       <table border="0">
+       <tr>
+       <td><input type="submit" value="Continue" onclick="return verify();" name="_cont" /></td><td><p><span style="font-weight: bold;">Before clicking continue:</span><br />&bull; Check your PostgreSQL connection using the "Test Connection" button.<br />&bull; Be aware that your database information will be transmitted unencrypted several times.</p></td>
+       </tr>
+       </table>
+     </div>
     </form>
     <?php
     break;
@@ -1820,6 +2305,7 @@
        !isset($_POST['db_name']) ||
        !isset($_POST['db_user']) ||
        !isset($_POST['db_pass']) ||
+       !isset($_POST['db_driver']) ||
        !isset($_POST['sitename']) ||
        !isset($_POST['sitedesc']) ||
        !isset($_POST['copyright']) ||
@@ -1833,6 +2319,12 @@
       $template->footer();
       exit;
     }
+    if ( !in_array($_POST['db_driver'], array('mysql', 'postgresql')) )
+    {
+      echo 'Invalid database driver.';
+      $template->footer();
+      exit;
+    }
     switch($_POST['urlscheme'])
     {
       case "ugly":
--- a/plugins/PrivateMessages.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/PrivateMessages.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:PrivateMessages, which is used to manage private message functions. Also handles buddy lists.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialAdmin.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialAdmin.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,7 +4,7 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Administration, which is the AJAX frontend to the various Admin pagelets. This plugin cannot be disabled.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
@@ -530,28 +530,28 @@
   {
     if(isset($_POST['enable_uploads']) && getConfig('enable_uploads') != '1')
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","upload_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'upload_enable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('enable_uploads', '1');
     }
     else if ( !isset($_POST['enable_uploads']) && getConfig('enable_uploads') == '1' )
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","upload_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'upload_disable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('enable_uploads', '0');
     }
     if(isset($_POST['enable_imagemagick']) && getConfig('enable_imagemagick') != '1')
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","magick_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'magick_enable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('enable_imagemagick', '1');
     }
     else if ( !isset($_POST['enable_imagemagick']) && getConfig('enable_imagemagick') == '1' )
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","magick_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'magick_disable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('enable_imagemagick', '0');
@@ -566,14 +566,14 @@
     }
     if(isset($_POST['file_history']) && getConfig('file_history') != '1' )
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","filehist_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'filehist_enable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('file_history', '1');
     }
     else if ( !isset($_POST['file_history']) && getConfig('file_history') == '1' )
     {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","filehist_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'filehist_disable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('file_history', '0');
@@ -582,7 +582,7 @@
     {
       $old = getConfig('imagemagick_path');
       $oldnew = "{$old}||{$_POST['imagemagick_path']}";
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","magick_path",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($oldnew) . '");');
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'magick_path\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',\'' . $db->escape($session->username) . '\',\'' . $db->escape($oldnew) . '\');');
       if ( !$q )
         $db->_die();
       setConfig('imagemagick_path', $_POST['imagemagick_path']);
@@ -651,7 +651,7 @@
     switch($_GET['action'])
     {
       case "enable":
-        $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","plugin_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
+        $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'plugin_enable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',"' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
         if ( !$q )
           $db->_die();
         setConfig('plugin_'.$_GET['plugin'], '1');
@@ -664,7 +664,7 @@
         }
         if ( !in_array($_GET['plugin'], $plugins->system_plugins) )
         {
-          $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","plugin_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
+          $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'plugin_disable\',' . time() . ',\'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\',"' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
           if ( !$q )
             $db->_die();
           setConfig('plugin_'.$_GET['plugin'], '0');
@@ -905,279 +905,6 @@
   <?php
 }
 
-/*
-function page_Admin_UserManager() {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
-  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
-  {
-    $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
-    echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
-    echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
-    return;
-  }
-  
-  if ( isset($_GET['src']) && $_GET['src'] == 'get' && !empty($_GET['user']) )
-  {
-    $_POST['go'] = true;
-    $_POST['username'] = $_GET['user'];
-  }
-  
-  if(isset($_POST['go']))
-  {
-    // We need the user ID before we can do anything
-    $q = $db->sql_query('SELECT user_id,username,email,real_name,style,user_level,account_active FROM '.table_prefix.'users WHERE username=\'' . $db->escape($_POST['username']) . '\'');
-    if ( !$q )
-    {
-      die('Error selecting user ID: '.mysql_error());
-    }
-    if ( $db->numrows() < 1 )
-    {
-      echo('User does not exist, please enter another username.');
-      return;
-    }
-    $r = $db->fetchrow();
-    $db->free_result();
-    if(isset($_POST['save']))
-    {
-      $_POST['level'] = intval($_POST['level']);
-      
-      $new_level = $_POST['level'];
-      $old_level = intval($r['user_level']);
-      
-      if ( defined('ENANO_DEMO_MODE') )
-      {
-        echo '<div class="error-box">You cannot delete or modify user accounts in demo mode - they are cleaned up once every two hours.</div>';
-        $re = Array('permission denied');
-      }
-      else
-      {
-        $re = $session->update_user((int)$r['user_id'], $_POST['new_username'], false, $_POST['new_pass'], $_POST['email'], $_POST['real_name'], false, $_POST['level']);
-      }
-      
-      if($re == 'success')
-      {
-        
-        if ( $new_level != $old_level )
-        {
-          $user_id = intval($r['user_id']);
-          // We need to update group memberships
-          if ( $old_level == USER_LEVEL_ADMIN ) 
-          {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_from_admin",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_POST['new_username']) . '");');
-            if ( !$q )
-              $db->_die();
-            $session->remove_user_from_group($user_id, GROUP_ID_ADMIN);
-          }
-          else if ( $old_level == USER_LEVEL_MOD ) 
-          {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_from_mod",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_POST['new_username']) . '");');
-            if ( !$q )
-              $db->_die();
-            $session->remove_user_from_group($user_id, GROUP_ID_MOD);
-          }
-          
-          if ( $new_level == USER_LEVEL_ADMIN )
-          {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_to_admin",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_POST['new_username']) . '");');
-            if ( !$q )
-              $db->_die();
-            $session->add_user_to_group($user_id, GROUP_ID_ADMIN, false);
-          }
-          else if ( $new_level == USER_LEVEL_MOD )
-          {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_to_mod",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_POST['new_username']) . '");');
-            if ( !$q )
-              $db->_die();
-            $session->add_user_to_group($user_id, GROUP_ID_MOD, false);
-          }
-        }
-        
-        // update account activation
-        if ( isset($_POST['account_active']) )
-        {
-          // activate account
-          $q = $db->sql_query('UPDATE '.table_prefix.'users SET account_active=1 WHERE user_id=' . intval($r['user_id']) . ';');
-          if ( !$q )
-            $db->_die();
-        }
-        else
-        {
-          // deactivate account and throw away the old key
-          $actkey = sha1 ( microtime() . mt_rand() );
-          $q = $db->sql_query('UPDATE '.table_prefix.'users SET account_active=0,activation_key=\'' . $actkey . '\' WHERE user_id=' . intval($r['user_id']) . ';');
-          if ( !$q )
-            $db->_die();
-        }
-        
-        echo('<div class="info-box">Your changes have been saved.</div>');
-      }
-      else
-      {
-        echo('<div class="error-box">Error saving changes: '.implode('<br />', $re).'</div>');
-      }
-      $q = $db->sql_query('SELECT user_id,username,email,real_name,style,user_level,account_active FROM '.table_prefix.'users WHERE username=\''.$db->escape($_POST['username']).'\'');
-      if ( !$q )
-      {
-        die('Error selecting user ID: '.mysql_error());
-      }
-      if($db->numrows($q) < 1)
-      {
-        die('User does not exist, please enter another username.');
-      }
-      $r = mysql_fetch_object($q);
-      $db->free_result();
-    }
-    elseif(isset($_POST['deleteme']) && isset($_POST['delete_conf']))
-    {
-      if ( defined('ENANO_DEMO_MODE') )
-      {
-        echo '<div class="error-box">You cannot delete or modify user accounts in demo mode - they are cleaned up once every two hours.</div>';
-      }
-      else
-      {
-        $q = $db->sql_query('DELETE FROM users WHERE user_id='.$r['user_id'].';');
-        if($q)
-        {
-          echo '<div class="error-box">The user account "'.$r['username'].'" was deleted.</div>';
-        }
-        else
-        {
-          echo '<div class="error-box">The user account "'.$r['username'].'" could not be deleted due to a database error.<br /><br />'.$db->get_error().'</div>';
-        }
-      }
-    }
-    else
-    {
-      $disabled = ( $r['user_id'] == $session->user_id ) ? ' disabled="disabled" ' : '';
-      $evt_get_score = ( getConfig('pw_strength_enable') == '1' ) ? 'onkeyup="password_score_field(this);" style="margin-right: 7px;" ' : '';
-      $meter         = ( getConfig('pw_strength_enable') == '1' ) ? '<tr><td></td><td><div id="pwmeter"></div><p><small>Password complexity requirements are not enforced here.</small></p></td></tr>' : '';
-      echo('
-      <h3>Edit User Info</h3>
-      <form action="'.makeUrl($paths->nslist['Special'].'Administration', 'module='.$paths->cpage['module']).'" method="post">
-        <table border="0" style="margin-left: 0.2in;">   
-          <tr><td>Username:</td><td><input type="text" name="new_username" value="'.$r['username'].'" /></td></tr>
-          <tr><td>New Password:</td><td><input ' . $disabled . ' type="password" name="new_pass" '.$evt_get_score.'/></td></tr>
-          '.$meter.'
-          <tr><td>E-mail:</td><td><input ' . $disabled . ' type="text" name="email" value="'.$r['email'].'" /></td></tr>
-          <tr><td>Real Name:</td><td><input ' . $disabled . ' type="text" name="real_name" value="'.$r['real_name'].'" /></td></tr>
-          ' . ( ( !empty($disabled) ) ? '<tr><td colspan="2"><small>To change your e-mail address, password, or real name, please use the user control panel.</small></td></tr>' : '' ) . '
-          <tr><td>User level:</td><td><select name="level"><option '); if($r['user_level']==USER_LEVEL_CHPREF) echo('SELECTED'); echo(' value="'.USER_LEVEL_CHPREF.'">Regular User</option><option '); if($r['user_level']==USER_LEVEL_MOD) echo('SELECTED'); echo(' value="'.USER_LEVEL_MOD.'">Moderator</option><option '); if($r['user_level']==USER_LEVEL_ADMIN) echo('SELECTED'); echo(' value="'.USER_LEVEL_ADMIN.'">Administrator</option></select></td></tr>
-          <tr><td></td><td><label><input type="checkbox" name="account_active"' . ( $r['account_active'] == '1' ? ' checked="checked"' : '' ) . ' /> Account is active</label><br /><small>If this is unchecked, the activation key will be reset, meaning that any activation e-mails sent will be invalidated.</small></td></tr>
-          <tr><td>Delete user:</td><td><input type="hidden" name="go" /><input type="hidden" name="username" value="'.$r['username'].'" /><input onclick="return confirm(\'This is your last warning.\n\nAre you sure you want to delete this user account? Even if you delete this user account, the username will be shown in page edit history, comments, and other areas of the site.\n\nDeleting a user account CANNOT BE UNDONE and should only be done in extreme circumstances.\n\nIf the user has violated the site policy, deleting the account will not prevent him from using the site, for that you need to add a new ban rule.\n\nContinue deleting this user account?\')" type="submit" name="deleteme" value="Delete this user" style="color: red;" /> <label><input type="checkbox" name="delete_conf" /> I\'m absolutely sure</label>
-          <tr><td align="center" colspan="2">
-          <input type="submit" name="save" value="Save Changes" /></td></tr>
-        </table>
-      </form>
-      ');
-    }
-  }
-  else if(isset($_POST['clearsessions'])) 
-  {
-    if ( defined('ENANO_DEMO_MODE') )
-    {
-      echo '<div class="error-box">Sorry Charlie, no can do. You might mess up other people logged into the demo site.</div>';
-    }
-    else
-    {
-      // Get the current session information so the user doesn't get logged out
-      $aes = new AESCrypt();
-      $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 )
-      {
-        die('Error selecting session key info block B: '.$db->get_error());
-      }
-      if ( $db->numrows($qb) < 1 )
-      {
-        die('Error: cannot read admin session info block B, aborting table clear process');
-      }
-      $qa = $db->sql_query('SELECT session_key,salt,auth_level,source_ip,time FROM '.table_prefix.'session_keys WHERE session_key=\''.md5($session->sid).'\' AND user_id='.$session->user_id.' AND auth_level='.USER_LEVEL_MEMBER);
-      if ( !$qa )
-      {
-        die('Error selecting session key info block A: '.$db->get_error());
-      }
-      if ( $db->numrows($qa) < 1 )
-      {
-        die('Error: cannot read user session info block A, aborting table clear process');
-      }
-      $ra = mysql_fetch_object($qa);
-      $rb = mysql_fetch_object($qb);
-      $db->free_result($qa);
-      $db->free_result($qb);
-      
-      $db->sql_query('DELETE FROM '.table_prefix.'session_keys;');
-      $db->sql_query('INSERT INTO '.table_prefix.'session_keys( session_key,salt,user_id,auth_level,source_ip,time ) VALUES( \''.$ra->session_key.'\', \''.$ra->salt.'\', \''.$session->user_id.'\', \''.$ra->auth_level.'\', \''.$ra->source_ip.'\', '.$ra->time.' ),( \''.$rb->session_key.'\', \''.$rb->salt.'\', \''.$session->user_id.'\', \''.$rb->auth_level.'\', \''.$rb->source_ip.'\', '.$rb->time.' )');
-      
-      echo('
-        <div class="info-box">The session key table has been cleared. Your database should be a little bit smaller now.</div>
-      ');
-    }
-  }   
-  echo('
-  <h3>User Management</h3>
-  <form action="'.makeUrl($paths->nslist['Special'].'Administration', 'module='.$paths->cpage['module']).'" method="post" onsubmit="if(!submitAuthorized) return false;">
-    <p>Username: '.$template->username_field('username').' <input type="submit" name="go" value="Go" /></p>
-    <h3>Clear session keys table</h3>
-     <p>It\'s a good idea to clean out your session keys table every once in a while, since this helps to reduce database size. During this process you will be logged off and (hopefully) logged back on automatically. The side effects of this include all users except you being logged off.</p>
-     <p><input type="submit" name="clearsessions" value="Clear session keys table" /></p>
-  </form>
-  ');
-  if(isset($_GET['action']) && isset($_GET['user']))
-  {
-    switch($_GET['action'])
-    {
-      case "activate":
-        $e = $db->sql_query('SELECT activation_key FROM '.table_prefix.'users WHERE username=\'' . $db->escape($_GET['user']) . '\'');
-        if($e)
-        {
-          $row = $db->fetchrow();
-          $db->free_result();
-          if($session->activate_account($_GET['user'], $row['activation_key'])) { echo '<div class="info-box">The user account "'.$_GET['user'].'" has been activated.</div>'; $db->sql_query('DELETE FROM '.table_prefix.'logs WHERE time_id=' . $db->escape($_GET['logid'])); }
-          else echo '<div class="warning-box">The user account "'.$_GET['user'].'" has NOT been activated, possibly because the account is already active.</div>';
-        } else echo '<div class="error-box">Error activating account: '.mysql_error().'</div>';
-        break;
-      case "sendemail":
-        if($session->send_activation_mail($_GET['user'])) { echo '<div class="info-box">The user "'.$_GET['user'].'" has been sent an e-mail with an activation link.</div>'; $db->sql_query('DELETE FROM '.table_prefix.'logs WHERE time_id=' . $db->escape($_GET['logid'])); }
-        else echo '<div class="error-box">The user account "'.$_GET['user'].'" has not been activated, probably because of a bad SMTP configuration.</div>';
-        break;
-      case "deny":
-        $e = $db->sql_query('DELETE FROM '.table_prefix.'logs WHERE log_type=\'admin\' AND action=\'activ_req\' AND edit_summary=\'' . $db->escape($_GET['user']) . '\';');
-        if(!$e) echo '<div class="error-box">Error during row deletion: '.mysql_error().'</div>';
-        else echo '<div class="info-box">All activation requests for the user "'.$_GET['user'].'" have been deleted.</div>';
-        break;
-    }
-  }
-  $q = $db->sql_query('SELECT l.log_type, l.action, l.time_id, l.date_string, l.author, l.edit_summary, u.user_coppa FROM '.table_prefix.'logs AS l
-                         LEFT JOIN '.table_prefix.'users AS u
-                           ON ( u.username = l.edit_summary OR u.username IS NULL )
-                         WHERE log_type=\'admin\' AND action=\'activ_req\' ORDER BY time_id DESC;');
-  if($q)
-  {
-    if($db->numrows() > 0)
-    {
-      $n = $db->numrows();
-      if($n == 1) $s = $n . ' user is';
-      else $s = $n . ' users are';
-      echo '<h3>'.$s . ' awaiting account activation</h3>';
-      echo '<div class="tblholder">
-            <table border="0" cellspacing="1" cellpadding="4" width="100%">
-            <tr><th>Date of request</th><th>Requested by</th><th>Requested for</th><th>COPPA user</th><th colspan="3">Actions</th></tr>';
-      $cls = 'row2';
-      while($row = $db->fetchrow())
-      {
-        if($cls == 'row2') $cls = 'row1';
-        else $cls = 'row2';
-        $coppa = ( $row['user_coppa'] == '1' ) ? '<b>Yes</b>' : 'No';
-        echo '<tr><td class="'.$cls.'">'.date('F d, Y h:i a', $row['time_id']).'</td><td class="'.$cls.'">'.$row['author'].'</td><td class="'.$cls.'">'.$row['edit_summary'].'</td><td style="text-align: center;" class="' . $cls . '">' . $coppa . '</td><td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&amp;action=activate&amp;user='.$row['edit_summary'].'&amp;logid='.$row['time_id']).'">Activate now</a></td><td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&amp;action=sendemail&amp;user='.$row['edit_summary'].'&amp;logid='.$row['time_id']).'">Send activation e-mail</a></td><td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&amp;action=deny&amp;user='.$row['edit_summary'].'&amp;logid='.$row['time_id']).'">Deny request</a></td></tr>';
-      }
-      echo '</table>';
-    }
-    $db->free_result();
-  }
-}
-*/
-
 function page_Admin_GroupManager()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -1876,7 +1603,7 @@
       $id = md5( microtime() . mt_rand() );
       
       $minor = isset($_POST['minor']) ? 'true' : 'false';
-      $q='INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $db->escape($_POST['page_id']) . '\', \'' . $db->escape($_POST['namespace']) . '\', \''.$data.'\', \''.$id.'\', \''.$session->username.'\', \''.$db->escape(htmlspecialchars($_POST['summary'])).'\', '.$minor.');';
+      $q='INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $db->escape($_POST['page_id']) . '\', \'' . $db->escape($_POST['namespace']) . '\', \''.$db->escape($data).'\', \''.$id.'\', \''.$session->username.'\', \''.$db->escape(htmlspecialchars($_POST['summary'])).'\', '.$minor.');';
       if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
       
       $query = 'UPDATE '.table_prefix.'page_text SET page_text=\''.$db->escape($data).'\',char_tag=\''.$id.'\' WHERE page_id=\'' . $db->escape($_POST['page_id']) . '\' AND namespace=\'' . $db->escape($_POST['namespace']) . '\';';
@@ -1889,12 +1616,12 @@
     ?>
     <p>
     <textarea name="content" rows="20" cols="60" style="width: 100%;"><?php echo htmlspecialchars($content); ?></textarea><br />
-    Edit summary: <input name="summary" value="<?php if(isset($_POST['summary'])) echo $_POST['summary']; ?>" size="40" /><br />
+    Edit summary: <input name="summary" value="<?php if(isset($_POST['summary'])) echo htmlspecialchars($_POST['summary']); ?>" size="40" /><br />
     <label><input type="checkbox" name="minor" <?php if(isset($_POST['minor'])) echo 'checked="checked" '; ?>/>  This is a minor edit</label>
     </p>
     <p>
-    <input type="hidden" name="page_id" value="<?php echo $_POST['page_id']; ?>" />
-    <input type="hidden" name="namespace" value="<?php echo $_POST['namespace']; ?>" />
+    <input type="hidden" name="page_id" value="<?php echo htmlspecialchars($_POST['page_id']); ?>" />
+    <input type="hidden" name="namespace" value="<?php echo htmlspecialchars($_POST['namespace']); ?>" />
     <input type="submit" name="save" value="Save changes" style="font-weight: bold;" />&nbsp;&nbsp;<input type="submit" name="preview" value="Show preview" />&nbsp;&nbsp;<input type="submit" name="revert" value="Revert changes" onclick="return confirm('Do you really want to revert your changes?');" />&nbsp;&nbsp;<input type="submit" name="cancel" value="Cancel" onclick="return confirm('Do you really want to cancel your changes?');" />
     </p>
     <?php
@@ -2527,6 +2254,10 @@
     return;
   }
   
+  if ( ENANO_DBLAYER != 'MYSQL' )
+    die('<h3>Not supported</h3>
+          <p>This function is only supported under the MySQL database driver.</p>');
+  
   if(isset($_GET['submitting']) && $_GET['submitting'] == 'yes' && defined('ENANO_DEMO_MODE') )
   {
     redirect(makeUrlComplete('Special', 'Administration'), 'Access denied', 'You\'ve got to be kidding me. Forget it, kid.', 4 );
@@ -2538,13 +2269,11 @@
     
     if(defined('SQL_BACKUP_CRYPT'))
       // Try to increase our time limit
-      @set_time_limit(300); // five minutes
+      @set_time_limit(0);
     // Do the actual export
     $aesext = ( defined('SQL_BACKUP_CRYPT') ) ? '.tea' : '';
     $filename = 'enano_backup_' . date('ymd') . '.sql' . $aesext;
     ob_start();
-    header('Content-disposition: attachment, filename="'.$filename.'";');
-    header('Content-type: application/transact-sql');
     // Spew some headers
     $headdate = date('F d, Y \a\t h:i a');
     echo <<<HEADER
@@ -2574,12 +2303,17 @@
       // THE FOLLOWING COMMENT DOES NOT APPLY AS OF 1.0.
       // Sorry folks - this script CAN'T backup enano_files and enano_search_index due to the sheer size of the tables.
       // If encryption is enabled the log data will be excluded too.
-      echo export_table(
+      $result = export_table(
         $t,
         isset($_POST['do_struct']),
         ( isset($_POST['do_data']) ),
         false
         ) . "\n";
+      if ( !$result )
+      {
+        $db->_die();
+      }
+      echo $result;
     }
     $data = ob_get_contents();
     ob_end_clean();
@@ -2591,6 +2325,8 @@
       $tea = new TEACrypt();
       $data = $tea->encrypt($data, $session->private_key);
     }
+    header('Content-disposition: attachment, filename="'.$filename.'";');
+    header('Content-type: application/transact-sql');
     header('Content-length: '.strlen($data));
     echo $data;
     exit;
@@ -2605,7 +2341,14 @@
     <p>Additional tables to export:</p>
     <p><select name="additional_tables[]" multiple="multiple">
        <?php
-         $q = $db->sql_query('SHOW TABLES;') or $db->_die('Somehow we were denied the request to get the list of tables.');
+         if ( ENANO_DBLAYER == 'MYSQL' )
+         {
+           $q = $db->sql_query('SHOW TABLES;') or $db->_die('Somehow we were denied the request to get the list of tables.');
+         }
+         else if ( ENANO_DBLAYER == 'PGSQL' )
+         {
+           $q = $db->sql_query('SELECT relname FROM pg_stat_user_tables ORDER BY relname;') or $db->_die('Somehow we were denied the request to get the list of tables.');
+         }
          while($row = $db->fetchrow_num())
          {
            if(!in_array($row[0], $system_table_list)) echo '<option value="'.$row[0].'">'.$row[0].'</option>';
--- a/plugins/SpecialCSS.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialCSS.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:CSS, which is used in template files to reference the style sheet. Disabling or deleting this plugin will result in site instability.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialGroups.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialGroups.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides group moderators and site administrators with the ability to control who is part of their groups. 
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -59,14 +59,14 @@
     $db->free_result();
     $members = array();
     $pending = array();
-    $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,m.pending,COUNT(c.comment_id)
+    $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,m.pending,COUNT(c.comment_id) AS num_comments
                            FROM '.table_prefix.'users AS u
                            LEFT JOIN '.table_prefix.'group_members AS m
                              ON ( m.user_id = u.user_id )
                            LEFT JOIN '.table_prefix.'comments AS c
                              ON ( c.name = u.username )
                            WHERE m.group_id=' . $gid . '
-                           GROUP BY u.user_id
+                           GROUP BY u.user_id,u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,m.pending
                            ORDER BY m.is_mod DESC,u.username ASC;');
     if ( !$q )
     {
@@ -200,7 +200,7 @@
             $db->_die('SpecialGroups.php, line ' . __LINE__);
           echo '<div class="info-box">The user "' . $username . '" has been added to this usergroup.</div>';
           
-          $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id)
+          $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id) AS num_comments
                                  FROM '.table_prefix.'users AS u
                                  LEFT JOIN '.table_prefix.'group_members AS m
                                    ON ( m.user_id = u.user_id )
@@ -209,7 +209,7 @@
                                  WHERE m.group_id=' . $gid . '
                                    AND m.pending!=1
                                    AND u.user_id=' . $uid . '
-                                 GROUP BY u.user_id
+                                 GROUP BY u.user_id,u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod
                                  ORDER BY m.is_mod DESC,u.username ASC
                                  LIMIT 1;');
           if ( !$q )
@@ -267,7 +267,7 @@
         $db->_die('SpecialGroups.php, line ' . __LINE__);
       echo '<div class="info-box">You have been added to this group.</div>';
       
-      $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id)
+      $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id) AS num_comments
                              FROM '.table_prefix.'users AS u
                              LEFT JOIN '.table_prefix.'group_members AS m
                                ON ( m.user_id = u.user_id )
@@ -276,7 +276,7 @@
                              WHERE m.group_id=' . $gid . '
                                AND m.pending!=1
                                AND u.user_id=' . $session->user_id . '
-                             GROUP BY u.user_id
+                             GROUP BY u.user_id,u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod
                              ORDER BY m.is_mod DESC,u.username ASC
                              LIMIT 1;');
       if ( !$q )
@@ -370,7 +370,7 @@
                 <td class='{$cls}'>{$member['username']}</td>
                 <td class='{$cls}'>{$addy}</td>
                 <td class='{$cls}'>{$date}</td>
-                <td class='{$cls}'>{$member['COUNT(c.comment_id)']}</td>
+                <td class='{$cls}'>{$member['num_comments']}</td>
                 <td class='{$cls}' style='text-align: center;'><input type='checkbox' name='with_user[{$member['member_id']}]' /></td>
               </tr>";
       }
@@ -418,7 +418,7 @@
               <td class='{$cls}'>{$member['username']}</td>
               <td class='{$cls}'>{$addy}</td>
               <td class='{$cls}'>{$date}</td>
-              <td class='{$cls}'>{$member['COUNT(c.comment_id)']}</td>
+              <td class='{$cls}'>{$member['num_comments']}</td>
               " . ( ( $can_do_admin_stuff ) ? "
               <td class='{$cls}' style='text-align: center;'><input type='checkbox' name='del_user[{$member['member_id']}]' /></td>
               " : '' ) . "
@@ -442,7 +442,7 @@
               <td class='{$cls}'>{$member['username']}</td>
               <td class='{$cls}'>{$addy}</td>
               <td class='{$cls}'>{$date}</td>
-              <td class='{$cls}'>{$member['COUNT(c.comment_id)']}</td>
+              <td class='{$cls}'>{$member['num_comments']}</td>
               " . ( ( $can_do_admin_stuff ) ? "
               <td class='{$cls}' style='text-align: center;'><input type='checkbox' name='del_user[{$member['member_id']}]' /></td>
               " : '' ) . "
--- a/plugins/SpecialPageFuncs.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialPageFuncs.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:CreatePage, which can be used to create new pages. Also adds the About Enano and GNU General Public License pages.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -109,6 +109,11 @@
       
       exit;
     }
+    $code = $plugins->setHook('page_create_request');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
     if ( substr($urlname, 0, 8) == 'Project:' )
     {
       $template->header();
@@ -442,9 +447,25 @@
                 </a>
               </td>
               <td style="text-align: center;">
-                <a href="http://www.mysql.com/" onclick="window.open(this.href); return false;" style="background: none; padding: 0;">
-                  <img alt="Database engine powered by MySQL" src="<?php echo scriptPath; ?>/images/about-powered-mysql.png" style="border-width: 0px;" width="88" height="31" />
-                </a>
+                <?php
+                switch(ENANO_DBLAYER)
+                {
+                  case 'MYSQL':
+                    ?>
+                    <a href="http://www.mysql.com/" onclick="window.open(this.href); return false;" style="background: none; padding: 0;">
+                      <img alt="Database engine powered by MySQL" src="<?php echo scriptPath; ?>/images/about-powered-mysql.png" style="border-width: 0px;" width="88" height="31" />
+                    </a>
+                    <?php
+                    break;
+                  case 'PGSQL':
+                    ?>
+                    <a href="http://www.postgresql.org/" onclick="window.open(this.href); return false;" style="background: none; padding: 0;">
+                      <img alt="Database engine powered by PostgreSQL" src="<?php echo scriptPath; ?>/images/about-powered-pgsql.png" style="border-width: 0px;" width="90" height="30" />
+                    </a>
+                    <?php
+                    break;
+                }
+                ?>
               </td>
             </tr>
           </table>
@@ -454,7 +475,23 @@
       <tr><td style="width: 100px;" class="row2"><?php echo $lang->get('meta_enano_about_lbl_webserver'); ?></td><td class="row2"><?php if(isset($_SERVER['SERVER_SOFTWARE'])) echo $_SERVER['SERVER_SOFTWARE']; else echo 'Unable to determine web server software.'; ?></td></tr>
       <tr><td style="width: 100px;" class="row1"><?php echo $lang->get('meta_enano_about_lbl_serverplatform'); ?></td><td class="row1"><?php echo $platform; ?></td></tr>
       <tr><td style="width: 100px;" class="row2"><?php echo $lang->get('meta_enano_about_lbl_phpversion'); ?></td><td class="row2"><?php echo PHP_VERSION; ?></td></tr>
-      <tr><td style="width: 100px;" class="row1"><?php echo $lang->get('meta_enano_about_lbl_mysqlversion'); ?></td><td class="row1"><?php echo mysql_get_server_info($db->_conn); ?></td></tr>
+      <?php
+      switch(ENANO_DBLAYER)
+      {
+        case 'MYSQL':
+          ?>
+          <tr><td style="width: 100px;" class="row1"><?php echo $lang->get('meta_enano_about_lbl_mysqlversion'); ?></td><td class="row1"><?php echo mysql_get_server_info($db->_conn); ?></td></tr>
+          <?php
+          break;
+        case 'PGSQL':
+          $pg_serverdata = pg_version($db->_conn);
+          $pg_version = $pg_serverdata['server'];
+          ?>
+          <tr><td style="width: 100px;" class="row1"><?php echo $lang->get('meta_enano_about_lbl_pgsqlversion'); ?></td><td class="row1"><?php echo $pg_version; ?></td></tr>
+          <?php
+          break;
+      }
+      ?>
     </table>
   </div>
   <?php
--- a/plugins/SpecialSearch.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialSearch.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Search, which is a frontend to the Enano search engine.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialUpdownload.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialUpdownload.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the pages Special:UploadFile and Special:DownloadFile. UploadFile is used to upload files to the site, and DownloadFile fetches the file from the database, creates thumbnails if necessary, and sends the file to the user.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  * SpecialUpdownload.php - handles uploading and downloading of user-uploaded files - possibly the most rigorously security-enforcing script in all of Enano, although sessions.php comes in a close second
  *
--- a/plugins/SpecialUserFuncs.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialUserFuncs.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the pages Special:Login, Special:Logout, Special:Register, and Special:Preferences.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -150,6 +150,7 @@
   
   if ( isset($_GET['act']) && $_GET['act'] == 'getkey' )
   {
+    header('Content-type: application/json');
     $username = ( $session->user_logged_in ) ? $session->username : false;
     $response = Array(
       'username' => $username,
@@ -1504,12 +1505,15 @@
                             array('%', '_'),
                             $finduser);
     $finduser = $db->escape($finduser);
-    $username_where = 'u.username LIKE "' . $finduser . '"';
+    $username_where = ENANO_SQLFUNC_LOWERCASE . '(u.username) LIKE \'%' . strtolower($finduser) . '%\'';
     $finduser_url = 'finduser=' . rawurlencode($_GET['finduser']) . '&';
   }
   else
   {
-    $username_where = 'u.username REGEXP "^' . $startletter_sql . '"';
+    if ( ENANO_DBLAYER == 'MYSQL' )
+      $username_where = 'lcase(u.username) REGEXP lcase("^' . $startletter_sql . '")';
+    else if ( ENANO_DBLAYER == 'PGSQL' )
+      $username_where = 'lower(u.username) ~ lower(\'^' . $startletter_sql . '\')';
     $finduser_url = '';
   }
   
@@ -1533,7 +1537,7 @@
                </tr>';
                
   // determine number of rows
-  $q = $db->sql_query('SELECT u.user_id FROM '.table_prefix.'users AS u WHERE ' . $username_where . ' AND u.username != "Anonymous";');
+  $q = $db->sql_query('SELECT u.user_id FROM '.table_prefix.'users AS u WHERE ' . $username_where . ' AND u.username != \'Anonymous\';');
   if ( !$q )
     $db->_die();
   
@@ -1550,7 +1554,7 @@
   $q = $db->sql_unbuffered_query('SELECT u.user_id, u.username, u.reg_time, u.email, u.user_level, u.reg_time, x.email_public FROM '.table_prefix.'users AS u
                                     LEFT JOIN '.table_prefix.'users_extra AS x
                                       ON ( u.user_id = x.user_id )
-                                    WHERE ' . $username_where . ' AND u.username != "Anonymous"
+                                    WHERE ' . $username_where . ' AND u.username != \'Anonymous\'
                                     ORDER BY ' . $sort_sqllet . ' ' . $target_order . ';');
   if ( !$q )
     $db->_die();
@@ -1580,7 +1584,7 @@
               ' .
               '<div style="float: left;">
                 <form action="' . makeUrlNS('Special', 'Memberlist') . '" method="get" onsubmit="if ( !submitAuthorized ) return false;">'
-               . ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->nslist[$paths->namespace] . $paths->cpage['urlname_nons'] ) . '" />' : '' )
+               . ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->page ) . '" />' : '' )
                . ( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : '')
                . '<p>Find a member: ' . $template->username_field('finduser') . ' <input type="submit" value="Go" /><br /><small>You may use the following wildcards: * to match multiple characters, ? to match a single character.</small></p>'
                . '</form>
--- a/plugins/SpecialUserPrefs.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/SpecialUserPrefs.php	Wed Dec 19 22:55:40 2007 -0500
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Preferences.
 Author: Dan Fuhry
-Version: 1.0.2
+Version: 1.0.3
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2
+ * Version 1.0.3
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute it and/or modify it under the terms of the GNU General Public License
@@ -47,7 +47,7 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   
-  if ( $paths->namespace != 'Special' || $paths->cpage['urlname_nons'] != 'Preferences' )
+  if ( $paths->namespace != 'Special' || $paths->page_id != 'Preferences' )
     return false;
   
   $tb .= "<ul>$template->toolbar_menu</ul>";
@@ -599,7 +599,7 @@
       $code = $plugins->setHook('userprefs_body');
       foreach ( $code as $cmd )
       {
-        if ( eval($code) )
+        if ( eval($cmd) )
           $good = true;
       }
       if ( !$good )
--- a/plugins/admin/PageGroups.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/admin/PageGroups.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/admin/SecurityLog.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/admin/SecurityLog.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -62,7 +62,7 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
   if ( $session->auth_level < USER_LEVEL_ADMIN )
   {
-    $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","seclog_unauth",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+    $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES(\'security\',\'seclog_unauth\',' . time() . ',"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
     if ( !$q )
       $db->_die();
     die('Security log: unauthorized attempt to fetch. Call has been logged and reported to the administrators.');
--- a/plugins/admin/UserManager.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/plugins/admin/UserManager.php	Wed Dec 19 22:55:40 2007 -0500
@@ -205,14 +205,14 @@
           // We need to update group memberships
           if ( $existing_level == USER_LEVEL_ADMIN ) 
           {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_from_admin",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
+            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'u_from_admin\',' . time() . ',"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
             if ( !$q )
               $db->_die();
             $session->remove_user_from_group($user_id, GROUP_ID_ADMIN);
           }
           else if ( $existing_level == USER_LEVEL_MOD ) 
           {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_from_mod",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
+            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'u_from_mod\',' . time() . ',"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
             if ( !$q )
               $db->_die();
             $session->remove_user_from_group($user_id, GROUP_ID_MOD);
@@ -220,14 +220,14 @@
           
           if ( $user_level == USER_LEVEL_ADMIN )
           {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_to_admin",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
+            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'u_to_admin\',' . time() . ',"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
             if ( !$q )
               $db->_die();
             $session->add_user_to_group($user_id, GROUP_ID_ADMIN, false);
           }
           else if ( $user_level == USER_LEVEL_MOD )
           {
-            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","u_to_mod",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
+            $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES(\'security\',\'u_to_mod\',' . time() . ',"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($username) . '");');
             if ( !$q )
               $db->_die();
             $session->add_user_to_group($user_id, GROUP_ID_MOD, false);
@@ -293,7 +293,7 @@
     $q = $db->sql_query('SELECT u.user_id AS authoritative_uid, u.username, u.email, u.real_name, u.signature, u.account_active, u.user_level, x.* FROM '.table_prefix.'users AS u
                            LEFT JOIN '.table_prefix.'users_extra AS x
                              ON ( u.user_id = x.user_id OR x.user_id IS NULL )
-                           WHERE ( lcase(u.username) = \'' . $db->escape(strtolower($username)) . '\' OR u.username = \'' . $db->escape($username) . '\' ) AND u.user_id != 1;');
+                           WHERE ( ' . ENANO_SQLFUNC_LOWERCASE . '(u.username) = \'' . $db->escape(strtolower($username)) . '\' OR u.username = \'' . $db->escape($username) . '\' ) AND u.user_id != 1;');
     if ( !$q )
       $db->_die();
     
--- a/themes/oxygen/css/bleu.css	Fri Dec 07 18:47:37 2007 -0500
+++ b/themes/oxygen/css/bleu.css	Wed Dec 19 22:55:40 2007 -0500
@@ -233,19 +233,24 @@
 td.mdg-menu-btm                   {              height: 12px;     background: url(../images/bleu/border-btm.gif);   }
 
 /* Buttons and textboxes - these settings are used almost everywhere */
-input, textarea, select                   { border: 1px solid #406080; background-color: #F2F2F2; padding: 3px; font-family: arial, helvetica, sans-serif; font-size: 8pt; }
+input, textarea, select, button           { border: 1px solid #406080; background-color: #F2F2F2; padding: 3px; font-family: arial, helvetica, sans-serif; font-size: 8pt; }
 input:hover, textarea:hover, select:hover { border: 1px solid #6080A0; background-color: #F8F8F8; padding: 3px; }
 input:focus, textarea:focus, select:focus { border: 1px solid #90B0D0; background-color: #FFFFFF; padding: 3px; }
 label                                     { padding: 3px; cursor: pointer; font-family: arial, helvetica, sans-serif; font-size: 8pt; }
 label:hover                               { padding: 3px; cursor: pointer; background-color: #F0F0F0; }
 input#pageheading                         { font-size: 14pt; border-bottom: 1px solid #90B0D0; margin-bottom: 0; }
 
-input[type ^="button"], input[type ^="submit"] {
+input[type ^="button"], input[type ^="submit"], button {
   background-image: url(../images/buttonbg.gif);
   background-repeat: repeat-x;
   color: #202020;
 }
 
+input[type ^="image"][disabled ^="disabled"] {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
 /* JWS window theming */
 div.jswindow                      { border: 2px solid #7090B0; border-top: 5px solid #7090B0; padding: 0px; font-family: Trebuchet MS, tahoma, verdana, arial, sans-serif; font-size: 9pt; display: none; position: absolute; background-color: #FFFFFF; }
 div.titlebar                      { background-color: #7090B0; color: #FFFFFF; font-family: Trebuchet MS, tahoma, verdana, arial, sans-serif; font-size: 9pt; padding-bottom: 4px; cursor: default; }
--- a/upgrade.php	Fri Dec 07 18:47:37 2007 -0500
+++ b/upgrade.php	Wed Dec 19 22:55:40 2007 -0500
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.0.3 (Dyrad)
  * upgrade.php - upgrade script
  * Copyright (C) 2006-2007 Dan Fuhry
  *
@@ -70,7 +70,7 @@
 // Everything related to versions goes here!
 
 // Valid versions to upgrade from
-$valid_versions = Array('1.0b1', '1.0b2', '1.0b3', '1.0b4', '1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.1.1', '1.0.2b1', '1.0.2', 'Stable1.0ToUnstable1.1');
+$valid_versions = Array('1.0b1', '1.0b2', '1.0b3', '1.0b4', '1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.1.1', '1.0.2b1', '1.0.2', '1.0.3', 'Stable1.0ToUnstable1.1');
 
 // Basically a list of dependencies, which should be resolved automatically
 // If, for example, upgrading from 1.0b1 to 1.0RC1 requires one extra query that would not
@@ -86,7 +86,8 @@
     '1.0' => Array('1.0.1'),
     '1.0.1' => Array('1.0.1.1'),
     '1.0.1.1' => Array('1.0.2b1'),
-    '1.0.2b1' => Array('Stable1.0ToUnstable1.1'),
+    '1.0.2b1' => Array('1.0.2'),
+    '1.0.2' => Array('Stable1.0ToUnstable1.1'),
     'Stable1.0ToUnstable1.1' => Array('1.1.1')
   );
 $this_version   = '1.1.1';
--- a/upgrade.sql	Fri Dec 07 18:47:37 2007 -0500
+++ b/upgrade.sql	Wed Dec 19 22:55:40 2007 -0500
@@ -14,6 +14,7 @@
 ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_lang smallint(5) NOT NULL;
 ---END Stable1.0ToUnstable1.1---
 ---BEGIN 1.0.2---
+-- No DB changes in this release
 ---END 1.0.2---
 ---BEGIN 1.0.2b1---
 -- This is really optional, but could reduce confusion if regex page groups get truncated for no apparent reason.