Major underlying changes to namespace handling. Each namespace is handled by its own class which extends Namespace_Default. Much greater customization/pluggability potential, at the possible expense of some code reusing (though code reusing has been avoided thus far). Also a bit better handling of page passwords [SECURITY].
authorDan
Sat, 03 Jan 2009 18:11:18 -0500
changeset 800 9cdfe82c56cd
parent 799 4629ad98ee88
child 801 eb8b23f11744
Major underlying changes to namespace handling. Each namespace is handled by its own class which extends Namespace_Default. Much greater customization/pluggability potential, at the possible expense of some code reusing (though code reusing has been avoided thus far). Also a bit better handling of page passwords [SECURITY].
ajax.php
includes/clientside/jsres.php
includes/clientside/static/ajax.js
includes/comment.php
includes/common.php
includes/functions.php
includes/namespaces/admin.php
includes/namespaces/api.php
includes/namespaces/default.php
includes/namespaces/file.php
includes/namespaces/special.php
includes/namespaces/template.php
includes/namespaces/user.php
includes/output.php
includes/pageprocess.php
includes/pageutils.php
includes/render.php
language/english/core.json
--- a/ajax.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/ajax.php	Sat Jan 03 18:11:18 2009 -0500
@@ -32,6 +32,7 @@
       $revid = ( isset($_GET['revid']) ) ? intval($_GET['revid']) : 0;
       $page = new PageProcessor($paths->page_id, $paths->namespace, $revid);
       $page->password = $password;
