Another sweep from the optimization monster.
authorDan
Wed, 02 Jul 2008 19:36:44 -0400
changeset 592 27377179fe58
parent 591 2529833a7731
child 593 4f9bec0d65c1
Another sweep from the optimization monster. template: rewrote parts of tplWikiFormat to do faster validation and less eval()ing template: sidebar is now cached for anonymous users (thanks to Richard Greene for the suggestion) dbal: rewrote SQL query parser, much cruft taken out template: made tplWikiFormat depend more on RenderMan common: moved some lesser-used includes out of main loading sequence Expect a lot of changes to AES code in the next commit!
ajax.php
includes/clientside/static/ajax.js
includes/clientside/static/enano-lib-basic.js
includes/clientside/static/functions.js
includes/common.php
includes/constants.php
includes/dbal.php
includes/functions.php
includes/pageprocess.php
includes/paths.php
includes/render.php
includes/sessions.php
includes/template.php
index.php
plugins/SpecialSearch.php
--- a/ajax.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/ajax.php	Wed Jul 02 19:36:44 2008 -0400
@@ -14,76 +14,6 @@
  
   define('ENANO_INTERFACE_AJAX', '');
  
-  // fillusername should be done without the help of the rest of Enano - all we need is the DBAL
-  if ( isset($_GET['_mode']) && $_GET['_mode'] == 'fillusername' )
-  {
-    // setup and load a very basic, specialized instance of the Enano API
-    function microtime_float()
-    {
-      list($usec, $sec) = explode(" ", microtime());
-      return ((float)$usec + (float)$sec);
-    }
-    // Determine directory (special case for development servers)
-    if ( strpos(__FILE__, '/repo/') && file_exists('.enanodev') )
-    {
-      $filename = str_replace('/repo/', '/', __FILE__);
-    }
-    else
-    {
-      $filename = __FILE__;
-    }
-    define('ENANO_ROOT', dirname($filename));
-    require(ENANO_ROOT.'/includes/functions.php');
-    require(ENANO_ROOT.'/includes/dbal.php');
-    require(ENANO_ROOT.'/includes/json2.php');
-    
-    require(ENANO_ROOT . '/config.php');
-    unset($dbuser, $dbpasswd);
-    if ( !isset($dbdriver) )
-      $dbdriver = 'mysql';
-    
-    $db = new $dbdriver();
-    
-    $db->connect();
-    
-    // result is sent using JSON
-    $return = Array(
-        'mode' => 'success',
-        'users_real' => Array()
-      );
-    
-    // should be connected to the DB now
-    $name = (isset($_GET['name'])) ? $db->escape($_GET['name']) : false;
-    if ( !$name )
-    {
-      $return = array(
-        'mode' => 'error',
-        'error' => 'Invalid URI'
-      );
-      die( enano_json_encode($return) );
-    }
-    $allowanon = ( isset($_GET['allowanon']) && $_GET['allowanon'] == '1' ) ? '' : ' AND user_id > 1';
-    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username) LIKE ' . ENANO_SQLFUNC_LOWERCASE . '(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
-    if ( !$q )
-    {
-      $db->die_json();
-    }
-    $i = 0;
-    while($r = $db->fetchrow())
-    {
-      $return['users_real'][] = $r['username'];
-      $i++;
-    }
-    $db->free_result();
-    
-    // all done! :-)
-    $db->close();
-    
-    echo enano_json_encode( $return );
-    
-    exit;
-  }
- 
   require('includes/common.php');
   
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -93,6 +23,7 @@
   
   switch($_GET['_mode']) {
     case "checkusername":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::checkusername($_GET['name']);
       break;
     case "getsource":
@@ -230,18 +161,7 @@
       break;
     case "savepage":
       /* **** OBSOLETE **** */
-      $summ = ( isset($_POST['summary']) ) ? $_POST['summary'] : '';
-      $minor = isset($_POST['minor']);
-      $e = PageUtils::savepage($paths->page_id, $paths->namespace, $_POST['text'], $summ, $minor);
-      if ( $e == 'good' )
-      {
-        $page = new PageProcessor($paths->page_id, $paths->namespace);
-        $page->send();
-      }
-      else
-      {
-        echo '<p>Error saving the page: '.$e.'</p>';
-      }
+      
       break;
     case "savepage_json":
       header('Content-type: application/json');
@@ -434,6 +354,7 @@
       echo enano_json_encode($result);
       break;
     case "histlist":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::histlist($paths->page_id, $paths->namespace);
       break;
     case "rollback":
@@ -445,6 +366,7 @@
       echo enano_json_encode($result);
       break;
     case "comments":
+      require_once(ENANO_ROOT.'/includes/comment.php');
       $comments = new Comments($paths->page_id, $paths->namespace);
       if ( isset($_POST['data']) )
       {
@@ -463,33 +385,42 @@
       echo enano_json_encode($result);
       break;
     case "flushlogs":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::flushlogs($paths->page_id, $paths->namespace);
       break;
     case "deletepage":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $reason = ( isset($_POST['reason']) ) ? $_POST['reason'] : false;
       if ( empty($reason) )
         die($lang->get('page_err_need_reason'));
       echo PageUtils::deletepage($paths->page_id, $paths->namespace, $reason);
       break;
     case "delvote":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::delvote($paths->page_id, $paths->namespace);
       break;
     case "resetdelvotes":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::resetdelvotes($paths->page_id, $paths->namespace);
       break;
     case "getstyles":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::getstyles($_GET['id']);
       break;
     case "catedit":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::catedit($paths->page_id, $paths->namespace);
       break;
     case "catsave":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::catsave($paths->page_id, $paths->namespace, $_POST);
       break;
     case "setwikimode":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::setwikimode($paths->page_id, $paths->namespace, (int)$_GET['mode']);
       break;
     case "setpass":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::setpass($paths->page_id, $paths->namespace, $_POST['password']);
       break;
     case "fillusername":
@@ -537,9 +468,11 @@
       }
       break;
     case "preview":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       echo PageUtils::genPreview($_POST['text']);
       break;
     case "pagediff":
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $id1 = ( isset($_GET['diff1']) ) ? (int)$_GET['diff1'] : false;
       $id2 = ( isset($_GET['diff2']) ) ? (int)$_GET['diff2'] : false;
       if(!$id1 || !$id2) { echo '<p>Invalid request.</p>'; $template->footer(); break; }
@@ -558,6 +491,7 @@
       else echo $rdns;
       break;
     case 'acljson':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $parms = ( isset($_POST['acl_params']) ) ? rawurldecode($_POST['acl_params']) : false;
       echo PageUtils::acl_json($parms);
       break;
--- a/includes/clientside/static/ajax.js	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/clientside/static/ajax.js	Wed Jul 02 19:36:44 2008 -0400
@@ -149,7 +149,11 @@
   if ( !box )
     return false;
   
-  var newname = ( obj.getElementsByTagName('input')[0] ).value;
+  var input = obj.getElementsByTagName('input')[0];
+  console.debug(obj, input);
+  if ( !input )
+    return false;
+  var newname = input.value;
   newname = trim(newname);
   
   if ( newname.length < 1 )
--- a/includes/clientside/static/enano-lib-basic.js	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/clientside/static/enano-lib-basic.js	Wed Jul 02 19:36:44 2008 -0400
@@ -465,6 +465,7 @@
   ajaxEditTheme: 'theme-manager.js',
   ajaxToggleSystemThemes: 'theme-manager.js',
   ajaxInstallTheme: 'theme-manager.js',
+  ajaxInitRankEdit: 'rank-manager.js'
 };
 
 var placeholder_instances = {};
