Made some more changes to the way namespaces are handled, for optimization purposes. This is a bit of a structural reorganization: $paths->pages is obsoleted in its entirety; calculating page existence and metadata is now the job of the Namespace_* backend class. There are many things in PageProcessor that should be reorganized, and page actions in general should really be rethought. This is probably the beginning of a long process that will be taking place over the course of the betas.
authorDan
Tue, 05 May 2009 00:10:26 -0400
changeset 953 323c4cd1aa37
parent 952 d52dfa1f08da
child 954 995d36f39ba3
Made some more changes to the way namespaces are handled, for optimization purposes. This is a bit of a structural reorganization: $paths->pages is obsoleted in its entirety; calculating page existence and metadata is now the job of the Namespace_* backend class. There are many things in PageProcessor that should be reorganized, and page actions in general should really be rethought. This is probably the beginning of a long process that will be taking place over the course of the betas.
includes/dbal.php
includes/functions.php
includes/namespaces/default.php
includes/namespaces/file.php
includes/namespaces/special.php
includes/namespaces/user.php
includes/pageprocess.php
includes/pageutils.php
includes/paths.php
includes/plugins.php
includes/render.php
includes/search.php
includes/sessions.php
includes/stats.php
includes/template.php
index.php
language/english/admin.json
plugins/SpecialAdmin.php
plugins/SpecialPageFuncs.php
plugins/SpecialUpdownload.php
plugins/SpecialUserFuncs.php
plugins/SpecialUserPrefs.php
plugins/admin/CacheManager.php
plugins/admin/PageManager.php
--- a/includes/dbal.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/dbal.php	Tue May 05 00:10:26 2009 -0400
@@ -427,10 +427,24 @@
   }
   
   function fetchrow($r = false) {
-    if(!$this->_conn) return false;
-    if(!$r) $r = $this->latest_result;
-    if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    if ( !$this->_conn )
+      return false;
+    
+    if ( !$r )
+      $r = $this->latest_result;
+    
+    if ( !$r )
+      $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
+    
     $row = mysql_fetch_assoc($r);
+    /*
+    if ( empty($row) )
+    {
+      $GLOBALS['do_gzip'] = false;
+      echo '<pre>' . enano_debug_print_backtrace(true) . '</pre>';
+    }
+    */
+    
     return integerize_array($row);
   }
   
--- a/includes/functions.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/functions.php	Tue May 05 00:10:26 2009 -0400
@@ -380,16 +380,7 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
 
   $idata = RenderMan::strToPageID($page_id);
-  $page_id_key = $paths->nslist[ $idata[1] ] . $idata[0];
-  $page_id_key = sanitize_page_id($page_id_key);
-  $page_data = @$paths->pages[$page_id_key];
-  $title = ( isset($page_data['name']) ) ?
-    ( ( $page_data['namespace'] == 'Article' || !$show_ns ) ?
-      '' :
-      $paths->nslist[ $idata[1] ] )
-    . $page_data['name'] :
-    ( $show_ns ? $paths->nslist[$idata[1]] : '' ) . str_replace('_', ' ', dirtify_page_id( $idata[0] ) );
-  return $title;
+  return get_page_title_ns($idata[0], $idata[1]);
 }
 
 /**
@@ -402,19 +393,9 @@
 function get_page_title_ns($page_id, $namespace)
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-
-  $ns_prefix = ( isset($paths->nslist[ $namespace ]) ) ? $paths->nslist[ $namespace ] : $namespace . substr($paths->nslist['Special'], -1);
-  $page_id_key = $ns_prefix . $page_id;
-  if ( isPage($page_id_key) )
-  {
-    $page_data = $paths->pages[$page_id_key];
-  }
-  else
-  {
-    $page_data = array();
-  }
-  $title = ( isset($page_data['name']) ) ? $page_data['name'] : $ns_prefix . str_replace('_', ' ', dirtify_page_id( $page_id ) );
-  return $title;
+  
+  $ns = namespace_factory($page_id, $namespace);
+  return $ns->title;
 }
 
 /**
@@ -578,32 +559,24 @@
  * @return bool True if the page exists, false otherwise
  */
 
-function isPage($p) {
+function isPage($p)
+{
   global $db, $session, $paths, $template, $plugins; // Common objects
-
-  // Try the easy way first ;-)
-  if ( isset( $paths->pages[ $p ] ) )
-  {
-    return true;
-  }
-
-  // Special case for Special, Template, and Admin pages that can't have slashes in their URIs
-  $ns_test = RenderMan::strToPageID( $p );
-
-  if($ns_test[1] != 'Special' && $ns_test[1] != 'Template' && $ns_test[1] != 'Admin')
-  {
-    return false;
-  }
-
-  $particles = explode('/', $p);
-  if ( isset ( $paths->pages[ $particles[ 0 ] ] ) )
-  {
-    return true;
-  }
-  else
-  {
-    return false;
-  }
+  static $ispage_cache = array();
+  if ( isset($ispage_cache[$p]) )
+    return $ispage_cache[$p];
+  
+  list($page_id, $namespace) = RenderMan::strToPageID($p);
+  $cdata = $paths->get_cdata($page_id, $namespace);
+  if ( !isset($cdata['page_exists']) )
+  {
+    $class = ( class_exists($_ = "Namespace_$namespace") ) ? $_ : "Namespace_Default";
+    $page = new $class($page_id, $namespace);
+    return $page->exists();
+  }
+  
+  $ispage_cache[$p] = $cdata['page_exists'];
+  return $cdata['page_exists'];
 }
 
 /**
@@ -615,6 +588,13 @@
 
 function namespace_factory($page_id, $namespace, $revision_id = 0)
 {
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  static $objcache = array();
+  $pathskey = $paths->get_pathskey($page_id, $namespace) . ":$revision_id";
+  if ( isset($objcache[$pathskey]) )
+    return $objcache[$pathskey];
+  
   if ( !class_exists("Namespace_$namespace") )
   {
     if ( file_exists(ENANO_ROOT . "/includes/namespaces/" . strtolower($namespace) . ".php") )
@@ -626,11 +606,13 @@
   {
     $class = "Namespace_$namespace";
     $ns = new $class($page_id, $namespace, $revision_id);
+    $objcache[$pathskey] = $ns;
     return $ns;
   }
   else
   {
     $ns = new Namespace_Default($page_id, $namespace, $revision_id);
+    $objcache[$pathskey] = $ns;
     return $ns;
   }
 }
@@ -915,22 +897,7 @@
 
 function display_page_headers()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
-  if($session->get_permissions('vote_reset') && $paths->cpage['delvotes'] > 0)
-  {
-    $delvote_ips = unserialize($paths->cpage['delvote_ips']);
-    $hr = htmlspecialchars(implode(', ', $delvote_ips['u']));
-    
-    $string_id = ( $paths->cpage['delvotes'] == 1 ) ? 'delvote_lbl_votes_one' : 'delvote_lbl_votes_plural';
-    $string = $lang->get($string_id, array('num_users' => $paths->cpage['delvotes']));
-    
-    echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;" id="mdgDeleteVoteNoticeBox">
-            <b>' . $lang->get('etc_lbl_notice') . '</b> ' . $string . '<br />
-            <b>' . $lang->get('delvote_lbl_users_that_voted') . '</b> ' . $hr . '<br />
-            <a href="'.makeUrl($paths->page, 'do=deletepage').'" onclick="ajaxDeletePage(); return false;">' . $lang->get('delvote_btn_deletepage') . '</a>  |  <a href="'.makeUrl($paths->page, 'do=resetvotes').'" onclick="ajaxResetDelVotes(); return false;">' . $lang->get('delvote_btn_resetvotes') . '</a>
-          </div>';
-  }
+  // Deprecated.
 }
 
 /**
@@ -2362,8 +2329,11 @@
   $this_page = ceil ( $start / $perpage );
   $i = 0;
   
-  $paginator = generate_paginator($this_page, $num_pages, $result_url, $perpage, 0);
-  $out .= $paginator;
+  if ( $num_results > 0 )
+  {
+    $paginator = generate_paginator($this_page, $num_pages, $result_url, $perpage, 0);
+    $out .= $paginator;
+  }
 
   $cls = 'row2';
 
@@ -2386,7 +2356,7 @@
       {
         if ( isset($callers[$j]) )
         {
-          $tmp = ( is_callable($callers[$j]) ) ? @call_user_func($callers[$j], $val, $row) : $val;
+          $tmp = ( is_callable($callers[$j]) ) ? call_user_func($callers[$j], $val, $row) : $val;
 
           if ( is_string($tmp) )
           {
@@ -2401,7 +2371,8 @@
     $out .= $footer;
   }
 
-  $out .= $paginator;
+  if ( $num_results > 0 )
+    $out .= $paginator;
 
   return $out;
 }
@@ -2429,9 +2400,8 @@
   $this_page = ceil ( $start / $perpage );
 
   $paginator = generate_paginator($this_page, $num_pages, $result_url, $perpage, 0);
-  $out .= $paginator;
   
-  if ( $total > 1 )
+  if ( $num_results > 1 )
   {
     $out .= $paginator;
   }
@@ -2457,7 +2427,7 @@
     $out .= $footer;
   }
 
-  if ( $total > 1 )
+  if ( $num_results > 1 )
     $out .= $paginator;
 
   return $out;
@@ -4617,9 +4587,11 @@
  * Logs something in the profiler.
  * @param string Point name or message
  * @param bool Optional. If true (default), a backtrace will be generated and added to the profiler data. False disables this, often for security reasons.
+ * @param resource Optional. If specified, bases the time difference off of this event instead of the previous event/
+ * @return resource Event ID
  */
 
-function profiler_log($point, $allow_backtrace = true)
+function profiler_log($point, $allow_backtrace = true, $parent_event = false)
 {
   if ( !defined('ENANO_DEBUG') )
     return false;
@@ -4634,13 +4606,31 @@
       'point' => $point,
       'time' => microtime_float(),
       'backtrace' => $backtrace,
-      'mem' => false
+      'mem' => false,
+      'parent_event' => $parent_event
     );
   if ( function_exists('memory_get_usage') )
   {
     $_profiler[ count($_profiler) - 1 ]['mem'] = memory_get_usage();
   }
-  return true;
+  return count($_profiler) - 1;
+}
+
+/**
+ * Insert a message (an event without any time data) into the profiler.
+ * @param string Message
+ */
+
+function profiler_message($message)
+{
+  if ( !defined('ENANO_DEBUG') )
+    return false;
+  
+  global $_profiler;
+  
+  $_profiler[] = array(
+      'message' => $message,
+    );
 }
 
 /**
@@ -4676,6 +4666,17 @@
     // if ( $time_since_last < 0.01 )
     //   continue;
     
+    if ( isset($entry['message']) )
+    {
+      $html .= "<!-- ########################################################## -->\n<tr>\n  <th colspan=\"2\">Message $i</th>\n</tr>";
+      
+      $html .= '<tr>' . "\n";
+      $html .= '  <td class="row2">Message:</td>' . "\n";
+      $html .= '  <td class="row1">' . htmlspecialchars($entry['message']) . '</td>' . "\n";
+      $html .= '</tr>' . "\n";
+      continue;
+    }
+    
     $html .= "<!-- ########################################################## -->\n<tr>\n  <th colspan=\"2\">Event $i</th>\n</tr>";
     
     $html .= '<tr>' . "\n";
@@ -4690,15 +4691,25 @@
     $html .= '  <td class="row1">' . $time . 's</td>' . "\n";
     $html .= '</tr>' . "\n";
     
+    $time_label = 'Time since last event:';
+    if ( $entry['parent_event'] && is_int($entry['parent_event']) && isset($profile[$entry['parent_event']]) )
+    {
+      $time_last = $profile[$entry['parent_event']]['time'];
+      $time_label = "Time since event #{$entry['parent_event']}:";
+    }
+    
     $time = $entry['time'] - $time_last;
     if ( $time < 0.0001 )
-      $time = 'Marginal';
+      $time_html = 'Marginal';
     else
-      $time = "{$time}s";
+      $time_html = number_format($time, 6) . "s";
     
+    if ( $time > 0.02 )
+      $time_html = "<span style=\"background-color: #a00; padding: 4px; color: #fff; font-weight: bold;\">$time_html</span>";
+      
     $html .= '<tr>' . "\n";
-    $html .= '  <td class="row2">Time since last event:</td>' . "\n";
-    $html .= '  <td class="row1">' . $time . '</td>' . "\n";
+    $html .= '  <td class="row2">' . $time_label . '</td>' . "\n";
+    $html .= '  <td class="row1">' . $time_html . '</td>' . "\n";
     $html .= '</tr>' . "\n";
     
     if ( $entry['backtrace'] )
@@ -4903,6 +4914,7 @@
     $cache->purge('page_meta');
     $cache->purge('anon_sidebar');
     $cache->purge('plugins');
+    $cache->purge('wiki_edit_notice');
     
     $data_files = array(
         'aes_decrypt.php',
--- a/includes/namespaces/default.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/namespaces/default.php	Tue May 05 00:10:26 2009 -0400
@@ -64,6 +64,42 @@
   public $title = '';
   
   /**
+   * PathManager info array ("cdata") for this page. (The one with urlname, name, namespace, delvotes, delvote_ips, protected, visible, etc.)
+   * @var array
+   */
+  
+  public $cdata = array();
+  
+  /**
+   * ACL calculation instance for this page.
+   * @var object(Session_ACLPageInfo)
+   */
+  
+  public $perms = false;
+  
+  /**
+   * Protection calculation
+   * @var bool
+   */
+  
+  public $page_protected = false;
+  
+  /**
+   * Wiki mode calculation
+   * @var bool
+   */
+  
+  public $wiki_mode = false;
+  
+  /**
+   * Page conditions. These represent the final decision as to whether an action is allowed or not. They are set to true if ACLs permit AND if
+   * the action "makes sense." (e.g., you can't vote to delete a non-wikimode page.)
+   * @var array
+   */
+  
+  public $conds = array();
+  
+  /**
    * Constructor.
    */
   
@@ -75,50 +111,65 @@
     $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' )
+    // grab the cdata
+    $this->build_cdata();
+    
+    $this->page_protected = $this->cdata['really_protected'] ? true : false;
+    switch($this->cdata['wiki_mode'])
     {
-      $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();
+      case 0: $this->wiki_mode = false; break;
+      case 1: $this->wiki_mode = true; break;
+      default: case 2: $this->wiki_mode = getConfig('wiki_mode') == 1; break;
+    }
+  }
+  
+  /**
+   * Build the page's cdata.
+   */
+  
+  public function build_cdata()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    static $cdata_cache = array();
+    $pathskey = $paths->get_pathskey($this->page_id, $this->namespace);
+    if ( isset($cdata_cache[$pathskey]) )
+    {
+      $this->cdata = $cdata_cache[$pathskey];
+      $this->exists = $cdata_cache[$pathskey]['page_exists'];
+      $this->title = $cdata_cache[$pathskey]['name'];
+      return null;
+    }
+    
+    $this->exists = false;
+    $ns_char = substr($paths->nslist['Special'], -1);
       
-      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();
+    $page_name = $this->namespace == 'Article' ? dirtify_page_id($this->page_id) : "{$this->namespace}{$ns_char}" . dirtify_page_id($this->page_id);
+    $this->cdata = array(
+      'name' => $page_name,
+      'urlname' => $this->page_id,
+      'namespace' => $this->namespace,
+      'special' => 0,
+      'visible' => 0,
+      'comments_on' => 1,
+      'protected' => 0,
+      'delvotes' => 0,
+      'delvote_ips' => '',
+      'wiki_mode' => 2,
+      'page_exists' => false,
+      'page_format' => getConfig('default_page_format', 'wikitext')
+    );
+    
+    if ( $data_from_db = Namespace_Default::get_cdata_from_db($this->page_id, $this->namespace) )
+    {
+      $this->exists = true;
+      $this->cdata = $data_from_db;
+      $this->cdata['page_exists'] = true;
+      $this->title = $this->cdata['name'];
     }