+      
       $have_draft = false;
       if ( $src = $page->fetch_source() )
       {
@@ -151,6 +152,8 @@
       break;
     case "getpage":
       // echo PageUtils::getpage($paths->page, false, ( (isset($_GET['oldid'])) ? $_GET['oldid'] : false ));
+      $output = new Output_Striptease();
+      
       $revision_id = ( (isset($_GET['oldid'])) ? intval($_GET['oldid']) : 0 );
       $page = new PageProcessor( $paths->page_id, $paths->namespace, $revision_id );
       
--- a/includes/clientside/jsres.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/clientside/jsres.php	Sat Jan 03 18:11:18 2009 -0500
@@ -270,7 +270,8 @@
 header("Last-Modified: $date");
 header("ETag: \"$etag\"");
 header("Expires: $expires");
-header("Content-Length: " . strlen($everything));
+if ( !$do_gzip )
+  header("Content-Length: " . strlen($everything));
 
 $local_end = microtime_float();
 $local_gentime = $local_end - $local_start;
--- a/includes/clientside/static/ajax.js	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/clientside/static/ajax.js	Sat Jan 03 18:11:18 2009 -0500
@@ -13,7 +13,7 @@
   enableUnload();
   setAjaxLoading();
   var redir = ( disable_redirect ) ? '&redirect=no' : '';
-  ajaxGet(scriptPath + '/ajax.php?title=' + physical_title +'&_mode=getpage&noheaders' + redir, function() {
+  ajaxGet(append_sid(scriptPath + '/ajax.php?title=' + physical_title +'&_mode=getpage&noheaders' + redir), function() {
     // Allow for 404 here, it's generated by the "page not found" error message
     // (even with noheaders specified, probably should be fixed)
     if ( ajax.readyState == 4 && ( ajax.status == 200 || ajax.status == 404 ) ) {
--- a/includes/comment.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/comment.php	Sat Jan 03 18:11:18 2009 -0500
@@ -16,7 +16,7 @@
  * Class that handles comments. Has HTML/Javascript frontend support.
  * @package Enano CMS
  * @subpackage Comment manager
- * @license GNU General Public License <http://www.gnu.org/licenses/gpl.html>
+ * @license GNU General Public License <http://www.gnu.org/licenses/gpl-2.0.html>
  */
 
 class Comments
--- a/includes/common.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/common.php	Sat Jan 03 18:11:18 2009 -0500
@@ -136,6 +136,7 @@
 require_once(ENANO_ROOT.'/includes/paths.php');
 require_once(ENANO_ROOT.'/includes/sessions.php');
 require_once(ENANO_ROOT.'/includes/template.php');
+require_once(ENANO_ROOT.'/includes/output.php');
 require_once(ENANO_ROOT.'/includes/plugins.php');
 require_once(ENANO_ROOT.'/includes/cache.php');
 require_once(ENANO_ROOT.'/includes/lang.php');
@@ -144,6 +145,7 @@
 require_once(ENANO_ROOT.'/includes/email.php');
 require_once(ENANO_ROOT.'/includes/json2.php');
 require_once(ENANO_ROOT.'/includes/pageprocess.php');
+require_once(ENANO_ROOT.'/includes/namespaces/default.php');
 require_once(ENANO_ROOT.'/includes/tagcloud.php');
 
 strip_magic_quotes_gpc();
@@ -442,6 +444,14 @@
   
   $paths->init();
   
+  // setup output format
+  if ( defined('ENANO_OUTPUT_FORMAT') )
+    $class = 'Output_' . ENANO_OUTPUT_FORMAT;
+  else
+    $class = ( isset($_GET['noheaders']) ) ? 'Output_Naked' : 'Output_HTML';
+    
+  $output = new $class();
+  
   // We're ready for whatever life throws us now, at least from an API point of view.
   define('ENANO_MAINSTREAM', '');
   
--- a/includes/functions.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/functions.php	Sat Jan 03 18:11:18 2009 -0500
@@ -277,7 +277,7 @@
   {
     $logged_in = true;
   }
-  return $logged_in ? getConfig('main_page_alt', getConfig('main_page')) : getConfig('main_page');
+  return $logged_in && getConfig('main_page_alt_enable', '0') == '1' ? getConfig('main_page_alt', getConfig('main_page')) : getConfig('main_page');
 }
 
 /**
@@ -523,10 +523,10 @@
 function csrf_request_confirm()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
+  global $lang, $output;
   
   // If the token was overridden with the correct one, the user confirmed the action using this form. Continue exec.
-  if ( isset($_POST['cstok']) || isset($_GET ['cstok']) )
+  if ( isset($_POST['cstok']) || isset($_GET['cstok']) )
   {
     // using the if() check makes sure that the token isn't in a cookie, since $_REQUEST includes $_COOKIE.
     $token_check =& $_REQUEST['cstok'];
@@ -537,8 +537,8 @@
     }
   }
   
-  $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($lang->get('user_csrf_confirm_title'));
-  $template->header();
+  $output->set_title($lang->get('user_csrf_confirm_title'));
+  $output->header();
   
   // initial info
   echo '<p>' . $lang->get('user_csrf_confirm_body') . '</p>';
@@ -564,9 +564,9 @@
   // insert the right CSRF token
   echo '<input type="hidden" name="cstok" value="' . $session->csrf_token . '" />';
   echo '<p><input type="submit" value="' . $lang->get('user_csrf_confirm_btn_continue') . '" /></p>';
-  echo '</form>';
+  echo '</form><script type="text/javascript">addOnloadHook(function(){load_component(\'expander\');});</script>';
   
-  $template->footer();
+  $output->footer();
   
   exit;
 }
@@ -638,6 +638,35 @@
 }
 
 /**
+ * Returns the appropriate Namespace_* object for a page.
+ * @param string Page ID
+ * @param string Namespace
+ * @param int Revision ID
+ */
+
+function namespace_factory($page_id, $namespace, $revision_id = 0)
+{
+  if ( !class_exists("Namespace_$namespace") )
+  {
+    if ( file_exists(ENANO_ROOT . "/includes/namespaces/" . strtolower($namespace) . ".php") )
+    {
+      require(ENANO_ROOT . "/includes/namespaces/" . strtolower($namespace) . ".php");
+    }
+  }
+  if ( class_exists("Namespace_$namespace") )
+  {
+    $class = "Namespace_$namespace";
+    $ns = new $class($page_id, $namespace, $revision_id);
+    return $ns;
+  }
+  else
+  {
+    $ns = new Namespace_Default($page_id, $namespace, $revision_id);
+    return $ns;
+  }
+}
+
+/**
  * These are some old functions that were used with the Midget codebase. They are deprecated and should not be used any more.
  */
 
@@ -683,22 +712,10 @@
 }
 
 function arrayItemBottom($arr, $keyname) {
-  $keylist = array_keys($arr);
-  $keyflop = array_flip($keylist);
-  $idx = $keyflop[$keyname];
-  $sz = sizeof($arr); $sz--;
-  while( $orig != $arr[$keylist[$sz]] ) {
-    // echo 'Keyname: '.$keylist[$idx] . '<br />'; flush(); ob_flush(); // Debugger
-    if($idx > $sz) return $arr;
-    if($keylist[$idx] == '' || $keylist[$idx] < 0 || !$keylist[$idx]) {
-      echo 'Infinite loop caught in arrayItemBottom(<br /><pre>';
-      print_r($arr);
-      echo '</pre><br />, '.$keyname.');<br /><br />EnanoCMS: Critical error during function call, exiting to prevent excessive server load.';
-      exit;
-    }
-    $arr = arrayItemDown($arr, $keylist[$idx]);
-    $idx++;
-  }
+  $b = $arr[$keyname];
+  unset($arr[$keyname]);
+  $arr[$keyname] = $b;
+  unset($b);
   return $arr;
 }
 
@@ -822,18 +839,11 @@
     exit;
   }
   
-  $theme = ( defined('ENANO_CONFIG_FETCHED') ) ? getConfig('theme_default') : 'oxygen';
-  $style = ( defined('ENANO_CONFIG_FETCHED') ) ? '__foo__' : 'bleu';
-  
-  $tpl = new template_nodb();
-  $tpl->load_theme($theme, $style);
-  $tpl->tpl_strings['SITE_NAME'] = getConfig('site_name');
-  $tpl->tpl_strings['SITE_DESC'] = getConfig('site_desc');
-  $tpl->tpl_strings['COPYRIGHT'] = getConfig('copyright_notice');
-  $tpl->tpl_strings['PAGE_NAME'] = $t;
-  $tpl->header();
+  $output = new Output_Safe();
+  $output->set_title($t);
+  $output->header();
   echo $p;
-  $tpl->footer();
+  $output->footer();
 
   exit;
 }
@@ -918,169 +928,7 @@
 
 function show_category_info()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
-  
-  if ( $paths->namespace == 'Category' )
-  {
-    // Show member pages and subcategories
-    $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->page_id) . '\'
-                           ORDER BY is_category DESC, p.name ASC;');
-    if ( !$q )
-    {
-      $db->_die();
-    }
-    echo '<h3>' . $lang->get('onpage_cat_heading_subcategories') . '</h3>';
-    echo '<div class="tblholder">';
-    echo '<table border="0" cellspacing="1" cellpadding="4">';
-    echo '<tr>';
-    $ticker = 0;
-    $counter = 0;
-    $switched = false;
-    $class  = 'row1';
-    while ( $row = $db->fetchrow() )
-    {
-      if ( $row['is_category'] == 0 && !$switched )
-      {
-        if ( $counter > 0 )
-        {
-          // Fill-in
-          while ( $ticker < 3 )
-          {
-            $ticker++;
-            echo '<td class="' . $class . '" style="width: 33.3%;"></td>';
-          }
-        }
-        else
-        {
-          echo '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_subcategories') . '</td>';
-        }
-        echo '</tr></table></div>' . "\n\n";
-        echo '<h3>' . $lang->get('onpage_cat_heading_pages') . '</h3>';
-        echo '<div class="tblholder">';
-        echo '<table border="0" cellspacing="1" cellpadding="4">';
-        echo '<tr>';
-        $counter = 0;
-        $ticker = -1;
-        $switched = true;
-      }
-      $counter++;
-      $ticker++;
-      if ( $ticker == 3 )
-      {
-        echo '</tr><tr>';
-        $ticker = 0;
-        $class = ( $class == 'row3' ) ? 'row1' : 'row3';
-      }
-      echo "<td class=\"{$class}\" style=\"width: 33.3%;\">"; // " to workaround stupid jEdit bug
-      
-      $link = makeUrlNS($row['namespace'], sanitize_page_id($row['urlname']));
-      echo '<a href="' . $link . '"';
-      $key = $paths->nslist[$row['namespace']] . sanitize_page_id($row['urlname']);
-      if ( !isPage( $key ) )
-      {
-        echo ' class="wikilink-nonexistent"';
-      }
-      echo '>';
-      $title = get_page_title_ns($row['urlname'], $row['namespace']);
-      echo htmlspecialchars($title);
-      echo '</a>';
-      
-      echo "</td>";
-    }
-    if ( !$switched )
-    {
-      if ( $counter > 0 )
-      {
-        // Fill-in
-        while ( $ticker < 2 )
-        {
-          $ticker++;
-          echo '<td class="' . $class . '" style="width: 33.3%;"></td>';
-        }
-      }
-      else
-      {
-        echo '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_subcategories') . '</td>';
-      }
-      echo '</tr></table></div>' . "\n\n";
-      echo '<h3>' . $lang->get('onpage_cat_heading_pages') . '</h3>';
-      echo '<div class="tblholder">';
-      echo '<table border="0" cellspacing="1" cellpadding="4">';
-      echo '<tr>';
-      $counter = 0;
-      $ticker = 0;
-      $switched = true;
-    }
-    if ( $counter > 0 )
-    {
-      // Fill-in
-      while ( $ticker < 2 )
-      {
-        $ticker++;
-        echo '<td class="' . $class . '" style="width: 33.3%;"></td>';
-      }
-    }
-    else
-    {
-      echo '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_pages') . '</td>';
-    }
-    echo '</tr></table></div>' . "\n\n";
-  }
-  
-  if ( $paths->namespace != 'Special' && $paths->namespace != 'Admin' )
-  {
-    echo '<div class="mdg-comment" style="margin: 10px 0 0 0;" id="category_box_wrapper">';
-    echo '<div style="float: right;">';
-    echo '(<a href="#" onclick="ajaxCatToTag(); return false;">' . $lang->get('tags_catbox_link') . '</a>)';
-    echo '</div>';
-    echo '<div id="mdgCatBox">' . $lang->get('catedit_catbox_lbl_categories') . ' ';
-    
-    $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
-  LEFT JOIN {$prefix}pages AS p
-    ON ( ( p.urlname = c.page_id AND p.namespace = c.namespace ) OR ( p.urlname IS NULL AND p.namespace IS NULL ) )
-  WHERE $where
-  ORDER BY p.name ASC, c.page_id ASC;
-EOF;
-    $q = $db->sql_query($sql);
-    if ( !$q )
-      $db->_die();
-    
-    if ( $row = $db->fetchrow() )
-    {
-      $list = array();
-      do
-      {
-        $cid = sanitize_page_id($row['category_id']);
-        $title = get_page_title_ns($cid, 'Category');
-        $link = makeUrlNS('Category', $cid);
-        $list[] = '<a href="' . $link . '">' . htmlspecialchars($title) . '</a>';
-      }
-      while ( $row = $db->fetchrow() );
-      echo implode(', ', $list);
-    }
-    else
-    {
-      echo $lang->get('catedit_catbox_lbl_uncategorized');
-    }
-    
-    $can_edit = ( $session->get_permissions('edit_cat') && ( !$paths->page_protected || $session->get_permissions('even_when_protected') ) );
-    if ( $can_edit )
-    {
-      $edit_link = '<a href="' . makeUrl($paths->page, 'do=catedit', true) . '" onclick="ajaxCatEdit(); return false;">' . $lang->get('catedit_catbox_link_edit') . '</a>';
-      echo ' [ ' . $edit_link . ' ]';
-    }
-    
-    echo '</div></div>';
-    
-  }
-  
+  throw new Exception('show_category_info() is deprecated. Use Namespace_*::display_categories().');
 }
 
 /**
@@ -1089,146 +937,7 @@
 
 function show_file_info($page = false)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
-  
-  $local_page_id = $paths->page_id;
-  $local_namespace = $paths->namespace;
-  
-  if ( is_object($page) )
-  {
-    $local_page = $page->page_id;
-    $local_namespace = $page->namespace;
-  }
-  
-  // Prevent unnecessary work
-  if ( $local_namespace != 'File' )
-    return null;
-  
-  $selfn = $local_page_id;
-  if ( substr($paths->cpage['name'], 0, strlen($paths->nslist['File'])) == $paths->nslist['File'])
-  {
-    $selfn = substr($local_page_id, strlen($paths->nslist['File']), strlen($local_page_id));
-  }
-  $selfn = $db->escape($selfn);
-  $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
-                    . "  LEFT JOIN " . table_prefix . "logs AS l\n"
-                    . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
-                    . "  WHERE f.page_id = '$selfn'\n"
-                    . "    ORDER BY f.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>' . $lang->get('onpage_filebox_heading') . '</h3>
-            <p>' . $lang->get('onpage_filebox_msg_not_found', array('upload_link' => makeUrlNS('Special', 'UploadFile/'.$local_page_id))) . '</p>
-          </div>
-          <br />';
-    return;
-  }
-  $r = $db->fetchrow();
-  $mimetype = $r['mimetype'];
-  $datestring = enano_date('F d, Y h:i a', (int)$r['time_id']);
-  echo '<div class="mdg-comment" style="margin-left: 0;">
-          <h3>' . $lang->get('onpage_filebox_heading') . '</h3>
-          <p>' . $lang->get('onpage_filebox_lbl_type') . ' '.$r['mimetype'].'<br />';
-  
-  $size = $r['size'] . ' ' . $lang->get('etc_unit_bytes');
-  if ( $r['size'] >= 1048576 )
-  {
-    $size .= ' (' . ( round($r['size'] / 1048576, 1) ) . ' ' . $lang->get('etc_unit_megabytes_short') . ')';
-  }
-  else if ( $r['size'] >= 1024 )
-  {
-    $size .= ' (' . ( round($r['size'] / 1024, 1) ) . ' ' . $lang->get('etc_unit_kilobytes_short') . ')';
-  }
-  
-  echo $lang->get('onpage_filebox_lbl_size', array('size' => $size));
-  
-  echo '<br />' . $lang->get('onpage_filebox_lbl_uploaded') . ' ' . $datestring . '</p>';
-  if ( substr($mimetype, 0, 6) != 'image/' && ( substr($mimetype, 0, 5) != 'text/' || $mimetype == 'text/html' || $mimetype == 'text/javascript' ) )
-  {
-    echo '<div class="warning-box">
-            ' . $lang->get('onpage_filebox_msg_virus_warning') . '
-          </div>';
-  }
-  if ( substr($mimetype, 0, 6) == 'image/' )
-  {
-    echo '<p>
-            <a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn).'">
-              <img style="border: 0;" alt="'.$paths->page.'" src="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.htmlspecialchars(urlSeparator).'preview').'" />
-            </a>
-          </p>';
-  }
-  echo '<p>
-          <a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.'/'.$r['time_id'].htmlspecialchars(urlSeparator).'download').'">
-            ' . $lang->get('onpage_filebox_btn_download') . '
-          </a>';
-  if(!$paths->page_protected && ( $paths->wiki_mode || $session->get_permissions('upload_new_version') ))
-  {
-    echo '  |  <a href="'.makeUrlNS('Special', 'UploadFile'.'/'.$selfn).'">
-            ' . $lang->get('onpage_filebox_btn_upload_new') . '
-          </a>';
-  }
-  echo '</p>';
-  if ( $db->numrows() > 1 )
-  {
-    // requery, sql_result_seek() doesn't work on postgres
-    $db->free_result();
-    $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
-                    . "  LEFT JOIN " . table_prefix . "logs AS l\n"
-                    . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
-                    . "  WHERE f.page_id = '$selfn'\n"
-                    . "    ORDER BY f.time_id DESC;");
-    if ( !$q )
-      $db->_die();
-    
-    echo '<h3>' . $lang->get('onpage_filebox_heading_history') . '</h3><p>';
-    $last_rollback_id = false;
-    while ( $r = $db->fetchrow() )
-    {
-      echo '(<a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.'/'.$r['time_id'].htmlspecialchars(urlSeparator).'download').'">' . $lang->get('onpage_filebox_btn_this_version') . '</a>) ';
-      if ( $session->get_permissions('history_rollback') && $last_rollback_id )
-        echo ' (<a href="#rollback:' . $last_rollback_id . '" onclick="ajaxRollback(\''.$last_rollback_id.'\'); return false;">' . $lang->get('onpage_filebox_btn_revert') . '</a>) ';
-      else if ( $session->get_permissions('history_rollback') && !$last_rollback_id )
-        echo ' (' . $lang->get('onpage_filebox_btn_current') . ') ';
-      $last_rollback_id = $r['log_id'];
-      $mimetype = $r['mimetype'];
-      $datestring = enano_date('F d, Y h:i a', (int)$r['time_id']);
-      
-      echo $datestring.': '.$r['mimetype'].', ';
-      
-      $fs = $r['size'];
-      $fs = (int)$fs;
-      
-      if($fs >= 1048576)
-      {
-        $fs = round($fs / 1048576, 1);
-        $size = $fs . ' ' . $lang->get('etc_unit_megabytes_short');
-      }
-      else
-      if ( $fs >= 1024 )
-      {
-        $fs = round($fs / 1024, 1);
-        $size = $fs . ' ' . $lang->get('etc_unit_kilobytes_short');
-      }
-      else
-      {
-        $size = $fs . ' ' . $lang->get('etc_unit_bytes');
-      }
-      
-      echo $size;
-      
-      echo '<br />';
-    }
-    echo '</p>';
-  }
-  $db->free_result();
-  echo '</div><br />';
+  throw new Exception('show_file_info() is deprecated. Use Namespace_File::show_info().');
 }
 
 /**
@@ -1262,14 +971,17 @@
 function display_page_footers()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  if(isset($_GET['nofooters'])) return;
+  
+  if ( isset($_GET['nofooters']) )
+  {
+    return;
+  }
+  
   $code = $plugins->setHook('send_page_footers');
   foreach ( $code as $cmd )
   {
     eval($cmd);
   }
-  show_file_info();
-  show_category_info();
 }
 
 /**
@@ -2915,6 +2627,8 @@
     $char = strtolower($char);
     $char = intval(hexdec($char));
     $char = chr($char);
+    if ( preg_match('/^[\w\.\/:;\(\)@\[\]_-]$/', $char) )
+      continue;
     $page_id = str_replace($matches[0][$id], $char, $page_id);
   }
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/admin.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+require_once(ENANO_ROOT . '/includes/namespaces/special.php');
+class Namespace_Admin extends Namespace_Special
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/api.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+class Namespace_API extends Namespace_Default
+{
+  function send()
+  {
+    global $output, $session;
+    $uri = scriptPath . '/' . $this->page_id;
+    if ( $output->naked )
+    {
+      $sep = ( strstr($uri, '?') ) ? '&' : '?';
+      $uri .= "{$sep}noheaders";
+    }
+    if ( $session->sid_super )
+    {
+      $sep = ( strstr($uri, '?') ) ? '&' : '?';
+      $uri .= "{$sep}auth={$session->sid_super}";
+    }
+    redirect( $uri, '', '', 0 );
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/default.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,735 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * The default handler for namespaces. Basically fetches the page text from the database. Other namespaces should extend this class.
+ * @package Enano
+ * @subpackage PageHandler
+ * @author Dan Fuhry <dan@enanocms.org>
+ * @license GNU General Public License <http://www.gnu.org/licenses/gpl-2.0.html>
+ */
+
+class Namespace_Default
+{
+  /**
+   * Page ID
+   * @var string
+   */
+  
+  public $page_id;
+  
+  /**
+   * Namespace
+   * @var string
+   */
+  
+  public $namespace;
+  
+  /**
+   * Local copy of the page text
+   */
+  
+  public $text_cache;
+  
+  /**
+   * Revision ID to send. If 0, the latest revision.
+   * @var int
+   */
+  
+  public $revision_id = 0;
+  
+  /**
+   * Tracks whether the page exists
+   * @var bool
+   */
+  
+  public $exists = false;
+  
+  /**
+   * Page title
+   * @var string
+   */
+  
+  public $title = '';
+  
+  /**
+   * Constructor.
+   */
+  
+  public function __construct($page_id, $namespace, $revision_id = 0)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    $this->page_id = sanitize_page_id($page_id);
+    $this->namespace = $namespace;
+    $this->revision_id = intval($revision_id);
+    
+    // only do this if calling from the (very heavily feature filled) abstract
+    // this will still be called if you're using your own handler but not replacing the constructor
+    if ( __CLASS__ == 'Namespace_Default' )
+    {
+      $this->exists = false;
+      // NOTE! These should already be WELL sanitized before we reach this stage.
+      $q = $db->sql_query('SELECT name FROM ' . table_prefix . "pages WHERE urlname = '$this->page_id' AND namespace = '$this->namespace';");
+      if ( !$q )
+        $db->_die();
+      
+      if ( $db->numrows() < 1 )
+      {
+        // we still have a chance... some older databases don't do dots in the page title right
+        if ( strstr(dirtify_page_id($this->page_id), '.') )
+        {
+          $page_id = str_replace('.', '.2e', $page_id);
+          
+          $q = $db->sql_query('SELECT name FROM ' . table_prefix . "pages WHERE urlname = '$page_id' AND namespace = '$this->namespace';");
+          if ( !$q )
+            $db->_die();
+          
+          if ( $db->numrows() < 1 )
+          {
+            $this->title = $paths->nslist[$namespace] . dirtify_page_id($page_id);
+          }
+          else
+          {
+            list($this->title) = $db->fetchrow_num();
+            $this->exists = true;
+            $this->page_id = $page_id;
+          }
+        }
+        else
+        {
+          $this->title = $paths->nslist[$namespace] . dirtify_page_id($page_id);
+        }
+      }
+      else
+      {
+        list($this->title) = $db->fetchrow_num();
+        $this->exists = true;
+      }
+      $db->free_result();
+    }
+  }
+  
+  /**
+   * Pulls the page's actual text from the database.
+   */
+  
+  function fetch_text()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    if ( !empty($this->text_cache) )
+    {
+      return $this->text_cache;
+    }
+    
+    if ( $this->revision_id > 0 && is_int($this->revision_id) )
+    {
+    
+      $q = $db->sql_query('SELECT page_text, char_tag, time_id FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $this->page_id . '\' AND namespace=\'' . $this->namespace . '\' AND log_id=' . $this->revision_id . ';');
+      if ( !$q )
+      {
+        $this->send_error('Error during SQL query.', true);
+      }
+      if ( $db->numrows() < 1 )
+      {
+        // Compatibility fix for old pages with dots in the page ID
+        if ( strstr($this->page_id, '.2e') )
+        {
+          $db->free_result();
+          $page_id = str_replace('.2e', '.', $this->page_id);
+          $q = $db->sql_query('SELECT page_text, char_tag, time_id FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $this->namespace . '\' AND log_id=' . $this->revision_id . ';');
+          if ( !$q )
+          {
+            $this->send_error('Error during SQL query.', true);
+          }
+          if ( $db->numrows() < 1 )
+          {
+            $this->page_exists = false;
+            return 'err_no_text_rows';
+          }
+        }
+        else
+        {
+          $this->page_exists = false;
+          return 'err_no_text_rows';
+        }
+      }
+      else
+      {
+        $row = $db->fetchrow();
+      }
+      
+      $db->free_result();
+      
+    }
+    else
+    {
+      $q = $db->sql_query('SELECT t.page_text, t.char_tag, l.time_id FROM '.table_prefix."page_text AS t\n"
+                        . "  LEFT JOIN " . table_prefix . "logs AS l\n"
+                        . "    ON ( l.page_id = t.page_id AND l.namespace = t.namespace )\n"
+                        . "  WHERE t.page_id='$this->page_id' AND t.namespace='$this->namespace'\n"
+                        . "  ORDER BY l.time_id DESC LIMIT 1;");
+      if ( !$q )
+      {
+        $this->send_error('Error during SQL query.', true);
+      }
+      if ( $db->numrows() < 1 )
+      {
+        // Compatibility fix for old pages with dots in the page ID
+        if ( strstr($this->page_id, '.2e') )
+        {
+          $db->free_result();
+          $page_id = str_replace('.2e', '.', $this->page_id);
+          $q = $db->sql_query('SELECT page_text, char_tag FROM '.table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $this->namespace . '\';');
+          if ( !$q )
+          {
+            $this->send_error('Error during SQL query.', true);
+          }
+          if ( $db->numrows() < 1 )
+          {
+            $this->page_exists = false;
+            return 'err_no_text_rows';
+          }
+        }
+        else
+        {
+          $this->page_exists = false;
+          return 'err_no_text_rows';
+        }
+      }
+      
+      $row = $db->fetchrow();
+      $db->free_result();
+      
+    }
+    
+    if ( !empty($row['char_tag']) )
+    {
+      // This page text entry uses the old text-escaping format
+      $from = array(
+          "{APOS:{$row['char_tag']}}",
+          "{QUOT:{$row['char_tag']}}",
+          "{SLASH:{$row['char_tag']}}"
+        );
+      $to = array("'", '"',  '\\');
+      $row['page_text'] = str_replace($from, $to, $row['page_text']);
+    }
+    
+    $this->text_cache = $row['page_text'];
+    
+    if ( isset($row['time_id']) )
+    {
+      $this->revision_time = intval($row['time_id']);
+    }
+    
+    return $row['page_text'];
+  }
+  
+  /**
+   * Send the page.
+   */
+  
+  public function send()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $output;
+    
+    $output->add_before_footer($this->display_categories());
+    
+    if ( $this->exists )
+      $this->send_from_db();
+    else
+    {
+      // This is the DEPRECATED way to extend namespaces. It's left in only for compatibility with older plugins.
+      ob_start();
+      $code = $plugins->setHook('page_not_found');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+      $c = ob_get_contents();
+      if ( !empty($c) )
+      {
+        ob_end_clean();
+        echo $c;
+      }
+      else
+      {
+        $output->header();
+        $this->error_404();
+        $output->footer();
+      }
+    }
+  }
+   
+  /**
+   * The "real" send-the-page function. The reason for this is so other namespaces can re-use the code
+   * to fetch the page from the DB while being able to install their own wrappers.
+   */
+  
+  public function send_from_db($incl_inner_headers = true, $send_headers = true)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    global $output;
+    
+    $text = $this->fetch_text();
+    
+    $text = preg_replace('/([\s]*)__NOBREADCRUMBS__([\s]*)/', '', $text);
+    $text = preg_replace('/([\s]*)__NOTOC__([\s]*)/', '', $text);
+    
+    $redir_enabled = false;
+    if ( preg_match('/^#redirect \[\[([^\]]+?)\]\]/i', $text, $match ) )
+    {
+      $redir_enabled = true;
+      
+      $oldtarget = RenderMan::strToPageID($match[1]);
+      $oldtarget[0] = sanitize_page_id($oldtarget[0]);
+      
+      $url = makeUrlNS($oldtarget[1], $oldtarget[0], false, true);
+      $page_id_key = $paths->nslist[ $oldtarget[1] ] . $oldtarget[0];
+      $page_data = $paths->pages[$page_id_key];
+      $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$oldtarget[1]] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $oldtarget[0] ) ) );
+      if ( !isset($page_data['name']) )
+      {
+        $cls = 'class="wikilink-nonexistent"';
+      }
+      else
+      {
+        $cls = '';
+      }
+      $a = '<a ' . $cls . ' href="' . $url . '">' . $title . '</a>';
+      $redir_html = '<br /><div class="mdg-infobox">
+              <table border="0" width="100%" cellspacing="0" cellpadding="0">
+                <tr>
+                  <td valign="top">
+                    <img alt="Cute wet-floor icon" src="'.scriptPath.'/images/redirector.png" />
+                  </td>
+                  <td valign="top" style="padding-left: 10px;">
+                    ' . $lang->get('page_msg_this_is_a_redirector', array( 'redirect_target' => $a )) . '
+                  </td>
+                </tr>
+              </table>
+            </div>
+            <br />
+            <hr style="margin-left: 1em; width: 200px;" />';
+      $text = str_replace($match[0], '', $text);
+      $text = trim($text);
+    }
+    
+    if ( $send_headers )
+    {
+      $output->set_title($this->title);
+      $output->header();
+    }
+    $this->do_breadcrumbs();
+    
+    if ( $incl_inner_headers )
+    {
+      display_page_headers();
+    }
+    
+    if ( $this->revision_id )
+    {
+      echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;">
+              <b>' . $lang->get('page_msg_archived_title') . '</b><br />
+              ' . $lang->get('page_msg_archived_body', array(
+                  'archive_date' => enano_date('F d, Y', $this->revision_time),
+                  'archive_time' => enano_date('h:i a', $this->revision_time),
+                  'current_link' => makeUrlNS($this->namespace, $this->page_id),
+                  'restore_link' => makeUrlNS($this->namespace, $this->page_id, 'do=edit&amp;revid='.$this->revision_id),
+                  'restore_onclick' => 'ajaxEditor(\''.$this->revision_id.'\'); return false;',
+                )) . '
+            </div>';
+    }
+    
+    if ( $redir_enabled )
+    {
+      echo $redir_html;
+    }
+    
+    $code = $plugins->setHook('pageprocess_render_head');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
+    if ( $incl_inner_headers )
+    {
+      $text = '?>' . RenderMan::render($text);
+    }
+    else
+    {
+      $text = '?>' . $text;
+      $text = preg_replace('/<nowiki>(.*?)<\/nowiki>/s', '\\1', $text);
+    }
+    
+    eval ( $text );
+    
+    $code = $plugins->setHook('pageprocess_render_tail');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
+    if ( $incl_inner_headers )
+    {
+      display_page_footers();
+    }
+    
+    if ( $send_headers )
+      $output->footer();
+  }
+  
+  /**
+   * Echoes out breadcrumb data, if appropriate.
+   * @access private
+   */
+  
+  function do_breadcrumbs()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    if ( strpos($this->text_cache, '__NOBREADCRUMBS__') !== false )
+      return false;
+    
+    $mode = getConfig('breadcrumb_mode');
+    
+    if ( $mode == 'never' )
+      // Breadcrumbs are disabled
+      return true;
+      
+    // Minimum depth for breadcrumb display
+    $threshold = ( $mode == 'always' ) ? 0 : 1;
+    
+    $breadcrumb_data = explode('/', $this->page_id);
+    if ( count($breadcrumb_data) > $threshold )
+    {
+      // If we're not on a subpage of the main page, add "Home" to the list
+      $show_home = false;
+      if ( $mode == 'always' )
+      {
+        $show_home = true;
+      }
+      echo '<!-- Start breadcrumbs -->
+            <div class="breadcrumbs">
+              ';
+      if ( $show_home )
+      {
+        // Display the "home" link first.
+        $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
+        if ( $pathskey !== get_main_page() )
+          echo '<a href="' . makeUrl(get_main_page(), false, true) . '">';
+        echo $lang->get('onpage_btn_breadcrumbs_home');
+        if ( $pathskey !== get_main_page() )
+          echo '</a>';
+      }
+      foreach ( $breadcrumb_data as $i => $crumb )
+      {
+        $cumulative = implode('/', array_slice($breadcrumb_data, 0, ( $i + 1 )));
+        if ( $show_home && $cumulative === get_main_page() )
+          continue;
+        if ( $show_home || $i > 0 )
+          echo ' &raquo; ';
+        $title = ( isPage($cumulative) ) ? get_page_title($cumulative) : get_page_title($crumb);
+        if ( $i + 1 == count($breadcrumb_data) )
+        {
+          echo htmlspecialchars($title);
+        }
+        else
+        {
+          $exists = ( isPage($cumulative) ) ? '' : ' class="wikilink-nonexistent"';
+          echo '<a href="' . makeUrl($cumulative, false, true) . '"' . $exists . '>' . htmlspecialchars($title) . '</a>';
+        }
+      }
+      echo '</div>
+            <!-- End breadcrumbs -->
+            ';
+    }
+  }
+  
+  public function error_404($userpage = false)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang, $output;
+    
+    @header('HTTP/1.1 404 Not Found');
+    
+    $msg = ( $pp = $paths->sysmsg('Page_not_found') ) ? $pp : '{STANDARD404}';
+    
+    $standard_404 = '';
+    
+    if ( $userpage )
+    {
+      $standard_404 .= '<h3>' . $lang->get('page_msg_404_title_userpage') . '</h3>
+             <p>' . $lang->get('page_msg_404_body_userpage');
+    }
+    else
+    {
+      $standard_404 .= '<h3>' . $lang->get('page_msg_404_title') . '</h3>
+             <p>' . $lang->get('page_msg_404_body');
+    }
+    if ( $session->get_permissions('create_page') )
+    {
+      $standard_404 .= ' ' . $lang->get('page_msg_404_create', array(
+          'create_flags' => 'href="'.makeUrlNS($this->namespace, $this->page_id, 'do=edit', true).'" onclick="ajaxEditor(); return false;"',
+          'mainpage_link' => makeUrl(get_main_page(), false, true)
+        ));
+    }
+    else
+    {
+      $standard_404 .= ' ' . $lang->get('page_msg_404_gohome', array(
+          'mainpage_link' => makeUrl(get_main_page(), false, true)
+        ));
+    }
+    $standard_404 .= '</p>';
+    if ( $session->get_permissions('history_rollback') )
+    {
+      $e = $db->sql_query('SELECT * FROM ' . table_prefix . 'logs WHERE action=\'delete\' AND page_id=\'' . $this->page_id . '\' AND namespace=\'' . $this->namespace . '\' ORDER BY time_id DESC;');
+      if ( !$e )
+      {
+        $db->_die('The deletion log could not be selected.');
+      }
+      if ( $db->numrows() > 0 )
+      {
+        $r = $db->fetchrow();
+        $standard_404 .= '<p>' . $lang->get('page_msg_404_was_deleted', array(
+                  'delete_time' => enano_date('d M Y h:i a', $r['time_id']),
+                  'delete_reason' => htmlspecialchars($r['edit_summary']),
+                  'rollback_flags' => 'href="'.makeUrl($paths->page, 'do=rollback&amp;id='.$r['log_id']).'" onclick="ajaxRollback(\''.$r['log_id'].'\'); return false;"'
+                ))
+              . '</p>';
+        if ( $session->user_level >= USER_LEVEL_ADMIN )
+        {
+          $standard_404 .= '<p>' . $lang->get('page_msg_404_admin_opts', array(
+                    'detag_link' => makeUrl($paths->page, 'do=detag', true)
+                  ))
+                . '</p>';
+        }
+      }
+      $db->free_result();
+    }
+    $standard_404 .= '<p>
+            ' . $lang->get('page_msg_404_http_response') . '
+          </p>';
+          
+    $parser = $template->makeParserText($msg);
+    $parser->assign_vars(array(
+        'STANDARD404' => $standard_404
+      ));
+    
+    $msg = RenderMan::render($parser->run());
+    eval( '?>' . $msg );
+  }
+  
+  /**
+   * Display the categories a page is in. If the current page is a category, its contents will also be printed.
+   */
+  
+  function display_categories()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    $html = '';
+    
+    if ( $this->namespace == 'Category' )
+    {
+      // Show member pages and subcategories
+      $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($this->page_id) . '\'
+                             ORDER BY is_category DESC, p.name ASC;');
+      if ( !$q )
+      {
+        $db->_die();
+      }
+      $html .= '<h3>' . $lang->get('onpage_cat_heading_subcategories') . '</h3>';
+      $html .= '<div class="tblholder">';
+      $html .= '<table border="0" cellspacing="1" cellpadding="4">';
+      $html .= '<tr>';
+      $ticker = 0;
+      $counter = 0;
+      $switched = false;
+      $class  = 'row1';
+      while ( $row = $db->fetchrow() )
+      {
+        if ( $row['is_category'] == 0 && !$switched )
+        {
+          if ( $counter > 0 )
+          {
+            // Fill-in
+            while ( $ticker < 3 )
+            {
+              $ticker++;
+              $html .= '<td class="' . $class . '" style="width: 33.3%;"></td>';
+            }
+          }
+          else
+          {
+            $html .= '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_subcategories') . '</td>';
+          }
+          $html .= '</tr></table></div>' . "\n\n";
+          $html .= '<h3>' . $lang->get('onpage_cat_heading_pages') . '</h3>';
+          $html .= '<div class="tblholder">';
+          $html .= '<table border="0" cellspacing="1" cellpadding="4">';
+          $html .= '<tr>';
+          $counter = 0;
+          $ticker = -1;
+          $switched = true;
+        }
+        $counter++;
+        $ticker++;
+        if ( $ticker == 3 )
+        {
+          $html .= '</tr><tr>';
+          $ticker = 0;
+          $class = ( $class == 'row3' ) ? 'row1' : 'row3';
+        }
+        $html .= "<td class=\"{$class}\" style=\"width: 33.3%;\">"; // " to workaround stupid jEdit bug
+        
+        $link = makeUrlNS($row['namespace'], sanitize_page_id($row['urlname']));
+        $html .= '<a href="' . $link . '"';
+        $key = $paths->nslist[$row['namespace']] . sanitize_page_id($row['urlname']);
+        if ( !isPage( $key ) )
+        {
+          $html .= ' class="wikilink-nonexistent"';
+        }
+        $html .= '>';
+        $title = get_page_title_ns($row['urlname'], $row['namespace']);
+        $html .= htmlspecialchars($title);
+        $html .= '</a>';
+        
+        $html .= "</td>";
+      }
+      if ( !$switched )
+      {
+        if ( $counter > 0 )
+        {
+          // Fill-in
+          while ( $ticker < 2 )
+          {
+            $ticker++;
+            $html .= '<td class="' . $class . '" style="width: 33.3%;"></td>';
+          }
+        }
+        else
+        {
+          $html .= '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_subcategories') . '</td>';
+        }
+        $html .= '</tr></table></div>' . "\n\n";
+        $html .= '<h3>' . $lang->get('onpage_cat_heading_pages') . '</h3>';
+        $html .= '<div class="tblholder">';
+        $html .= '<table border="0" cellspacing="1" cellpadding="4">';
+        $html .= '<tr>';
+        $counter = 0;
+        $ticker = 0;
+        $switched = true;
+      }
+      if ( $counter > 0 )
+      {
+        // Fill-in
+        while ( $ticker < 2 )
+        {
+          $ticker++;
+          $html .= '<td class="' . $class . '" style="width: 33.3%;"></td>';
+        }
+      }
+      else
+      {
+        $html .= '<td class="' . $class . '">' . $lang->get('onpage_cat_msg_no_pages') . '</td>';
+      }
+      $html .= '</tr></table></div>' . "\n\n";
+    }
+    
+    if ( $this->namespace != 'Special' && $this->namespace != 'Admin' )
+    {
+      $html .= '<div class="mdg-comment" style="margin: 10px 0 0 0;" id="category_box_wrapper">';
+      $html .= '<div style="float: right;">';
+      $html .= '(<a href="#" onclick="ajaxCatToTag(); return false;">' . $lang->get('tags_catbox_link') . '</a>)';
+      $html .= '</div>';
+      $html .= '<div id="mdgCatBox">' . $lang->get('catedit_catbox_lbl_categories') . ' ';
+      
+      $where = '( c.page_id=\'' . $db->escape($this->page_id) . '\' AND c.namespace=\'' . $db->escape($this->namespace) . '\' )';
+      $prefix = table_prefix;
+      $sql = <<<EOF
+SELECT c.category_id FROM {$prefix}categories AS c
+  LEFT JOIN {$prefix}pages AS p
+    ON ( ( p.urlname = c.page_id AND p.namespace = c.namespace ) OR ( p.urlname IS NULL AND p.namespace IS NULL ) )
+  WHERE $where
+  ORDER BY p.name ASC, c.page_id ASC;
+EOF;
+      $q = $db->sql_query($sql);
+      if ( !$q )
+        $db->_die();
+      
+      if ( $row = $db->fetchrow() )
+      {
+        $list = array();
+        do
+        {
+          $cid = sanitize_page_id($row['category_id']);
+          $title = get_page_title_ns($cid, 'Category');
+          $link = makeUrlNS('Category', $cid);
+          $list[] = '<a href="' . $link . '">' . htmlspecialchars($title) . '</a>';
+        }
+        while ( $row = $db->fetchrow() );
+        $html .= implode(', ', $list);
+      }
+      else
+      {
+        $html .= $lang->get('catedit_catbox_lbl_uncategorized');
+      }
+      
+      $can_edit = ( $session->get_permissions('edit_cat') && ( !$paths->page_protected || $session->get_permissions('even_when_protected') ) );
+      if ( $can_edit )
+      {
+        $edit_link = '<a href="' . makeUrl($paths->page, 'do=catedit', true) . '" onclick="ajaxCatEdit(); return false;">' . $lang->get('catedit_catbox_link_edit') . '</a>';
+        $html .= ' [ ' . $edit_link . ' ]';
+      }
+      
+      $html .= '</div></div>';
+    }
+    return $html;
+  }
+  /**
+   * Just tell us if the current page exists or not.
+   * @return bool
+   */
+   
+  function exists()
+  {
+    return $this->exists;
+  }
+}
+
+/**
+ * The namespaces that use the default handler.
+ */
+
+class Namespace_Article extends Namespace_Default
+{
+}
+
+class Namespace_Project extends Namespace_Default
+{
+}
+
+class Namespace_Help extends Namespace_Default
+{
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/file.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,176 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+class Namespace_File extends Namespace_Default
+{
+  function send()
+  {
+    global $output;
+    
+    $output->add_before_footer($this->show_info());
+    $output->add_before_footer($this->display_categories());
+    
+    if ( $this->exists )
+    {
+      $this->send_from_db();
+    }
+    else
+    {
+      $output->header();
+      $this->error_404();
+      $output->footer();
+    }
+  }
+  
+  function show_info()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    $local_page_id = $this->page_id;
+    $local_namespace = $this->namespace;
+    $html = '';
+    
+    // Prevent unnecessary work
+    if ( $local_namespace != 'File' )
+      return null;
+    
+    $selfn = $local_page_id;
+    if ( substr($paths->cpage['name'], 0, strlen($paths->nslist['File'])) == $paths->nslist['File'])
+    {
+      $selfn = substr($local_page_id, strlen($paths->nslist['File']), strlen($local_page_id));
+    }
+    $selfn = $db->escape($selfn);
+    $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
+                      . "  LEFT JOIN " . table_prefix . "logs AS l\n"
+                      . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
+                      . "  WHERE f.page_id = '$selfn'\n"
+                      . "    ORDER BY f.time_id DESC;");
+    if ( !$q )
+    {
+      $db->_die('The file type could not be fetched.');
+    }
+    
+    if ( $db->numrows() < 1 )
+    {
+      $html .= '<div class="mdg-comment" style="margin-left: 0;">
+              <h3>' . $lang->get('onpage_filebox_heading') . '</h3>
+              <p>' . $lang->get('onpage_filebox_msg_not_found', array('upload_link' => makeUrlNS('Special', 'UploadFile/'.$local_page_id))) . '</p>
+            </div>
+            <br />';
+      return $html;
+    }
+    $r = $db->fetchrow();
+    $mimetype = $r['mimetype'];
+    $datestring = enano_date('F d, Y h:i a', (int)$r['time_id']);
+    $html .= '<div class="mdg-comment" style="margin-left: 0;">
+            <h3>' . $lang->get('onpage_filebox_heading') . '</h3>
+            <p>' . $lang->get('onpage_filebox_lbl_type') . ' '.$r['mimetype'].'<br />';
+    
+    $size = $r['size'] . ' ' . $lang->get('etc_unit_bytes');
+    if ( $r['size'] >= 1048576 )
+    {
+      $size .= ' (' . ( round($r['size'] / 1048576, 1) ) . ' ' . $lang->get('etc_unit_megabytes_short') . ')';
+    }
+    else if ( $r['size'] >= 1024 )
+    {
+      $size .= ' (' . ( round($r['size'] / 1024, 1) ) . ' ' . $lang->get('etc_unit_kilobytes_short') . ')';
+    }
+    
+    $html .= $lang->get('onpage_filebox_lbl_size', array('size' => $size));
+    
+    $html .= '<br />' . $lang->get('onpage_filebox_lbl_uploaded') . ' ' . $datestring . '</p>';
+    if ( substr($mimetype, 0, 6) != 'image/' && ( substr($mimetype, 0, 5) != 'text/' || $mimetype == 'text/html' || $mimetype == 'text/javascript' ) )
+    {
+      $html .= '<div class="warning-box">
+              ' . $lang->get('onpage_filebox_msg_virus_warning') . '
+            </div>';
+    }
+    if ( substr($mimetype, 0, 6) == 'image/' )
+    {
+      $html .= '<p>
+              <a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn).'">
+                <img style="border: 0;" alt="'.$paths->page.'" src="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.htmlspecialchars(urlSeparator).'preview').'" />
+              </a>
+            </p>';
+    }
+    $html .= '<p>
+            <a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.'/'.$r['time_id'].htmlspecialchars(urlSeparator).'download').'">
+              ' . $lang->get('onpage_filebox_btn_download') . '
+            </a>';
+    if(!$paths->page_protected && ( $paths->wiki_mode || $session->get_permissions('upload_new_version') ))
+    {
+      $html .= '  |  <a href="'.makeUrlNS('Special', 'UploadFile'.'/'.$selfn).'">
+              ' . $lang->get('onpage_filebox_btn_upload_new') . '
+            </a>';
+    }
+    $html .= '</p>';
+    if ( $db->numrows() > 1 )
+    {
+      // requery, sql_result_seek() doesn't work on postgres
+      $db->free_result();
+      $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
+                      . "  LEFT JOIN " . table_prefix . "logs AS l\n"
+                      . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
+                      . "  WHERE f.page_id = '$selfn'\n"
+                      . "    ORDER BY f.time_id DESC;");
+      if ( !$q )
+        $db->_die();
+      
+      $html .= '<h3>' . $lang->get('onpage_filebox_heading_history') . '</h3><p>';
+      $last_rollback_id = false;
+      while ( $r = $db->fetchrow() )
+      {
+        $html .= '(<a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.'/'.$r['time_id'].htmlspecialchars(urlSeparator).'download').'">' . $lang->get('onpage_filebox_btn_this_version') . '</a>) ';
+        if ( $session->get_permissions('history_rollback') && $last_rollback_id )
+          $html .= ' (<a href="#rollback:' . $last_rollback_id . '" onclick="ajaxRollback(\''.$last_rollback_id.'\'); return false;">' . $lang->get('onpage_filebox_btn_revert') . '</a>) ';
+        else if ( $session->get_permissions('history_rollback') && !$last_rollback_id )
+          $html .= ' (' . $lang->get('onpage_filebox_btn_current') . ') ';
+        $last_rollback_id = $r['log_id'];
+        $mimetype = $r['mimetype'];
+        $datestring = enano_date('F d, Y h:i a', (int)$r['time_id']);
+        
+        $html .= $datestring.': '.$r['mimetype'].', ';
+        
+        $fs = $r['size'];
+        $fs = (int)$fs;
+        
+        if($fs >= 1048576)
+        {
+          $fs = round($fs / 1048576, 1);
+          $size = $fs . ' ' . $lang->get('etc_unit_megabytes_short');
+        }
+        else
+        if ( $fs >= 1024 )
+        {
+          $fs = round($fs / 1024, 1);
+          $size = $fs . ' ' . $lang->get('etc_unit_kilobytes_short');
+        }
+        else
+        {
+          $size = $fs . ' ' . $lang->get('etc_unit_bytes');
+        }
+        
+        $html .= $size;
+        
+        $html .= '<br />';
+      }
+      $html .= '</p>';
+    }
+    $db->free_result();
+    $html .= '</div><br />';
+    return $html;
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/special.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+class Namespace_Special extends Namespace_Default
+{
+  public function __construct($page_id, $namespace, $revision_id = 0)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    $this->page_id = sanitize_page_id($page_id);
+    $this->namespace = $namespace;
+    $this->revision_id = intval($revision_id);
+    
+    $this->exists = function_exists("page_{$this->namespace}_{$this->page_id}");
+  }
+  
+  function send()
+  {
+    global $output;
+    
+    if ( $this->exists )
+    {
+      @call_user_func("page_{$this->namespace}_{$this->page_id}");
+    }
+    else
+    {
+      $output->header();
+      $this->error_404();
+      $output->footer();
+    }
+  }
+  
+  function error_404()
+  {
+    global $lang, $output;
+    $func_name = "page_{$this->namespace}_{$this->page_id}";
+    
+    if ( $this->namespace == 'Admin' )
+      die_semicritical($lang->get('page_msg_admin_404_title'), $lang->get('page_msg_admin_404_body', array('func_name' => $func_name)), true);
+    
+    $title = $lang->get('page_err_custompage_function_missing_title');
+    $message = $lang->get('page_err_custompage_function_missing_body', array( 'function_name' => $fname ));
+                
+    $output->set_title($title);
+    $output->header();
+    echo "<p>$message</p>";
+    $output->footer();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/template.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+class Namespace_Template extends Namespace_Default
+{
+  function send()
+  {
+    global $output;
+    
+    $output->add_before_footer($this->display_categories());
+    $output->header();
+    
+    if ( $this->exists )
+    {
+      $text = $this->fetch_text();
+      $text = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '\\1', $text);
+      $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text);
+      
+      $text = RenderMan::render( $text );
+      
+      eval( '?>' . $text );
+    }
+    else
+    {
+      $this->error_404();
+    }
+    
+    $output->footer();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/namespaces/user.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,472 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+class Namespace_User extends Namespace_Default
+{
+  public function send()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $email;
+    global $lang, $output;
+    
+    /**
+     * PLUGGING INTO USER PAGES
+     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+     * Userpages are highly programmable and extendable using a number of
+     * hooks. These hooks are:
+     *
+     *   - userpage_sidebar_left
+     *   - userpage_sidebar_right
+     *   - userpage_tabs_links
+     *   - userpage_tabs_body
+     *
+     * You can add a variety of sections to user pages, including new tabs
+     * and new sections on the tables. To add a tab, attach to
+     * userpage_tabs_links and echo out:
+     *
+     *   <li><a href="#tab:YOURTABID">YOUR TAB TEXT</a></li>
+     *
+     * Then hook into userpage_tabs_body and echo out:
+     *
+     *   <div id="tab:YOURTABID">YOUR TAB CONTENT</div>
+     *
+     * The userpage javascript runtime will take care of everything else,
+     * meaning transitions, click events, etc. Currently it's not possible
+     * to add custom click events to tabs, but any DOM-related JS that needs
+     * to run in your tab can be run onload and the effects will be seen when
+     * your tab is clicked. YOURTABID should be lowercase alphanumeric and
+     * have a short prefix so as to assure that it remains specific to your
+     * plugin.
+     *
+     * To hook into the "profile" tab, use userpage_sidebar_{left,right}. Just
+     * echo out table cells as normal. The table on the left (the wide one) has
+     * four columns, and the one on the right has one column.
+     * 
+     * See plugins.php for a guide on creating and attaching to hooks.
+     */
+    
+    $page_urlname = dirtify_page_id($this->page_id);
+    if ( $this->page_id == $paths->page_id && $this->namespace == $paths->namespace )
+    {
+      $page_name = ( isset($paths->cpage['name']) ) ? $paths->cpage['name'] : $this->page_id;
+    }
+    else
+    {
+      $page_name = ( isset($paths->pages[$this->page_id]) ) ? $paths->pages[$this->page_id]['name'] : $this->page_id;
+    }
+    
+    $target_username = strtr($page_urlname, 
+      Array(
+        '_' => ' ',
+        '<' => '&lt;',
+        '>' => '&gt;'
+        ));
+    
+    $target_username = preg_replace('/^' . str_replace('/', '\\/', preg_quote($paths->nslist['User'])) . '/', '', $target_username);
+    list($target_username) = explode('/', $target_username);
+    
+    if ( ( $page_name == str_replace('_', ' ', $this->page_id) || $page_name == $paths->nslist['User'] . str_replace('_', ' ', $this->page_id) ) || !$this->page_exists )
+    {
+      $page_name = $lang->get('userpage_page_title', array('username' => $target_username));
+    }
+    else
+    {
+      // User has a custom title for their userpage
+      $page_name = $paths->pages[ $paths->nslist[$this->namespace] . $this->page_id ]['name'];
+    }
+    
+    $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($page_name);
+    
+    $q = $db->sql_query('SELECT u.username, u.user_id AS authoritative_uid, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, x.*, COUNT(c.comment_id) AS n_comments
+                           FROM '.table_prefix.'users u
+                           LEFT JOIN '.table_prefix.'users_extra AS x
+                             ON ( u.user_id = x.user_id OR x.user_id IS NULL ) 
+                           LEFT JOIN '.table_prefix.'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.username, u.user_id, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, 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();
+    
+    $user_exists = true;
+    
+    if ( $db->numrows() < 1 )
+    {
+      $user_exists = false;
+    }
+    else
+    {
+      $userdata = $db->fetchrow();
+      if ( $userdata['authoritative_uid'] == 1 )
+      {
+        // Hide data for anonymous user
+        $user_exists = false;
+        unset($userdata);
+      }
+    }
+    
+    // get the user's rank
+    if ( $user_exists )
+    {
+      $rank_data = $session->get_user_rank(intval($userdata['authoritative_uid']));
+    }
+    else
+    {
+      // get the rank data for the anonymous user (placeholder basically)
+      $rank_data = $session->get_user_rank(1);
+    }
+    
+    // add the userpage script to the header
+    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/userpage.js"></script>');
+    
+    $output->header();
+    
+    // if ( $send_headers )
+    // {
+    //  display_page_headers();
+    // }
+   
+    //
+    // BASIC INFORMATION
+    // Presentation of username/rank/avatar/basic info
+    //
+    
+    if ( $user_exists )
+    {
+    
+      ?>
+      <div id="userpage_wrap">
+        <ul id="userpage_links">
+          <li><a href="#tab:profile"><?php echo $lang->get('userpage_tab_profile'); ?></a></li>
+          <li><a href="#tab:content"><?php echo $lang->get('userpage_tab_content'); ?></a></li>
+          <?php
+          $code = $plugins->setHook('userpage_tabs_links');
+          foreach ( $code as $cmd )
+          {
+            eval($cmd);
+          }
+          ?>
+        </ul>
+        
+        <div id="tab:profile">
+      
+      <?php
+      
+      echo '<table border="0" cellspacing="0" cellpadding="0">
+              <tr>';
+                
+      echo '    <td valign="top">';
+      
+      echo '<div class="tblholder">
+              <table border="0" cellspacing="1" cellpadding="4">';
+              
+      // heading
+      echo '    <tr>
+                  <th colspan="' . ( $session->user_level >= USER_LEVEL_ADMIN ? '3' : '4' ) . '">
+                    ' . $lang->get('userpage_heading_basics', array('username' => htmlspecialchars($target_username))) . '
+                  </th>
+                  ' . (
+                    $session->user_level >= USER_LEVEL_ADMIN ?
+                    '<th class="subhead" style="width: 25%;"><a href="' . makeUrlNS('Special', 'Administration', 'module=' . $paths->nslist['Admin'] . 'UserManager&src=get&user=' . urlencode($target_username), true) . '" onclick="ajaxAdminUser(\'' . addslashes($target_username) . '\'); return false;">&raquo; ' . $lang->get('userpage_btn_administer_user') . '</a></th>'
+                      : ''
+                  ) . '
+                </tr>';
+                
+      // avi/rank/username
+      echo '    <tr>
+                  <td class="row3" colspan="4">
+                    ' . (
+                        $userdata['user_has_avatar'] == 1 ?
+                        '<div style="float: left; margin-right: 10px;">
+                          <img alt="' . $lang->get('usercp_avatar_image_alt', array('username' => $userdata['username'])) . '" src="' . make_avatar_url(intval($userdata['authoritative_uid']), $userdata['avatar_type'], $userdata['email']) . '" />
+                         </div>'
+                        : ''
+                      ) . '
+                      <span style="font-size: x-large; ' . $rank_data['rank_style'] . '">' . htmlspecialchars($userdata['username']) . '</span>
+                      ' . ( !empty($rank_data['user_title']) ? '<br />' . htmlspecialchars($rank_data['user_title']) : '' ) . '
+                      ' . ( !empty($rank_data['rank_title']) ? '<br />' . htmlspecialchars($lang->get($rank_data['rank_title'])) : '' ) . '
+                  </td>
+                </tr>';
+                
+      // join date & total comments
+      echo '<tr>';
+      echo '  <td class="row2" style="text-align: right; width: 25%;">
+                ' . $lang->get('userpage_lbl_joined') . '
+              </td>
+              <td class="row1" style="text-align: left; width: 25%;">
+                ' . enano_date('F d, Y h:i a', $userdata['reg_time']) . '
+              </td>';
+      echo '  <td class="row2" style="text-align: right; width: 25%;">
+                ' . $lang->get('userpage_lbl_num_comments') . '
+              </td>
+              <td class="row1" style="text-align: left; width: 25%;">
+                ' . $userdata['n_comments'] . '
+              </td>';
+      echo '</tr>';
+      
+      // real name
+      if ( !empty($userdata['real_name']) )
+      {
+        echo '<tr>
+                <td class="row2" style="text-align: right;">
+                  ' . $lang->get('userpage_lbl_real_name') . '
+                </td>
+                <td class="row1" colspan="3" style="text-align: left;">
+                  ' . htmlspecialchars($userdata['real_name']) . '
+                </td>
+              </tr>';
+      }
+                
+      // latest comments
+      
+      echo '<tr><th class="subhead" colspan="4">' . $lang->get('userpage_heading_comments', array('username' => htmlspecialchars($target_username))) . '</th></tr>';
+      $q = $db->sql_query('SELECT page_id, namespace, subject, time FROM '.table_prefix.'comments WHERE name=\'' . $db->escape($target_username) . '\' AND user_id=' . $userdata['authoritative_uid'] . ' AND approved=1 ORDER BY time DESC LIMIT 7;');
+      if ( !$q )
+        $db->_die();
+      
+      $comments = Array();
+      $no_comments = false;
+      
+      if ( $row = $db->fetchrow() )
+      {
+        do 
+        {
+          $row['time'] = enano_date('F d, Y', $row['time']);
+          $comments[] = $row;
+        }
+        while ( $row = $db->fetchrow() );
+      }
+      else
+      {
+        $no_comments = true;
+      }
+      
+      echo '<tr><td class="row3" colspan="4">';
+      echo '<div style="border: 1px solid #000000; padding: 0px; width: 100%; clip: rect(0px,auto,auto,0px); overflow: auto; background-color: transparent;" class="tblholder">';
+      
+      echo '<table border="0" cellspacing="1" cellpadding="4" style="width: 200%;"><tr>';
+      $class = 'row1';
+      
+      $tpl = '  <td class="{CLASS}">
+                  <a href="{PAGE_LINK}" <!-- BEGINNOT page_exists -->class="wikilink-nonexistent"<!-- END page_exists -->>{PAGE}</a><br />
+                  <small>{lang:userpage_comments_lbl_posted} {DATE}<br /></small>
+                  <b><a href="{COMMENT_LINK}">{SUBJECT}</a></b>
+                </td>';
+      $parser = $template->makeParserText($tpl);
+      
+      if ( count($comments) > 0 )
+      {
+        foreach ( $comments as $comment )
+        {
+          $c_page_id = $paths->nslist[ $comment['namespace'] ] . sanitize_page_id($comment['page_id']);
+          if ( isset($paths->pages[ $c_page_id ]) )
+          {
+            $parser->assign_bool(array(
+              'page_exists' => true
+              ));
+            $page_title = htmlspecialchars($paths->pages[ $c_page_id ]['name']);
+          }
+          else
+          {
+            $parser->assign_bool(array(
+              'page_exists' => false
+              ));
+            $page_title = htmlspecialchars(dirtify_page_id($c_page_id));
+          }
+          $parser->assign_vars(array(
+              'CLASS' => $class,
+              'PAGE_LINK' => makeUrlNS($comment['namespace'], sanitize_page_id($comment['page_id'])),
+              'PAGE' => $page_title,
+              'SUBJECT' => $comment['subject'],
+              'DATE' => $comment['time'],
+              'COMMENT_LINK' => makeUrlNS($comment['namespace'], sanitize_page_id($comment['page_id']), 'do=comments', true)
+            ));
+          $class = ( $class == 'row3' ) ? 'row1' : 'row3';
+          echo $parser->run();
+        }
+      }
+      else
+      {
+        echo '<td class="' . $class . '">' . $lang->get('userpage_msg_no_comments') . '</td>';
+      }
+      echo '</tr></table>';
+      
+      echo '</div>';
+      echo '</td></tr>';
+      
+      $code = $plugins->setHook('userpage_sidebar_left');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+              
+      echo '  </table>
+            </div>';
+            
+      echo '</td>';
+      
+      //
+      // CONTACT INFORMATION
+      //
+      
+      echo '    <td valign="top" style="width: 150px; padding-left: 10px;">';
+      
+      echo '<div class="tblholder">
+              <table border="0" cellspacing="1" cellpadding="4">';
+      
+      //
+      // Main part of sidebar
+      //
+      
+      // Contact information
+      
+      echo '<tr><th class="subhead">' . $lang->get('userpage_heading_contact') . '</th></tr>';
+      
+      $class = 'row3';
+      
+      if ( $userdata['email_public'] == 1 )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        $email_link = $email->encryptEmail($userdata['email']);
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_email') . ' ' . $email_link . '</td></tr>';
+      }
+      
+      $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+      if ( $session->user_logged_in )
+      {
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_btn_send_pm', array('username' => htmlspecialchars($target_username), 'pm_link' => makeUrlNS('Special', 'PrivateMessages/Compose/to/' . $this->page_id, false, true))) . '</td></tr>';
+      }
+      else
+      {
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_btn_send_pm_guest', array('username' => htmlspecialchars($target_username), 'login_flags' => 'href="' . makeUrlNS('Special', 'Login/' . $paths->nslist[$this->namespace] . $this->page_id) . '" onclick="ajaxStartLogin(); return false;"')) . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_aim']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_aim') . ' ' . $userdata['user_aim'] . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_yahoo']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_yim') . ' ' . $userdata['user_yahoo'] . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_msn']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        $email_link = $email->encryptEmail($userdata['user_msn']);
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_wlm') . ' ' . $email_link . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_xmpp']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        $email_link = $email->encryptEmail($userdata['user_xmpp']);
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_xmpp') . ' ' . $email_link . '</td></tr>';
+      }
+      
+      // Real life
+      
+      echo '<tr><th class="subhead">' . $lang->get('userpage_heading_real_life', array('username' => htmlspecialchars($target_username))) . '</th></tr>';
+      
+      if ( !empty($userdata['user_location']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_location') . ' ' . $userdata['user_location'] . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_job']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_job') . ' ' . $userdata['user_job'] . '</td></tr>';
+      }
+      
+      if ( !empty($userdata['user_hobbies']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_hobbies') . ' ' . $userdata['user_hobbies'] . '</td></tr>';
+      }
+      
+      if ( empty($userdata['user_location']) && empty($userdata['user_job']) && empty($userdata['user_hobbies']) )
+      {
+        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
+        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_msg_no_contact_info', array('username' => htmlspecialchars($target_username))) . '</td></tr>';
+      }
+      
+      $code = $plugins->setHook('userpage_sidebar_right');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+      
+      echo '  </table>
+            </div>';
+      echo '</td>';
+      
+      //
+      // End of profile
+      //
+      
+      echo '</tr></table>';
+      
+      echo '</div>'; // tab:profile
+    
+    }
+    
+    // User's own content
+    
+    echo '<span class="menuclear"></span>';
+    
+    echo '<div id="tab:content">';
+    
+    if ( $this->exists )
+    {
+      $this->send_from_db(true, false);
+    }
+    else
+    {
+      $this->error_404(true);
+    }
+    
+    echo '</div>'; // tab:content
+    
+    $code = $plugins->setHook('userpage_tabs_body');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
+    if ( $user_exists )
+    {
+      echo '</div>'; // userpage_wrap
+    }
+    else
+    {
+      if ( !is_valid_ip($target_username) )
+      {
+        echo '<p>' . $lang->get('userpage_msg_user_not_exist', array('username' => htmlspecialchars($target_username))) . '</p>';
+      }
+    }
+    
+    // if ( $send_headers )
+    // {
+    //  display_page_footers();
+    // }
+    
+    $output->footer();
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/output.php	Sat Jan 03 18:11:18 2009 -0500
@@ -0,0 +1,262 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.5 (Caoineag alpha 5)
+ * output.php - Controls output format, messages of death, that kind of stuff
+ * Copyright (C) 2006-2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * Abstract class to define how output handlers should act.
+ * @package Enano
+ * @subpackage UI
+ */
+
+abstract class Output_Base
+{
+  /**
+   * Page title
+   * @var string
+   */
+  
+  public $title = 'Untitled';
+  
+  /**
+   * To allow scripts to determine whether we are outputting headers or not.
+   * @var bool
+   */
+  
+  public $naked = false;
+  
+  /**
+   * Added content
+   * @var string
+   * @var string
+   * @var string
+   * @var string
+   */
+  
+  public $before_header = '', $after_header = '', $before_footer = '', $after_footer = '';
+  
+  /**
+   * Call this to send content headers (e.g. the first third of the document if HTML) in place of $template->header().
+   * @access public
+   */
+  
+  abstract public function header();
+  
+  /**
+   * Call this to send extra stuff after the content (equivalent of $template->footer()).
+   * @access public
+   */
+  
+  abstract public function footer();
+  
+  /**
+   * Add some code just before the header.
+   * @access public
+   */
+  
+  public function add_before_header($code)
+  {
+    $this->before_header .= $code;
+  }
+  
+  /**
+   * Add some code just after the header.
+   * @access public
+   */
+  
+  public function add_after_header($code)
+  {
+    $this->after_header .= $code;
+  }
+  
+  /**
+   * Add some code just before the footer.
+   * @access public
+   */
+  
+  public function add_before_footer($code)
+  {
+    $this->before_footer .= $code;
+  }
+  
+  /**
+   * Add some code just after the footer.
+   * @access public
+   */
+  
+  public function add_after_footer($code)
+  {
+    $this->after_footer .= $code;
+  }
+  
+  /**
+   * Send any required HTML headers through, e.g. Content-type.
+   * @access public
+   */
+  
+  public function http_headers()
+  {
+    header('Content-type: text/html');
+  }
+  
+  /**
+   * Set the title of the page being output.
+   * @param string Page name
+   */
+  
+  public function set_title($title)
+  {
+    $this->title = $title;
+  }
+  
+  /**
+   * Avoid sending things out of order.
+   * @var bool
+   * @var bool
+   */
+  
+  public $headers_sent = false, $footers_sent = false;
+}
+
+/**
+ * HTML outputter.
+ */
+
+class Output_HTML extends Output_Base
+{
+  public function header()
+  {
+    if ( $this->headers_sent )
+      return;
+    
+    $this->headers_sent = true;
+    
+    ob_start();
+  }
+  
+  public function footer()
+  {
+    global $template;
+    if ( !$this->headers_sent )
+      return;
+    
+    $this->headers_sent = false;
+    $content = ob_get_contents();
+    ob_end_clean();
+    
+    ob_start();
+    echo $this->before_header;
+    echo $template->getHeader();
+    echo $this->after_header;
+    echo $content;
+    echo $this->before_footer;
+    echo $template->getFooter();
+    echo $this->after_footer;
+    
+  }
+  
+  public function set_title($title)
+  {
+    global $template;
+    $template->assign_vars(array(
+        'PAGE_NAME' => $title
+      ));
+  }
+}
+
+/**
+ * Outputter that bypasses $template->header() and $template->footer(), but still shows HTML added via {before,after}_{header,footer}.
+ */
+
+class Output_Striptease extends Output_HTML
+{
+  public function header()
+  {
+    echo $this->before_header;
+    echo $this->after_header;
+  }
+  
+  public function footer()
+  {
+    echo $this->before_footer;
+    echo $this->after_footer;
+  }
+}
+
+/**
+ * Outputter that bypasses $template->header() and $template->footer().
+ */
+
+class Output_Naked extends Output_HTML
+{
+  public $naked = true;
+  
+  public function header()
+  {
+  }
+  
+  public function footer()
+  {
+  }
+}
+
+/**
+ * Safe template outputter
+ */
+
+class Output_Safe
+{
+  protected $template;
+  protected $headers_sent = false;
+  public function __construct()
+  {
+    $this->template = new template_nodb();
+    $theme = ( defined('ENANO_CONFIG_FETCHED') ) ? getConfig('theme_default') : 'oxygen';
+    $style = ( defined('ENANO_CONFIG_FETCHED') ) ? '__foo__' : 'bleu';
+    
+    $this->template->load_theme($theme, $style);
+    $this->template->tpl_strings['SITE_NAME'] = getConfig('site_name');
+    $this->template->tpl_strings['SITE_DESC'] = getConfig('site_desc');
+    $this->template->tpl_strings['COPYRIGHT'] = getConfig('copyright_notice');
+    $this->template->tpl_strings['PAGE_NAME'] = 'Untitled';
+  }
+  public function header()
+  {
+    if ( $this->headers_sent )
+      return;
+    
+    $this->headers_sent = true;
+    
+    $this->template->header();
+  }
+  
+  public function footer()
+  {
+    global $template;
+    if ( !$this->headers_sent )
+    {
+      $this->template->header();
+    }
+    
+    $this->headers_sent = false;
+    $this->template->footer();
+    
+  }
+  
+  public function set_title($title)
+  {
+    $this->template->tpl_strings['PAGE_NAME'] = $title;
+  }
+}
+
+?>
--- a/includes/pageprocess.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/pageprocess.php	Sat Jan 03 18:11:18 2009 -0500
@@ -19,7 +19,7 @@
  * @package Enano
  * @subpackage UI
  * @copyright 2007 Dan Fuhry