--- a/includes/clientside/static/functions.js	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/clientside/static/functions.js	Wed Jul 02 19:36:44 2008 -0400
@@ -558,6 +558,7 @@
   return z;
 }
 
+var shift = false;
 function isKeyPressed(event)
 {
   if (event.shiftKey==1)
--- a/includes/common.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/common.php	Wed Jul 02 19:36:44 2008 -0400
@@ -138,20 +138,10 @@
 require_once(ENANO_ROOT.'/includes/template.php');
 require_once(ENANO_ROOT.'/includes/plugins.php');
 require_once(ENANO_ROOT.'/includes/lang.php');
-require_once(ENANO_ROOT.'/includes/comment.php');
-require_once(ENANO_ROOT.'/includes/wikiformat.php');
-require_once(ENANO_ROOT.'/includes/diff.php');
 require_once(ENANO_ROOT.'/includes/render.php');
-require_once(ENANO_ROOT.'/includes/stats.php');
-require_once(ENANO_ROOT.'/includes/pageutils.php');
-require_once(ENANO_ROOT.'/includes/js-compressor.php');
 require_once(ENANO_ROOT.'/includes/rijndael.php');
 require_once(ENANO_ROOT.'/includes/email.php');
-require_once(ENANO_ROOT.'/includes/search.php');
-require_once(ENANO_ROOT.'/includes/json.php');
 require_once(ENANO_ROOT.'/includes/json2.php');
-require_once(ENANO_ROOT.'/includes/math.php');
-require_once(ENANO_ROOT.'/includes/wikiengine/Tables.php');
 require_once(ENANO_ROOT.'/includes/pageprocess.php');
 require_once(ENANO_ROOT.'/includes/tagcloud.php');
 
@@ -198,6 +188,8 @@
 // END BACKGROUND AND ENVIRONMENT CHECKS
 //
 
+profiler_log('Background/environment checks done');
+
 //
 // MAIN API INITIALIZATION
 //
@@ -308,7 +300,6 @@
         <pre>
 &lt;?php
 define("ENANO_ALLOW_LOAD_NOLANG", 1);
-$_GET["title"] = "langinstall";
 require("includes/common.php");
 install_language("eng", "English", "English", ENANO_ROOT . "/language/english/enano.json");</pre>');
   }
--- a/includes/constants.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/constants.php	Wed Jul 02 19:36:44 2008 -0400
@@ -76,6 +76,13 @@
 define('IMAGE_TYPE_GIF', 2);
 define('IMAGE_TYPE_JPG', 3);
 
+// token types
+define('TOKEN_VARIABLE', 1);
+define('TOKEN_BOOLOP', 2);
+define('TOKEN_PARENTHLEFT', 3);
+define('TOKEN_PARENTHRIGHT', 4);
+define('TOKEN_NOT', 5);
+
 //
 // User types - don't touch these
 //
--- a/includes/dbal.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/dbal.php	Wed Jul 02 19:36:44 2008 -0400
@@ -32,7 +32,10 @@
   $debug = $debug[0]['file'] . ', line ' . $debug[0]['line'];
   echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>";
 }
- 
+
+global $db_sql_parse_time;
+$db_sql_parse_time = 0;
+
 class mysql {
   var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
   var $row = array();
@@ -309,79 +312,46 @@
   }
   
   /**
-   * Checks a SQL query for possible signs of injection attempts
+   * Performs heuristic analysis on a SQL query to check for known attack patterns.
    * @param string $q the query to check
    * @return bool true if query passed check, otherwise false
    */
   
   function check_query($q, $debug = false)
   {
-    if($debug) echo "\$db-&gt;check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n";
-    $sz = strlen($q);
-    $quotechar = false;
-    $quotepos  = 0;
-    $prev_is_quote = false;
-    $just_started = false;
-    for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) )
+    global $db_sql_parse_time;
+    $ts = microtime_float();
+    
+    // remove properly escaped quotes
+    $q = str_replace(array("\\\"", "\\'"), '', $q);
+    
+    // make sure quotes match
+    foreach ( array('"', "'") as $quote )
     {
-      $next = substr($q, $i+1, 1);
-      $next2 = substr($q, $i+2, 1);
-      $prev = substr($q, $i-1, 1);
-      $prev2 = substr($q, $i-2, 1);
-      if(isset($c) && in_array($c, Array('"', "'", '`')))
+      if ( get_char_count($q, $quote) % 2 == 1 )
       {
-        if($quotechar)
-        {
-          if (
-              ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') ||
-              ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c )
-            )
-          {
-            $quotechar = false;
-            if($debug) echo('$db-&gt;check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />');
-            $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q));
-            if($debug) echo('$db-&gt;check_query(): Filtered query: '.$q.'<br />');
-            $i = $quotepos;
-          }
-        }
-        else
-        {
-          $quotechar = $c;
-          $quotepos  = $i;
-          $just_started = true;
-        }
-        if($debug) echo '$db-&gt;check_query(): found quote char as pos: '.$i.'<br />';
-        continue;
-      }
-      $just_started = false;
-    }
-    if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1);
-    for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1))
-    {
-      if ( 
-           ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' )
-        || ( in_array($c, Array('"', "'", '`')) )
-         ) // Don't permit semicolons in mid-query, and never allow comments
-      {
-        // Injection attempt!
-        if($debug)
-        {
-          $e = '';
-          for($j=$i-5;$j<$i+5;$j++)
-          {
-            if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>';
-            else $e .= $c;
-          }
-          echo 'Injection attempt caught at pos: '.$i.'<br />';
-        }
+        // mismatched quotes
         return false;
       }
+      // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
+      $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
     }
+    $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
+    
+    // quotes are now matched out. does this string have a comment marker in it?
+    if ( strstr($q, '--') )
+    {
+      return false;
+    }
+    
     if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
     {
       if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
       return false;
     }
+    
+    $ts = microtime_float() - $ts;
+    $db_sql_parse_time += $ts;
     return true;
   }
   
@@ -1066,72 +1036,39 @@
   
   function check_query($q, $debug = false)
   {
-    if($debug) echo "\$db-&gt;check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n";
-    $sz = strlen($q);
-    $quotechar = false;
-    $quotepos  = 0;
-    $prev_is_quote = false;
-    $just_started = false;
-    for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) )
+    global $db_sql_parse_time;
+    $ts = microtime_float();
+    
+    // remove properly escaped quotes
+    $q = str_replace(array("\\\"", "\\'"), '', $q);
+    
+    // make sure quotes match
+    foreach ( array('"', "'") as $quote )
     {
-      $next = substr($q, $i+1, 1);
-      $next2 = substr($q, $i+2, 1);
-      $prev = substr($q, $i-1, 1);
-      $prev2 = substr($q, $i-2, 1);
-      if(isset($c) && in_array($c, Array('"', "'", '`')))
+      if ( get_char_count($q, $quote) % 2 == 1 )
       {
-        if($quotechar)
-        {
-          if (
-              ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') ||
-              ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c )
-            )
-          {
-            $quotechar = false;
-            if($debug) echo('$db-&gt;check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />');
-            $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q));
-            if($debug) echo('$db-&gt;check_query(): Filtered query: '.$q.'<br />');
-            $i = $quotepos;
-          }
-        }
-        else
-        {
-          $quotechar = $c;
-          $quotepos  = $i;
-          $just_started = true;
-        }
-        if($debug) echo '$db-&gt;check_query(): found quote char as pos: '.$i.'<br />';
-        continue;
-      }
-      $just_started = false;
-    }
-    if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1);
-    for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1))
-    {
-      if ( 
-           ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' )
-        || ( in_array($c, Array('"', "'", '`')) )
-         ) // Don't permit semicolons in mid-query, and never allow comments
-      {
-        // Injection attempt!
-        if($debug)
-        {
-          $e = '';
-          for($j=$i-5;$j<$i+5;$j++)
-          {
-            if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>';
-            else $e .= $c;
-          }
-          echo 'Injection attempt caught at pos: '.$i.'<br />';
-        }
+        // mismatched quotes
         return false;
       }
+      // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
+      $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
     }