+        
+    $this->cdata = Namespace_Default::bake_cdata($this->cdata);
+    
+    $cdata_cache[$pathskey] = $this->cdata;
   }
   
   /**
@@ -287,6 +338,8 @@
     
     $text = $this->fetch_text();
     
+    profiler_log("Namespace [$this->namespace, $this->page_id]: pulled text from DB");
+    
     $text = preg_replace('/([\s]*)__NOBREADCRUMBS__([\s]*)/', '', $text);
     $text = preg_replace('/([\s]*)__NOTOC__([\s]*)/', '', $text);
     
@@ -299,8 +352,7 @@
       $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];
+      $page_data = $paths->get_cdata($oldtarget[0], $oldtarget[1]);
       $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']) )
       {
@@ -331,7 +383,6 @@
     
     if ( $send_headers )
     {
-      $template->init_vars($this);
       $output->set_title($this->title);
       $output->header();
     }
@@ -339,7 +390,23 @@
     
     if ( $incl_inner_headers )
     {
-      display_page_headers();
+      if ( !$this->perms )
+        $this->perms = $session->fetch_page_acl($this->page_id, $this->namespace);
+      
+      if ( $this->perms->get_permissions('vote_reset') && $this->cdata['delvotes'] > 0)
+      {
+        $delvote_ips = unserialize($this->cdata['delvote_ips']);
+        $hr = htmlspecialchars(implode(', ', $delvote_ips['u']));
+        
+        $string_id = ( $this->cdata['delvotes'] == 1 ) ? 'delvote_lbl_votes_one' : 'delvote_lbl_votes_plural';
+        $string = $lang->get($string_id, array('num_users' => $this->cdata['delvotes']));
+        
+        echo '<div class="info-box" style="margin-left: 0; margin-top: 5px;" id="mdgDeleteVoteNoticeBox">
+                <b>' . $lang->get('etc_lbl_notice') . '</b> ' . $string . '<br />
+                <b>' . $lang->get('delvote_lbl_users_that_voted') . '</b> ' . $hr . '<br />
+                <a href="'.makeUrl($paths->page, 'do=deletepage').'" onclick="ajaxDeletePage(); return false;">' . $lang->get('delvote_btn_deletepage') . '</a>  |  <a href="'.makeUrl($paths->page, 'do=resetvotes').'" onclick="ajaxResetDelVotes(); return false;">' . $lang->get('delvote_btn_resetvotes') . '</a>
+              </div>';
+      }
     }
     
     if ( $this->revision_id )
@@ -363,8 +430,7 @@
     }
     else
     {
-      $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-      $page_format = $paths->pages[$pathskey]['page_format'];
+      $page_format = $this->cdata['page_format'];
     }
     
     if ( $redir_enabled )
@@ -378,6 +444,8 @@
       eval($cmd);
     }
     
+    $prof_contentevent = profiler_log("Namespace [$this->namespace, $this->page_id]: headers and preprocessing done - about to send content");
+    
     if ( $incl_inner_headers )
     {
       if ( $page_format === 'wikitext' )
@@ -400,6 +468,8 @@
     
     eval ( $text );
     
+    profiler_log("Namespace [$this->namespace, $this->page_id]: content sent", true, $prof_contentevent);
+    
     $code = $plugins->setHook('pageprocess_render_tail');
     foreach ( $code as $cmd )
     {
@@ -411,6 +481,8 @@
       display_page_footers();
     }
     
+    profiler_log("Namespace [$this->namespace, $this->page_id]: sent footers");
+    
     if ( $send_headers )
       $output->footer();
   }
@@ -709,7 +781,7 @@
           $link = makeUrlNS('Category', $cid);
           $list[] = '<a href="' . $link . '">' . htmlspecialchars($title) . '</a>';
         }
-        while ( $row = $db->fetchrow() );
+        while ( $row = $db->fetchrow($q) );
         $html .= implode(', ', $list);
       }
       else
@@ -728,15 +800,189 @@
     }
     return $html;
   }
+  
+  /**
+   * Pull in switches as to whether a specific toolbar button should be used or not. This sets things up according to the current page being displayed.
+   * @return array Associative
+   */
+  
+  function set_conds()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    if ( !$this->perms )
+      $this->perms = $session->fetch_page_acl($this->page_id, $this->namespace);
+    
+    $enforce_protection = ( $this->page_protected && ( ( $session->check_acl_scope('even_when_protected', $this->namespace) && !$this->perms->get_permissions('even_when_protected') ) || !$session->check_acl_scope('even_when_protected', $this->namespace) ) );
+    
+    $conds = array();
+    
+    // Article: always show
+    $conds['article'] = true;
+    
+    // Discussion: Show if comments are enabled on the site, and if comments are on for this page.
+    $conds['comments'] = $this->perms->get_permissions('read') && getConfig('enable_comments', '1')=='1' && $this->cdata['comments_on'] == 1;
+    
+    // Edit: Show if we have permission to edit the page, and if we don't have protection in effect
+    $conds['edit'] = $this->perms->get_permissions('read') && $session->check_acl_scope('edit_page', $this->namespace) && $this->perms->get_permissions('edit_page') && !$enforce_protection;
+    
+    // View source: Show if we have permission to view source and either ACLs prohibit editing or protection is in effect
+    $conds['viewsource'] = $session->check_acl_scope('view_source', $this->namespace) && $this->perms->get_permissions('view_source') && ( !$this->perms->get_permissions('edit_page') || $enforce_protection ) && $this->namespace != 'API';
+    
+    // History: Show if we have permission to see history and if the page exists
+    $conds['history'] = $session->check_acl_scope('history_view', $this->namespace) && $this->exists && $this->perms->get_permissions('history_view');
+    
+    // Rename: Show if the page exists, if we have permission to rename, and if protection isn't in effect
+    $conds['rename'] = $session->check_acl_scope('rename', $this->namespace) && $this->exists && $this->perms->get_permissions('rename') && !$enforce_protection;
+    
+    // Vote-to-delete: Show if we have Wiki Mode on, if we have permission to vote for deletion, and if the page exists (can't vote to delete a nonexistent page)
+    $conds['delvote'] = $this->wiki_mode && $session->check_acl_scope('vote_delete', $this->namespace) && $this->perms->get_permissions('vote_delete') && $this->exists;
+    
+    // Reset votes: Show if we have Wiki Mode on, if we have permission to reset votes, if the page exists, and if there's at least one vote
+    $conds['resetvotes'] = $session->check_acl_scope('vote_reset', $this->namespace) && $this->wiki_mode && $this->exists && $this->perms->get_permissions('vote_reset') && $this->cdata['delvotes'] > 0;
+    
+    // Delete page: Show if the page exists and if we have permission to delete it
+    $conds['delete'] = $session->check_acl_scope('delete_page', $this->namespace) && $this->exists && $this->perms->get_permissions('delete_page');
+    
+    // Printable view: Show if the page exists
+    $conds['printable'] = $this->exists;
+    
+    // Protect: Show if we have Wiki Mode on, if the page exists, and if we have permission to protect the page.
+    $conds['protect'] = $session->check_acl_scope('protect', $this->namespace) && $this->wiki_mode && $this->exists && $this->perms->get_permissions('protect');
+    
+    // Set Wiki Mode: Show if the page exists and if we have permission to set wiki mode
+    $conds['setwikimode'] = $session->check_acl_scope('set_wiki_mode', $this->namespace) && $this->exists && $this->perms->get_permissions('set_wiki_mode');
+    
+    // Clear logs: Show if we have permission to clear logs
+    $conds['clearlogs'] = $session->check_acl_scope('clear_logs', $this->namespace) && $this->perms->get_permissions('clear_logs');
+    
+    // Set password: a little bit complicated. If there's a password, check for password_reset; else, check for password_set.
+    $conds['password'] = empty($this->cdata['password']) ?
+                           $session->check_acl_scope('password_set', $this->namespace) && $this->perms->get_permissions('password_set') :
+                           $session->check_acl_scope('password_reset', $this->namespace) && $this->perms->get_permissions('password_reset');
+    
+    // Edit ACLs: Show if this is a non-Enano page that's calling the Enano API and (a) if we have permissions to edit ACLs or (b) we're an admin AND ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL is on
+    $conds['acledit'] = $this->namespace != 'API' && $session->check_acl_scope('edit_acl', $this->namespace) && ( $this->perms->get_permissions('edit_acl') || ( defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL') &&  $session->user_level >= USER_LEVEL_ADMIN ) );
+    
+    // Admin page: Show if the page exists and if we're an admin
+    $conds['adminpage'] = $session->user_level >= USER_LEVEL_ADMIN && $this->exists;
+    
+    // Allow plugins to change stuff
+    $code = $plugins->setHook('page_conds_set');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
+    $this->conds = $conds;
+  }
+  
+  /**
+   * Return page conditions
+   * @return array
+   */
+  
+  public function get_conds()
+  {
+    if ( empty($this->conds) )
+      $this->set_conds();
+    
+    return $this->conds;
+  }
+  
   /**
    * Just tell us if the current page exists or not.
    * @return bool
    */
    
-  function exists()
+  public function exists()
   {
     return $this->exists;
   }
+  
+  /**
+   * Return cdata
+   * @return array
+   */
+  
+  public function get_cdata()
+  {
+    return $this->cdata;
+  }
+  
+  /**
+   * Bake, or finalize the processing of, a cdata array.
+   * @static
+   * @access public
+   */
+  
+  public static function bake_cdata($cdata)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // urlname_nons is the actual page_id.
+    $cdata['urlname_nons'] = $cdata['urlname'];
+    if ( isset($paths->nslist[ $cdata['namespace'] ]) )
+    {
+      $cdata['urlname'] = $paths->nslist[ $cdata['namespace'] ] . $cdata['urlname'];
+    }
+    else
+    {
+      $ns_char = substr($paths->nslist['Special'], -1);
+      $cdata['urlname'] = $cdata['namespace'] . $ns_char . $cdata['urlname'];
+    }
+    
+    // fix up deletion votes
+    if ( empty($cdata['delvotes']) )
+      $cdata['delvotes'] = 0;
+    
+    // calculate wiki mode
+    $cdata['really_wiki_mode'] = ( $cdata['wiki_mode'] == 1 || ( $cdata['wiki_mode'] == 2 && getConfig('wiki_mode', 0) == 1 ) );
+    
+    // calculate protection
+    $cdata['really_protected'] = ( $cdata['protected'] > 0 );
+    if ( $cdata['protected'] == 2 )
+    {
+      $cdata['really_protected'] = !$session->user_logged_in || ( $session->user_logged_in && $session->reg_time + 86400*4 > time() );
+    }
+    
+    return $cdata;
+  }
+  
+  /**
+   * Grabs raw (unbaked) cdata from the database, caching if possible.
+   * @param string Page ID
+   * @param string Namespace.
+   * @static
+   */
+  
+  public static function get_cdata_from_db($page_id, $namespace)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    static $cache = array();
+    
+    $pathskey = $paths->get_pathskey($page_id, $namespace);
+    if ( isset($cache[$pathskey]) )
+      return $cache[$pathskey];
+    
+    $page_id_db = $db->escape($page_id);
+    $namespace_db = $db->escape($namespace);
+    
+    $q = $db->sql_query('SELECT * FROM ' . table_prefix . "pages WHERE urlname = '$page_id_db' AND namespace = '$namespace_db';");
+    if ( !$q )
+      $db->_die();
+    
+    if ( $db->numrows() < 1 )
+    {
+      $db->free_result();
+      $cache[$pathskey] = false;
+      return false;
+    }
+    
+    $row = $db->fetchrow();
+    $cache[$pathskey] = $row;
+    return $row;
+  }
 }
 
 /**
@@ -755,3 +1001,7 @@
 {
 }
 
+class Namespace_Category extends Namespace_Default
+{
+}
+
--- a/includes/namespaces/file.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/namespaces/file.php	Tue May 05 00:10:26 2009 -0400
@@ -48,12 +48,7 @@
     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);
+    $selfn = $db->escape($this->page_id);
     $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"
--- a/includes/namespaces/special.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/namespaces/special.php	Tue May 05 00:10:26 2009 -0400
@@ -20,9 +20,50 @@
     
     $this->page_id = sanitize_page_id($page_id);
     $this->namespace = $namespace;
+    $this->build_cdata();
     $this->revision_id = intval($revision_id);
     
+    $this->page_protected = true;
+    $this->wiki_mode = 0;
+  }
+  
+  public function build_cdata()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
     $this->exists = function_exists("page_{$this->namespace}_{$this->page_id}");
+    
+    if ( isset($paths->pages[ $paths->get_pathskey($this->page_id, $this->namespace) ]) )
+    {
+      $page_name = $paths->pages[ $paths->get_pathskey($this->page_id, $this->namespace) ]['name'];
+    }
+    else
+    {
+      $page_name = "{$paths->nslist[ $this->namespace ]}{$this->page_id}";
+      if ( ($_ = $lang->get('specialpage_' . strtolower($this->page_id))) !== 'specialpage_' . strtolower($this->page_id) )
+      {
+        $page_name = $_;
+      }
+    }
+    
+    $this->cdata = array(
+        'name' => $page_name,
+        'urlname' => $this->page_id,
+        'namespace' => $this->namespace,
+        'special' => 0,
+        'visible' => 0,
+        'comments_on' => 0,
+        'protected' => 0,
+        'delvotes' => 0,
+        'delvote_ips' => '',
+        'wiki_mode' => 2,
+        'page_exists' => false,
+        'page_format' => getConfig('default_page_format', 'wikitext')
+      );
+    $this->cdata = Namespace_Default::bake_cdata($this->cdata);
+    
+    $this->title =& $this->cdata['name'];
   }
   
   function send()
@@ -31,7 +72,7 @@
     
     if ( $this->exists )
     {
-      @call_user_func("page_{$this->namespace}_{$this->page_id}");
+      call_user_func("page_{$this->namespace}_{$this->page_id}");
     }
     else
     {
@@ -57,4 +98,12 @@
     echo "<p>$message</p>";
     $output->footer();
   }
+  
+  function set_conds()
+  {
+    parent::set_conds();
+    
+    $this->conds['printable'] = false;
+    $this->conds['adminpage'] = false;
+  }
 }
--- a/includes/namespaces/user.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/namespaces/user.php	Tue May 05 00:10:26 2009 -0400
@@ -58,13 +58,7 @@
     
     $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;
-    }
+    $page_name = $this->cdata['name'];
     
     $target_username = strtr($page_urlname, 
       Array(
@@ -80,13 +74,8 @@
     {
       $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);
+    $output->set_title($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
@@ -275,7 +264,7 @@
             $parser->assign_bool(array(
               'page_exists' => true
               ));
-            $page_title = htmlspecialchars($paths->pages[ $c_page_id ]['name']);
+            $page_title = get_page_title($c_page_id);
           }
           else
           {
--- a/includes/pageprocess.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/pageprocess.php	Tue May 05 00:10:26 2009 -0400
@@ -181,10 +181,6 @@
     
     if ( !$this->perms->get_permissions('read') )
     {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
       // Permission denied to read page. Is this one of our core pages that must always be allowed?
       // NOTE: Not even the administration panel will work if ACLs deny access to it.
       if ( $this->namespace == 'Special' && in_array($this->page_id, array('Login', 'Logout', 'LangExportJSON', 'CSS')) )
@@ -218,10 +214,6 @@
     $admin_fail = false;
     if ( $this->namespace == 'Admin' && strstr($this->page_id, '/') )
     {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
       $this->page_id = substr($this->page_id, 0, strpos($this->page_id, '/'));
       $funcname = "page_{$this->namespace}_{$this->page_id}";
       if ( function_exists($funcname) )
@@ -231,21 +223,19 @@
     }
     if ( isPage($pathskey) )
     {
-      if ( $this->send_headers )
-      {
-        $template->init_vars($this);
-      }
-      if ( $paths->pages[$pathskey]['special'] == 1 )
+      $cdata = $this->ns->get_cdata();
+      
+      if ( $cdata['special'] == 1 )
       {
         $this->send_headers = false;
         $strict_no_headers = true;
         $GLOBALS['output'] = new Output_Naked();
       }
-      if ( isset($paths->pages[$pathskey]['password']) )
+      if ( isset($cdata['password']) )
       {
-        if ( $paths->pages[$pathskey]['password'] != '' && $paths->pages[$pathskey]['password'] != sha1('') )
+        if ( $cdata['password'] != '' && $cdata['password'] != sha1('') )
         {
-          $password =& $paths->pages[$pathskey]['password'];
+          $password =& $cdata['password'];
           if ( $this->password != $password )
           {
             $this->err_wrong_password();
@@ -253,7 +243,7 @@
           }
         }
       }
-      if ( isset($paths->pages[$pathskey]['require_admin']) && $paths->pages[$pathskey]['require_admin'] )
+      if ( isset($cdata['require_admin']) && $cdata['require_admin'] )
       {
         if ( $session->auth_level < USER_LEVEL_ADMIN )
         {
@@ -288,11 +278,6 @@
     
     // We are all done. Ship off the page.
     
-    if ( $this->send_headers )
-    {
-      $template->init_vars($this);
-    }
-    
     $this->ns->send();
   }
   
@@ -325,15 +310,12 @@
     {
       return '';
     }
-    $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-    if ( isPage($pathskey) )
+    $cdata = $this->ns->get_cdata();
+    if ( isset($cdata['password']) )
     {
-      if ( isset($paths->pages[$pathskey]['password']) )
+      if ( $cdata['password'] != sha1('') && $cdata['password'] !== $this->password && !empty($cdata['password']) )
       {
-        if ( $paths->pages[$pathskey]['password'] != sha1('') && $paths->pages[$pathskey]['password'] !== $this->password && !empty($paths->pages[$pathskey]['password']) )
-        {
-          return false;
-        }
+        return false;
       }
     }
     return $this->fetch_text();
@@ -462,9 +444,9 @@
     }
     
     // Set page_format
-    $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
     // Using @ due to warning thrown when saving new page
-    if ( @$paths->pages[ $pathskey ]['page_format'] !== $page_format )
+    $cdata = $this->ns->get_cdata();
+    if ( @$cdata['page_format'] !== $page_format )
     {
       // Note: no SQL injection to worry about here. Everything that goes into this is sanitized already, barring some rogue plugin.
       // (and if there's a rogue plugin running, we have bigger things to worry about anyway.)
@@ -906,15 +888,7 @@
     }
     
     // Retrieve page metadata
-    $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-    if ( !isPage($pathskey) )
-    {
-      return array(
-        'success' => false,
-        'error' => 'page_metadata_not_found'
-        );
-    }
-    $metadata =& $paths->pages[$pathskey];
+    $metadata = $this->ns->get_cdata();
     
     // Log the action
     $username = $db->escape($session->username);
@@ -1000,8 +974,7 @@
       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];
+        $page_data = $this->ns->get_cdata();
         $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>');
@@ -1125,14 +1098,13 @@
       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];
+        $old_page = namespace_factory($oldtarget[0], $oldtarget[1]);
+        $page_data = $old_page->get_cdata();
         $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>';
         
         $url = makeUrlNS($this->namespace, $this->page_id, 'redirect=no', true);
-        $page_id_key = $paths->nslist[ $this->namespace ] . $this->page_id;
-        $page_data = $paths->pages[$page_id_key];
+        $page_data = $this->ns->get_cdata();
         $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$this->namespace] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $this->page_id ) ) );
         $b = '<a href="' . $url . '">' . $title . '</a>';
         
--- a/includes/pageutils.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/pageutils.php	Tue May 05 00:10:26 2009 -0400
@@ -50,14 +50,18 @@
   public static function getsource($page, $password = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!isPage($page))
+    if ( !isPage($page) )
     {
       return '';
     }
     
-    if(strlen($paths->pages[$page]['password']) == 40)
+    list($page_id, $namespace) = RenderMan::strToPageID($page);
+    $ns = namespace_factory($page_id, $namespace);
+    $cdata = $ns->get_cdata();
+    
+    if ( strlen($cdata['password']) == 40 )
     {
-      if(!$password || ( $password != $paths->pages[$page]['password']))
+      if(!$password || ( $password != $cdata['password']))
       {
         return 'invalid_password';
       }
@@ -111,65 +115,10 @@
   public static function savepage($page_id, $namespace, $message, $summary = 'No edit summary given', $minor = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    $uid = sha1(microtime());
-    $pname = $paths->nslist[$namespace] . $page_id;
     
-    if(!$session->get_permissions('edit_page'))
-      return 'Access to edit pages is denied.';
-    
-    if(!isPage($pname))
-    {
-      $create = PageUtils::createPage($page_id, $namespace);
-      if ( $create != 'good' )
-        return 'The page did not exist, and I was not able to create it. The reported error was: ' . $create;
-      $paths->page_exists = true;
-    }
-    
-    // Check page protection
-    
-    $is_protected = false;
-    $page_data =& $paths->pages[$pname];
-    // Is the protection semi?
-    if ( $page_data['protected'] == 2 )
-    {
-      $is_protected = true;
-      // Page is semi-protected. Has the user been here for at least 4 days?
-      // 345600 seconds = 4 days
-      if ( $session->user_logged_in && ( $session->reg_time + 345600 ) <= time() )
-        $is_protected = false;
-    }
-    // Is the protection full?
-    else if ( $page_data['protected'] == 1 )
-    {
-      $is_protected = true;
-    }
-    
-    // If it's protected and we DON'T have even_when_protected rights, bail out
-    if ( $is_protected && !$session->get_permissions('even_when_protected') )
-    {
-      return 'You don\'t have the necessary permissions to edit this page.';
-    }
-    
-    // We're skipping the wiki mode check here because by default edit_page pemissions are AUTH_WIKIMODE.
-    // The exception here is the user's own userpage, which is overridden at the time of account creation.
-    // At that point it's set to AUTH_ALLOW, but obviously only for the user's own userpage.
-    
-    // Strip potentially harmful tags and PHP from the message, dependent upon permissions settings
-    $message = RenderMan::preprocess_text($message, false, false);
-    
-    $msg = $db->escape($message);
-    
-    $minor = $minor ? ENANO_SQL_BOOLEAN_TRUE : ENANO_SQL_BOOLEAN_FALSE;
-    $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.enano_date('d M Y h:i a').'\', \'' . $paths->page_id . '\', \'' . $paths->namespace . '\', ' . ENANO_SQL_MULTISTRING_PRFIX . '\'' . $msg . '\', \'' . $uid . '\', \'' . $session->username . '\', \'' . $db->escape(htmlspecialchars($summary)) . '\', ' . $minor . ');';
-    if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
-    
-    $q = 'UPDATE ' . table_prefix.'page_text SET page_text=' . ENANO_SQL_MULTISTRING_PRFIX . '\'' . $msg . '\',char_tag=\'' . $uid . '\' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';';
-    $e = $db->sql_query($q);
-    if(!$e) $db->_die('Enano was unable to save the page contents. Your changes have been lost <tt>:\'(</tt>.');
-      
-    $paths->rebuild_page_index($page_id, $namespace);
-      
-    return 'good';
+    $page = new PageProcessor($page_id, $namespace);
+    $cdata = $page->ns->get_cdata();
+    return $page->update_page($message, $summary, $minor, $cdata['page_format']);
   }
   
   /**
@@ -278,47 +227,8 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
-    $pname = $paths->nslist[$namespace] . $page_id;
-    $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;
-    
-    if ( !$session->get_permissions('protect') )
-    {
-      return('Insufficient access rights');
-    }
-    if ( !$wiki )
-    {
-      return('Page protection only has an effect when Wiki Mode is enabled.');
-    }
-    if ( !preg_match('#^([0-9]+){1}$#', (string)$level) )
-    {
-      return('Invalid $level parameter.');
-    }
-    
-    switch($level)
-    {
-      case 0:
-        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.enano_date('d M Y h:i a').'\', \'page\', \'unprot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
-        break;
-      case 1:
-        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.enano_date('d M Y h:i a').'\', \'page\', \'prot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
-        break;
-      case 2:
-        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.enano_date('d M Y h:i a').'\', \'page\', \'semiprot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
-        break;
-      default:
-        return 'PageUtils::protect(): Invalid value for $level';
-        break;
-    }
-    if(!$db->sql_query($q)) $db->_die('The log entry for the page protection could not be inserted.');
-    
-    $q = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=' . $level . ' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-    if ( !$q )
-    {
-      $db->_die('The pages table was not updated.');
-    }
-    
-    return('good');
+    $page = new PageProcessor($page_id, $namespace);
+    return $page->protect_page($level, $reason);
   }
   
   /**
@@ -339,7 +249,9 @@
     
     ob_start();
     
-    $pname = $paths->nslist[$namespace] . $page_id;
+    $pname = $paths->get_pathskey($page_id, $namespace);
+    $ns = namespace_factory($page_id, $namespace);
+    $cdata = $ns->get_cdata();
     
     if ( !isPage($pname) )
     {
@@ -348,15 +260,15 @@
     
     if ( isPage($pname['password']) )
     {
-      $password_exists = ( !empty($paths->pages[$pname]['password']) && $paths->pages[$pname]['password'] !== sha1('') );
-      if ( $password_exists && $password !== $paths->pages[$pname]['password'] )
+      $password_exists = ( !empty($cdata['password']) && $cdata['password'] !== sha1('') );
+      if ( $password_exists && $password !== $cdata['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;
+    $wiki = ( ( $cdata['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $cdata['wiki_mode'] == 1) ? true : false;
+    $prot = ( ( $cdata['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $cdata['protected'] == 1) ? true : false;
     
     $q = 'SELECT log_id,time_id,date_string,page_id,namespace,author,edit_summary,minor_edit FROM ' . table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND is_draft != 1 ORDER BY time_id DESC;';
     if(!$db->sql_query($q)) $db->_die('The history data for the page "' . $paths->cpage['name'] . '" could not be selected.');
@@ -1055,40 +967,8 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    $pname = $paths->nslist[$namespace] . $page_id;
-    
-    $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;
-    $wiki = ( ( $paths->pages[$pname]['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $paths->pages[$pname]['wiki_mode'] == 1) ? true : false;
-    
-    if( empty($name)) 
-    {
-      return($lang->get('ajax_rename_too_short'));
-    }
-    if( ( $session->get_permissions('rename') && ( ( $prot && $session->get_permissions('even_when_protected') ) || !$prot ) ) && ( $paths->namespace != 'Special' && $paths->namespace != 'Admin' ))
-    {
-      $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.enano_date('d M Y h:i a').'\', \'page\', \'rename\', \'' . $db->escape($paths->page_id) . '\', \'' . $paths->namespace . '\', \'' . $db->escape($session->username) . '\', \'' . $db->escape($paths->cpage['name']) . '\')');
-      if ( !$e )
-      {
-        $db->_die('The page title could not be updated.');
-      }
-      $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET name=\'' . $db->escape($name) . '\' WHERE urlname=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\';');
-      if ( !$e )
-      {
-        $db->_die('The page title could not be updated.');
-      }
-      else
-      {
-        $subst = array(
-          'page_name_old' => $paths->pages[$pname]['name'],
-          'page_name_new' => $name
-          );
-        return $lang->get('ajax_rename_success', $subst);
-      }
-    }
-    else
-    {
-      return($lang->get('etc_access_denied'));
-    }
+    $page = new PageProcessor($page_id, $namespace);
+    return $page->rename_page($name);
   }
   
   /**
@@ -1120,7 +1000,7 @@
     
     // If the page exists, make a backup of it in case it gets spammed/vandalized
     // If not, the admin's probably deleting a trash page
-    if ( isset($paths->pages[ $paths->nslist[$namespace] . $page_id ]) )
+    if ( isPage($paths->get_pathskey($page_id, $namespace)) )
     {
       $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
       if(!$e) $db->_die('The current page text could not be selected; as a result, creating the backup of the page failed. Please make a backup copy of the page by clicking Edit this page and then clicking Save Changes.');
@@ -1256,8 +1136,11 @@
       return 'The page does not exist.';
     }
     
-    $cv  =& $paths->pages[$pname]['delvotes'];
-    $ips =  $paths->pages[$pname]['delvote_ips'];
+    $ns = namespace_factory($page_id, $namespace);
+    $cdata = $ns->get_cdata();
+    
+    $cv  =& $cdata['delvotes'];
+    $ips =& $cdata['delvote_ips'];
     
     if ( empty($ips) )
     {
@@ -1424,10 +1307,15 @@
       $cat_current[] = $r;
     }
     $db->free_result();
-    $cat_all = Array();
-    foreach ( $paths->pages as $i => $_ )
+    
+    $cat_all = array();
+    $q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
+    if ( !$q )
+      $db->_die();
+    
+    while ( $row = $db->fetchrow() )
     {
-      if($paths->pages[$i]['namespace']=='Category') $cat_all[] = $paths->pages[$i];
+      $cat_all[] = Namespace_Default::bake_cdata($row);
     }
     
     // Make $cat_all an associative array, like $paths->pages
@@ -1498,12 +1386,17 @@
     if(!$session->get_permissions('edit_cat')) return('Insufficient privileges to change category information');
     
     $page_perms = $session->fetch_page_acl($page_id, $namespace);
-    $page_data =& $paths->pages[$paths->nslist[$namespace].$page_id];
+    $ns = namespace_factory($page_id, $namespace);
+    $page_data = $ns->get_cdata();
     
-    $cat_all = Array();
-    foreach ( $paths->pages as $i => $_ )
+    $cat_all = array();
+    $q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
+    if ( !$q )
+      $db->_die();
+    
+    while ( $row = $db->fetchrow() )
     {
-      if($paths->pages[$i]['namespace']=='Category') $cat_all[] = $paths->pages[$i];
+      $cat_all[] = Namespace_Default::bake_cdata($row);
     }
     
     // Make $cat_all an associative array, like $paths->pages
@@ -1597,13 +1490,16 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang, $cache;
     // Determine permissions
-    if($paths->pages[$paths->nslist[$namespace].$page_id]['password'] != '')
+    $ns = namespace_factory($page_id, $namespace);
+    $cdata = $ns->get_cdata();
+    if ( $cdata['password'] != '' )
       $a = $session->get_permissions('password_reset');
     else
       $a = $session->get_permissions('password_set');
-    if(!$a)
+    if ( !$a )
       return $lang->get('etc_access_denied');
-    if(!isset($pass)) return('Password was not set on URL');
+    if ( !isset($pass) )
+      return('Password was not set on URL');
     $p = $pass;
     if ( !preg_match('#([0-9a-f]){40,40}#', $p) )
     {
@@ -1617,7 +1513,6 @@
     {
       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/paths.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/paths.php	Tue May 05 00:10:26 2009 -0400
@@ -163,30 +163,6 @@
       eval($cmd);
     }
     
-    if ( $page_cache = $cache->fetch('page_meta') )
-    {
-      $this->pages = array_merge($this->pages, $page_cache);
-    }
-    else
-    {
-      $e = $db->sql_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
-                          . '  delvote_ips,wiki_mode,password,page_format FROM '.table_prefix.'pages ORDER BY name;');
-      
-      if( !$e )
-      {
-        $db->_die('The error seems to have occured while selecting the page information. File: includes/paths.php; line: '.__LINE__);
-      }
-      while($r = $db->fetchrow())
-      {
-        $r = $this->calculate_metadata_from_row($r);
-        
-        $this->pages[$r['urlname']] = $r;
-        $this->pages[] =& $this->pages[$r['urlname']];
-      }
-      
-      $this->update_metadata_cache();
-    }
-    $db->free_result();
     if ( defined('ENANO_INTERFACE_INDEX') || defined('ENANO_INTERFACE_AJAX') || defined('IN_ENANO_UPGRADE') )
     {
       $title = $this->parse_url(false);
@@ -391,6 +367,39 @@
     $session->init_permissions();
   }
   
+  /**
+   * Fetch cdata (metadata) for a page.
+   * @param string Page ID
+   * @param string Namespace
+   * @return array
+   */
+  
+  function get_cdata($page_id, $namespace)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    $pathskey = $this->get_pathskey($page_id, $namespace);
+    if ( isset($this->pages[$pathskey]) )
+      return $this->pages[$pathskey];
+    
+    $page = namespace_factory($page_id, $namespace);
+    $cdata = $page->get_cdata();
+    
+    $this->pages[$pathskey] = $cdata;
+    return $cdata;
+  }
+  
+  /**
+   * For a given page ID and namespace, generate a flat string that can be used to access $paths->pages.
+   * @param string Page ID
+   * @param string Namespace
+   */
+  
+  function get_pathskey($page_id, $namespace)
+  {
+    return ( isset($this->nslist[$namespace]) ) ? "{$this->nslist[$namespace]}{$page_id}" : "{$namespace}:{$page_id}";
+  }
+  
   function add_page($flags)
   {
     global $lang;
@@ -404,10 +413,9 @@
     }
     
     $flags['require_admin'] = ( $flags['namespace'] === 'Admin' );