- * @license GNU General Public License <http://www.gnu.org/licenses/gpl.html>
+ * @license GNU General Public License <http://www.gnu.org/licenses/gpl-2.0.html>
  */
 
 class PageProcessor
@@ -34,6 +34,13 @@
   var $namespace;
   
   /**
+   * The instance of the namespace processor for the namespace we're doing.
+   * @var object
+   */
+  
+  var $ns;
+  
+  /**
    * The title of the page sent to the template parser
    * @var string
    */
@@ -193,6 +200,7 @@
     }
     
     // Is there a custom function registered for handling this namespace?
+    // DEPRECATED (even though it only saw its way into one alpha release.)
     if ( $proc = $paths->get_namespace_processor($this->namespace) )
     {
       // yes, just call that
@@ -226,6 +234,7 @@
       {
         $this->send_headers = false;
         $strict_no_headers = true;
+        $GLOBALS['output'] = new Output_Naked();
       }
       if ( isset($paths->pages[$pathskey]['password']) )
       {
@@ -271,125 +280,15 @@
       require_once(ENANO_ROOT.'/includes/stats.php');
       doStats($this->page_id, $this->namespace);
     }
-    if ( $this->namespace == 'Special' || $this->namespace == 'Admin' )
-    {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
-      
-      $this->revision_time = time();
-      
-      if ( !$this->page_exists )
-      {
-        $func_name = "page_{$this->namespace}_{$this->page_id}";
-        
-        die_semicritical($lang->get('page_msg_admin_404_title'), $lang->get('page_msg_admin_404_body', array('func_name' => $func_name)), (!$this->send_headers));
-      }
-      $func_name = "page_{$this->namespace}_{$this->page_id}";
-      if ( function_exists($func_name) )
-      {
-        $result = @call_user_func($func_name);
-        return $result;
-      }
-      else
-      {
-        $title = $lang->get('page_err_custompage_function_missing_title');
-        $message = $lang->get('page_err_custompage_function_missing_body', array( 'function_name' => $fname ));
-                    
-        if ( $this->send_headers )
-        {
-          $template->tpl_strings['PAGE_NAME'] = $title;
-          $template->header();
-          echo "<p>$message</p>";
-          $template->footer();
-        }
-        else
-        {
-          echo "<h2>$title</h2>
-                <p>$message</p>";
-        }
-        return false;
-      }
-    }
-    else if ( $this->namespace == 'User' && strpos($this->page_id, '/') === false )
-    {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
-      
-      $this->_handle_userpage();
-    }
-    else if ( ( $this->namespace == 'Template' || $this->namespace == 'System' ) && $this->page_exists )
+    
+    // We are all done. Ship off the page.
+    
+    if ( $this->send_headers )
     {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
-      
-      $this->header();
-      
-      $text = $this->fetch_text();
-      $text = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '\\1', $text);
-      $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text);
-      
-      $text = RenderMan::render( $text );
-      
-      eval( '?>' . $text );
-      
-      $this->footer();
-    }
-    else if ( $this->namespace == 'API' )
-    {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
-      
-      $uri = scriptPath . '/' . $this->page_id;
-      if ( !$this->send_headers )
-      {
-        $sep = ( strstr($uri, '?') ) ? '&' : '?';
-        $uri .= "{$sep}noheaders";
-      }
-      redirect( $uri, '', '', 0 );
+      $template->init_vars($this);
     }