+    $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
+    
+    // quotes are now matched out. does this string have a comment marker in it?
+    if ( strstr($q, '--') )
+    {
+      return false;
+    }
+    
     if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
     {
       if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
       return false;
     }
+    
+    $ts = microtime_float() - $ts;
+    $db_sql_parse_time += $ts;
     return true;
   }
   
--- a/includes/functions.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/functions.php	Wed Jul 02 19:36:44 2008 -0400
@@ -4325,6 +4325,10 @@
   
   foreach ( $profile as $i => $entry )
   {
+    // $time_since_last = $entry['time'] - $time_last;
+    // if ( $time_since_last < 0.01 )
+    //   continue;
+    
     $html .= "<!-- ########################################################## -->\n<tr>\n  <th colspan=\"2\">Event $i</th>\n</tr>";
     
     $html .= '<tr>' . "\n";
--- a/includes/pageprocess.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/pageprocess.php	Wed Jul 02 19:36:44 2008 -0400
@@ -157,8 +157,6 @@
     if ( !is_int($revision_id) )
       $revision_id = 0;
     
-    profiler_log("PageProcessor [{$namespace}:{$page_id}]: Ran initial checks");
-    
     $this->_setup( $page_id, $namespace, $revision_id );
   }
   
@@ -172,7 +170,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Started send process");
+    profiler_log('PageProcessor: send() called');
     
     if ( !$this->perms->get_permissions('read') )
     {
@@ -190,7 +188,6 @@
       {
         // Page isn't whitelisted, behave as normal
         $this->err_access_denied();
-        profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
         return false;
       }
     }
@@ -237,7 +234,6 @@
           if ( $this->password != $password )
           {
             $this->err_wrong_password();
-            profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
             return false;
           }
         }
@@ -245,6 +241,7 @@
     }
     if ( $this->page_exists && $this->namespace != 'Special' && $this->namespace != 'Admin' && $do_stats )
     {
+      require_once(ENANO_ROOT.'/includes/stats.php');
       doStats($this->page_id, $this->namespace);
     }
     if ( $this->namespace == 'Special' || $this->namespace == 'Admin' )
@@ -265,9 +262,7 @@
       $func_name = "page_{$this->namespace}_{$this->page_id}";
       if ( function_exists($func_name) )
       {
-        profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Calling special/admin page");
         $result = @call_user_func($func_name);
-        profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
         return $result;
       }
       else
@@ -287,7 +282,6 @@
           echo "<h2>$title</h2>
                 <p>$message</p>";
         }
-        profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
         return false;
       }
     }
@@ -372,13 +366,11 @@
         $template->init_vars($this);
       }
       
-      // die($this->page_id);
+      $text = $this->fetch_text();
       
-      $text = $this->fetch_text();
       if ( $text == 'err_no_text_rows' )
       {
         $this->err_no_rows();
-        profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
         return false;
       }
       else
@@ -409,7 +401,6 @@
         }
       }
     }
-    profiler_log("PageProcessor [{$this->namespace}:{$this->page_id}]: Finished send process");
   }
   
   /**
@@ -1029,6 +1020,7 @@
     global $lang;
     
     $text = $this->fetch_text();
+    
     $text = preg_replace('/([\s]*)__NOBREADCRUMBS__([\s]*)/', '', $text);
     $text = preg_replace('/([\s]*)__NOTOC__([\s]*)/', '', $text);
     
@@ -1228,7 +1220,6 @@
     }
     else
     {
-      
       $q = $db->sql_query('SELECT t.page_text, t.char_tag, l.time_id FROM '.table_prefix."page_text AS t\n"
                         . "  LEFT JOIN " . table_prefix . "logs AS l\n"
                         . "    ON ( l.page_id = t.page_id AND l.namespace = t.namespace )\n"
--- a/includes/paths.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/paths.php	Wed Jul 02 19:36:44 2008 -0400
@@ -113,7 +113,7 @@
       eval($cmd);
     }
     
-    $this->wiki_mode = (int)getConfig('wiki_mode')=='1';
+    $this->wiki_mode = ( getConfig('wiki_mode') == '1' ) ? 1 : 0;
     $this->template_cache = Array();
   }
   function parse_url($sanitize = true)
--- a/includes/render.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/render.php	Wed Jul 02 19:36:44 2008 -0400
@@ -217,6 +217,9 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
+    require_once(ENANO_ROOT.'/includes/wikiformat.php');
+    require_once(ENANO_ROOT.'/includes/wikiengine/Tables.php');
+    
     profiler_log("RenderMan: starting wikitext render");
     
     $random_id = md5( time() . mt_rand() );
@@ -474,13 +477,19 @@
   /**
    * Parses internal links (wikilinks) in a block of text.
    * @param string Text to process
+   * @param string Optional. If included will be used as a template instead of using the default syntax.
    * @return string
    */
   
-  public static function parse_internal_links($text)
+  public static function parse_internal_links($text, $tplcode = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
+    if ( is_string($tplcode) )
+    {
+      $parser = $template->makeParserText($tplcode);
+    }
+    
     // stage 1 - links with alternate text
     preg_match_all('/\[\[([^\[\]<>\{\}\|]+)\|(.+?)\]\]/', $text, $matches);
     foreach ( $matches[0] as $i => $match )
@@ -493,7 +502,19 @@
       $quot = '"';
       $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"';
       
-      $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+      if ( $tplcode )
+      {
+        $parser->assign_vars(array(
+            'HREF' => $url,
+            'FLAGS' => $exists,
+            'TEXT' => $inner_text
+          ));
+        $link = $parser->run();
+      }
+      else
+      {
+        $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+      }
       
       $text = str_replace($match, $link, $text);
     }
@@ -510,7 +531,19 @@
       $quot = '"';
       $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"';
       
-      $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+      if ( $tplcode )
+      {
+        $parser->assign_vars(array(
+            'HREF' => $url,
+            'FLAGS' => $exists,
+            'TEXT' => $inner_text
+          ));
+        $link = $parser->run();
+      }
+      else
+      {
+        $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+      }
       
       $text = str_replace($match, $link, $text);
     }
@@ -845,6 +878,7 @@
   public static function diff($str1, $str2)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
+    require_once(ENANO_ROOT.'/includes/diff.php');
     $str1 = explode("\n", $str1);
     $str2 = explode("\n", $str2);
     $diff = new Diff($str1, $str2);
--- a/includes/sessions.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/sessions.php	Wed Jul 02 19:36:44 2008 -0400
@@ -586,8 +586,8 @@
         . '    ON g.group_id=m.group_id' . "\n"
         . '  WHERE ( m.user_id='.$this->user_id.'' . "\n" 
         . '    OR g.group_name=\'Everyone\')' . "\n"