+    $flags['page_exists'] = true;
     
-    $pages_len = sizeof($this->pages) / 2;
-    $this->pages[$pages_len] = $flags;
-    $this->pages[$flags['urlname']] =& $this->pages[$pages_len];
+    $this->pages[$flags['urlname']] = $flags;
   }
   
   function main_page()
@@ -564,7 +572,7 @@
     $title = $this->parse_url(false);
     list(, $ns) = RenderMan::strToPageID($title);
     $title = substr($title, strlen($this->nslist[$ns]));
-    $regex = '/^' . str_replace('/', '\\/', preg_quote($this->nslist[$this->namespace])) . '\\/?/';
+    $regex = '/^' . str_replace('/', '\\/', preg_quote($this->nslist[$ns])) . '\\/?/';
     $title = preg_replace($regex, '', $title);
     $title = explode('/', $title);
     $id = $id + 1;
@@ -604,42 +612,14 @@
   }
   
   /**
-   * Updates the cache containing all page metadata.
+   * Deprecated.
    */
   
   function update_metadata_cache()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
-    if ( getConfig('cache_thumbs') != '1' )
-      return false;
-    
-    $e = $db->sql_unbuffered_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
-                          . '  delvote_ips,wiki_mode,password,page_format FROM '.table_prefix.'pages ORDER BY name;');
-    if ( !$e )
-      $db->_die();
-    
-    $md_array = array();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      $row = $this->calculate_metadata_from_row($row);
-      $md_array[$row['urlname']] = $row;
-    }
-    
-    // import cache functions
-    global $cache;
-    
-    // store data (TTL 20 minutes)
-    try
-    {
-      $cache->store('page_meta', $md_array, 20);
-    }
-    catch ( Exception $e )
-    {
-    }
-    
-    return true;
+    return false;
   }
   
   /**
@@ -650,34 +630,7 @@
   
   function calculate_metadata_from_row($r)
   {
-    $r['urlname_nons'] = $r['urlname'];
-    if ( isset($this->nslist[$r['namespace']]) )
-    {
-      $r['urlname'] = $this->nslist[$r['namespace']] . $r['urlname']; // Applies the User:/File:/etc prefixes to the URL names
-    }
-    else
-    {
-      $ns_char = substr($this->nslist['Special'], -1);
-      $r['urlname'] = $r['namespace'] . $ns_char . $r['urlname'];
-    }
-    
-    if ( $r['delvotes'] == null)
-    {
-      $r['delvotes'] = 0;
-    }
-    if ( $r['protected'] == 0 || $r['protected'] == 1 )
-    {
-      $r['really_protected'] = (int)$r['protected'];
-    }
-    else if ( $r['protected'] == 2 && getConfig('wiki_mode') == '1')
-    {
-      $r['really_protected'] = 1;
-    }
-    else if ( $r['protected'] == 2 && getConfig('wiki_mode') == '0' )
-    {
-      $r['really_protected'] = 0;
-    }
-    return $r;
+    return Namespace_Default::bake_cdata($r);
   }
   
   /**
@@ -838,7 +791,6 @@
       $db->_die();
     
     $sha1_blank = sha1('');
-    $query_func = ( ENANO_DBLAYER == 'MYSQL' ) ? 'mysql_query' : 'pg_query';
     
     //
     // Index $pages_in_batch pages at a time
@@ -1082,65 +1034,6 @@
   }
   
   /**
-   * Creates an instance of the Searcher class, including index info
-   * @return object
-   */
-   
-  function makeSearcher($match_case = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $search = new Searcher();
-    $q = $db->sql_query('SELECT word,page_names FROM '.table_prefix.'search_index;');
-    if(!$q)
-    {
-      echo $db->get_error();
-      return false;
-    }
-    $idx = Array();
-    while($row = $db->fetchrow($q))
-    {
-      $row['word'] = rtrim($row['word'], "\0");
-      $idx[$row['word']] = $row['page_names'];
-    }
-    $db->free_result();
-    $search->index = $idx;
-    if($match_case)
-      $search->match_case = true;
-    return $search;
-  }
-  
-  /**
-   * Creates an associative array filled with the values of all the page titles
-   * @return array
-   */
-   
-  function get_page_titles()
-  {
-    $texts = Array();
-    for ( $i = 0; $i < sizeof($this->pages) / 2; $i++ )
-    {
-      $texts[$this->pages[$i]['urlname']] = $this->pages[$i]['name'];
-    }
-    return $texts;
-  }
-  
-  /**
-   * Creates an instance of the Searcher class, including index info for page titles
-   * @return object
-   */
-   
-  function makeTitleSearcher($match_case = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $search = new Searcher();
-    $texts = $this->get_page_titles();
-    $search->buildIndex($texts);
-    if($match_case)
-      $search->match_case = true;
-    return $search;
-  }
-  
-  /**
    * Returns a list of groups that a given page is a member of.
    * @param string Page ID
    * @param string Namespace
--- a/includes/plugins.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/plugins.php	Tue May 05 00:10:26 2009 -0400
@@ -96,6 +96,7 @@
     {
       if ( in_array($plugin, $this->system_plugins) )
         continue;
+      
       if ( $this->loaded_plugins[$plugin]['status'] & PLUGIN_OUTOFDATE )
       {
         // it's out of date, don't load
--- a/includes/render.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/render.php	Tue May 05 00:10:26 2009 -0400
@@ -55,7 +55,7 @@
   public static function getTemplate($id, $parms)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!isset($paths->pages[$paths->nslist['Template'].$id])) 
+    if ( !isPage($paths->get_pathskey($id, 'Template')) ) 
     {
       return '[['.$paths->nslist['Template'].$id.']]';
     }
@@ -65,7 +65,8 @@
     }
     else
     {
-      $text = RenderMan::getPage($id, 'Template', 0, true, true, 0);
+      $page = new PageProcessor($id, 'Template');
+      $text = $page->fetch_text();
       $paths->template_cache[$id] = $text;
     }
     
@@ -95,7 +96,7 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     $fetch_ns = 'Template';
-    if(!isset($paths->pages[$paths->nslist['Template'].$id])) 
+    if ( !isPage($paths->get_pathskey($id, 'Template')) ) 
     {
       // Transclusion of another page
       // 1.1.5: Now You, Too, Can Be A Template, Even If You're Just A Plain Old Article! (TM)
--- a/includes/search.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/search.php	Tue May 05 00:10:26 2009 -0400
@@ -251,16 +251,15 @@
             $inc = 1;
 
             // Is this search term present in the page's title? If so, give extra points
-            preg_match("/^ns=$ns_list;pid=(.+)$/", $match, $piecesparts);
-            $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
-            if ( isPage($pathskey) )
+            preg_match("/^ns=$ns_list;pid=(.+)$/", $pages, $piecesparts);
+            $title = get_page_title_ns($piecesparts[2], $piecesparts[1]);
+            
+            $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
+            if ( $test_func($title, $row['word']) || $test_func($piecesparts[2], $row['word']) )
             {
-              $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
-              if ( $test_func($paths->pages[$pathskey]['name'], $row['word']) || $test_func($paths->pages[$pathskey]['urlname_nons'], $row['word']) )
-              {
-                $inc = 1.5;
-              }
+              $inc = 1.5;
             }
+          
             if ( isset($scores[$match]) )
             {
               $scores[$match] = $scores[$match] + $inc;
@@ -392,20 +391,15 @@
         $inc = 1;
 
         // Is this search term present in the page's title? If so, give extra points
-        preg_match("/^ns=$ns_list;pid=(.+)$/", $id, $piecesparts);
-        $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
-        if ( isPage($pathskey) )
+        preg_match("/^ns=$ns_list;pid=(.+)$/", $pages, $piecesparts);
+        $title = get_page_title_ns($piecesparts[2], $piecesparts[1]);
+        
+        $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
+        if ( $test_func($title, $row['word']) || $test_func($piecesparts[2], $row['word']) )
         {
-          $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
-          foreach ( array_merge($query_phrase['any'], $query_phrase['req']) as $term )
-          {
-            if ( $test_func($paths->pages[$pathskey]['name'], $term) || $test_func($paths->pages[$pathskey]['urlname_nons'], $term) )
-            {
-              $inc = 1.5;
-              break;
-            }
-          }
+          $inc = 1.5;
         }
+        
         if ( isset($scores[$id]) )
         {
           $scores[$id] = $scores[$id] + $inc;
--- a/includes/sessions.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/sessions.php	Tue May 05 00:10:26 2009 -0400
@@ -670,7 +670,7 @@
           $captcha_good = true;
         }
       }
-      if ( $policy != 'disable' && !$captcha_good )
+      if ( $lockout_data['lockout_policy'] != 'disable' && !$captcha_good )
       {
         if ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] )
         {
@@ -2929,12 +2929,6 @@
       return $objcache[$namespace][$page_id];
     }
     
-    //if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
-    //{
-    //  // Page does not exist
-    //  return false;
-    //}
-    
     $objcache[$namespace][$page_id] = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
     $object =& $objcache[$namespace][$page_id];
     
@@ -4384,12 +4378,11 @@
     $this->namespace = $namespace;
     
     $pathskey = $paths->nslist[$this->namespace].sanitize_page_id($this->page_id);
-    $ppwm = 2;
-    if ( isset($paths->pages[$pathskey]) )
-    {
-      if ( isset($paths->pages[$pathskey]['wiki_mode']) )
-        $ppwm = $paths->pages[$pathskey]['wiki_mode'];
-    }
+    $ns = namespace_factory($this->page_id, $this->namespace);
+    $cdata = $ns->get_cdata();
+    $ppwm = $cdata['wiki_mode'];
+    unset($ns, $cdata);
+    
     if ( $ppwm == 1 && ( $session->user_logged_in || getConfig('wiki_mode_require_login') != '1' ) )
       $this->wiki_mode = true;
     else if ( $ppwm == 1 && !$session->user_logged_in && getConfig('wiki_mode_require_login') == '1' )
--- a/includes/stats.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/stats.php	Tue May 05 00:10:26 2009 -0400
@@ -65,25 +65,9 @@
   
   while ( $row = $db->fetchrow() )
   {
-    if ( isset($paths->pages[ $paths->nslist[ $row['namespace'] ] . $row['page_id'] ]) )
-    {
-      $page_data =& $paths->pages[ $paths->nslist[ $row['namespace'] ] . $row['page_id'] ];
-      $title = $page_data['name'];
-      $page_id = $page_data['urlname'];
-    }
-    else if ( !isset($paths->nslist[$row['namespace']]) )
-    {
-      $title = $row['namespace'] . ':' . $row['page_id'];
-      $page_id = sanitize_page_id($title);
-    }
-    else
-    {
-      $title = dirtify_page_id( $paths->nslist[$row['namespace']] . $row['page_id'] );
-      $title = str_replace('_', ' ', $title);
-      $page_id = sanitize_page_id($title);
-    }
+    $title = get_page_title_ns($row['page_id'], $row['namespace']);
     $data[] = array(
-        'page_urlname' => $page_id,
+        'page_urlname' => $paths->get_pathskey($row['page_id'], $row['namespace']),
         'page_title' => $title,
         'num_hits' => $row['num_hits']
       );
--- a/includes/template.php	Mon May 04 23:07:00 2009 -0400
+++ b/includes/template.php	Tue May 05 00:10:26 2009 -0400
@@ -14,12 +14,27 @@
  
 class template
 {
-  var $tpl_strings, $tpl_bool, $vars_assign_history, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list, $named_theme_list, $default_theme, $default_style, $plugin_blocks, $plugin_blocks_content, $namespace_string, $style_list, $theme_loaded, $initted_to_page_id, $initted_to_namespace;
+  var $tpl_strings, $tpl_bool, $vars_assign_history, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list, $named_theme_list, $default_theme, $default_style, $plugin_blocks, $plugin_blocks_content, $namespace_string, $style_list, $theme_loaded;
+  
+  var $theme_initted = false;
+  var $page_initted = false;
+  var $elements = false;
+  var $page_id = false;
+  var $namespace = false;
   
-  var $initted_to_theme = array(
-      'theme' => false,
-      'style' => false
-    );
+  /**
+   * Page action conditions
+   * @var array
+   */
+  
+  var $conds = array();
+  
+  /**
+   * The PageProcessor for the current page
+   * @var object
+   */
+  
+  var $page = false;
   
   /**
    * The list of themes that are critical for Enano operation. This doesn't include oxygen which
@@ -287,7 +302,7 @@
   function sidebar_widget($t, $h, $use_normal_section = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!defined('ENANO_TEMPLATE_LOADED'))
+    if ( !$this->theme_loaded )
     {
       $this->load_theme($session->theme, $session->style);
     }
@@ -365,141 +380,65 @@
   }
   
   /**
-   * Initializes all variables related to on-page content. This includes sidebars and what have you.
-   * @param object Optional PageProcessor object to use for passing metadata and permissions on. If omitted, uses information from $paths and $session.
-   * @param bool If true, re-inits even if already initted with this page_id and namespace
+   * Change the theme we're supposed to display.
+   * @param string Theme name
+   * @param string Style name; optional
+   */
+  
+  function set_theme($theme = false, $style = false)
+  {
+    $this->theme_initted = false;
+    $this->load_theme($theme, $style);
+  }
+  
+  /**
+   * Change the page we're supposed to generate for
+   * @param mixed Page ID *or* PageProcessor. If a PageProcessor, pulls permission info and such from that; if not, starts a PageProcessor. YOU SHOULD USE A PageProcessor WHENEVER POSSIBLE! It improves efficiency.
+   * @param string Namespace; not required if including a PageProcessor.
    */
   