-    else if ( !$this->page_exists )
-    {
-      // Perhaps this is hooked?
-      ob_start();
-      
-      $code = $plugins->setHook('page_not_found');
-      foreach ( $code as $cmd )
-      {
-        eval($cmd);
-      }
-      
-      $ob = ob_get_contents();
-      
-      if ( empty($ob) )
-      {
-        if ( $this->send_headers )
-        {
-          $template->init_vars($this);
-        }
-        $this->err_page_not_existent();
-      }
-      else
-      {
-        // Something sent content, so we'll assume the page exist...ed at least according to the plugin
-        if ( $this->namespace != 'Special' && $this->namespace != 'Admin' && $do_stats )
-        {
-          require_once(ENANO_ROOT.'/includes/stats.php');
-          doStats($this->page_id, $this->namespace);
-        }
-      }
-    }
-    else
-    {
-      $this->send_from_db($strict_no_headers);
-    }
+    
+    $this->ns->send();
   }
   
   /**
@@ -450,6 +349,8 @@
   
   function fetch_source()
   {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
     if ( !$this->perms->get_permissions('view_source') )
     {
       return false;
@@ -458,6 +359,17 @@
     {
       return '';
     }
+    $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
+    if ( isset($paths->pages[$pathskey]) )
+    {
+      if ( isset($paths->pages[$pathskey]['password']) )
+      {
+        if ( $paths->pages[$pathskey]['password'] != sha1('') && $paths->pages[$pathskey]['password'] !== $this->password && !empty($paths->pages[$pathskey]['password']) )
+        {
+          return false;
+        }
+      }
+    }
     return $this->fetch_text();
   }
   
@@ -1013,48 +925,10 @@
     
     $this->perms = $session->fetch_page_acl( $page_id, $namespace );
     
-    // Exception for Admin: pages
-    if ( $this->namespace == 'Admin' )
-    {
-      $fname = "page_Admin_{$this->page_id}";
-    }
-    
-    // Does the page "exist"?
-    $pathskey = $paths->nslist[$namespace] . $page_id_cleaned;
+    // resolve namespace
+    $this->ns = namespace_factory($this->page_id, $this->namespace, $this->revision_id);
     
-    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;
-    }
-    else if ( !isset( $paths->pages[ $pathskey ] ) && ( ( $this->namespace == 'Admin' && !function_exists($fname) ) || ( $this->namespace != 'Admin' ) ) )
-    {
-      $this->page_exists = false;
-    }
-    else
-    {
-      $this->page_exists = true;
-    }
-    
-    // Compatibility with older databases
-    if ( strstr($this->page_id, '.2e') && !$this->page_exists )
-    {
-      $page_id = str_replace('.2e', '.', $page_id);
-      
-      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;
-      }
-      else if ( !isset( $paths->pages[ $paths->nslist[$namespace] . $page_id ] ) && ( $this->namespace == 'Admin' && !function_exists($fname) ) )
-      {
-        $this->page_exists = false;
-      }
-      else
-      {
-        $this->page_exists = true;
-      }
-      
-    }
-    
+    $this->page_exists = $this->ns->exists();
     $this->title = get_page_title_ns($this->page_id, $this->namespace);
     
     profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Ran _setup()");
@@ -1068,129 +942,22 @@
   function render($incl_inner_headers = true, $_errormsg = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    $text = $this->fetch_text();
+    global $output, $lang;
     
-    $text = preg_replace('/([\s]*)__NOBREADCRUMBS__([\s]*)/', '', $text);
-    $text = preg_replace('/([\s]*)__NOTOC__([\s]*)/', '', $text);
-    
-    $redir_enabled = false;
-    if ( preg_match('/^#redirect \[\[([^\]]+?)\]\]/i', $text, $match ) )
+    if ( count($this->redirect_stack) > 0 )
     {
-      $redir_enabled = true;
-      
-      $oldtarget = RenderMan::strToPageID($match[1]);
-      $oldtarget[0] = sanitize_page_id($oldtarget[0]);
-      
-      $url = makeUrlNS($oldtarget[1], $oldtarget[0], false, true);
-      $page_id_key = $paths->nslist[ $oldtarget[1] ] . $oldtarget[0];
-      $page_data = $paths->pages[$page_id_key];
-      $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$oldtarget[1]] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $oldtarget[0] ) ) );
-      if ( !isset($page_data['name']) )
-      {
-        $cls = 'class="wikilink-nonexistent"';
-      }
-      else
-      {
-        $cls = '';
-      }
-      $a = '<a ' . $cls . ' href="' . $url . '">' . $title . '</a>';
-      $redir_html = '<br /><div class="mdg-infobox">
-              <table border="0" width="100%" cellspacing="0" cellpadding="0">
-                <tr>
-                  <td valign="top">
-                    <img alt="Cute wet-floor icon" src="'.scriptPath.'/images/redirector.png" />
-                  </td>
-                  <td valign="top" style="padding-left: 10px;">
-                    ' . $lang->get('page_msg_this_is_a_redirector', array( 'redirect_target' => $a )) . '
-                  </td>
-                </tr>
-              </table>
-            </div>
-            <br />
-            <hr style="margin-left: 1em; width: 200px;" />';
-      $text = str_replace($match[0], '', $text);
-      $text = trim($text);
-    }
-    
-    $template->tpl_strings['PAGE_NAME'] = htmlspecialchars( $this->title );
-    
-    $this->header();
-    $this->do_breadcrumbs();
-    
-    if ( $_errormsg )
-    {
-      echo $_errormsg;
-    }
-    
-    if ( $incl_inner_headers )
-    {
-      if ( count($this->redirect_stack) > 0 )
+      $stack = array_reverse($this->redirect_stack);
+      foreach ( $stack as $oldtarget )
       {
-        $stack = array_reverse($this->redirect_stack);
-        foreach ( $stack as $oldtarget )
-        {
-          $url = makeUrlNS($oldtarget[1], $oldtarget[0], 'redirect=no', true);
-          $page_id_key = $paths->nslist[ $oldtarget[1] ] . $oldtarget[0];
-          $page_data = $paths->pages[$page_id_key];
-          $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$oldtarget[1]] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $oldtarget[0] ) ) );
-          $a = '<a href="' . $url . '">' . $title . '</a>';
-          echo '<small>' . $lang->get('page_msg_redirected_from', array('from' => $a)) . '<br /></small>';
-        }
+        $url = makeUrlNS($oldtarget[1], $oldtarget[0], 'redirect=no', true);
+        $page_id_key = $paths->nslist[ $oldtarget[1] ] . $oldtarget[0];
+        $page_data = $paths->pages[$page_id_key];
+        $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$oldtarget[1]] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $oldtarget[0] ) ) );
+        $a = '<a href="' . $url . '">' . $title . '</a>';
+        $output->add_after_header('<small>' . $lang->get('page_msg_redirected_from', array('from' => $a)) . '<br /></small>');
       }
-      display_page_headers();
-    }
-    
-    if ( $this->revision_id )
-    {
-      echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;">
-              <b>' . $lang->get('page_msg_archived_title') . '</b><br />
-              ' . $lang->get('page_msg_archived_body', array(
-                  'archive_date' => enano_date('F d, Y', $this->revision_time),
-                  'archive_time' => enano_date('h:i a', $this->revision_time),
-                  'current_link' => makeUrlNS($this->namespace, $this->page_id),
-                  'restore_link' => makeUrlNS($this->namespace, $this->page_id, 'do=edit&amp;revid='.$this->revision_id),
-                  'restore_onclick' => 'ajaxEditor(\''.$this->revision_id.'\'); return false;',
-                )) . '
-            </div>';
-    }
-    
-    if ( $redir_enabled )
-    {
-      echo $redir_html;
     }
-    
-    $code = $plugins->setHook('pageprocess_render_head');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    
-    if ( $incl_inner_headers )
-    {
-      $text = '?>' . RenderMan::render($text);
-    }
-    else
-    {
-      $text = '?>' . $text;
-      $text = preg_replace('/<nowiki>(.*?)<\/nowiki>/s', '\\1', $text);
-    }
-    
-    eval ( $text );
-    
-    $code = $plugins->setHook('pageprocess_render_tail');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    
-    if ( $incl_inner_headers )
-    {
-      display_page_footers();
-    }
-    
-    $this->footer();
+    $this->ns->send($incl_inner_headers, $_errormsg);
   }
   
   /**
@@ -1222,582 +989,7 @@
   
   function fetch_text()
   {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    if ( !empty($this->text_cache) )
-    {
-      return $this->text_cache;
-    }
-    
-    if ( $this->revision_id > 0 && is_int($this->revision_id) )
-    {
-    
-      $q = $db->sql_query('SELECT page_text, char_tag, time_id FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $this->page_id . '\' AND namespace=\'' . $this->namespace . '\' AND log_id=' . $this->revision_id . ';');
-      if ( !$q )
-      {
-        $this->send_error('Error during SQL query.', true);
-      }
-      if ( $db->numrows() < 1 )
-      {
-        // Compatibility fix for old pages with dots in the page ID
-        if ( strstr($this->page_id, '.2e') )
-        {
-          $db->free_result();
-          $page_id = str_replace('.2e', '.', $this->page_id);
-          $q = $db->sql_query('SELECT page_text, char_tag, time_id FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $this->namespace . '\' AND log_id=' . $this->revision_id . ';');
-          if ( !$q )
-          {
-            $this->send_error('Error during SQL query.', true);
-          }
-          if ( $db->numrows() < 1 )
-          {
-            $this->page_exists = false;
-            return 'err_no_text_rows';
-          }
-        }
-        else
-        {
-          $this->page_exists = false;
-          return 'err_no_text_rows';
-        }
-      }
-      else
-      {
-        $row = $db->fetchrow();
-      }
-      
-      $db->free_result();
-      
-    }
-    else
-    {
-      $q = $db->sql_query('SELECT t.page_text, t.char_tag, l.time_id FROM '.table_prefix."page_text AS t\n"
-                        . "  LEFT JOIN " . table_prefix . "logs AS l\n"
-                        . "    ON ( l.page_id = t.page_id AND l.namespace = t.namespace )\n"
-                        . "  WHERE t.page_id='$this->page_id' AND t.namespace='$this->namespace'\n"
-                        . "  ORDER BY l.time_id DESC LIMIT 1;");
-      if ( !$q )
-      {
-        $this->send_error('Error during SQL query.', true);
-      }
-      if ( $db->numrows() < 1 )
-      {
-        // Compatibility fix for old pages with dots in the page ID
-        if ( strstr($this->page_id, '.2e') )
-        {
-          $db->free_result();
-          $page_id = str_replace('.2e', '.', $this->page_id);
-          $q = $db->sql_query('SELECT page_text, char_tag FROM '.table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $this->namespace . '\';');
-          if ( !$q )
-          {
-            $this->send_error('Error during SQL query.', true);
-          }
-          if ( $db->numrows() < 1 )
-          {
-            $this->page_exists = false;
-            return 'err_no_text_rows';
-          }
-        }
-        else
-        {
-          $this->page_exists = false;
-          return 'err_no_text_rows';
-        }
-      }
-      
-      $row = $db->fetchrow();
-      $db->free_result();
-      
-    }
-    
-    if ( !empty($row['char_tag']) )
-    {
-      // This page text entry uses the old text-escaping format
-      $from = array(
-          "{APOS:{$row['char_tag']}}",
-          "{QUOT:{$row['char_tag']}}",
-          "{SLASH:{$row['char_tag']}}"
-        );
-      $to = array("'", '"',  '\\');
-      $row['page_text'] = str_replace($from, $to, $row['page_text']);
-    }
-    
-    $this->text_cache = $row['page_text'];
-    
-    if ( isset($row['time_id']) )
-    {
-      $this->revision_time = intval($row['time_id']);
-    }
-    
-    return $row['page_text'];
-    
-  }
-  
-  /**
-   * Handles the extra overhead required for user pages.
-   * @access private
-   */
-   
-  function _handle_userpage()
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $email;
-    global $lang;
-    
-    /**
-     * PLUGGING INTO USER PAGES
-     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-     * Userpages are highly programmable and extendable using a number of
-     * hooks. These hooks are:
-     *
-     *   - userpage_sidebar_left
-     *   - userpage_sidebar_right
-     *   - userpage_tabs_links
-     *   - userpage_tabs_body
-     *
-     * You can add a variety of sections to user pages, including new tabs
-     * and new sections on the tables. To add a tab, attach to
-     * userpage_tabs_links and echo out:
-     *
-     *   <li><a href="#tab:YOURTABID">YOUR TAB TEXT</a></li>
-     *
-     * Then hook into userpage_tabs_body and echo out:
-     *
-     *   <div id="tab:YOURTABID">YOUR TAB CONTENT</div>
-     *
-     * The userpage javascript runtime will take care of everything else,
-     * meaning transitions, click events, etc. Currently it's not possible
-     * to add custom click events to tabs, but any DOM-related JS that needs
-     * to run in your tab can be run onload and the effects will be seen when
-     * your tab is clicked. YOURTABID should be lowercase alphanumeric and
-     * have a short prefix so as to assure that it remains specific to your
-     * plugin.
-     *
-     * To hook into the "profile" tab, use userpage_sidebar_{left,right}. Just
-     * echo out table cells as normal. The table on the left (the wide one) has
-     * four columns, and the one on the right has one column.
-     * 
-     * See plugins.php for a guide on creating and attaching to hooks.
-     */
-    
-    $page_urlname = dirtify_page_id($this->page_id);
-    if ( $this->page_id == $paths->page_id && $this->namespace == $paths->namespace )
-    {
-      $page_name = ( isset($paths->cpage['name']) ) ? $paths->cpage['name'] : $this->page_id;
-    }
-    else
-    {
-      $page_name = ( isset($paths->pages[$this->page_id]) ) ? $paths->pages[$this->page_id]['name'] : $this->page_id;
-    }
-    
-    $target_username = strtr($page_urlname, 
-      Array(
-        '_' => ' ',
-        '<' => '&lt;',
-        '>' => '&gt;'
-        ));
-    
-    $target_username = preg_replace('/^' . str_replace('/', '\\/', preg_quote($paths->nslist['User'])) . '/', '', $target_username);
-    list($target_username) = explode('/', $target_username);
-    
-    if ( ( $page_name == str_replace('_', ' ', $this->page_id) || $page_name == $paths->nslist['User'] . str_replace('_', ' ', $this->page_id) ) || !$this->page_exists )
-    {
-      $page_name = $lang->get('userpage_page_title', array('username' => $target_username));
-    }
-    else
-    {
-      // User has a custom title for their userpage
-      $page_name = $paths->pages[ $paths->nslist[$this->namespace] . $this->page_id ]['name'];
-    }
-    
-    $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($page_name);
-    
-    $q = $db->sql_query('SELECT u.username, u.user_id AS authoritative_uid, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, x.*, COUNT(c.comment_id) AS n_comments
-                           FROM '.table_prefix.'users u
-                           LEFT JOIN '.table_prefix.'users_extra AS x
-                             ON ( u.user_id = x.user_id OR x.user_id IS NULL ) 
-                           LEFT JOIN '.table_prefix.'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.username, u.user_id, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, 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();
-    
-    $user_exists = true;
-    
-    if ( $db->numrows() < 1 )
-    {
-      $user_exists = false;
-    }
-    else
-    {
-      $userdata = $db->fetchrow();
-      if ( $userdata['authoritative_uid'] == 1 )
-      {
-        // Hide data for anonymous user
-        $user_exists = false;
-        unset($userdata);
-      }
-    }
-    
-    // get the user's rank
-    if ( $user_exists )
-    {
-      $rank_data = $session->get_user_rank(intval($userdata['authoritative_uid']));
-    }
-    else
-    {
-      // get the rank data for the anonymous user (placeholder basically)
-      $rank_data = $session->get_user_rank(1);
-    }
-    
-    // add the userpage script to the header
-    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/userpage.js"></script>');
-    
-    $this->header();
-    
-    // if ( $send_headers )
-    // {
-    //  display_page_headers();
-    // }
-   
-    //
-    // BASIC INFORMATION
-    // Presentation of username/rank/avatar/basic info
-    //
-    
-    if ( $user_exists )
-    {
-    
-      ?>
-      <div id="userpage_wrap">
-        <ul id="userpage_links">
-          <li><a href="#tab:profile"><?php echo $lang->get('userpage_tab_profile'); ?></a></li>
-          <li><a href="#tab:content"><?php echo $lang->get('userpage_tab_content'); ?></a></li>
-          <?php
-          $code = $plugins->setHook('userpage_tabs_links');
-          foreach ( $code as $cmd )
-          {
-            eval($cmd);
-          }
-          ?>
-        </ul>
-        
-        <div id="tab:profile">
-      
-      <?php
-      
-      echo '<table border="0" cellspacing="0" cellpadding="0">
-              <tr>';
-                
-      echo '    <td valign="top">';
-      
-      echo '<div class="tblholder">
-              <table border="0" cellspacing="1" cellpadding="4">';
-              
-      // heading
-      echo '    <tr>
-                  <th colspan="' . ( $session->user_level >= USER_LEVEL_ADMIN ? '3' : '4' ) . '">
-                    ' . $lang->get('userpage_heading_basics', array('username' => htmlspecialchars($target_username))) . '
-                  </th>
-                  ' . (
-                    $session->user_level >= USER_LEVEL_ADMIN ?
-                    '<th class="subhead" style="width: 25%;"><a href="' . makeUrlNS('Special', 'Administration', 'module=' . $paths->nslist['Admin'] . 'UserManager&src=get&user=' . urlencode($target_username), true) . '" onclick="ajaxAdminUser(\'' . addslashes($target_username) . '\'); return false;">&raquo; ' . $lang->get('userpage_btn_administer_user') . '</a></th>'
-                      : ''
-                  ) . '
-                </tr>';
-                
-      // avi/rank/username
-      echo '    <tr>
-                  <td class="row3" colspan="4">
-                    ' . (
-                        $userdata['user_has_avatar'] == 1 ?
-                        '<div style="float: left; margin-right: 10px;">
-                          <img alt="' . $lang->get('usercp_avatar_image_alt', array('username' => $userdata['username'])) . '" src="' . make_avatar_url(intval($userdata['authoritative_uid']), $userdata['avatar_type'], $userdata['email']) . '" />
-                         </div>'
-                        : ''
-                      ) . '
-                      <span style="font-size: x-large; ' . $rank_data['rank_style'] . '">' . htmlspecialchars($userdata['username']) . '</span>
-                      ' . ( !empty($rank_data['user_title']) ? '<br />' . htmlspecialchars($rank_data['user_title']) : '' ) . '
-                      ' . ( !empty($rank_data['rank_title']) ? '<br />' . htmlspecialchars($lang->get($rank_data['rank_title'])) : '' ) . '
-                  </td>
-                </tr>';
-                
-      // join date & total comments
-      echo '<tr>';
-      echo '  <td class="row2" style="text-align: right; width: 25%;">
-                ' . $lang->get('userpage_lbl_joined') . '
-              </td>
-              <td class="row1" style="text-align: left; width: 25%;">
-                ' . enano_date('F d, Y h:i a', $userdata['reg_time']) . '
-              </td>';
-      echo '  <td class="row2" style="text-align: right; width: 25%;">
-                ' . $lang->get('userpage_lbl_num_comments') . '
-              </td>
-              <td class="row1" style="text-align: left; width: 25%;">
-                ' . $userdata['n_comments'] . '
-              </td>';
-      echo '</tr>';
-      
-      // real name
-      if ( !empty($userdata['real_name']) )
-      {
-        echo '<tr>
-                <td class="row2" style="text-align: right;">
-                  ' . $lang->get('userpage_lbl_real_name') . '
-                </td>
-                <td class="row1" colspan="3" style="text-align: left;">
-                  ' . htmlspecialchars($userdata['real_name']) . '
-                </td>
-              </tr>';
-      }
-                
-      // latest comments
-      
-      echo '<tr><th class="subhead" colspan="4">' . $lang->get('userpage_heading_comments', array('username' => htmlspecialchars($target_username))) . '</th></tr>';
-      $q = $db->sql_query('SELECT page_id, namespace, subject, time FROM '.table_prefix.'comments WHERE name=\'' . $db->escape($target_username) . '\' AND user_id=' . $userdata['authoritative_uid'] . ' AND approved=1 ORDER BY time DESC LIMIT 7;');
-      if ( !$q )
-        $db->_die();
-      
-      $comments = Array();
-      $no_comments = false;
-      
-      if ( $row = $db->fetchrow() )
-      {
-        do 
-        {
-          $row['time'] = enano_date('F d, Y', $row['time']);
-          $comments[] = $row;
-        }
-        while ( $row = $db->fetchrow() );
-      }
-      else
-      {
-        $no_comments = true;
-      }
-      
-      echo '<tr><td class="row3" colspan="4">';
-      echo '<div style="border: 1px solid #000000; padding: 0px; width: 100%; clip: rect(0px,auto,auto,0px); overflow: auto; background-color: transparent;" class="tblholder">';
-      
-      echo '<table border="0" cellspacing="1" cellpadding="4" style="width: 200%;"><tr>';
-      $class = 'row1';
-      
-      $tpl = '  <td class="{CLASS}">
-                  <a href="{PAGE_LINK}" <!-- BEGINNOT page_exists -->class="wikilink-nonexistent"<!-- END page_exists -->>{PAGE}</a><br />
-                  <small>{lang:userpage_comments_lbl_posted} {DATE}<br /></small>
-                  <b><a href="{COMMENT_LINK}">{SUBJECT}</a></b>
-                </td>';
-      $parser = $template->makeParserText($tpl);
-      
-      if ( count($comments) > 0 )
-      {
-        foreach ( $comments as $comment )
-        {
-          $c_page_id = $paths->nslist[ $comment['namespace'] ] . sanitize_page_id($comment['page_id']);
-          if ( isset($paths->pages[ $c_page_id ]) )
-          {
-            $parser->assign_bool(array(
-              'page_exists' => true
-              ));
-            $page_title = htmlspecialchars($paths->pages[ $c_page_id ]['name']);
-          }
-          else
-          {
-            $parser->assign_bool(array(
-              'page_exists' => false
-              ));
-            $page_title = htmlspecialchars(dirtify_page_id($c_page_id));
-          }
-          $parser->assign_vars(array(
-              'CLASS' => $class,
-              'PAGE_LINK' => makeUrlNS($comment['namespace'], sanitize_page_id($comment['page_id'])),
-              'PAGE' => $page_title,
-              'SUBJECT' => $comment['subject'],
-              'DATE' => $comment['time'],
-              'COMMENT_LINK' => makeUrlNS($comment['namespace'], sanitize_page_id($comment['page_id']), 'do=comments', true)
-            ));
-          $class = ( $class == 'row3' ) ? 'row1' : 'row3';
-          echo $parser->run();
-        }
-      }
-      else
-      {
-        echo '<td class="' . $class . '">' . $lang->get('userpage_msg_no_comments') . '</td>';
-      }
-      echo '</tr></table>';
-      
-      echo '</div>';
-      echo '</td></tr>';
-      
-      $code = $plugins->setHook('userpage_sidebar_left');
-      foreach ( $code as $cmd )
-      {
-        eval($cmd);
-      }
-              
-      echo '  </table>
-            </div>';
-            
-      echo '</td>';
-      
-      //
-      // CONTACT INFORMATION
-      //
-      
-      echo '    <td valign="top" style="width: 150px; padding-left: 10px;">';
-      
-      echo '<div class="tblholder">
-              <table border="0" cellspacing="1" cellpadding="4">';
-      
-      //
-      // Main part of sidebar
-      //
-      
-      // Contact information
-      
-      echo '<tr><th class="subhead">' . $lang->get('userpage_heading_contact') . '</th></tr>';
-      
-      $class = 'row3';
-      
-      if ( $userdata['email_public'] == 1 )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        $email_link = $email->encryptEmail($userdata['email']);
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_email') . ' ' . $email_link . '</td></tr>';
-      }
-      
-      $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-      if ( $session->user_logged_in )
-      {
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_btn_send_pm', array('username' => htmlspecialchars($target_username), 'pm_link' => makeUrlNS('Special', 'PrivateMessages/Compose/to/' . $this->page_id, false, true))) . '</td></tr>';
-      }
-      else
-      {
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_btn_send_pm_guest', array('username' => htmlspecialchars($target_username), 'login_flags' => 'href="' . makeUrlNS('Special', 'Login/' . $paths->nslist[$this->namespace] . $this->page_id) . '" onclick="ajaxStartLogin(); return false;"')) . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_aim']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_aim') . ' ' . $userdata['user_aim'] . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_yahoo']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_yim') . ' ' . $userdata['user_yahoo'] . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_msn']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        $email_link = $email->encryptEmail($userdata['user_msn']);
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_wlm') . ' ' . $email_link . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_xmpp']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        $email_link = $email->encryptEmail($userdata['user_xmpp']);
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_xmpp') . ' ' . $email_link . '</td></tr>';
-      }
-      
-      // Real life
-      
-      echo '<tr><th class="subhead">' . $lang->get('userpage_heading_real_life', array('username' => htmlspecialchars($target_username))) . '</th></tr>';
-      
-      if ( !empty($userdata['user_location']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_location') . ' ' . $userdata['user_location'] . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_job']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_job') . ' ' . $userdata['user_job'] . '</td></tr>';
-      }
-      
-      if ( !empty($userdata['user_hobbies']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_lbl_hobbies') . ' ' . $userdata['user_hobbies'] . '</td></tr>';
-      }
-      
-      if ( empty($userdata['user_location']) && empty($userdata['user_job']) && empty($userdata['user_hobbies']) )
-      {
-        $class = ( $class == 'row1' ) ? 'row3' : 'row1';
-        echo '<tr><td class="'.$class.'">' . $lang->get('userpage_msg_no_contact_info', array('username' => htmlspecialchars($target_username))) . '</td></tr>';
-      }
-      
-      $code = $plugins->setHook('userpage_sidebar_right');
-      foreach ( $code as $cmd )
-      {
-        eval($cmd);
-      }
-      
-      echo '  </table>
-            </div>';
-      echo '</td>';
-      
-      //
-      // End of profile
-      //
-      
-      echo '</tr></table>';
-      
-      echo '</div>'; // tab:profile
-    
-    }
-    
-    // User's own content
-    
-    $send_headers = $this->send_headers;
-    $this->send_headers = false;
-    
-    echo '<span class="menuclear"></span>';
-    
-    echo '<div id="tab:content">';
-    
-    if ( $this->page_exists )
-    {
-      $this->render();
-    }
-    else
-    {
-      $this->err_page_not_existent(true);
-    }
-    
-    echo '</div>'; // tab:content
-    
-    $code = $plugins->setHook('userpage_tabs_body');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    
-    if ( $user_exists )
-    {
-      echo '</div>'; // userpage_wrap
-    }
-    else
-    {
-      if ( !is_valid_ip($target_username) )
-      {
-        echo '<p>' . $lang->get('userpage_msg_user_not_exist', array('username' => htmlspecialchars($target_username))) . '</p>';
-      }
-    }
-    
-    // if ( $send_headers )
-    // {
-    //  display_page_footers();
-    // }
-    
-    $this->send_headers = $send_headers;
-    unset($send_headers);
-    
-    $this->footer();
-    
+    return $this->ns->fetch_text();
   }
   
   /**
@@ -1910,13 +1102,13 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    $title = 'Password required';
+    $title = $lang->get('page_msg_passrequired_title');
     $message = ( empty($this->password) ) ?
                  '<p>' . $lang->get('page_msg_passrequired') . '</p>' :
                  '<p>' . $lang->get('page_msg_pass_wrong') . '</p>';
     $message .= '<form action="' . makeUrlNS($this->namespace, $this->page_id) . '" method="post">
                    <p>
-                     <label>' . $lang->get('page_lbl_password') . ' <input name="pagepass" type="password" /></label>&nbsp;&nbsp;<input type="submit" value="Submit" />
+                     <label>' . $lang->get('page_lbl_password') . ' <input name="pagepass" type="password" /></label>&nbsp;&nbsp;<input type="submit" value="' . $lang->get('page_btn_password_submit') . '" />
                    </p>
                  </form>';
     if ( $this->send_headers )
@@ -1959,158 +1151,6 @@
   }
   
   /**
-   * Tell the user the page doesn't exist, and present them with their options.
-   * @access private
-   */
-   
-  function err_page_not_existent($userpage = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    @header('HTTP/1.1 404 Not Found');
-    
-    $this->header();
-    $this->do_breadcrumbs();
-    
-    $msg = ( $pp = $paths->sysmsg('Page_not_found') ) ? $pp : '{STANDARD404}';
-    
-    $standard_404 = '';
-    
-    if ( $userpage )
-    {
-      $standard_404 .= '<h3>' . $lang->get('page_msg_404_title_userpage') . '</h3>
-             <p>' . $lang->get('page_msg_404_body_userpage');
-    }
-    else
-    {
-      $standard_404 .= '<h3>' . $lang->get('page_msg_404_title') . '</h3>
-             <p>' . $lang->get('page_msg_404_body');
-    }
-    if ( $session->get_permissions('create_page') )
-    {
-      $standard_404 .= ' ' . $lang->get('page_msg_404_create', array(
-          'create_flags' => 'href="'.makeUrlNS($this->namespace, $this->page_id, 'do=edit', true).'" onclick="ajaxEditor(); return false;"',
-          'mainpage_link' => makeUrl(get_main_page(), false, true)
-        ));
-    }
-    else
-    {
-      $standard_404 .= ' ' . $lang->get('page_msg_404_gohome', array(
-          'mainpage_link' => makeUrl(get_main_page(), false, true)
-        ));
-    }
-    $standard_404 .= '</p>';
-    if ( $session->get_permissions('history_rollback') )
-    {
-      $e = $db->sql_query('SELECT * FROM ' . table_prefix . 'logs WHERE action=\'delete\' AND page_id=\'' . $this->page_id . '\' AND namespace=\'' . $this->namespace . '\' ORDER BY time_id DESC;');
-      if ( !$e )
-      {
-        $db->_die('The deletion log could not be selected.');
-      }
-      if ( $db->numrows() > 0 )
-      {
-        $r = $db->fetchrow();
-        $standard_404 .= '<p>' . $lang->get('page_msg_404_was_deleted', array(
-                  'delete_time' => enano_date('d M Y h:i a', $r['time_id']),
-                  'delete_reason' => htmlspecialchars($r['edit_summary']),
-                  'rollback_flags' => 'href="'.makeUrl($paths->page, 'do=rollback&amp;id='.$r['log_id']).'" onclick="ajaxRollback(\''.$r['log_id'].'\'); return false;"'
-                ))
-              . '</p>';
-        if ( $session->user_level >= USER_LEVEL_ADMIN )
-        {
-          $standard_404 .= '<p>' . $lang->get('page_msg_404_admin_opts', array(
-                    'detag_link' => makeUrl($paths->page, 'do=detag', true)
-                  ))
-                . '</p>';
-        }
-      }
-      $db->free_result();
-    }
-    $standard_404 .= '<p>
-            ' . $lang->get('page_msg_404_http_response') . '
-          </p>';
-          
-    $parser = $template->makeParserText($msg);
-    $parser->assign_vars(array(
-        'STANDARD404' => $standard_404
-      ));
-    
-    $msg = RenderMan::render($parser->run());
-    eval( '?>' . $msg );
-    
-    $this->footer();
-  }
-  
-  /**
-   * Echoes out breadcrumb data, if appropriate.
-   * @access private
-   */
-  
-  function do_breadcrumbs()
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    if ( strpos($this->text_cache, '__NOBREADCRUMBS__') !== false )
-      return false;
-    
-    $mode = getConfig('breadcrumb_mode');
-    
-    if ( $mode == 'never' )
-      // Breadcrumbs are disabled
-      return true;
-      
-    // Minimum depth for breadcrumb display
-    $threshold = ( $mode == 'always' ) ? 0 : 1;
-    
-    $breadcrumb_data = explode('/', $this->page_id);
-    if ( count($breadcrumb_data) > $threshold )
-    {
-      // If we're not on a subpage of the main page, add "Home" to the list
-      $show_home = false;
-      if ( $mode == 'always' )
-      {
-        $show_home = true;
-      }
-      echo '<!-- Start breadcrumbs -->
-            <div class="breadcrumbs">
-              ';
-      if ( $show_home )
-      {
-        // Display the "home" link first.
-        $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-        if ( $pathskey !== get_main_page() )
-          echo '<a href="' . makeUrl(get_main_page(), false, true) . '">';
-        echo $lang->get('onpage_btn_breadcrumbs_home');
-        if ( $pathskey !== get_main_page() )
-          echo '</a>';
-      }
-      foreach ( $breadcrumb_data as $i => $crumb )
-      {
-        $cumulative = implode('/', array_slice($breadcrumb_data, 0, ( $i + 1 )));
-        if ( $show_home && $cumulative === get_main_page() )
-          continue;
-        if ( $show_home || $i > 0 )
-          echo ' &raquo; ';
-        $title = ( isPage($cumulative) ) ? get_page_title($cumulative) : get_page_title($crumb);
-        if ( $i + 1 == count($breadcrumb_data) )
-        {
-          echo htmlspecialchars($title);
-        }
-        else
-        {
-          $exists = ( isPage($cumulative) ) ? '' : ' class="wikilink-nonexistent"';
-          echo '<a href="' . makeUrl($cumulative, false, true) . '"' . $exists . '>' . htmlspecialchars($title) . '</a>';
-        }
-      }
-      echo '</div>
-            <!-- End breadcrumbs -->
-            ';
-    }
-  }
-  
-  /**
    * Send an error message and die. For debugging or critical technical errors only - nothing that would under normal circumstances be shown to the user.
    * @param string Error message
    * @param bool If true, send DBAL's debugging information as well
--- a/includes/pageutils.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/pageutils.php	Sat Jan 03 18:11:18 2009 -0500
@@ -323,12 +323,13 @@
   
   /**
    * Generates an HTML table with history information in it.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
+   * @param string the page ID
+   * @param string the namespace
+   * @param string page password
    * @return string
    */
   