-        . '    ' . ( enano_version() == '1.0RC1' ? '' : 'AND ( m.pending != 1 OR m.pending IS NULL )' ) . '' . "\n"
-        . '  ORDER BY group_id ASC;'); // Make sure "Everyone" comes first so the permissions can be overridden
+        . '    ' . ( /* quick hack for upgrade compatibility reasons */ enano_version() == '1.0RC1' ? '' : 'AND ( m.pending != 1 OR m.pending IS NULL )' ) . '' . "\n"
+        . '  ORDER BY group_id ASC;'); // The ORDER BY is to make sure "Everyone" comes first so the permissions can be overridden
       if($row = $db->fetchrow())
       {
         do {
@@ -2801,6 +2801,8 @@
     $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];
     
+    profiler_log("session: fetched ACLs for page {$namespace}:{$page_id}");
+    
     return $object;
   }
   
@@ -3020,48 +3022,12 @@
     // Cache the sitewide permissions for later use
     $this->acl_base_cache = $this->perms;
     
-    // Eliminate types that don't apply to this namespace
-    foreach ( $this->perms AS $i => $perm )
-    {
-      if ( !in_array ( $paths->namespace, $this->acl_scope[$i] ) && !in_array('All', $this->acl_scope[$i]) )
-      {
-        unset($this->perms[$i]);
-      }
-    }
-    
-    // PAGE group info
-    $pg_list = $paths->get_page_groups($paths->page_id, $paths->namespace);
-    $pg_info = '';
-    foreach ( $pg_list as $g_id )
-    {
-      $pg_info .= ' ( page_id=\'' . $g_id . '\' AND namespace=\'__PageGroup\' ) OR';
-    }
+    profiler_log('session: base ACL set calculated');
     
-    // Build a query to grab ACL info
-    $bs = 'SELECT rules,target_type,target_id FROM '.table_prefix.'acl WHERE ( ';
-    $q = Array();
-    $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
-    if(count($this->groups) > 0)
-    {
-      foreach($this->groups as $g_id => $g_name)
-      {
-        $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
-      }
-    }
-    // The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
-    // permissions to override group permissions.
-    $bs .= implode(" OR\n    ", $q) . " )\n  AND (" . $pg_info . ' ( page_id=\''.$db->escape($paths->page_id).'\' AND namespace=\''.$db->escape($paths->namespace).'\' ) )     
-      ORDER BY target_type ASC, page_id ASC, namespace ASC;';
-    $q = $this->sql($bs);
-    if ( $row = $db->fetchrow() )
-    {
-      do {
-        $rules = $this->string_to_perm($row['rules']);
-        $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
-        $this->acl_merge_with_current($rules, $is_everyone);
-      } while ( $row = $db->fetchrow() );
-    }
-    
+    // Load and calculate permissions for the current page
+    $page_acl = $this->fetch_page_acl($paths->page_id, $paths->namespace);
+    $this->perms = $page_acl->perms;
+    $this->acl_defaults_used = $page_acl->acl_defaults_used;
   }
   
   /**
@@ -3558,6 +3524,8 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     // Setup EnanoMath and Diffie-Hellman
+    require_once(ENANO_ROOT.'/includes/math.php');
+    
     global $dh_supported;
     $dh_supported = true;
     try
--- a/includes/template.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/includes/template.php	Wed Jul 02 19:36:44 2008 -0400
@@ -86,23 +86,9 @@
     // List out all CSS files for this theme
     foreach ( $this->theme_list as $i => &$theme )
     {
-      $theme['css'] = array();
-      $dir = ENANO_ROOT . "/themes/{$theme['theme_id']}/css";
-      if ( $dh = @opendir($dir) )
-      {
-        while ( ( $file = @readdir($dh) ) !== false )
-        {
-          if ( preg_match('/\.css$/', $file) )
-            $theme['css'][] = preg_replace('/\.css$/', '', $file);
-        }
-        closedir($dh);
-      }
-      // No CSS files? If so, nuke it.
-      if ( count($theme['css']) < 1 )
-      {
-        unset($this->theme_list[$i]);
-      }
+      $theme['css'] = $this->get_theme_css_files($theme['theme_id']);
     }
+    unset($theme);
     $this->theme_list = array_values($this->theme_list);
     // Create associative array of themes
     foreach ( $this->theme_list as $i => &$theme )
@@ -116,6 +102,33 @@
   }
   
   /**
+   * Gets the list of available CSS files (styles) for the specified theme.
+   * @param string Theme ID
+   * @return array
+   */
+  
+  function get_theme_css_files($theme_id)
+  {
+    $css = array();
+    $dir = ENANO_ROOT . "/themes/{$theme_id}/css";
+    if ( $dh = @opendir($dir) )
+    {
+      while ( ( $file = @readdir($dh) ) !== false )
+      {
+        if ( preg_match('/\.css$/', $file) )
+          $css[] = preg_replace('/\.css$/', '', $file);
+      }
+      closedir($dh);
+    }
+    // No CSS files? If so, nuke it.
+    if ( count($css) < 1 )
+    {
+      unset($this->theme_list[$theme_id]);
+    }
+    return $css;
+  }
+  
+  /**
    * Failsafe constructor for upgrades.
    */
   
@@ -566,8 +579,6 @@
     // PAGE TOOLBAR (on-page controls/actions)
     //
     
-    profiler_log('template: var init: finished initial setup, starting toolbar');
-    
     // Initialize the toolbar
     $tb = '';
     
@@ -948,8 +959,6 @@
     // OTHER SWITCHES
     //
     
-    profiler_log('template: var init: finshed toolbar, starting other switches');
-    
     $is_opera = (isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'Opera')) ? true : false;
     
     $this->tpl_bool = Array(
@@ -1043,8 +1052,6 @@
     
     $admin_link = $parser->run();
     
-    profiler_log('template: var init: finished sidebar/misc processing, starting dynamic vars and finalization');
-    
     $SID = ($session->sid_super) ? $session->sid_super : '';
     
     $urlname_clean = str_replace('\'', '\\\'', str_replace('\\', '\\\\', dirtify_page_id($local_fullpage)));
@@ -1111,8 +1118,6 @@
       }
       $js_dynamic .= "\n    //]]>\n    </script>";
       
-    profiler_log('template: var init: finished JS dynamic vars and assigning final var set');
-    
     $tpl_strings = Array(
       'PAGE_NAME'=>htmlspecialchars($local_cdata['name']),
       'PAGE_URLNAME'=> $urlname_clean,
@@ -1154,14 +1159,14 @@
     
     $this->assign_vars($tpl_strings, true);
     
+    profiler_log('template: var init: finished toolbar building and initial assign()');
+    
     //
     // COMPILE THE SIDEBAR
     //
     
     // This is done after the big assign_vars() so that sidebar code has access to the newly assigned variables
     
-    profiler_log('template: var init: finished final var set, executing and applying sidebar templates');
-    
     list($this->tpl_strings['SIDEBAR_LEFT'], $this->tpl_strings['SIDEBAR_RIGHT'], $min) = $this->fetch_sidebar();
     $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;
@@ -1299,7 +1304,11 @@
       $t = str_replace('[[EnanoPoweredLinkLong]]', $lang->get('page_enano_powered_long', array('about_uri' => $this->tpl_strings['URL_ABOUT_ENANO'])), $t);
       
       if ( defined('ENANO_DEBUG') )
+      {
         $t = str_replace('</body>', '<div id="profile" style="margin: 10px;">' . profiler_make_html() . '</div></body>', $t);
+        // ob_end_clean();
+        // return profiler_make_html();
+      }
       
       return $t;
     }