-  function init_vars($page = false, $force_init = false)
+  function set_page($page_id_or_pp, $namespace = false)
   {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $email;
-    global $lang;
-    
-    if(!$this->theme || !$this->style)
+    if ( is_object($page_id_or_pp) && get_class($page_id_or_pp) === 'PageProcessor' )
     {
-      $this->load_theme();
-    }
-    
-    if ( defined('ENANO_TEMPLATE_LOADED') )
-    {
-      // trigger_error("\$template->init_vars() called more than once", E_USER_WARNING);
-      // die_semicritical('Illegal call', '<p>$template->init_vars() was called multiple times, this is not supposed to happen. Exiting with fatal error.</p>');
-    }
-    else
-    {
-      @define('ENANO_TEMPLATE_LOADED', '');
+      $this->page_initted = false;
+      $page =& $page_id_or_pp;
+      $this->page = $page;
+      $this->page_id = $page->page_id;
+      $this->namespace = $page->namespace;
     }
-    
-    if ( is_object($page) && ( @get_class($page) == 'PageProcessor' || preg_match('/^Namespace_/', @get_class($page)) ) )
+    else if ( is_string($page_id_or_pp) )
     {
-      $page_append = substr($paths->fullpage, strlen($paths->page));
-      if ( isset($paths->nslist[$page->namespace]) )
-      {
-        $local_page = $paths->nslist[$page->namespace] . $page->page_id;
-      }
-      else
-      {
-        $local_page = $page->namespace . substr($paths->nslist['Special'], -1) . $page->page_id . $page_append;
-      }
-      $local_fullpage = $local_page . $page_append;
-      $local_page_id =& $page->page_id;
-      $local_namespace =& $page->namespace;
-      $local_page_exists = $page->exists();
-      $perms =& $page->perms;
-      if ( !is_object($perms) )
-      {
-        unset($perms);
-        $perms = $session->fetch_page_acl($local_page_id, $local_namespace);
-      }
+      if ( !is_string($namespace) )
+        return false;
+      
+      if ( $page_id_or_pp === $this->page_id && $namespace === $this->namespace )
+        return true;
+      
+      $this->page_initted = false;
+      $this->page = false;
+      $this->page_id = sanitize_page_id($page_id_or_pp);
+      $this->namespace = $namespace;
     }
     else
     {
-      $local_page =& $paths->page;
-      $local_page_id =& $paths->page_id;
-      $local_fullpage =& $paths->fullpage;
-      $local_namespace =& $paths->namespace;
-      $local_page_exists =& $paths->page_exists;
-      $local_page_protected =& $paths->page_protected;
-      $perms =& $session;
-    }
-    
-    if ( $local_page_id === $this->initted_to_page_id && $local_namespace === $this->initted_to_namespace && $this->theme === $this->initted_to_theme['theme'] && $this->style === $this->initted_to_theme['style'] && !$force_init )
-    {
-      // we're already initted with this page.
-      return true;
-    }
-    
-    profiler_log("template: starting var init");
-    
-    $this->initted_to_page_id = $local_page_id;
-    $this->initted_to_namespace = $local_namespace;
-    $this->initted_to_theme = array(
-        'theme' => $this->theme,
-        'style' => $this->style
-      );
-    
-    require(ENANO_ROOT . "/themes/{$this->theme}/theme.cfg");
-    
-    if ( $local_page_exists && isPage($local_page) )
-    {
-      $local_cdata =& $paths->pages[$local_page];
+      return false;
     }
-    else
-    {
-      // if the page doesn't exist but we're trying to load it, it was requested manually and $paths->cpage should match it.
-      if ( $paths->page_id == $local_page_id )
-      {
-        // load metadata from cpage
-        $local_cdata =& $paths->cpage;
-      }
-      else
-      {
-        // generate our own failsafe metadata
-        $local_cdata = array(
-            'urlname' => $local_page,
-            'urlname_nons' => $local_page_id,
-            'namespace' => $local_namespace,
-            'name' => get_page_title_ns($local_page_id, $local_namespace),
-            'comments_on' => 0,
-            'protected' => 0,
-            'wiki_mode' => 2,
-            'delvotes' => 0,
-            'delvote_ips' => serialize(array())
-          );
-      }
-    }
+    return true;
+  }
+  
+  /**
+   * Global, only-called-once init. Goes to all themes.
+   */
+  
+  function init_global_vars()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    global $email;
     