-  public static function histlist($page_id, $namespace)
+  public static function histlist($page_id, $namespace, $password = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
@@ -339,6 +340,21 @@
     ob_start();
     
     $pname = $paths->nslist[$namespace] . $page_id;
+    
+    if ( !isPage($pname) )
+    {
+      return 'DNE';
+    }
+    
+    if ( isset($paths->pages[$pname]['password']) )
+    {
+      $password_exists = ( !empty($paths->pages[$pname]['password']) && $paths->pages[$pname]['password'] !== sha1('') );
+      if ( $password_exists && $password !== $paths->pages[$pname]['password'] )
+      {
+        return '<p>' . $lang->get('history_err_wrong_password') . '</p>';
+      }
+    }
+    
     $wiki = ( ( $paths->pages[$pname]['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $paths->pages[$pname]['wiki_mode'] == 1) ? true : false;
     $prot = ( ( $paths->pages[$pname]['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $paths->pages[$pname]['protected'] == 1) ? true : false;
     
@@ -1491,7 +1507,7 @@
   public static function setpass($page_id, $namespace, $pass)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
+    global $lang, $cache;
     // Determine permissions
     if($paths->pages[$paths->nslist[$namespace].$page_id]['password'] != '')
       $a = $session->get_permissions('password_reset');
@@ -1513,6 +1529,7 @@
     {
       die('PageUtils::setpass(): Error during update query: '.$db->get_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
     }
+    $cache->purge('page_meta');
     // Is the new password blank?
     if ( $p == '' )
     {
--- a/includes/render.php	Sat Jan 03 17:54:26 2009 -0500
+++ b/includes/render.php	Sat Jan 03 18:11:18 2009 -0500
@@ -49,6 +49,11 @@
       unset($perms);
       unset($perms); // PHP <5.1.5 Zend bug
       $perms = $session->fetch_page_acl($page_id, $namespace);
+      if ( !$perms )
+      {
+        $session->init_permissions();
+        $perms = $session->fetch_page_acl($page_id, $namespace);
+      };
     }
     
     if(!$perms->get_permissions('read'))
--- a/language/english/core.json	Sat Jan 03 17:54:26 2009 -0500
+++ b/language/english/core.json	Sat Jan 03 18:11:18 2009 -0500
@@ -151,9 +151,11 @@
       msg_rb_success_delete: 'The deletion of this page, which occurred on %dateline%, has been undone. This page has been restored, but comments and categorization data may have been lost.',
       msg_rb_success_reupload: 'The file has been restored to the version uploaded on %dateline%.',
       
+      msg_passrequired_title: 'Password required',
       msg_passrequired: 'Access to this page requires a password. Please enter the password for this page below:',
       msg_pass_wrong: 'The password you entered for this page was incorrect. Please enter the password for this page below:',
       lbl_password: 'Password:',
+      btn_password_submit: 'Submit',
       
       msg_404_title: 'There is no page with this title yet.',
       msg_404_body: 'You have requested a page that doesn\'t exist yet.',
@@ -408,7 +410,8 @@
       log_delete: 'Deleted page',
       log_uploadnew: 'Uploaded new file version',
       lbl_comparingrevisions: 'Comparing revisions:',
-      summary_none_given: 'No edit summary provided.'
+      summary_none_given: 'No edit summary provided.',
+      err_wrong_password: 'Please enter the password for this page before viewing its history.'
     },
     catedit: {
       title: 'Select which categories this page should be included in.',