@@ -1381,8 +1390,28 @@
       $this->init_vars();
     }
     
+    $cache_file = ENANO_ROOT . '/cache/' . $this->theme . '-' . str_replace('/', '-', $file) . '.php';
+    if ( file_exists($cache_file) )
+    {
+      // this is about the time of the breaking change to cache file format
+      if ( filemtime($cache_file) > 1215038089 )
+      {
+        $result = @include($cache_file);
+        if ( isset($md5) )
+        {
+          if ( $md5 == md5_file(ENANO_ROOT . "/themes/{$this->theme}/$file") )
+          {
+            $result = $this->compile_template_text_post($result);
+            return $result;
+          }
+        }
+      }
+    }
+    
     $compiled = $this->compile_template($file);
-    return eval($compiled);
+    $result = eval($compiled);
+    
+    return $result;
   }
   
   /**
@@ -1475,30 +1504,7 @@
                            </p>');
     }
     
-    // Check for cached copy
-    // This will make filenames in the pattern of theme-file.tpl.php
-    $cache_file = ENANO_ROOT . '/cache/' . $this->theme . '-' . str_replace('/', '-', $filename) . '.php';
-    
-    // Only use cached copy if caching is enabled
-    //   (it is enabled by default I think)
-    if ( file_exists($cache_file) && getConfig('cache_thumbs') == '1' )
-    {
-      // Cache files are auto-generated, but otherwise are normal PHP files
-      include($cache_file);
-      
-      // Fetch content of the ORIGINAL
-      $text = file_get_contents($tpl_file_fullpath);
-      
-      // $md5 will be set by the cached file
-      // This makes sure that a cached copy of the template is used only if its MD5
-      // matches the MD5 of the file that the compiled file was compiled from.
-      if ( isset($md5) && $md5 == md5($text) )
-      {
-        return $this->compile_template_text_post(str_replace('\\"', '"', $tpl_text));
-      }
-    }
-    
-    // We won't use the cached copy here
+    // We won't use the cached copy here.
     $text = file_get_contents($tpl_file_fullpath);
     
     // This will be used later when writing the cached file
@@ -1507,6 +1513,9 @@
     // Preprocessing and checks complete - compile the code
     $text = $this->compile_tpl_code($text);
     
+    // Generate cache filename
+    $cache_file = ENANO_ROOT . '/cache/' . $this->theme . '-' . str_replace('/', '-', $filename) . '.php';
+    
     // Perhaps caching is enabled and the admin has changed the template?
     if ( is_writable( ENANO_ROOT . '/cache/' ) && getConfig('cache_thumbs') == '1' )
     {
@@ -1517,19 +1526,22 @@
         return $text;
       }
       
-      // Escape the compiled code so it can be eval'ed
-      $text_escaped = addslashes($text);
-      $notice = <<<EOF
+      // Final contents of cache file
+      $file_contents = <<<EOF
+<?php
 
 /*
  * NOTE: This file was automatically generated by Enano and is based on compiled code. Do not edit this file.
  * If you edit this file, any changes you make will be lost the next time the associated source template file is edited.
  */
 
+\$md5 = '$md5';
+
+$text
 EOF;
       // This is really just a normal PHP file that sets a variable or two and exits.
       // $tpl_text actually will contain the compiled code
-      fwrite($h, '<?php ' . $notice . ' $md5 = \'' . $md5 . '\'; $tpl_text = \'' . $text_escaped . '\'; ?>');
+      fwrite($h, $file_contents);
       fclose($h);
     }
     
@@ -1613,6 +1625,26 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
+    $START = microtime_float();
+    
+    // localize the whole string first
+    preg_match_all('/\{lang:([a-z0-9]+_[a-z0-9_]+)\}/', $message, $matches);
+    foreach ( $matches[1] as $i => $string_id )
+    {
+      $string = $lang->get($string_id);
+      $string = str_replace('\\', '\\\\', $string);
+      $string = str_replace('\'', '\\\'', $string);
+      $message = str_replace_once($matches[0][$i], $string, $message);
+    }
+    
+    // first: the hackish optimization -
+    // if it's only a bunch of letters, numbers and spaces, just skip this sh*t.
+    
+    if ( preg_match('/^[\w\s\.]*$/i', $message) )
+    {
+      return $message;
+    }
+    
     $filter_links = false;
     $tplvars = $this->extract_vars($filename);
     if($session->sid_super) $as = htmlspecialchars(urlSeparator).'auth='.$session->sid_super;
@@ -1636,185 +1668,45 @@
     
     // Conditionals
     
-    preg_match_all('#\{if ([A-Za-z0-9_ \(\)&\|\!-]*)\}(.*?)\{\/if\}#is', $message, $links);
-    
-    // Temporary exception from coding standards - using tab length of 4 here for clarity
-    for ( $i = 0; $i < sizeof($links[1]); $i++ )
-    {
-        $condition =& $links[1][$i];
-        $message = str_replace('{if '.$condition.'}'.$links[2][$i].'{/if}', '{CONDITIONAL:'.$i.':'.$random_id.'}', $message);
-        
-        // Time for some manual parsing...
-        $chk = false;
-        $current_id = '';
-        $prn_level = 0;
-        // Used to keep track of where we are in the conditional
-        // Object of the game: turn {if this && ( that OR !something_else )} ... {/if} into if( ( isset($this->tpl_bool['that']) && $this->tpl_bool['that'] ) && ...
-        // Method of attack: escape all variables, ignore all else. Non-valid code is filtered out by a regex above.
-        $in_var_now = true;
-        $in_var_last = false;
-        $current_var = '';
-        $current_var_start_pos = 0;
-        $current_var_end_pos     = 0;
-        $j = -1;
-        $condition = $condition . ' ';
-        $d = strlen($condition);
-        while($j < $d)
-        {
-            $j++;
-            $in_var_last = $in_var_now;
-            
-            $char = substr($condition, $j, 1);
-            $in_var_now = ( preg_match('#^([A-z0-9_]*){1}$#', $char) ) ? true : false;
-            if(!$in_var_last && $in_var_now)
-            {
-                $current_var_start_pos = $j;
-            }
-            if($in_var_last && !$in_var_now)
-            {
-                $current_var_end_pos = $j;
-            }
-            if($in_var_now)
-            {
-                $current_var .= $char;
-                continue;
-            }
-            // OK we are not inside of a variable. That means that we JUST hit the end because the counter ($j) will be advanced to the beginning of the next variable once processing here is complete.
-            if($char != ' ' && $char != '(' && $char != ')' && $char != 'A' && $char != 'N' && $char != 'D' && $char != 'O' && $char != 'R' && $char != '&' && $char != '|' && $char != '!' && $char != '<' && $char != '>' && $char != '0' && $char != '1' && $char != '2' && $char != '3' && $char != '4' && $char != '5' && $char != '6' && $char != '7' && $char != '8' && $char != '9')
-            {
-                // XSS attack! Bail out
-                $errmsg    = '<p><b>Error:</b> Syntax error (possibly XSS attack) caught in template code:</p>';
-                $errmsg .= '<pre>';
-                $errmsg .= '{if '.htmlspecialchars($condition).'}';
-                $errmsg .= "\n    ";
-                for ( $k = 0; $k < $j; $k++ )
-                {
-                    $errmsg .= " ";
-                }
-                // Show position of error
-                $errmsg .= '<span style="color: red;">^</span>';
-                $errmsg .= '</pre>';
-                $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $errmsg, $message);
-                continue 2;
-            }
-            if($current_var != '')
-            {
-                $cd = '( isset($this->tpl_bool[\''.$current_var.'\']) && $this->tpl_bool[\''.$current_var.'\'] )';
-                $cvt = substr($condition, 0, $current_var_start_pos) . $cd . substr($condition, $current_var_end_pos, strlen($condition));
-                $j = $j + strlen($cd) - strlen($current_var);
-                $current_var = '';
-                $condition = $cvt;
-                $d = strlen($condition);
-            }
-        }
-        $condition = substr($condition, 0, strlen($condition)-1);
-        $condition = '$chk = ( '.$condition.' ) ? true : false;';
-        eval($condition);
-        
-        if($chk)
-        {
-            if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
-            else $c = $links[2][$i];
-            $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-        }
-        else
-        {
-            if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
-            else $c = '';
-            $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-        }
-    }
-    
-    preg_match_all('#\{!if ([A-Za-z_-]*)\}(.*?)\{\/if\}#is', $message, $links);
-    
-    for($i=0;$i<sizeof($links[1]);$i++)
-    {
-      $message = str_replace('{!if '.$links[1][$i].'}'.$links[2][$i].'{/if}', '{CONDITIONAL:'.$i.':'.$random_id.'}', $message);
-      if(isset($this->tpl_bool[$links[1][$i]]) && $this->tpl_bool[$links[1][$i]]) {
-        if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
-        else $c = '';
-        $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-      } else {
-        if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
-        else $c = $links[2][$i];
-        $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-      }
-    }
-    
-    preg_match_all('/\{lang:([a-z0-9]+_[a-z0-9_]+)\}/', $message, $matches);
-    foreach ( $matches[1] as $i => $string_id )
-    {
-      $string = $lang->get($string_id);
-      $string = str_replace('\\', '\\\\', $string);
-      $string = str_replace('\'', '\\\'', $string);
-      $message = str_replace_once($matches[0][$i], $string, $message);
-    }
+    $message = $this->twf_parse_conditionals($message);
     
     /*
      * HTML RENDERER
      */
      
     // Images