-    // calculate protection
-    if ( !isset($local_page_protected) )
-    {
-      if ( $local_cdata['protected'] == 0 )
-      {
-        $local_page_protected = false;
-      }
-      else if ( $local_cdata['protected'] == 1 )
-      {
-        $local_page_protected = true;
-      }
-      else if ( $local_cdata['protected'] == 2 )
-      {
-        if (
-             ( !$session->user_logged_in || // Is the user logged in?
-               ( $session->user_logged_in && $session->reg_time + ( 4 * 86400 ) >= time() ) ) // If so, have they been registered for 4 days?
-             && !$perms->get_permissions('even_when_protected') ) // And of course, is there an ACL that overrides semi-protection?
-        {
-          $local_page_protected = true;
-        }
-        else
-        {
-          $local_page_protected = false;
-        }
-      }
-    }
-    
-    $tplvars = $this->extract_vars('elements.tpl');
-    
-    if(isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE'))
+    // IE PNG fixing code
+    if ( isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') )
     {
       $this->add_header('
         <!--[if lt IE 7]>
@@ -537,414 +476,15 @@
         ');
     }
     
-    // Get the "article" button text (depends on namespace)
-    switch($local_namespace) {
-      case "Article":
-      default:
-        $ns = $lang->get('onpage_lbl_page_article');
-        break;
-      case "Admin":
-        $ns = $lang->get('onpage_lbl_page_admin');
-        break;
-      case "System":
-        $ns = $lang->get('onpage_lbl_page_system');
-        break;
-      case "File":
-        $ns = $lang->get('onpage_lbl_page_file');
-        break;
-      case "Help":
-        $ns = $lang->get('onpage_lbl_page_help');
-        break;
-      case "User":
-        $ns = $lang->get('onpage_lbl_page_user');
-        break;
-      case "Special":
-        $ns = $lang->get('onpage_lbl_page_special');
-        break;
-      case "Template":
-        $ns = $lang->get('onpage_lbl_page_template');
-        break;
-      case "Project":
-        $ns = $lang->get('onpage_lbl_page_project');
-        break;
-      case "Category":
-        $ns = $lang->get('onpage_lbl_page_category');
-        break;
-      case "API":
-        $ns = $lang->get('onpage_lbl_page_external');
-        break;
-    }
-    $this->namespace_string = $ns;
-    unset($ns);
-    $code = $plugins->setHook('page_type_string_set');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    $ns =& $this->namespace_string;
-    
-    //
-    // PAGE TOOLBAR (on-page controls/actions)
-    //
-    
-    // Initialize the toolbar
-    $tb = '';
-    $this->toolbar_menu = '';
-    
-    // Create "xx page" button
-    
-    $btn_selected = ( isset($tplvars['toolbar_button_selected'])) ? $tplvars['toolbar_button_selected'] : $tplvars['toolbar_button'];
-    $parser = $this->makeParserText($btn_selected);
-    
-    $parser->assign_vars(array(
-        'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxReset()); return false; }" title="' . $lang->get('onpage_tip_article') . '" accesskey="a"',
-        'PARENTFLAGS' => 'id="mdgToolbar_article"',
-        'HREF' => makeUrl($local_page, null, true),
-        'TEXT' => $this->namespace_string
-      ));
-    
-    $tb .= $parser->run();
-    
-    $button = $this->makeParserText($tplvars['toolbar_button']);
-    
-    // Page toolbar
-    // Comments button
-    if ( $perms->get_permissions('read') && getConfig('enable_comments', '1')=='1' && $local_cdata['comments_on'] == 1 )
-    {
-      $e = $db->sql_query('SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$local_page_id.'\' AND namespace=\''.$local_namespace.'\';');
-      if ( !$e )
-      {
-        $db->_die();
-      }
-      $num_comments = $db->numrows();
-      $approval_counts = array(COMMENT_UNAPPROVED => 0, COMMENT_APPROVED => 0, COMMENT_SPAM => 0);
-      
-      while ( $r = $db->fetchrow() )
-      {  
-        $approval_counts[$r['approved']]++;
-      }
-      
-      $db->free_result();
-      // $n = ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') ) ? (string)$num_comments : (string)$na;
-      if ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') && ( $approval_counts[COMMENT_UNAPPROVED] + $approval_counts[COMMENT_SPAM] ) > 0 )
-      {
-        $subst = array(
-            'num_comments' => $num_comments,
-            'num_app' => $approval_counts[COMMENT_APPROVED],
-            'num_unapp' => $approval_counts[COMMENT_UNAPPROVED],
-            'num_spam' => $approval_counts[COMMENT_SPAM]
-          );
-        $btn_text = $lang->get('onpage_btn_discussion_unapp', $subst);
-      }
-      else
-      {
-        $subst = array(
-          'num_comments' => $num_comments
-        );
-        $btn_text = $lang->get('onpage_btn_discussion', $subst);
-      }
-      
-      $button->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxComments()); return false; }" title="' . $lang->get('onpage_tip_comments') . '" accesskey="c"',
-          'PARENTFLAGS' => 'id="mdgToolbar_discussion"',
-          'HREF' => makeUrl($local_page, 'do=comments', true),
-          'TEXT' => $btn_text,
-        ));
-      
-      $tb .= $button->run();
-    }
-    // Edit button
-    if($perms->get_permissions('read') && $session->check_acl_scope('edit_page', $local_namespace) && ( $perms->get_permissions('edit_page') && ( ( $paths->page_protected && $perms->get_permissions('even_when_protected') ) || !$paths->page_protected ) ) )
-    {
-      $button->assign_vars(array(
-        'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_edit') . '" accesskey="e"',
-        'PARENTFLAGS' => 'id="mdgToolbar_edit"',
-        'HREF' => makeUrl($local_page, 'do=edit', true),
-        'TEXT' => $lang->get('onpage_btn_edit')
-        ));
-      $tb .= $button->run();
-    // View source button
-    }
-    else if ( $session->check_acl_scope('view_source', $local_namespace) && $perms->get_permissions('view_source') && ( !$perms->get_permissions('edit_page') || !$perms->get_permissions('even_when_protected') && $paths->page_protected ) && $local_namespace != 'API') 
-    {
-      $button->assign_vars(array(
-        'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_viewsource') . '" accesskey="e"',
-        'PARENTFLAGS' => 'id="mdgToolbar_edit"',
-        'HREF' => makeUrl($local_page, 'do=viewsource', true),
-        'TEXT' => $lang->get('onpage_btn_viewsource')
-        ));
-      $tb .= $button->run();
-    }
-    // History button
-    if ( $perms->get_permissions('read') && $session->check_acl_scope('history_view', $local_namespace) && $local_page_exists && $perms->get_permissions('history_view') )
-    {
-      $button->assign_vars(array(
-        'FLAGS'       => 'onclick="if ( !KILL_SWITCH ) { void(ajaxHistory()); return false; }" title="' . $lang->get('onpage_tip_history') . '" accesskey="h"',
-        'PARENTFLAGS' => 'id="mdgToolbar_history"',
-        'HREF'        => makeUrl($local_page, 'do=history', true),
-        'TEXT'        => $lang->get('onpage_btn_history')
-        ));
-      $tb .= $button->run();
-    }
-    
-    $menubtn = $this->makeParserText($tplvars['toolbar_menu_button']);
-    
-    // Additional actions menu
-    // Rename button
-    if ( $perms->get_permissions('read') && $session->check_acl_scope('rename', $local_namespace) && $local_page_exists && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ) )
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxRename()); return false; }" title="' . $lang->get('onpage_tip_rename') . '" accesskey="r"',
-          'HREF'  => makeUrl($local_page, 'do=rename', true),
-          'TEXT'  => $lang->get('onpage_btn_rename'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Vote-to-delete button
-    if ( $paths->wiki_mode && $session->check_acl_scope('vote_delete', $local_namespace) && $perms->get_permissions('vote_delete') && $local_page_exists)
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxDelVote()); return false; }" title="' . $lang->get('onpage_tip_delvote') . '" accesskey="d"',
-          'HREF'  => makeUrl($local_page, 'do=delvote', true),
-          'TEXT'  => $lang->get('onpage_btn_votedelete'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Clear-votes button
-    if ( $perms->get_permissions('read') && $session->check_acl_scope('vote_reset', $local_namespace) && $paths->wiki_mode && $local_page_exists && $perms->get_permissions('vote_reset') && $local_cdata['delvotes'] > 0)
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxResetDelVotes()); return false; }" title="' . $lang->get('onpage_tip_resetvotes') . '" accesskey="y"',
-          'HREF'  => makeUrl($local_page, 'do=resetvotes', true),
-          'TEXT'  => $lang->get('onpage_btn_votedelete_reset'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Printable page button
-    if ( $local_page_exists )
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'title="' . $lang->get('onpage_tip_printable') . '"',
-          'HREF'  => makeUrl($local_page, 'printable=yes', true),
-          'TEXT'  => $lang->get('onpage_btn_printable'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Protect button
-    if($perms->get_permissions('read') && $session->check_acl_scope('protect', $local_namespace) && $paths->wiki_mode && $local_page_exists && $perms->get_permissions('protect'))
-    {
-      switch($local_cdata['protected'])
-      {
-        case PROTECT_FULL: $protect_status = $lang->get('onpage_btn_protect_on'); break;
-        case PROTECT_SEMI: $protect_status = $lang->get('onpage_btn_protect_semi'); break;
-        case PROTECT_NONE: $protect_status = $lang->get('onpage_btn_protect_off'); break;
-      }
-      
-      $label = $this->makeParserText($tplvars['toolbar_label']);
-      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_protect') . ' ' . "<b><span id=\"tb_ajax_protect_status\">$protect_status</span></b>"));
-      $t0 = $label->run();
-      
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'accesskey="p" onclick="ajaxProtect(' . $local_cdata['protected'] . '); return false;" id="tb_ajax_protect_btn" title="' . $lang->get('onpage_tip_protect') . '"',
-          'HREF' => makeUrl($local_page, 'do=protect', true),
-          'TEXT' => $lang->get('onpage_btn_protect_change')
-        ));
-      $t1 = $menubtn->run();
-      
-      $this->toolbar_menu .= '        <table border="0" cellspacing="0" cellpadding="0">
-          <tr>
-            <td>'.$t0.'</td>
-            <td>'.$t1.'</td>
-          </tr>
-        </table>';
-    }
-    
-    // Wiki mode button
-    if($perms->get_permissions('read') && $session->check_acl_scope('set_wiki_mode', $local_namespace) && $local_page_exists && $perms->get_permissions('set_wiki_mode'))
-    {
-      // label at start
-      $label = $this->makeParserText($tplvars['toolbar_label']);
-      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_wikimode')));
-      $t0 = $label->run();
-      
-      // on button
-      $ctmp = '';
-      if ( $local_cdata['wiki_mode'] == 1 )
-      {
-        $ctmp = ' style="text-decoration: underline;"';
-      }
-      $menubtn->assign_vars(array(
-          'FLAGS' => /* 'onclick="if ( !KILL_SWITCH ) { ajaxSetWikiMode(1); return false; }" id="wikibtn_1" title="Forces wiki functions to be allowed on this page."'. */ $ctmp,
-          'HREF' => makeUrl($local_page, 'do=setwikimode&level=1', true),
-          'TEXT' => $lang->get('onpage_btn_wikimode_on')
-        ));
-      $t1 = $menubtn->run();
-      
-      // off button
-      $ctmp = '';
-      if ( $local_cdata['wiki_mode'] == 0 )
-      {
-        $ctmp=' style="text-decoration: underline;"';
-      }
-      $menubtn->assign_vars(array(
-          'FLAGS' => /* 'onclick="if ( !KILL_SWITCH ) { ajaxSetWikiMode(0); return false; }" id="wikibtn_0" title="Forces wiki functions to be disabled on this page."'. */ $ctmp,
-          'HREF' => makeUrl($local_page, 'do=setwikimode&level=0', true),
-          'TEXT' => $lang->get('onpage_btn_wikimode_off')
-        ));
-      $t2 = $menubtn->run();
-      
-      // global button
-      $ctmp = ''; 
-      if ( $local_cdata['wiki_mode'] == 2 )
-      {
-        $ctmp=' style="text-decoration: underline;"';
-      }
-      $menubtn->assign_vars(array(
-          'FLAGS' => /* 'onclick="if ( !KILL_SWITCH ) { ajaxSetWikiMode(2); return false; }" id="wikibtn_2" title="Causes this page to use the global wiki mode setting (default)"'. */ $ctmp,
-          'HREF' => makeUrl($local_page, 'do=setwikimode&level=2', true),
-          'TEXT' => $lang->get('onpage_btn_wikimode_global')
-        ));
-      $t3 = $menubtn->run();
-      
-      // Tack it onto the list of buttons that are already there...
-      $this->toolbar_menu .= '        <table border="0" cellspacing="0" cellpadding="0">
-          <tr>
-            <td>'.$t0.'</td>
-            <td>'.$t1.'</td>
-            <td>'.$t2.'</td>
-            <td>'.$t3.'</td>
-          </tr>
-        </table>';
-    }
-    
-    // Clear logs button
-    if ( $perms->get_permissions('read') && $session->check_acl_scope('clear_logs', $local_namespace) && $perms->get_permissions('clear_logs') )
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxClearLogs()); return false; }" title="' . $lang->get('onpage_tip_flushlogs') . '" accesskey="l"',
-          'HREF'  => makeUrl($local_page, 'do=flushlogs', true),
-          'TEXT'  => $lang->get('onpage_btn_clearlogs'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Delete page button
-    if ( $perms->get_permissions('read') && $session->check_acl_scope('delete_page', $local_namespace) && $perms->get_permissions('delete_page') && $local_page_exists )
-    {
-      $s = $lang->get('onpage_btn_deletepage');
-      if ( $local_cdata['delvotes'] == 1 )
-      {
-        $subst = array(
-          'num_votes' => $local_cdata['delvotes'],
-          'plural' => ''
-          );
-        $s .= $lang->get('onpage_btn_deletepage_votes', $subst);
-      }
-      else if ( $local_cdata['delvotes'] > 1 )
-      {
-        $subst = array(
-          'num_votes' => $local_cdata['delvotes'],
-          'plural' => $lang->get('meta_plural')
-          );
-        $s .= $lang->get('onpage_btn_deletepage_votes', $subst);
-      }
-      
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxDeletePage()); return false; }" title="' . $lang->get('onpage_tip_deletepage') . '" accesskey="k"',
-          'HREF'  => makeUrl($local_page, 'do=deletepage', true),
-          'TEXT'  => $s,
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-      
-    }
-    
-    // Password-protect button
-    if(isset($local_cdata['password']) && $session->check_acl_scope('password_set', $local_namespace) && $session->check_acl_scope('password_reset', $local_namespace))
-    {
-      if ( $local_cdata['password'] == '' )
-      {
-        $a = $perms->get_permissions('password_set');
-      }
-      else
-      {
-        $a = $perms->get_permissions('password_reset');
-      }
-    }
-    else if ( $session->check_acl_scope('password_set', $local_namespace) )
-    {
-      $a = $perms->get_permissions('password_set');
-    }
-    else
-    {
-      $a = false;
-    }
-    if ( $a && $perms->get_permissions('read') && $local_page_exists )
-    {
-      // label at start
-      $label = $this->makeParserText($tplvars['toolbar_label']);
-      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_password')));
-      $t0 = $label->run();
-      
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxSetPassword()); return false; }" title="' . $lang->get('onpage_tip_password') . '"',
-          'HREF'  => '#',
-          'TEXT'  => $lang->get('onpage_btn_password_set'),
-        ));
-      $t = $menubtn->run();
-      
-      $this->toolbar_menu .= '<table border="0" cellspacing="0" cellpadding="0"><tr><td>'.$t0.'</td><td><input type="password" id="mdgPassSetField" size="10" /></td><td>'.$t.'</td></tr></table>';
-    }
-    
-    // Manage ACLs button
-    if ( !$paths->external_api_page && $session->check_acl_scope('edit_acl', $local_namespace) && ( $perms->get_permissions('edit_acl') || ( defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL') &&  $session->user_level >= USER_LEVEL_ADMIN ) ) )
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { var s = ajaxOpenACLManager(); console.debug(s); return false; }" title="' . $lang->get('onpage_tip_aclmanager') . '" accesskey="m"',
-          'HREF'  => makeUrl($local_page, 'do=aclmanager', true),
-          'TEXT'  => $lang->get('onpage_btn_acl'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    // Administer page button
-    if ( $session->user_level >= USER_LEVEL_ADMIN && $local_page_exists )
-    {
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxAdminPage()); return false; }" title="' . $lang->get('onpage_tip_adminoptions') . '" accesskey="g"',
-          'HREF'  => makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'PageManager', true),
-          'TEXT'  => $lang->get('onpage_btn_admin'),
-        ));
-      $this->toolbar_menu .= $menubtn->run();
-    }
-    
-    if ( strlen($this->toolbar_menu) > 0 )
-    {
-      $button->assign_vars(array(
-        'FLAGS'       => 'id="mdgToolbar_moreoptions" onclick="if ( !KILL_SWITCH ) { return false; }" title="' . $lang->get('onpage_tip_moreoptions') . '"',
-        'PARENTFLAGS' => '',
-        'HREF'        => makeUrl($local_page, 'do=moreoptions', true),
-        'TEXT'        => $lang->get('onpage_btn_moreoptions')
-        ));
-      $tb .= $button->run();
-    }
-    
-    //
-    // OTHER SWITCHES
-    //
-    
     $is_opera = (isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'Opera')) ? true : false;
     $is_msie = (isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ? true : false;
     
-    $this->tpl_bool = Array(
-      'auth_admin' => $session->user_level >= USER_LEVEL_ADMIN ? true : false,
-      'user_logged_in' => $session->user_logged_in,
-      'opera' => $is_opera,
-      'msie' => $is_msie
-      );
+    $this->assign_bool(array(
+        'auth_admin' => $session->user_level >= USER_LEVEL_ADMIN ? true : false,
+        'user_logged_in' => $session->user_logged_in,
+        'opera' => $is_opera,
+        'msie' => $is_msie
+      ));
     
     if ( $session->sid_super )
     {
@@ -1009,47 +549,510 @@
 JSEOF;
     }
     
-    $code = $plugins->setHook('compile_template');
+    $this->assign_bool(array(
+        'fixed_menus' => false,
+        'export' => false,
+        'right_sidebar' => true,
+        'enable_uploads' => ( getConfig('enable_uploads') == '1' && $session->get_permissions('upload_files') ) ? true : false,
+        'stupid_mode' => false,
+      ));
+    
+    // Add the e-mail address client code to the header
+    $this->add_header($email->jscode());
+    
+    // Assign our main variables
+    $this->assign_vars(array(
+        'SITE_NAME' => htmlspecialchars(getConfig('site_name')),
+        'USERNAME' => $session->username,
+        'SITE_DESC' => htmlspecialchars(getConfig('site_desc')),
+        'SCRIPTPATH' => scriptPath,
+        'CONTENTPATH' => contentPath,
+        'CDNPATH' => cdnPath,
+        'ADMIN_SID_QUES' => $asq,
+        'ADMIN_SID_AMP' => $asa,
+        'ADMIN_SID_AMP_HTML' => $ash,
+        'ADMIN_SID_AUTO' => $as2,
+        'ADMIN_SID_RAW' =>  ( is_string($session->sid_super) ? $session->sid_super : '' ),
+        'CSRF_TOKEN' => $session->csrf_token,
+        'COPYRIGHT' => RenderMan::parse_internal_links(getConfig('copyright_notice')),
+        'REQUEST_URI' => ( defined('ENANO_CLI') ? '' : $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] ),
+        'SEARCH_ACTION' => makeUrlNS('Special', 'Search'),
+        'INPUT_TITLE' => ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->get_pathskey($this->page_id, $this->namespace) ) . '" />' : ''),
+        'INPUT_AUTH' => ( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : ''),
+        'MAIN_PAGE' => get_main_page(),
+        'UNREAD_PMS' => $session->unread_pms,
+        'JS_HEADER' => $js_head,
+        'JS_FOOTER' => $js_foot,
+        'URL_ABOUT_ENANO' => makeUrlNS('Special', 'About_Enano', '', true),
+      ), true);
+    
+    $tpl_strings = array();
+    foreach ( $paths->nslist as $ns_id => $ns_prefix )
+    {
+      $tpl_strings[ 'NS_' . strtoupper($ns_id) ] = $ns_prefix;
+    }
+    
+    $this->assign_vars($tpl_strings, true);
+  }
+  
+  /**
+   * Init theme vars, like sidebar, global JS, that kind of stuff.
+   */
+  
+  function init_theme_vars()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    // allows conditional testing of the theme ID (a bit crude, came from my NSIS days)
+    $this->assign_bool(array(
+        "theme_is_{$this->theme}" => true
+      ));
+    
+    $this->elements = $this->extract_vars('elements.tpl');
+    
+    // Generate the code for the Administration and Edit Sidebar buttons
+    // Once again, the new template parsing system can be used here
+    
+    $parser = $this->makeParserText($this->elements['sidebar_button']);
+    
+    $parser->assign_vars(Array(
+        'HREF'=>makeUrlNS('Special', 'Administration'),
+        'FLAGS'=>'onclick="if ( !KILL_SWITCH ) { void(ajaxStartAdminLogin()); return false; }"',
+        'TEXT'=>$lang->get('sidebar_btn_administration'),
+      ));
+    
+    $admin_link = $parser->run();
+    
+    $parser->assign_vars(Array(
+        'HREF'=>makeUrlNS('Special', 'EditSidebar'),
+        'FLAGS'=>'onclick="if ( !KILL_SWITCH ) { void(ajaxLoginNavTo(\'Special\', \'EditSidebar\', ' . USER_LEVEL_ADMIN . ')); return false; }"',
+        'TEXT'=>$lang->get('sidebar_btn_editsidebar'),
+      ));
+    
+    $sidebar_link = $parser->run();
+    
+    $this->assign_vars(array(
+        'ADMIN_LINK' => $admin_link,
+        'SIDEBAR_LINK' => $sidebar_link,
+        'THEME_ID' => $this->theme,
+        'STYLE_ID' => $this->style
+      ));
+    
+    $this->theme_initted = true;
+  }
+  
+  /**
+   * Init page vars, like the toolbar, local JS, etc.
+   */
+  
+  function init_page_vars()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    if ( !$this->page )
+    {
+      $this->page = new PageProcessor($this->page_id, $this->namespace);
+    }
+    
+    $conds = $this->page->ns->get_conds();
+    
+    $this->assign_bool(array(
+        'in_admin' => ( ( $this->page_id == 'Administration' && $this->namespace == 'Special' ) || $this->namespace == 'Admin' ),
+        'auth_rename' => ( $conds['rename'] )
+      ));
+    
+    // Get the "article" button text (depends on namespace)
+    switch ( $this->namespace )
+    {
+      case "Article":
+      default:
+        $ns = $lang->get('onpage_lbl_page_article');
+        break;
+      case "Admin":
+        $ns = $lang->get('onpage_lbl_page_admin');
+        break;
+      case "System":
+        $ns = $lang->get('onpage_lbl_page_system');
+        break;
+      case "File":
+        $ns = $lang->get('onpage_lbl_page_file');
+        break;
+      case "Help":
+        $ns = $lang->get('onpage_lbl_page_help');
+        break;
+      case "User":
+        $ns = $lang->get('onpage_lbl_page_user');
+        break;
+      case "Special":
+        $ns = $lang->get('onpage_lbl_page_special');
+        break;
+      case "Template":
+        $ns = $lang->get('onpage_lbl_page_template');
+        break;
+      case "Project":
+        $ns = $lang->get('onpage_lbl_page_project');
+        break;
+      case "Category":
+        $ns = $lang->get('onpage_lbl_page_category');
+        break;
+      case "API":
+        $ns = $lang->get('onpage_lbl_page_external');
+        break;
+    }
+    $this->namespace_string = $ns;
+    unset($ns);
+    // compatibility
+    $local_namespace =& $this->namespace;
+    $local_page_id =& $this->page_id;
+    $code = $plugins->setHook('page_type_string_set');
     foreach ( $code as $cmd )
     {
       eval($cmd);
     }