-    $j = preg_match_all('#\[\[:'.$paths->nslist['File'].'([\w\s0-9_\(\)!@%\^\+\|\.-]+?)\]\]#is', $message, $matchlist);
-    $matches = Array();
-    $matches['images'] = $matchlist[1];
-    for($i=0;$i<sizeof($matchlist[1]);$i++)
-    {
-      if(isPage($paths->nslist['File'].$matches['images'][$i]))
-      {
-        $message = str_replace('[[:'.$paths->nslist['File'].$matches['images'][$i].']]',
-                               '<img alt="'.$matches['images'][$i].'" style="border: 0" src="'.makeUrlNS('Special', 'DownloadFile/'.$matches['images'][$i]).'" />',
-                               $message);
-      }
-    }
+    $message = RenderMan::process_image_tags($message, $taglist);
+    $message = RenderMan::process_imgtags_stage2($message, $taglist);
     
     // Internal links
-    
-    $text_parser = $this->makeParserText($tplvars['sidebar_button']);
-    
-    preg_match_all("#\[\[([^\|\]\n\a\r\t]*?)\]\]#is", $message, $il);
-    for($i=0;$i<sizeof($il[1]);$i++)
-    {
-      $href = makeUrl(str_replace(' ', '_', $il[1][$i]), null, true);
-      $text_parser->assign_vars(Array(  
-          'HREF'  => $href,
-          'FLAGS' => '',
-          'TEXT'  => $il[1][$i]
-        ));
-      $message = str_replace("[[{$il[1][$i]}]]", $text_parser->run(), $message);
-    }
-    
-    preg_match_all('#\[\[([^\|\]\n\a\r\t]*?)\|([^\]\r\n\a\t]*?)\]\]#is', $message, $il);
-    for($i=0;$i<sizeof($il[1]);$i++)
-    {
-      $href = makeUrl(str_replace(' ', '_', $il[1][$i]), null, true);
-      $text_parser->assign_vars(Array(
-          'HREF'  => $href,
-          'FLAGS' => '',
-          'TEXT'  => $il[2][$i]
-        ));
-      $message = str_replace("[[{$il[1][$i]}|{$il[2][$i]}]]", $text_parser->run(), $message);
-    }
+    $message = RenderMan::parse_internal_links($message, $tplvars['sidebar_button']);
     
     // External links
-    // $message = preg_replace('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?) ([^\]]+)\\]#', '<a href="\\1://\\2">\\3</a><br style="display: none;" />', $message);
-    // $message = preg_replace('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?)\\]#', '<a href="\\1://\\2">\\1://\\2</a><br style="display: none;" />', $message);
     