+    $ns =& $this->namespace_string;
     
-    // Some additional sidebar processing
-    if ( $this->sidebar_extra != '' )
+    //
+    // PAGE TOOLBAR (on-page controls/actions)
+    //
+    
+    $local_page = $paths->get_pathskey($this->page_id, $this->namespace);
+    $local_cdata = $this->page->ns->get_cdata();
+    
+    // Initialize the toolbar
+    $tb = '';
+    $this->toolbar_menu = '';
+    
+    // Create "xx page" button
+    
+    $btn_selected = ( isset($this->elements['toolbar_button_selected'])) ? $this->elements['toolbar_button_selected'] : $this->elements['toolbar_button'];
+    $parser = $this->makeParserText($btn_selected);
+    
+    if ( $conds['article'] )
+    {
+      $parser->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxReset()); return false; }" title="' . $lang->get('onpage_tip_article') . '" accesskey="a"',
+          'PARENTFLAGS' => 'id="mdgToolbar_article"',
+          'HREF' => makeUrl($local_page, null, true),
+          'TEXT' => $this->namespace_string
+        ));
+      
+      $tb .= $parser->run();
+    }
+    
+    $button = $this->makeParserText($this->elements['toolbar_button']);
+    
+    // Page toolbar
+    // Comments button
+    if ( $conds['comments'] )
     {
-      $se = $this->sidebar_extra;
-      $parser = $this->makeParserText($tplvars['sidebar_section_raw']);
-      $parser->assign_vars(array(
-          'TITLE' => 'Links', // FIXME: l10n
-          'CONTENT' => $se
+      $e = $db->sql_query('SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$this->page_id.'\' AND namespace=\''.$this->namespace.'\';');
+      if ( !$e )
+      {
+        $db->_die();
+      }
+      $num_comments = $db->numrows();
+      $approval_counts = array(COMMENT_UNAPPROVED => 0, COMMENT_APPROVED => 0, COMMENT_SPAM => 0);
+      
+      while ( $r = $db->fetchrow() )
+      {  
+        $approval_counts[$r['approved']]++;
+      }
+      
+      $db->free_result();
+      // $n = ( $session->check_acl_scope('mod_comments', $this->namespace) && $perms->get_permissions('mod_comments') ) ? (string)$num_comments : (string)$na;
+      if ( $session->check_acl_scope('mod_comments', $this->namespace) && $this->page->perms->get_permissions('mod_comments') && ( $approval_counts[COMMENT_UNAPPROVED] + $approval_counts[COMMENT_SPAM] ) > 0 )
+      {
+        $subst = array(
+            'num_comments' => $num_comments,
+            'num_app' => $approval_counts[COMMENT_APPROVED],
+            'num_unapp' => $approval_counts[COMMENT_UNAPPROVED],
+            'num_spam' => $approval_counts[COMMENT_SPAM]
+          );
+        $btn_text = $lang->get('onpage_btn_discussion_unapp', $subst);
+      }
+      else
+      {
+        $subst = array(
+          'num_comments' => $num_comments
+        );
+        $btn_text = $lang->get('onpage_btn_discussion', $subst);
+      }
+      
+      $button->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxComments()); return false; }" title="' . $lang->get('onpage_tip_comments') . '" accesskey="c"',
+          'PARENTFLAGS' => 'id="mdgToolbar_discussion"',
+          'HREF' => makeUrl($local_page, 'do=comments', true),
+          'TEXT' => $btn_text,
         ));
       
-      $this->sidebar_extra = $parser->run();
+      $tb .= $button->run();
+    }
+    // Edit button
+    if( $conds['edit'] )
+    {
+      $button->assign_vars(array(
+        'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_edit') . '" accesskey="e"',
+        'PARENTFLAGS' => 'id="mdgToolbar_edit"',
+        'HREF' => makeUrl($local_page, 'do=edit', true),
+        'TEXT' => $lang->get('onpage_btn_edit')
+        ));
+      $tb .= $button->run();
+    // View source button
+    }
+    else if ( $conds['viewsource'] ) 
+    {
+      $button->assign_vars(array(
+        'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_viewsource') . '" accesskey="e"',
+        'PARENTFLAGS' => 'id="mdgToolbar_edit"',
+        'HREF' => makeUrl($local_page, 'do=viewsource', true),
+        'TEXT' => $lang->get('onpage_btn_viewsource')
+        ));
+      $tb .= $button->run();
+    }
+    // History button
+    if ( $conds['history'] )
+    {
+      $button->assign_vars(array(
+        'FLAGS'       => 'onclick="if ( !KILL_SWITCH ) { void(ajaxHistory()); return false; }" title="' . $lang->get('onpage_tip_history') . '" accesskey="h"',
+        'PARENTFLAGS' => 'id="mdgToolbar_history"',
+        'HREF'        => makeUrl($local_page, 'do=history', true),
+        'TEXT'        => $lang->get('onpage_btn_history')
+        ));
+      $tb .= $button->run();
+    }
+    
+    $menubtn = $this->makeParserText($this->elements['toolbar_menu_button']);
+    
+    // Additional actions menu
+    // Rename button
+    if ( $conds['rename'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxRename()); return false; }" title="' . $lang->get('onpage_tip_rename') . '" accesskey="r"',
+          'HREF'  => makeUrl($local_page, 'do=rename', true),
+          'TEXT'  => $lang->get('onpage_btn_rename'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
+    
+    // Vote-to-delete button
+    if ( $conds['delvote'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxDelVote()); return false; }" title="' . $lang->get('onpage_tip_delvote') . '" accesskey="d"',
+          'HREF'  => makeUrl($local_page, 'do=delvote', true),
+          'TEXT'  => $lang->get('onpage_btn_votedelete'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
+    
+    // Clear-votes button
+    if ( $conds['resetvotes'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxResetDelVotes()); return false; }" title="' . $lang->get('onpage_tip_resetvotes') . '" accesskey="y"',
+          'HREF'  => makeUrl($local_page, 'do=resetvotes', true),
+          'TEXT'  => $lang->get('onpage_btn_votedelete_reset'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
+    
+    // Printable page button
+    if ( $conds['printable'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'title="' . $lang->get('onpage_tip_printable') . '"',
+          'HREF'  => makeUrl($local_page, 'printable=yes', true),
+          'TEXT'  => $lang->get('onpage_btn_printable'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
     }
     
-    $this->sidebar_extra = $this->sidebar_extra . $this->sidebar_widgets;
+    // Protect button
+    if ( $conds['protect'] )
+    {
+      switch($this->page->ns->page_protected)
+      {
+        case PROTECT_FULL: $protect_status = $lang->get('onpage_btn_protect_on'); break;
+        case PROTECT_SEMI: $protect_status = $lang->get('onpage_btn_protect_semi'); break;
+        case PROTECT_NONE: $protect_status = $lang->get('onpage_btn_protect_off'); break;
+      }
+      
+      $label = $this->makeParserText($this->elements['toolbar_label']);
+      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_protect') . ' ' . "<b><span id=\"tb_ajax_protect_status\">$protect_status</span></b>"));
+      $t0 = $label->run();
+      
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'accesskey="p" onclick="ajaxProtect(' . $this->page->ns->page_protected . '); return false;" id="tb_ajax_protect_btn" title="' . $lang->get('onpage_tip_protect') . '"',
+          'HREF' => makeUrl($local_page, 'do=protect', true),
+          'TEXT' => $lang->get('onpage_btn_protect_change')
+        ));
+      $t1 = $menubtn->run();
+      
+      $this->toolbar_menu .= '        <table border="0" cellspacing="0" cellpadding="0">
+          <tr>
+            <td>'.$t0.'</td>
+            <td>'.$t1.'</td>
+          </tr>
+        </table>';
+    }
     
-    $this->tpl_bool['fixed_menus'] = false;
-    $this->tpl_bool['export'] = false;
-    $this->tpl_bool['right_sidebar'] = true;
-    $this->tpl_bool['auth_rename'] = ( $local_page_exists && $session->check_acl_scope('rename', $local_namespace) && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ));
-    $this->tpl_bool['enable_uploads'] = ( getConfig('enable_uploads') == '1' && $session->get_permissions('upload_files') ) ? true : false;
-    $this->tpl_bool['stupid_mode'] = false;
-    $this->tpl_bool['in_admin'] = ( ( $local_page_id == 'Administration' && $local_namespace == 'Special' ) || $local_namespace == 'Admin' );
+    // Wiki mode button
+    if ( $conds['setwikimode'] )
+    {
+      // label at start
+      $label = $this->makeParserText($this->elements['toolbar_label']);
+      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_wikimode')));
+      $t0 = $label->run();
+      
+      // on button
+      $ctmp = '';
+      if ( $local_cdata['wiki_mode'] == 1 )
+      {
+        $ctmp = ' style="text-decoration: underline;"';
+      }
+      $menubtn->assign_vars(array(
+          'FLAGS' => $ctmp,
+          'HREF' => makeUrl($local_page, 'do=setwikimode&level=1', true),
+          'TEXT' => $lang->get('onpage_btn_wikimode_on')
+        ));
+      $t1 = $menubtn->run();
+      
+      // off button
+      $ctmp = '';
+      if ( $local_cdata['wiki_mode'] == 0 )
+      {
+        $ctmp=' style="text-decoration: underline;"';
+      }
+      $menubtn->assign_vars(array(
+          'FLAGS' => $ctmp,
+          'HREF' => makeUrl($local_page, 'do=setwikimode&level=0', true),
+          'TEXT' => $lang->get('onpage_btn_wikimode_off')
+        ));
+      $t2 = $menubtn->run();
+      
+      // global button
+      $ctmp = ''; 
+      if ( $local_cdata['wiki_mode'] == 2 )
+      {
+        $ctmp=' style="text-decoration: underline;"';
+      }
+      $menubtn->assign_vars(array(
+          'FLAGS' => $ctmp,
+          'HREF' => makeUrl($local_page, 'do=setwikimode&level=2', true),
+          'TEXT' => $lang->get('onpage_btn_wikimode_global')
+        ));
+      $t3 = $menubtn->run();
+      
+      // Tack it onto the list of buttons that are already there...
+      $this->toolbar_menu .= '        <table border="0" cellspacing="0" cellpadding="0">
+          <tr>
+            <td>'.$t0.'</td>
+            <td>'.$t1.'</td>
+            <td>'.$t2.'</td>
+            <td>'.$t3.'</td>
+          </tr>
+        </table>';
+    }
     
-    // allows conditional testing of the theme ID (a bit crude, came from my NSIS days)
-    $this->tpl_bool["theme_is_{$this->theme}"] = true;
+    // Clear logs button
+    if ( $conds['clearlogs'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxClearLogs()); return false; }" title="' . $lang->get('onpage_tip_flushlogs') . '" accesskey="l"',
+          'HREF'  => makeUrl($local_page, 'do=flushlogs', true),
+          'TEXT'  => $lang->get('onpage_btn_clearlogs'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
     
-    $p = ( isset($_GET['printable']) ) ? '/printable' : '';
+    // Delete page button
+    if ( $conds['delete'] )
+    {
+      $s = $lang->get('onpage_btn_deletepage');
+      if ( $this->page->ns->cdata['delvotes'] == 1 )
+      {
+        $subst = array(
+          'num_votes' => $this->page->ns->cdata['delvotes'],
+          'plural' => ''
+          );
+        $s .= $lang->get('onpage_btn_deletepage_votes', $subst);
+      }
+      else if ( $this->page->ns->cdata['delvotes'] > 1 )
+      {
+        $subst = array(
+          'num_votes' => $this->page->ns->cdata['delvotes'],
+          'plural' => $lang->get('meta_plural')
+          );
+        $s .= $lang->get('onpage_btn_deletepage_votes', $subst);
+      }
+      
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxDeletePage()); return false; }" title="' . $lang->get('onpage_tip_deletepage') . '" accesskey="k"',
+          'HREF'  => makeUrl($local_page, 'do=deletepage', true),
+          'TEXT'  => $s,
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+      
+    }
     
-    // Add the e-mail address client code to the header
-    $this->add_header($email->jscode());
+    // Password-protect button
+    if ( $conds['password'] )
+    {
+      // label at start
+      $label = $this->makeParserText($this->elements['toolbar_label']);
+      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_password')));
+      $t0 = $label->run();
+      
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxSetPassword()); return false; }" title="' . $lang->get('onpage_tip_password') . '"',
+          'HREF'  => '#',
+          'TEXT'  => $lang->get('onpage_btn_password_set'),
+        ));
+      $t = $menubtn->run();
+      
+      $this->toolbar_menu .= '<table border="0" cellspacing="0" cellpadding="0"><tr><td>'.$t0.'</td><td><input type="password" id="mdgPassSetField" size="10" /></td><td>'.$t.'</td></tr></table>';
+    }
     
-    // Generate the code for the Log out and Change theme sidebar buttons
+    // Manage ACLs button
+    if ( $conds['acledit'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { var s = ajaxOpenACLManager(); console.debug(s); return false; }" title="' . $lang->get('onpage_tip_aclmanager') . '" accesskey="m"',
+          'HREF'  => makeUrl($local_page, 'do=aclmanager', true),
+          'TEXT'  => $lang->get('onpage_btn_acl'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
+    
+    // Administer page button
+    if ( $conds['adminpage'] )
+    {
+      $menubtn->assign_vars(array(
+          'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxAdminPage()); return false; }" title="' . $lang->get('onpage_tip_adminoptions') . '" accesskey="g"',
+          'HREF'  => makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'PageManager', true),
+          'TEXT'  => $lang->get('onpage_btn_admin'),
+        ));
+      $this->toolbar_menu .= $menubtn->run();
+    }
+    
+    if ( strlen($this->toolbar_menu) > 0 )
+    {
+      $button->assign_vars(array(
+        'FLAGS'       => 'id="mdgToolbar_moreoptions" onclick="if ( !KILL_SWITCH ) { return false; }" title="' . $lang->get('onpage_tip_moreoptions') . '"',
+        'PARENTFLAGS' => '',
+        'HREF'        => makeUrl($local_page, 'do=moreoptions', true),
+        'TEXT'        => $lang->get('onpage_btn_moreoptions')
+        ));
+      $tb .= $button->run();
+    }
+    
+    // Generate the code for the Log in, Log out, Change theme, Administration, and Edit Sidebar buttons
     // Once again, the new template parsing system can be used here
     
-    $parser = $this->makeParserText($tplvars['sidebar_button']);
+    $parser = $this->makeParserText($this->elements['sidebar_button']);
     
     $parser->assign_vars(Array(
         'HREF'=>makeUrlNS('Special', "Logout/{$session->csrf_token}/{$local_page}"),
@@ -1075,38 +1078,51 @@
     
     $theme_link = $parser->run();
     
-    $parser->assign_vars(Array(
-        'HREF'=>makeUrlNS('Special', 'Administration'),
-        'FLAGS'=>'onclick="if ( !KILL_SWITCH ) { void(ajaxStartAdminLogin()); return false; }"',
-        'TEXT'=>$lang->get('sidebar_btn_administration'),
-      ));
+    // Run hooks
+    $code = $plugins->setHook('tpl_compile_toolbar');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
     
-    $admin_link = $parser->run();
+    //
+    // ASSIGN VARIABLES
+    //
     
-    $parser->assign_vars(Array(
-        'HREF'=>makeUrlNS('Special', 'EditSidebar'),
-        'FLAGS'=>'onclick="if ( !KILL_SWITCH ) { void(ajaxLoginNavTo(\'Special\', \'EditSidebar\', ' . USER_LEVEL_ADMIN . ')); return false; }"',
-        'TEXT'=>$lang->get('sidebar_btn_editsidebar'),
-      ));
-    
-    $sidebar_link = $parser->run();
+    $this->assign_vars(array(
+        'PAGE_NAME' => htmlspecialchars($this->page->ns->cdata['name']),
+        'PAGE_URLNAME' => sanitize_page_id($this->page_id),
+        'TOOLBAR' => $tb,
+        'TOOLBAR_EXTRAS' => $this->toolbar_menu,
+        'STYLE_LINK' => makeUrlNS('Special', 'CSS', null, true), //contentPath.$paths->nslist['Special'].'CSS' . $p,
+        'LOGIN_LINK' => $login_link,
+        'LOGOUT_LINK' => $logout_link,
+        'THEME_LINK' => $theme_link
+      ), true);
+    $this->page_initted = true;
+  }
+  
+  /**
+   * Generates and assigns the Javascript system variables
+   */
+  
+  function generate_js_header()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
     
     $SID = ($session->sid_super) ? $session->sid_super : '';
     
+    $local_page = $paths->get_pathskey($this->page_id, $this->namespace);
+    $local_fullpage = $paths->get_pathskey($this->page_id, $this->namespace) . substr($paths->fullpage, strlen($paths->page));
+    
     $urlname_clean = str_replace('\'', '\\\'', str_replace('\\', '\\\\', dirtify_page_id($local_fullpage)));
     $urlname_clean = strtr( $urlname_clean, array( '<' => '&lt;', '>' => '&gt;' ) );
     
     $urlname_jssafe = sanitize_page_id($local_fullpage);
     $physical_urlname_jssafe = sanitize_page_id($paths->fullpage);
     
-    if ( $session->check_acl_scope('even_when_protected', $local_namespace) )
-    {
-      $protected = $paths->page_protected && !$perms->get_permissions('even_when_protected');
-    }
-    else
-    {
-      $protected = false;
-    }
+    $protected = is_object($this->page) ? $this->page->ns->cdata['really_protected'] : false;
     
     // Generate the dynamic javascript vars
     $js_dynamic = '    <script type="text/javascript">// <![CDATA[
@@ -1116,7 +1132,7 @@
       var physical_title = \'' . $physical_urlname_jssafe . '\';
       var on_main_page = ' . ( $local_page == get_main_page() ? 'true' : 'false' ) . ';
       var main_page_members = \'' . addslashes(get_main_page(true)) . '\';
-      var page_exists = '. ( ( $local_page_exists) ? 'true' : 'false' ) .';
+      var page_exists = '. ( ( is_object($this->page) ? $this->page->ns->exists() : true ) ? 'true' : 'false' ) .';
       var scriptPath = \'' . addslashes(scriptPath) . '\';
       var contentPath = \'' . addslashes(contentPath) . '\';
       var cdnPath = \'' . addslashes(cdnPath) . '\';
@@ -1131,84 +1147,37 @@
       var disable_redirect = ' . ( isset($_GET['redirect']) && $_GET['redirect'] == 'no' ? 'true' : 'false' ) . ';
       var pref_disable_js_fx = ' . ( @$session->user_extra['disable_js_fx'] == 1 ? 'true' : 'false' ) . ';
       var csrf_token = "' . $session->csrf_token . '";
-      var editNotice = \'' . ( (getConfig('wiki_edit_notice', '0')=='1') ? str_replace("\n", "\\\n", addslashes(RenderMan::render(getConfig('wiki_edit_notice_text')))) : '' ) . '\';
+      var editNotice = \'' . $this->get_wiki_edit_notice() . '\';
       var prot = ' . ( ($protected) ? 'true' : 'false' ) .'; // No, hacking this var won\'t work, it\'s re-checked on the server
       var ENANO_SPECIAL_CREATEPAGE = \''. makeUrl($paths->nslist['Special'].'CreatePage') .'\';
-      var ENANO_CREATEPAGE_PARAMS = \'_do=&pagename='. $urlname_clean .'&namespace=' . $local_namespace . '\';
+      var ENANO_CREATEPAGE_PARAMS = \'_do=&pagename='. $this->page_id .'&namespace=' . $this->namespace . '\';
       var ENANO_SPECIAL_CHANGESTYLE = \''. makeUrlNS('Special', 'ChangeStyle') .'\';
       var namespace_list = new Array();
       var msg_loading_component = \'' . addslashes($lang->get('ajax_msg_loading_component')) . '\';
       var AES_BITS = '.AES_BITS.';
       var AES_BLOCKSIZE = '.AES_BLOCKSIZE.';
       var pagepass = \''. ( ( isset($_REQUEST['pagepass']) ) ? sha1($_REQUEST['pagepass']) : '' ) .'\';
-      var ENANO_THEME_LIST = \'';
-          foreach($this->theme_list as $t) {
-            if($t['enabled'])
-            {
-              $js_dynamic .= '<option value="'.$t['theme_id'].'"';
-              // if($t['theme_id'] == $session->theme) $js_dynamic .= ' selected="selected"';
-              $js_dynamic .= '>'.$t['theme_name'].'</option>';
-            }
-          }
-      $js_dynamic .= '\';
-      var ENANO_CURRENT_THEME = \''. $session->theme .'\';
       var ENANO_LANG_ID = ' . $lang->lang_id . ';
       var ENANO_PAGE_TYPE = "' . addslashes($this->namespace_string) . '";';
-      foreach($paths->nslist as $k => $c)
-      {
-        $js_dynamic .= "namespace_list['{$k}'] = '$c';";
-      }
-      $js_dynamic .= "\n    //]]>\n    </script>";
-      
-    $tpl_strings = Array(
-      'PAGE_NAME' => htmlspecialchars($local_cdata['name']),
-      'PAGE_URLNAME' =>  $urlname_clean,
-      'SITE_NAME' => htmlspecialchars(getConfig('site_name')),
-      'USERNAME' => $session->username,
-      'SITE_DESC' => htmlspecialchars(getConfig('site_desc')),
-      'TOOLBAR' => $tb,
-      'SCRIPTPATH' => scriptPath,
-      'CONTENTPATH' => contentPath,
-      'CDNPATH' => cdnPath,
-      'ADMIN_SID_QUES' => $asq,
-      'ADMIN_SID_AMP' => $asa,
-      'ADMIN_SID_AMP_HTML' => $ash,
-      'ADMIN_SID_AUTO' => $as2,
-      'ADMIN_SID_RAW' =>  ( is_string($session->sid_super) ? $session->sid_super : '' ),
-      'CSRF_TOKEN' => $session->csrf_token,
-      'COPYRIGHT' => RenderMan::parse_internal_links(getConfig('copyright_notice')),
-      'TOOLBAR_EXTRAS' => $this->toolbar_menu,
-      'REQUEST_URI' => ( defined('ENANO_CLI') ? '' : $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] ),
-      'STYLE_LINK' => makeUrlNS('Special', 'CSS'.$p, null, true), //contentPath.$paths->nslist['Special'].'CSS' . $p,
-      'LOGIN_LINK' => $login_link,
-      'LOGOUT_LINK' => $logout_link,
-      'ADMIN_LINK' => $admin_link,
-      'THEME_LINK' => $theme_link,
-      'SIDEBAR_LINK' => $sidebar_link,
-      'SEARCH_ACTION' => makeUrlNS('Special', 'Search'),
-      'INPUT_TITLE' => ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars( $paths->nslist[$local_namespace] . $local_page_id ) . '" />' : ''),
-      'INPUT_AUTH' => ( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : ''),
-      'TEMPLATE_DIR' => scriptPath.'/themes/'.$this->theme,
-      'THEME_ID' => $this->theme,
-      'STYLE_ID' => $this->style,
-      'MAIN_PAGE' => get_main_page(),
-      'JS_HEADER' => $js_head,
-      'JS_FOOTER' => $js_foot,
-      'JS_DYNAMIC_VARS' => $js_dynamic,
-      'UNREAD_PMS' => $session->unread_pms,
-      'URL_ABOUT_ENANO' => makeUrlNS('Special', 'About_Enano', '', true),
-      'REPORT_URI' => makeUrl($local_fullpage, 'do=sql_report', true)
-      );
+    
+    foreach($paths->nslist as $k => $c)
+    {
+      $js_dynamic .= "namespace_list['{$k}'] = '$c';";
+    }
+    $js_dynamic .= "\n    //]]>\n    </script>";
     
-    foreach ( $paths->nslist as $ns_id => $ns_prefix )
-    {
-      $tpl_strings[ 'NS_' . strtoupper($ns_id) ] = $ns_prefix;
-    }
-    
-    $this->assign_vars($tpl_strings, true);
-    
-    profiler_log('template: var init: finished toolbar building and initial assign()');
-    
+    $this->assign_vars(array(
+        'JS_DYNAMIC_VARS' => $js_dynamic,
+        'REPORT_URI' => makeUrl($local_fullpage, 'do=sql_report', true)
+      ), true);
+  }
+  
+  /**
+   * Fetches, parses, and assigns the sidebar.
+   */
+  
+  function assign_sidebar()
+  {
     //
     // COMPILE THE SIDEBAR
     //
@@ -1219,6 +1188,72 @@
     $this->tpl_bool['sidebar_left']  = ( $this->tpl_strings['SIDEBAR_LEFT']  != $min) ? true : false;
     $this->tpl_bool['sidebar_right'] = ( $this->tpl_strings['SIDEBAR_RIGHT'] != $min) ? true : false;
     $this->tpl_bool['right_sidebar'] = $this->tpl_bool['sidebar_right']; // backward compatibility
+  }
+  
+  /**
+   * Initializes all variables related to on-page content. This includes sidebars and what have you.
+   * @param object Optional PageProcessor object to use for passing metadata and permissions on. If omitted, uses information from $paths and $session.
+   * @param bool If true, re-inits even if already initted with this page_id and namespace
+   */
+  
+  function init_vars($page = false, $force_init = false)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    $need_shared_init = ( !$this->theme_initted || !$this->page_initted );
+    
+    if ( $need_shared_init )
+    {
+      if ( !$this->theme || !$this->style )
+      {
+        $this->load_theme();
+      }
+      $code = $plugins->setHook('compile_template', true);
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+    }
+    
+    if ( !$this->theme_loaded )
+      $this->load_theme();
+    
+    require(ENANO_ROOT . "/themes/{$this->theme}/theme.cfg");
+    
+    if ( !$this->page_id || !$this->namespace )
+    {
+      $this->page_id = $paths->page_id;
+      $this->namespace = $paths->namespace;
+    }
+    
+    profiler_log('template: prepped for var set (loaded theme, ran compile_template hook)');
+    
+    $this->init_global_vars();
+    profiler_log('template: global vars set');
+    
+    if ( !$this->theme_initted )
+      $this->init_theme_vars();
+    
+    profiler_log('template: theme vars set');
+    
+    if ( !$this->page_initted && !empty($this->namespace) )
+    {
+      profiler_log('template: page vars set');
+      $this->init_page_vars();
+    }
+    else
+    {
+      profiler_message('template: skipped setting page vars');
+    }
+    
+    // Perform shared init (combine javascript, etc.)
+    if ( $need_shared_init )
+    {
+      $this->generate_js_header();
+      $this->assign_sidebar();
+      profiler_log('template: assigned sidebar and JS');
+    }
     
     // and finally one string value that needs to be symlinked...
     if ( !isset($this->tpl_strings['ADDITIONAL_HEADERS']) )