-    preg_match_all('/\[((https?|ftp|irc):\/\/([^@\s\]"\':]+)?((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?) ([^\]]+)\]/is', $message, $ext_link);
-    
-    // die('<pre>' . htmlspecialchars( print_r($ext_link, true) ) . '</pre>');
+    $url_regexp = <<<EOF
+(
+  (?:https?|ftp|irc):\/\/                            # protocol
+  (?:[^@\s\]"\':]+@)?                                # username (FTP only but whatever)
+  (?:(?:(?:[a-z0-9-]+\.)*)[a-z0-9-]+)                # hostname
+  (?:\/[A-z0-9_%\|~`!\!@#\$\^&?=\*\(\):;\.,\/-]*)? # path
+)
+EOF;
+
+    $text_parser = $this->makeParserText($tplvars['sidebar_button']);
+
+    preg_match_all('/\[' . $url_regexp . '[ ]([^\]]+)\]/isx', $message, $ext_link);
     
     for ( $i = 0; $i < count($ext_link[0]); $i++ )
     {
       $text_parser->assign_vars(Array(  
           'HREF'  => $ext_link[1][$i],
           'FLAGS' => '',
-          'TEXT'  => $ext_link[16][$i]
+          'TEXT'  => $ext_link[2][$i]
         ));
       $message = str_replace($ext_link[0][$i], $text_parser->run(), $message);
     }
     
-    preg_match_all('/\[((https?|ftp|irc):\/\/([^@\s\]"\':]+)?((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?)\]/is', $message, $ext_link);
+    preg_match_all('/\[' . $url_regexp . '\]/is', $message, $ext_link);
     
     for ( $i = 0; $i < count($ext_link[0]); $i++ )
     {
@@ -1826,34 +1718,182 @@
       $message = str_replace($ext_link[0][$i], $text_parser->run(), $message);
     }
     
-    $parser1 = $this->makeParserText($tplvars['sidebar_section']);
-    $parser2 = $this->makeParserText($tplvars['sidebar_section_raw']);
-                            
-    preg_match_all('#\{slider(2|)=([^\}]*?)\}(.*?)\{\/slider(2|)\}#is',  $message, $sb);
-    
-    // Modified to support the sweet new template var system
-    for($i=0;$i<sizeof($sb[1]);$i++)
-    {
-      $p = ($sb[1][$i] == '2') ? $parser2 : $parser1;
-      $p->assign_vars(Array('TITLE'=>$sb[2][$i],'CONTENT'=>$sb[3][$i]));
-      $message = str_replace("{slider{$sb[1][$i]}={$sb[2][$i]}}{$sb[3][$i]}{/slider{$sb[4][$i]}}", $p->run(), $message);
-    }
+    $TIME = microtime_float() - $START;
     
     /*
-    Extras ;-)
-    $message = preg_replace('##is', '', $message);
-    $message = preg_replace('##is', '', $message);
-    $message = preg_replace('##is', '', $message);
-    $message = preg_replace('##is', '', $message);
-    $message = preg_replace('##is', '', $message);
+    if ( $TIME > 0.02 )
+    {
+      echo 'template: tplWikiFormat took a while for this one. string dump:<pre>';
+      echo htmlspecialchars($message);
+      echo '</pre>';
+    }
     */
     
-    //die('<pre>'.htmlspecialchars($message).'</pre>');
-    //eval($message); exit;
+    return $message;
+  }
+  
+  /**
+   * Parses conditional {if} blocks in sidebars and other tplWikiFormatted things
+   * @param string A string potentially containing conditional blocks
+   * @return string Processed string
+   */
+  
+  function twf_parse_conditionals($message)
+  {
+    if ( !preg_match_all('/\{(!?)if ([a-z0-9_\(\)\|&! ]+)\}(.*?)(?:\{else\}(.*?))?\{\/if\}/is', $message, $matches) )
+    {
+      return $message;
+    }
+    foreach ( $matches[0] as $match_id => $full_block )
+    {
+      // 1 = "not" flag
+      // 2 = condition
+      // 3 = if true
+      // 4 = else
+      $condresult = $this->process_condition($matches[2][$match_id]);
+      if ( !empty($matches[1][$match_id]) )
+      {
+        if ( $condresult == 1 )
+          $condresult = 2;
+        else if ( $condresult == 2 )
+          $condresult = 1;
+      }
+      switch($condresult)
+      {
+        case 1:
+          // evaluated to false
+          $message = str_replace_once($full_block, $matches[4][$match_id], $message);
+          break;
+        case 2:
+          // evaluated to true
+          $message = str_replace_once($full_block, $matches[3][$match_id], $message);
+          break;
+        case 3:
+          $message = str_replace_once($full_block, "Syntax error: mismatched parentheses (" . htmlspecialchars($matches[2][$match_id]) . ")<br />\n", $message);
+          break;
+        case 4:
+          $message = str_replace_once($full_block, "Syntax error: illegal character (" . htmlspecialchars($matches[2][$match_id]) . ")<br />\n", $message);
+          break;
+        case 5:
+          $message = str_replace_once($full_block, "Syntax error: illegal sequence (" . htmlspecialchars($matches[2][$match_id]) . ")<br />\n", $message);
+          break;
+      }
+    }
     return $message;
   }
   
   /**
+   * Inner-loop parser for a conditional block. Verifies a string condition to make sure it's syntactically correct, then returns what it evaluates to.
+   * Return values:
+   *   1 - string evaluates to true
+   *   2 - string evaluates to false
+   *   3 - Syntax error - mismatched parentheses
+   *   4 - Syntax error - unknown token
+   *   5 - Syntax error - invalid sequence
+   * @param string
+   * @return int
+   * 
+   */
+  
+  function process_condition($condition)
+  {
+    // make sure parentheses are matched
+    $parentheses = preg_replace('/[^\(\)]/', '', $condition);
+    if ( !empty($parentheses) )
+    {
+      $i = 0;
+      $parentheses = enano_str_split($parentheses);
+      foreach ( $parentheses as $chr )
+      {
+        $inc = ( $chr == '(' ) ? 1 : -1;
+        $i += $inc;
+      }
+      if ( $i != 0 )
+      {
+        // mismatched parentheses
+        return 3;
+      }
+    }
+    // sequencer check
+    // first, pad all sequences of characters with spaces
+    $seqcheck = preg_replace('/([a-z0-9_]+)/i', '\\1 ', $condition);
+    $seqcheck = preg_replace('/([&|()!])/i', '\\1 ', $seqcheck);
+    // now shrink all spaces to one space each
+    $seqcheck = preg_replace('/[ ]+/', ' ', $seqcheck);
+    
+    // explode it. the allowed sequences are:
+    //   - TOKEN_NOT + TOKEN_VARIABLE
+    //   - TOKEN_NOT + TOKEN_PARENTHLEFT
+    //   - TOKEN_BOOLOP + TOKEN_NOT
+    //   - TOKEN_PARENTHRIGHT + TOKEN_NOT
+    //   - TOKEN_VARIABLE + TOKEN_BOOLOP
+    //   - TOKEN_BOOLOP + TOKEN_PARENTHLEFT
+    //   - TOKEN_PARENTHLEFT + TOKEN_VARIABLE
+    //   - TOKEN_BOOLOP + TOKEN_VARIABLE
+    //   - TOKEN_VARIABLE + TOKEN_PARENTHRIGHT
+    //   - TOKEN_PARENTHRIGHT + TOKEN_BOOLOP
+    $seqcheck = explode(' ', trim($seqcheck));
+    $last_item = TOKEN_BOOLOP;
+    foreach ( $seqcheck as $i => $token )
+    {
+      // determine type
+      if ( $token == '(' )
+      {
+        $type = TOKEN_PARENTHLEFT;
+      }
+      else if ( $token == ')' )
+      {
+        $type = TOKEN_PARENTHRIGHT;
+      }
+      else if ( $token == '!' )
+      {
+        $type = TOKEN_NOT;
+      }
+      else if ( strtolower($token) == 'and' || strtolower($token) == 'or' || $token == '&&' || $token == '||' )
+      {
+        $type = TOKEN_BOOLOP;
+      }
+      else if ( preg_match('/^[a-z0-9_]+$/i', $token) )
+      {
+        $type = TOKEN_VARIABLE;
+        // at this point it's considered safe to wrap it
+        $seqcheck[$i] = "( isset(\$this->tpl_bool['$token']) && \$this->tpl_bool['$token'] )";
+      }
+      else
+      {
+        // syntax error - doesn't match known token types
+        return 4;
+      }
+      // inner sequence check
+      if (
+           ( $last_item == TOKEN_BOOLOP && $type == TOKEN_NOT ) ||
+           ( $last_item == TOKEN_PARENTHRIGHT && $type == TOKEN_NOT ) ||
+           ( $last_item == TOKEN_NOT && $type == TOKEN_VARIABLE ) ||
+           ( $last_item == TOKEN_NOT && $type == TOKEN_PARENTHLEFT ) ||
+           ( $last_item == TOKEN_VARIABLE && $type == TOKEN_BOOLOP ) ||
+           ( $last_item == TOKEN_BOOLOP && $type == TOKEN_PARENTHLEFT ) ||
+           ( $last_item == TOKEN_PARENTHLEFT && $type == TOKEN_VARIABLE ) ||
+           ( $last_item == TOKEN_BOOLOP && $type == TOKEN_VARIABLE ) ||
+           ( $last_item == TOKEN_VARIABLE && $type == TOKEN_PARENTHRIGHT ) ||
+           ( $last_item == TOKEN_PARENTHRIGHT && $type == TOKEN_BOOLOP )
+         )
+      {
+        // sequence is good, continue
+      }
+      else
+      {
+        // sequence is invalid, break out
+        return 5;
+      }
+      $last_item = $type;
+    }
+    // passed all checks
+    $seqcheck = implode(' ', $seqcheck);
+    $result = eval("return ( $seqcheck ) ? true : false;");
+    return ( $result ) ? 2 : 1;
+  }
+  
+  /**
    * Print a text field that auto-completes a username entered into it.
    * @param string $name - the name of the form field
    * @return string
@@ -1977,6 +2017,25 @@
     $left = '';
     $right = '';
     
+    // check the cache
+    $cache_enable = getConfig('cache_thumbs') == '1' && !$session->user_logged_in;
+    $cache_file = ENANO_ROOT . "/cache/cache_anon_sidebar.php";
+    $cache_fresh = intval(getConfig('sidebar_anon_cache_time') + 600) >= time();
+    if ( $cache_enable && $cache_fresh )
+    {
+      @include($cache_file);
+      if ( isset($sidebar_cache) )
+      {
+        // we loaded the cache!
+        foreach ( $sidebar_cache as $i => $_ )
+        {
+          $block =& $sidebar_cache[$i];
+          $block = str_replace('$USERNAME$', $session->username, $block);
+        }
+        return $sidebar_cache;
+      }
+    }
+    
     if ( !$this->fetch_block('Links') )
       $this->initLinksWidget();
     
@@ -1988,9 +2047,11 @@
     
     if(isset($vars['sidebar_top'])) 
     {
-      $left  .= $this->parse($vars['sidebar_top']);
-      $right .= $this->parse($vars['sidebar_top']);
+      $top = $this->parse($vars['sidebar_top']);
+      $left  .= $top;
+      $right .= $top;
     }
+    
     while($row = $db->fetchrow())
     {
       switch($row['block_type'])
@@ -2033,19 +2094,42 @@
     $db->free_result();
     if(isset($vars['sidebar_bottom'])) 
     {
-      $left  .= $this->parse($vars['sidebar_bottom']);
-      $right .= $this->parse($vars['sidebar_bottom']);
+      $bottom = $this->parse($vars['sidebar_bottom']);
+      $left  .= $bottom;
+      $right .= $bottom;
     }
     $min = '';
     if(isset($vars['sidebar_top'])) 
     {
-      $min .= $this->parse($vars['sidebar_top']);
+      $min .= $top;
     }
     if(isset($vars['sidebar_bottom']))
     {
-      $min .= $this->parse($vars['sidebar_bottom']);
+      $min .= $bottom;
     }
-    return Array($left, $right, $min);
+    $return = Array($left, $right, $min);
+    if ( $cache_enable )
+    {
+      $cachestore = Language::var_export_string($return);
+      $cachestore = str_replace($session->username, '$USERNAME$', $cachestore);
+      $cachestore = <<<EOF
+<?php
+/**
+ * Automatically generated cache of the sidebar for guests.
+ * Do not modify this, it is refreshed every 15 minutes.
+ */
+
+\$sidebar_cache = $cachestore;
+EOF;
+      $fh = @fopen($cache_file, 'w');
+      if ( $fh )
+      {
+        fwrite($fh, $cachestore);
+        fclose($fh);
+      }
+      setConfig('sidebar_anon_cache_time', time());
+    }
+    return $return;
   }
   
   function initLinksWidget()
@@ -2053,6 +2137,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     // SourceForge/W3C buttons
     $ob = Array();
+    // FIXME: l10n
     $admintitle = ( $session->user_level >= USER_LEVEL_ADMIN ) ? 'title="You may disable this button in the admin panel under General Configuration."' : '';
     if(getConfig('sflogo_enabled')=='1')
     {
@@ -2676,11 +2761,8 @@
   
   function process_template($file)
   {
-    profiler_log("[template_nodb] STARTED eval of file $file");
     $compiled = $this->compile_template($file);
-    profiler_log("[template_nodb] COMPILED file $file");
     $result = eval($compiled);
-    profiler_log("[template_nodb] FINISHED eval of file $file");
     return $result;
   }
   
--- a/index.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/index.php	Wed Jul 02 19:36:44 2008 -0400
@@ -19,7 +19,7 @@
   define('ENANO_INTERFACE_INDEX', '');
   
   // For the mighty and brave.
-  // define('ENANO_DEBUG', '');
+  define('ENANO_DEBUG', '');
  
   // Set up gzip encoding before any output is sent
   
@@ -65,6 +65,7 @@
       break;
     case 'comments':
       $template->header();
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $sub = ( isset ($_GET['sub']) ) ? $_GET['sub'] : false;
       switch($sub)
       {
@@ -121,6 +122,7 @@
         redirect(makeUrl($paths->page), '', '', 0);
         break;
       }
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(isset($_POST['_save']))
       {
         $captcha_valid = true;
@@ -258,6 +260,7 @@
       $template->footer();
       break;
     case 'history':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $hist = PageUtils::histlist($paths->page_id, $paths->namespace);
       $template->header();
       echo $hist;
@@ -286,6 +289,7 @@
       $template->footer();
       break;
     case 'catedit':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(isset($_POST['__enanoSaveButton']))
       {
         unset($_POST['__enanoSaveButton']);
@@ -313,6 +317,7 @@
       break;
     case 'protect':
       if (!isset($_REQUEST['level'])) die_friendly('Invalid request', '<p>No protection level specified</p>');
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(!empty($_POST['reason']))
       {
         if(!preg_match('#^([0-2]*){1}$#', $_POST['level'])) die_friendly('Error protecting page', '<p>Request validation failed</p>');
@@ -349,6 +354,7 @@
       $template->footer();
       break;
     case 'rename':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(!empty($_POST['newname']))
       {
         $r = PageUtils::rename($paths->page_id, $paths->namespace, $_POST['newname']);
@@ -370,6 +376,7 @@
       {
         die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('etc_access_denied') . '</p>');
       }
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(isset($_POST['_downthejohn']))
       {
         $template->header();
@@ -388,6 +395,7 @@
       $template->footer();
       break;
     case 'delvote':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(isset($_POST['_ballotbox']))
       {
         $template->header();
@@ -416,6 +424,7 @@
       $template->footer();
       break;
     case 'resetvotes':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(!$session->get_permissions('vote_reset'))
       {
         die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('etc_access_denied') . '</p>');
@@ -442,6 +451,7 @@
       {
         die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('etc_access_denied') . '</p>');
       }
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       if(isset($_POST['_adiossucker']))
       {
         $reason = ( isset($_POST['reason']) ) ? $_POST['reason'] : false;
@@ -509,6 +519,8 @@
       }
       break;
     case 'diff':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
+      require_once(ENANO_ROOT.'/includes/diff.php');
       $template->header();
       $id1 = ( isset($_GET['diff1']) ) ? (int)$_GET['diff1'] : false;
       $id2 = ( isset($_GET['diff2']) ) ? (int)$_GET['diff2'] : false;
@@ -533,6 +545,7 @@
       die_friendly($lang->get('page_detag_success_title'), '<p>' . $lang->get('page_detag_success_body') . '</p>');
       break;
     case 'aclmanager':
+      require_once(ENANO_ROOT.'/includes/pageutils.php');
       $data = ( isset($_POST['data']) ) ? $_POST['data'] : Array('mode' => 'listgroups');
       PageUtils::aclmanager($data);
       break;
--- a/plugins/SpecialSearch.php	Mon Jun 30 17:22:29 2008 -0400
+++ b/plugins/SpecialSearch.php	Wed Jul 02 19:36:44 2008 -0400
@@ -65,6 +65,8 @@
   global $aggressive_optimize_html;
   global $lang;
   
+  require_once(ENANO_ROOT.'/includes/search.php');
+  
   $aggressive_optimize_html = false;
   
   if ( !$q = $paths->getParam(0) )