@@ -1232,19 +1267,6 @@
     {
       eval($cmd);
     }
-    
-    profiler_log("template: finished var init");
-  }
-  
-  /**
-   * Performs var init that is common to all pages (IOW, called only once)
-   * Not used yet because most stuff is either theme-dependent or page-dependent.
-   * @access private
-   */
-  
-  function init_vars_global()
-  {
-    
   }
   
   function header($simple = false) 
@@ -1252,6 +1274,8 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
+    define('ENANO_HEADERS_SENT', true);
+    
     echo $this->getHeader($simple);
   }
   
@@ -1270,21 +1294,22 @@
       $this->load_theme($session->theme, $session->style);
     }
     
+    if ( !$this->page_initted || !$this->theme_initted )
+      $this->init_vars();
+    
     // I feel awful doing this.
     if ( preg_match('/^W3C_Validator/', @$_SERVER['HTTP_USER_AGENT']) )
     {
       header('Content-type: application/xhtml+xml');
     }
     
-    $headers_sent = true;
-    if(!defined('ENANO_HEADERS_SENT'))
-      define('ENANO_HEADERS_SENT', '');
+    $header = '';
+    
     if ( !$this->no_headers )
     {
       $header = ( $simple ) ?
         $this->process_template('simple-header.tpl') :
         $this->process_template('header.tpl');
-      echo $header;
     }
     if ( !$simple && $session->user_logged_in && $session->unread_pms > 0 )
     {
@@ -1304,7 +1329,10 @@
             ' . $lang->get('page_sitedisabled_admin_msg_body', array('admin_link' => $admin_link)) . '
             </div>';
     }
+    
+    return $header;
   }
+  
   function getFooter($simple = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
@@ -1312,9 +1340,6 @@
     if ( !$this->no_headers )
     {
       
-      if(!defined('ENANO_HEADERS_SENT'))
-        $this->header();
-      
       global $_starttime;
       if(isset($_GET['sqldbg']) && $session->get_permissions('mod_misc'))
       {
@@ -1430,9 +1455,9 @@
   function process_template($file)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!defined('ENANO_TEMPLATE_LOADED'))
+    
+    if ( !$this->theme_initted || !$this->page_initted )
     {
-      $this->load_theme();
       $this->init_vars();
     }
     
@@ -1756,7 +1781,10 @@
     
     for($i=0;$i<sizeof($links);$i++)
     {
-      $message = str_replace('$'.$links[$i].'$', $this->tpl_strings[$links[$i]], $message);
+      if ( isset($this->tpl_strings[$links[$i]]) )
+      {
+        $message = str_replace('$'.$links[$i].'$', $this->tpl_strings[$links[$i]], $message);
+      }
     }
     
     // Conditionals
@@ -2503,6 +2531,26 @@
     return $this->parse($result);
   }
   
+  /**
+   * Return the wiki mode edit notice, rendered and addslashes()'ed.
+   * @return string
+   */
+  
+  function get_wiki_edit_notice()
+  {
+    global $cache;
+    
+    if ( getConfig('wiki_edit_notice', 0) != 1 )
+      return '';
+    
+    if ( $cached = $cache->fetch('wiki_edit_notice') )
+      return $cached;
+    
+    $notice = str_replace("\n", "\\\n", addslashes(RenderMan::render(getConfig('wiki_edit_notice_text'))));
+    $cache->store('wiki_edit_notice', $notice, 60);
+    return $notice;
+  }
+  
 } // class template
 
 /**
--- a/index.php	Mon May 04 23:07:00 2009 -0400
+++ b/index.php	Tue May 05 00:10:26 2009 -0400
@@ -25,6 +25,7 @@
   $aggressive_optimize_html = true;
   
   global $do_gzip;
+  // FIXME: make this configurable
   $do_gzip = true;
   
   if ( isset($_GET['nocompress']) )
@@ -70,6 +71,9 @@
       // echo PageUtils::getpage($paths->page, true, ( (isset($_GET['oldid'])) ? $_GET['oldid'] : false ));
       $rev_id = ( (isset($_GET['oldid'])) ? intval($_GET['oldid']) : 0 );
       $page = new PageProcessor( $paths->page_id, $paths->namespace, $rev_id );
+      // Feed this PageProcessor to the template processor. This prevents $template from starting another
+      // PageProcessor when we already have one going.
+      $template->set_page($page);
       $page->send_headers = true;
       $pagepass = ( isset($_REQUEST['pagepass']) ) ? sha1($_REQUEST['pagepass']) : '';
       $page->password = $pagepass;
--- a/language/english/admin.json	Mon May 04 23:07:00 2009 -0400
+++ b/language/english/admin.json	Tue May 05 00:10:26 2009 -0400
@@ -340,7 +340,7 @@
       heading_activate: 'User account activation',
       activate_intro_line1: 'If you would like to require users to confirm their e-mail addresses by way of account activation, you can enable this behavior here. If this option is set to "None", users will be able to register and use this site without confirming their e-mail addresses. If this option is set to "User", users will automatically be sent e-mails upon registration with a link to activate their accounts. And lastly, if this option is set to "Admin", users\' accounts will not be active until an administrator activates the account.',
       activate_intro_line2: 'You may also disable registration completely if needed.',
-      activate_intro_sfnet_warning: 'Note: because of abuse by project administrators, sending account activation e-mails will not work on SourceForge.net servers.',
+      activate_intro_sfnet_warning: 'Note: if you are using a SourceForge.net server, do not select User. SourceForge.net does not allow e-mails to be sent from project web sites.',
       field_activate: 'Account activation:',
       field_activate_disable: 'Disable registration',
       field_activate_none: 'None',
@@ -675,6 +675,8 @@
       cache_js_desc_body: 'The on-page tools that Enano provides require a significant amount of Javascript code, which is compressed and cached for better load times. This cache is updated whenever any of the Javascript files are changed, but it is an incremental cache, meaning that it cannot be refreshed manually (only cleared).',
       cache_thumbs_desc_title: 'Image thumbnails',
       cache_thumbs_desc_body: 'Thumbnails (preview-size versions of uploaded images) can take up to 2 seconds to generate, usually increasing load times and potentially causing increased server load. This cache is refreshed on demand. Clearing this cache may cause increased loads on your server.',
+      cache_wikieditnotice_desc_title: 'Wiki mode legal notice',
+      cache_wikieditnotice_desc_body: 'This notice is rendered and cached to reduce database queries. If you use templates in your edit notice, you need to clear this cache to see the changes take effect. This cache expires every 60 minutes.',
       
       msg_action_success: 'The action you requested was successful.',
       err_action_failed: 'There was an error during the requested action.'
--- a/plugins/SpecialAdmin.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/SpecialAdmin.php	Tue May 05 00:10:26 2009 -0400
@@ -44,26 +44,8 @@
     ));
 }
 
-$plugins->attachHook('session_started', 'SpecialAdmin_theme_init();');
 $plugins->attachHook('common_post', 'SpecialAdmin_include();');
 
-function SpecialAdmin_theme_init()
-{
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  // Admin pages that were too enormous to be in this file were split off into the plugins/admin/ directory in 1.0.1.
-  // Only load these files if we're looking to load the admin panel
-  list($pid, $ns) = RenderMan::strToPageID($paths->get_pageid_from_url());
-  if ( $ns == 'Admin' || ( $pid == 'Administration' && $ns == 'Special' ) )
-  {
-    // Set the theme
-    $session->theme = 'admin';
-    $session->style = 'default';
-    
-    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/admin-menu.js"></script>');
-  }
-}
-
 function SpecialAdmin_include()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -132,6 +114,7 @@
     if(isset($_POST['editmsg']))                 setConfig('wiki_edit_notice', '1');
     else                                         setConfig('wiki_edit_notice', '0');
     setConfig('wiki_edit_notice_text', $_POST['editmsg_text']);
+    $cache->purge('wiki_edit_notice');
     if(isset($_POST['guest_edit_require_captcha'])) setConfig('guest_edit_require_captcha', '1');
     else                                         setConfig('guest_edit_require_captcha', '0');
     
@@ -2078,6 +2061,9 @@
   }
   else
   {
+    $template->set_theme('admin', 'default');
+    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/admin-menu.js"></script>');
+    
     if( !isset( $_GET['noheaders'] ) ) 
     {
       $template->header();
--- a/plugins/SpecialPageFuncs.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/SpecialPageFuncs.php	Tue May 05 00:10:26 2009 -0400
@@ -313,7 +313,6 @@
   global $lang;
   
   $template->header();
-  $sz = sizeof( $paths->pages ) / 2;
   echo '<p>' . $lang->get('pagetools_allpages_blurb') . '</p>';
   
   $q = $db->sql_query('SELECT COUNT(urlname) FROM '.table_prefix.'pages WHERE visible!=0;');
@@ -321,6 +320,7 @@
     $db->_die();
   $row = $db->fetchrow_num();
   $count = $row[0];
+  $sz =& $count;
   
   switch($count % 4)
   {
@@ -378,36 +378,30 @@
   global $lang;
   
   $template->header();
-  $sz = sizeof($paths->pages) / 2;
-  echo '<p>' . $lang->get('pagetools_specialpages_blurb') . '</p><div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">';
-  $cclass='row1';
-  for ( $i = 0; $i < $sz; $i = $i)
+  echo '<p>' . $lang->get('pagetools_specialpages_blurb') . '</p><div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4"><tr>';
+  $cclass = 'row1';
+  $i = -1;
+  foreach ( $paths->pages as $cdata )
   {
-    if ( $cclass == 'row1' )
+    if ( $cdata['namespace'] != 'Special' )
+      continue;
+    
+    $i++;
+    if ( $i % 2 == 0 && $i > 0 )
     {
-      $cclass = 'row3';
-    }
-    else if ( $cclass == 'row3')
-    {
-      $cclass='row1';
+      echo '</tr><tr>';
+      $cclass = ( $cclass == 'row1' ) ? 'row3' : 'row1';
     }
-    echo '<tr>';
-    for ( $j = 0; $j < 2; $j = $j )
-    {
-      if ( $i < $sz && $paths->pages[$i]['namespace'] == 'Special' && $paths->pages[$i]['visible'] == 1)
-      {
-        echo '<td style="width: 50%" class="'.$cclass.'"><a href="'.makeUrl($paths->pages[$i]['urlname']).'">';
-        echo $paths->pages[$i]['name'].'</a></td>';
-        $j++;
-      }
-      else if ( $i >= $sz )
-      {  
-        echo '<td style="width: 50%" class="row2"></td>';
-        $j++;
-      }
-      $i++;
-    }
-    echo '</tr>';
+    echo '<td style="width: 50%;" class="' . $cclass . '">';
+    echo '<a href="' . makeUrl($cdata['urlname']) . '">';
+    echo htmlspecialchars($cdata['name']);
+    echo '</a>';
+    echo '</td>';
+  }
+  // close up the table if necessary
+  if ( $i % 2 > 0 )
+  {
+    echo "<td class=\"$cclass\"></td>";
   }
   echo '</table></div>';
   $template->footer();
--- a/plugins/SpecialUpdownload.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/SpecialUpdownload.php	Tue May 05 00:10:26 2009 -0400
@@ -101,17 +101,16 @@
       }
     }
     
-    if ( isset ( $paths->pages[ $paths->nslist['File'] . $filename ] ) && !isset ( $_POST['update'] ) )
+    $ns = namespace_factory($filename, 'File');
+    $cdata = $ns->get_cdata();
+    $is_protected = $cdata['really_protected'];
+    
+    if ( isPage($paths->get_pathskey($filename, 'File')) && !isset ( $_POST['update'] ) )
     {
       $upload_link = makeUrlNS('Special', 'UploadFile/'.$filename);
       die_friendly($lang->get('upload_err_title'), '<p>' . $lang->get('upload_err_already_exists', array('upload_link' => $upload_link)) . '</p>');
     }
-    else if ( isset($_POST['update']) && 
-            ( !isset($paths->pages[$paths->nslist['File'].$filename]) ||
-             (isset($paths->pages[$paths->nslist['File'].$filename]) &&
-               $paths->pages[$paths->nslist['File'].$filename]['protected'] == 1 )
-             )
-           )
+    else if ( isset($_POST['update']) && $is_protected )
     {
       die_friendly($lang->get('upload_err_title'), '<p>' . $lang->get('upload_err_replace_protected') . '</p>');
     }
--- a/plugins/SpecialUserFuncs.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/SpecialUserFuncs.php	Tue May 05 00:10:26 2009 -0400
@@ -121,6 +121,10 @@
   global $__login_status;
   global $lang;
   
+  require_once(ENANO_ROOT . '/includes/math.php');
+  require_once(ENANO_ROOT . '/includes/diffiehellman.php');
+  global $dh_supported;
+  
   $locked_out = false;
   // are we locked out?
   $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
@@ -607,7 +611,7 @@
       $template->load_theme($session->theme, $session->style);
       if(isset($_POST['return_to']))
       {
-        $name = ( isPage($_POST['return_to']['name']) ) ? $paths->pages[$_POST['return_to']]['name'] : $_POST['return_to'];
+        $name = get_page_title($_POST['return_to']);
         $subst = array(
             'username' => $session->username,
             'redir_target' => $name
--- a/plugins/SpecialUserPrefs.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/SpecialUserPrefs.php	Tue May 05 00:10:26 2009 -0400
@@ -43,7 +43,7 @@
   }
 }
 
-$plugins->attachHook('compile_template', 'userprefs_jbox_setup($button, $tb, $menubtn);');
+$plugins->attachHook('tpl_compile_sidebar', 'userprefs_jbox_setup($button, $tb, $menubtn);');
 
 function userprefs_jbox_setup(&$button, &$tb, &$menubtn)
 {
@@ -469,7 +469,7 @@
           $user_title_col = ", user_title = $colval";
         }
         $user_rank_col = '';
-        if ( intval($_POST['user_rank']) != $current_rank['rank_id'] && count($available_ranks) > 1 )
+        if ( isset($_POST['user_rank']) && intval($_POST['user_rank']) != $current_rank['rank_id'] && count($available_ranks) > 1 )
         {
           if ( $_POST['user_rank'] == 'NULL' )
           {
--- a/plugins/admin/CacheManager.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/admin/CacheManager.php	Tue May 05 00:10:26 2009 -0400
@@ -141,6 +141,13 @@
           $success = true;
         }
         break;
+      case 'wikieditnotice':
+        $cache->purge('wiki_edit_notice');
+        if ( $do_refresh )
+          $template->get_wiki_edit_notice();
+        
+        $success = true;
+        break;
       case 'all':
         $success = purge_all_caches();
         if ( $do_refresh )
@@ -164,6 +171,9 @@
           if ( !$success )
             break;
           
+          // wiki edit notice
+          $template->get_wiki_edit_notice();
+          
           // languages
           $q = $db->sql_query('SELECT lang_id FROM ' . table_prefix . 'language;');
           if ( !$q )
@@ -265,7 +275,7 @@
       
       <?php
       $class = 'row2';
-      $cache_list = array('page', 'ranks', 'sidebar', 'plugins', 'template', 'aes', 'lang', 'js', 'thumbs');
+      $cache_list = array('page', 'ranks', 'sidebar', 'plugins', 'template', 'aes', 'lang', 'js', 'thumbs', 'wikieditnotice');
       $code = $plugins->setHook('acp_cache_manager_list_caches');
       foreach ( $code as $cmd )
       {
--- a/plugins/admin/PageManager.php	Mon May 04 23:07:00 2009 -0400
+++ b/plugins/admin/PageManager.php	Tue May 05 00:10:26 2009 -0400
@@ -261,10 +261,6 @@
                   {
                     PageUtils::delete_page_files($page_id);
                   }
-                  
-                  // update $paths with the new pathskey
-                  $new_pathskey = $paths->nslist[$namespace_new] . $page_id_new;
-                  $paths->pages[$new_pathskey] =& $paths->pages[$pathskey];
                 }
                 
                 // Did we change the name of the page? If so, make PageProcessor log it