Major redesign of rendering pipeline that separates pages saved with MCE from pages saved with the plaintext editor (full description in long commit message)
authorDan
Mon, 16 Feb 2009 16:17:25 -0500
changeset 832 7152ca0a0ce9
parent 831 45e887f23282
child 833 16054ea5b525
Major redesign of rendering pipeline that separates pages saved with MCE from pages saved with the plaintext editor (full description in long commit message) - Pages are now stored with an extra metadata field called page_format which is "wikitext" or "xhtml" - New $flags parameter + RENDER_* constants added that control RenderMan::render() behavior - Several other changes: * Added a sprite API for Javascript and made editor use sprites when possible * Removed a number of config options from the default install schema, replaced with second parameter to getConfig() calls * MessageBox in editor mostly replaced with miniPrompt * A few bugfixes related to password changes (registration didn't even work) * Rewrote the bitfield compression algorithm used to serialize allowed MIME types * Fixed some typos in language files and strings * Fixed a Text_Wiki bug in Heading parser
ajax.php
images/editor/sprite.png
images/icons/abort.png
images/icons/abortretryignore-sprite.png
images/icons/fmt_sprite.png
images/icons/fmt_wikitext.png
images/icons/fmt_xhtml.png
images/icons/ignore.png
images/icons/retry.png
includes/clientside/css/enano-shared.css
includes/clientside/static/dynano.js
includes/clientside/static/editor.js
includes/clientside/static/functions.js
includes/clientside/static/messagebox.js
includes/clientside/static/template-compiler.js
includes/comment.php
includes/common.php
includes/constants.php
includes/functions.php
includes/namespaces/default.php
includes/namespaces/user.php
includes/pageprocess.php
includes/pageutils.php
includes/paths.php
includes/plugins.php
includes/render.php
includes/rijndael.php
includes/search.php
includes/sessions.php
includes/tagcloud.php
includes/template.php
includes/wikiengine/Parse/Mediawiki/Heading.php
index.php
install/includes/payload.php
install/upgrade.php
language/english/admin.json
language/english/core.json
language/english/install.json
language/english/tools.json
language/english/user.json
plugins/SpecialAdmin.php
plugins/SpecialUpdownload.php
plugins/SpecialUserFuncs.php
plugins/admin/UserManager.php
themes/oxygen/toolbar.tpl
--- a/ajax.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/ajax.php	Mon Feb 16 16:17:25 2009 -0500
@@ -37,7 +37,7 @@
       if ( $src = $page->fetch_source() )
       {
         $allowed = true;
-        $q = $db->sql_query('SELECT author, time_id, page_text, edit_summary FROM ' . table_prefix . 'logs WHERE log_type = \'page\' AND action = \'edit\'
+        $q = $db->sql_query('SELECT author, time_id, page_text, edit_summary, page_format FROM ' . table_prefix . 'logs WHERE log_type = \'page\' AND action = \'edit\'
                                AND page_id = \'' . $db->escape($paths->page_id) . '\'
                                AND namespace = \'' . $db->escape($paths->namespace) . '\'
                                AND is_draft = 1;');
@@ -76,6 +76,21 @@
           'have_draft' => false
         );
       
+      $return['page_format'] = $paths->cpage['page_format'];
+      if ( $return['page_format'] == 'xhtml' )
+      {
+        // gently process headings to make tinymce format them correctly
+        if ( preg_match_all('/^ *?(={1,6}) *(.+?) *\\1 *$/m', $return['src'], $matches) )
+        {
+          foreach ( $matches[0] as $i => $match )
+          {
+            $hi = strlen($matches[1][$i]);
+            $heading = "<h{$hi}>{$matches[2][$i]}</h{$hi}>";
+            $return['src'] = str_replace_once($match, $heading, $return['src']);
+          }
+        }
+      }
+      
       if ( $have_draft )
       {
         $row =& $draft_row;
@@ -86,6 +101,7 @@
         {
           $return['src'] = $row['page_text'];
           $return['edit_summary'] = $row['edit_summary'];
+          $return['page_format'] = $row['page_format'];
         }
       }
       
@@ -212,26 +228,37 @@
           else
           {
             $src = RenderMan::preprocess_text($request['src'], false, false);
-            
-            // Save the draft
-            $q = $db->sql_query('INSERT INTO ' . table_prefix . 'logs ( log_type, action, page_id, namespace, author, edit_summary, page_text, is_draft, time_id )
-                                   VALUES (
-                                     \'page\',
-                                     \'edit\',
-                                     \'' . $db->escape($paths->page_id) . '\',
-                                     \'' . $db->escape($paths->namespace) . '\',
-                                     \'' . $db->escape($session->username) . '\',
-                                     \'' . $db->escape($request['summary']) . '\',
-                                     \'' . $db->escape($src) . '\',
-                                     1,
-                                     ' . time() . '
-                                   );');
-            
-            // Done!
-            $return = array(
-                'mode' => 'success',
-                'is_draft' => true
+            $draft_format = $request['format'];
+            if ( !in_array($draft_format, array('xhtml', 'wikitext')) )
+            {
+              $return = array(
+                'mode' => 'error',
+                'error' => 'invalid_format'
               );
+            }
+            else
+            {
+              // Save the draft
+              $q = $db->sql_query('INSERT INTO ' . table_prefix . 'logs ( log_type, action, page_id, namespace, author, edit_summary, page_text, is_draft, time_id, page_format )
+                                     VALUES (
+                                       \'page\',
+                                       \'edit\',
+                                       \'' . $db->escape($paths->page_id) . '\',
+                                       \'' . $db->escape($paths->namespace) . '\',
+                                       \'' . $db->escape($session->username) . '\',
+                                       \'' . $db->escape($request['summary']) . '\',
+                                       \'' . $db->escape($src) . '\',
+                                       1,
+                                       ' . time() . ',
+                                       \'' . $draft_format . '\'
+                                     );');
+              
+              // Done!
+              $return = array(
+                  'mode' => 'success',
+                  'is_draft' => true
+                );
+            }
           }
         }
       }
@@ -280,7 +307,7 @@
         
         // Verification complete. Start the PageProcessor and let it do the dirty work for us.
         $page = new PageProcessor($paths->page_id, $paths->namespace);
-        if ( $page->update_page($request['src'], $request['summary'], ( $request['minor_edit'] == 1 )) )
+        if ( $page->update_page($request['src'], $request['summary'], ( $request['minor_edit'] == 1 ), $request['format']) )
         {
           $return = array(
               'mode' => 'success',
@@ -429,52 +456,66 @@
     case "fillusername":
       break;
     case "fillpagename":
-      $name = (isset($_GET['name'])) ? $_GET['name'] : false;
-      if(!$name) die('userlist = new Array(); namelist = new Array(); errorstring=\'Invalid URI\'');
-      $nd = RenderMan::strToPageID($name);
-      $c = 0;
-      $u = Array();
-      $n = Array();
-      
-      $name = sanitize_page_id($name);
-      $name = str_replace('_', ' ', $name);
-      
-      foreach ( $paths->pages as $i => $_ )
-      {
-        if( ( 
-            preg_match('#'.preg_quote($name).'(.*)#i', $paths->pages[$i]['name']) ||
-            preg_match('#'.preg_quote($name).'(.*)#i', $paths->pages[$i]['urlname']) ||
-            preg_match('#'.preg_quote($name).'(.*)#i', $paths->pages[$i]['urlname_nons']) ||
-            preg_match('#'.preg_quote(str_replace(' ', '_', $name)).'(.*)#i', $paths->pages[$i]['name']) ||
-            preg_match('#'.preg_quote(str_replace(' ', '_', $name)).'(.*)#i', $paths->pages[$i]['urlname']) ||
-            preg_match('#'.preg_quote(str_replace(' ', '_', $name)).'(.*)#i', $paths->pages[$i]['urlname_nons'])
-            ) &&
-           ( ( $nd[1] != 'Article' && $paths->pages[$i]['namespace'] == $nd[1] ) || $nd[1] == 'Article' )
-            && $paths->pages[$i]['visible']
-           )
-        {
-          $c++;
-          $u[] = $paths->pages[$i]['name'];
-          $n[] = $paths->pages[$i]['urlname'];
-        }
-      }
-      if($c > 0)
-      {
-        echo 'userlist = new Array(); namelist = new Array(); errorstring = false; '."\n";
-        for($i=0;$i<sizeof($u);$i++) // Can't use foreach because we need the value of $i and we need to use both $u and $n
-        {
-          echo "userlist[$i] = '".addslashes($n[$i])."';\n";
-          echo "namelist[$i] = '".addslashes(htmlspecialchars($u[$i]))."';\n";
-        }
-      } else {
-        die('userlist = new Array(); namelist = new Array(); errorstring=\'No page matches found.\'');
-      }
       break;
     case "preview":
       require_once(ENANO_ROOT.'/includes/pageutils.php');
       $template->init_vars();
       echo PageUtils::genPreview($_POST['text']);
       break;
+    case "transform":
+      header('Content-type: text/javascript');
+      if ( !isset($_GET['to']) )
+      {
+        echo enano_json_encode(array(
+            'mode' => 'error',
+            'error' => '"to" not specified'
+          ));
+        break;
+      }
+      if ( !isset($_POST['text']) )
+      {
+        echo enano_json_encode(array(
+            'mode' => 'error',
+            'error' => '"text" not specified (must be on POST)'
+          ));
+        break;
+      }
+      switch($_GET['to'])
+      {
+        case 'xhtml':
+          $result = RenderMan::render($_POST['text'], RENDER_WIKI_DEFAULT | RENDER_BLOCKONLY);
+          break;
+        case 'wikitext':
+          $result = RenderMan::reverse_render($_POST['text']);
+          break;
+        default:
+          $text =& $_POST['text'];
+          $result = false;
+          $code = $plugins->setHook('ajax_transform');
+          foreach ( $code as $cmd )
+          {
+            eval($cmd);
+          }
+          if ( !$result )
+          {
+            echo enano_json_encode(array(
+                'mode' => 'error',
+                'error' => 'Invalid target format'
+              ));
+            break;
+          }
+          break;
+      }
+      
+      // mostly for debugging, but I suppose this could be useful elsewhere.
+      if ( isset($_POST['plaintext']) )
+        die($result);
+      
+      echo enano_json_encode(array(
+          'mode' => 'transformed_text',
+          'text' => $result
+        ));
+      break;
     case "pagediff":
       require_once(ENANO_ROOT.'/includes/pageutils.php');
       $id1 = ( isset($_GET['diff1']) ) ? (int)$_GET['diff1'] : false;
Binary file images/editor/sprite.png has changed
Binary file images/icons/abort.png has changed
Binary file images/icons/abortretryignore-sprite.png has changed
Binary file images/icons/fmt_sprite.png has changed
Binary file images/icons/fmt_wikitext.png has changed
Binary file images/icons/fmt_xhtml.png has changed
Binary file images/icons/ignore.png has changed
Binary file images/icons/retry.png has changed
--- a/includes/clientside/css/enano-shared.css	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/css/enano-shared.css	Mon Feb 16 16:17:25 2009 -0500
@@ -784,7 +784,7 @@
 .abutton_red         { color:            #880000 !important; }
 .abutton_red:hover   { background-color: #880000 !important; }
 
-.abutton_img {
+.abutton_img, .abutton.icon {
   background-image: url('../../../images/mini-error.png');
   background-position: 4px center;
   background-repeat: no-repeat;
--- a/includes/clientside/static/dynano.js	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/static/dynano.js	Mon Feb 16 16:17:25 2009 -0500
@@ -36,6 +36,7 @@
     this.getContent = DN_mceFetchContent;
     this.setContent = DN_mceSetContent;
     this.makeSwitchable = DN_makeSwitchableTA;
+    this.isMCE = DN_isMCE;
   }
 }
 function __DNObjGetHeight(o) {
@@ -154,6 +155,11 @@
   return this;
 }
 
+function DN_isMCE()
+{
+  return ( this.object.dnIsMCE == 'yes' );
+}
+
 function DN_mceFetchContent()
 {
   if ( this.object.name )
@@ -274,24 +280,56 @@
   }
 }
 
-// A basic Wikitext to XHTML converter
 function DN_WikitextToXHTML(text)
 {
-  text = text.replace(/^===[\s]*(.+?)[\s]*===$/g, '<h3>$1</h3>');
-  text = text.replace(/'''(.+?)'''/g, '<b>$1</b>');
-  text = text.replace(/''(.+?)''/g, '<i>$1</i>');
-  text = text.replace(/\[(http|ftp|irc|mailto):([^ \]])+ ([^\]]+?)\]/g, '<a href="$1:$2">$4</a>');
-  return text;
+  return DN_AjaxGetTransformedText(text, 'xhtml');
+}
+
+function DN_XHTMLToWikitext(text)
+{
+  return DN_AjaxGetTransformedText(text, 'wikitext');
 }
 
-// Inverse of the previous function
-function DN_XHTMLToWikitext(text)
+// AJAX to the server to transform text
+function DN_AjaxGetTransformedText(text, to)
 {
-  text = text.replace(/<h3>(.+?)<\/h3>/g, '=== $1 ===');
-  text = text.replace(/<(b|strong)>(.+?)<\/(b|strong)>/g, "'''$2'''");
-  text = text.replace(/<(i|em)>(.+?)<\/(i|em)>/g, "''$2''");
-  text = text.replace(/<a href="([^" ]+)">(.+?)<\/a>/g, '[$1 $2]');
-  text = text.replace(/<\/?p>/g, '');
+  // get an XHR instance
+  var ajax = ajaxMakeXHR();
+  
+  var uri = stdAjaxPrefix + '&_mode=transform&to=' + to;
+  var parms = 'text=' + ajaxEscape(text);
+  try
+  {
+    ajax.open('POST', uri, false);
+    ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+    // Setting Content-length in Safari triggers a warning
+    if ( !is_Safari )
+    {
+      ajax.setRequestHeader("Content-length", parms.length);
+    }
+    ajax.send(parms);
+    // async request, so if status != 200 at this point then we're screwed
+    if ( ajax.readyState == 4 && ajax.status == 200 )
+    {
+      var response = String(ajax.responseText + '');
+      if ( !check_json_response(response) )
+      {
+        handle_invalid_json(response);
+        return text;
+      }
+      response = parseJSON(response);
+      if ( response.mode == 'error' )
+      {
+        alert(response.error);
+        return text;
+      }
+      return response.text;
+    }
+  }
+  catch(e)
+  {
+    console.warn('DN_AjaxGetTransformedText: XHR failed');
+  }
   return text;
 }
 
--- a/includes/clientside/static/editor.js	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/static/editor.js	Mon Feb 16 16:17:25 2009 -0500
@@ -5,6 +5,7 @@
 var AutosaveTimeoutObj = null;
 var editor_img_path = cdnPath + '/images/editor';
 var editor_save_lock = false;
+var editor_wikitext_transform_enable = true;
 
 window.ajaxEditor = function(revid)
 {
@@ -12,7 +13,7 @@
     return true;
   if ( editor_open )
     return true;
-  load_component(['l10n', 'template-compiler', 'messagebox']);
+  load_component(['l10n', 'template-compiler', 'messagebox', 'fadefilter', 'flyin']);
   selectButtonMinor('edit');
   selectButtonMajor('article');
   setAjaxLoading();
@@ -124,29 +125,34 @@
     var span_mce  = document.createElement('span');
     span_wiki.id  = 'enano_edit_btn_pt';
     span_mce.id   = 'enano_edit_btn_mce';
-    if ( readCookie('enano_editor_mode') == 'tinymce' )
+    
+    // to-wikitext button
+    var a = document.createElement('a');
+    a.href = '#';
+    a.className = 'abutton image abutton_green';
+    a.appendChild(gen_sprite(scriptPath + '/images/editor/sprite.png', 16, 16, 0, 96));
+    a.appendChild(document.createTextNode(' ' + $lang.get('editor_btn_wikitext')));
+    span_wiki.appendChild(a);
+    toggler.appendChild(span_wiki);
+    
+    // to-HTML button
+    var a = document.createElement('a');
+    a.href = '#';
+    a.className = 'abutton image abutton_blue';
+    a.appendChild(gen_sprite(scriptPath + '/images/editor/sprite.png', 16, 16, 0, 112));
+    a.appendChild(document.createTextNode(' ' + $lang.get('editor_btn_graphical')));
+    span_mce.appendChild(a);
+    toggler.appendChild(span_mce);
+    
+    if ( response.page_format == 'xhtml' )
     {
       // Current selection is TinyMCE - make span_wiki have the link and span_mce be plaintext
-      var a = document.createElement('a');
-      a.href = '#';
-      a.appendChild(document.createTextNode($lang.get('editor_btn_wikitext')));
-      span_wiki.appendChild(a);
-      toggler.appendChild(span_wiki);
-      toggler.appendChild(document.createTextNode(' | '));
-      span_mce.appendChild(document.createTextNode($lang.get('editor_btn_graphical')));
-      toggler.appendChild(span_mce);
+      span_mce.style.display = 'none';
     }
     else
     {
       // Current selection is wikitext - set span_wiki to plaintext and span_mce to link
-      span_wiki.appendChild(document.createTextNode($lang.get('editor_btn_wikitext')));
-      toggler.appendChild(span_wiki);
-      toggler.appendChild(document.createTextNode(' | '));
-      var a = document.createElement('a');
-      a.href = '#';
-      a.appendChild(document.createTextNode($lang.get('editor_btn_graphical')));
-      span_mce.appendChild(a);
-      toggler.appendChild(span_mce);
+      span_wiki.style.display = 'none';
     }
   }
   
@@ -223,6 +229,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_closeviewer'),
         IMAGE: editor_img_path + '/discard.gif',
+        SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 16),
         FLAGS: 'href="#" onclick="ajaxReset(true); return false;"'
       });
     toolbar += button.run();
@@ -343,6 +350,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_save'),
         IMAGE: editor_img_path + '/save.gif',
+        SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 64),
         FLAGS: 'href="#" onclick="ajaxEditorSave(); return false;"'
       });
     toolbar += button.run();
@@ -351,6 +359,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_preview'),
         IMAGE: editor_img_path + '/preview.gif',
+        SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 32),
         FLAGS: 'href="#" onclick="ajaxEditorGenPreview(); return false;"'
       });
     toolbar += button.run();
@@ -359,6 +368,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_revert'),
           IMAGE: editor_img_path + '/revert.gif',
+          SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 48),
         FLAGS: 'href="#" onclick="ajaxEditorRevertToLatest(); return false;"'
       });
     toolbar += button.run();
@@ -367,6 +377,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_diff'),
         IMAGE: editor_img_path + '/diff.gif',
+        SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 0),
         FLAGS: 'href="#" onclick="ajaxEditorShowDiffs(); return false;"'
       });
     toolbar += button.run();
@@ -375,6 +386,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_cancel'),
         IMAGE: editor_img_path + '/discard.gif',
+        SPRITE: gen_sprite_html(editor_img_path + '/sprite.png', 16, 16, 0, 16),
         FLAGS: 'href="#" onclick="ajaxEditorCancel(); return false;"'
       });
     toolbar += button.run();
@@ -389,6 +401,7 @@
     button.assign_vars({
         TITLE: $lang.get('editor_btn_savedraft'),
         IMAGE: editor_img_path + '/savedraft.gif',
+        SPRITE: false,
         FLAGS: 'href="#" onclick="ajaxPerformAutosave(); return false;" id="ajax_edit_savedraft_btn"'
       });
     toolbar += button.run();
@@ -467,29 +480,23 @@
   $dynano('ajaxEditArea').setContent(content);
   
   // If the editor preference is tinymce, switch the editor to TinyMCE now
-  if ( readCookie('enano_editor_mode') == 'tinymce' && allow_wysiwyg )
+  if ( response.page_format == 'xhtml' && allow_wysiwyg )
   {
-    $dynano('ajaxEditArea').switchToMCE();
+    $dynano('ajaxEditArea').switchToMCE(false);
   }
   
   if ( allow_wysiwyg )
   {
-    if ( readCookie('enano_editor_mode') == 'tinymce' )
-    {
-      var a = document.getElementById('enano_edit_btn_pt').getElementsByTagName('a')[0];
-      a.onclick = function() {
-        ajaxSetEditorPlain();
-        return false;
-      };
-    }
-    else
-    {
-      var a = document.getElementById('enano_edit_btn_mce').getElementsByTagName('a')[0];
-      a.onclick = function() {
-        ajaxSetEditorMCE();
-        return false;
-      };
-    }
+    var a = document.getElementById('enano_edit_btn_pt').getElementsByTagName('a')[0];
+    a.onclick = function() {
+      ajaxSetEditorPlain();
+      return false;
+    };
+    var a = document.getElementById('enano_edit_btn_mce').getElementsByTagName('a')[0];
+    a.onclick = function() {
+      ajaxSetEditorMCE();
+      return false;
+    };
   }
   
   // if we're using the modal window, fade it in
@@ -563,6 +570,7 @@
     minor_edit: is_minor,
     time: timestamp,
     draft: ( is_draft == true ),
+    format: ( $dynano('ajaxEditArea').isMCE() ) ? 'xhtml' : 'wikitext',
     used_draft: used_draft
   };
   
@@ -597,12 +605,14 @@
         // This will only be used if there was a lower-level error.
         if ( response.mode == 'error' )
         {
+          editor_save_lock = false;
           new MessageBox(MB_OK | MB_ICONSTOP, $lang.get('editor_err_server'), response.error);
           return false;
         }
         // This will be used if the PageProcessor generated errors (usually security/permissions related)
         if ( response.mode == 'errors' )
         {
+          editor_save_lock = false;
           // This will be true if the user entered a captcha code incorrectly, thus
           // invalidating the code and requiring a new image to be generated.
           if ( response.new_captcha )
@@ -625,6 +635,7 @@
         // If someone else got to the page first, warn the user
         if ( response.mode == 'obsolete' )
         {
+          editor_save_lock = false;
           // Update the local timestamp to allow override
           $dynano('ajaxEditArea').object._edTimestamp = response.time;
           new MessageBox(MB_OK | MB_ICONEXCLAMATION, $lang.get('editor_err_obsolete_title'), $lang.get('editor_err_obsolete_body', { author: response.author, timestamp: response.date_string, page_url: makeUrl(title, false, true) }));
@@ -668,6 +679,7 @@
             editor_open = false;
             editor_save_lock = false;
             enableUnload();
+            $dynano('ajaxEditArea').destroyMCE(false);
             changeOpac(0, 'ajaxEditContainer');
             ajaxGet(stdAjaxPrefix + '&_mode=getpage&noheaders', function(ajax)
               {
@@ -751,11 +763,34 @@
 
 window.ajaxEditorRevertToLatest = function()
 {
-  var mb = new MessageBox(MB_YESNO | MB_ICONQUESTION, $lang.get('editor_msg_revert_confirm_title'), $lang.get('editor_msg_revert_confirm_body'));
-  mb.onclick['Yes'] = function()
-  {
-    setTimeout('ajaxEditorRevertToLatestReal();', 750);
-  }
+  miniPromptMessage({
+      title: $lang.get('editor_msg_revert_confirm_title'),
+      message: $lang.get('editor_msg_revert_confirm_body'),
+      buttons: [
+        {
+          text: $lang.get('editor_btn_revert_confirm'),
+          color: 'red',
+          sprite: [ editor_img_path + '/sprite.png', 16, 16, 0, 48 ],
+          style: {
+            fontWeight: 'bold'
+          },
+          onclick: function()
+          {
+            ajaxEditorRevertToLatestReal();
+            miniPromptDestroy(this);
+            return false;
+          }
+        },
+        {
+          text: $lang.get('etc_cancel'),
+          onclick: function()
+          {
+            miniPromptDestroy(this);
+            return false;
+          }
+        }
+      ]
+    });
 }
 
 window.ajaxEditorRevertToLatestReal = function()
@@ -789,7 +824,11 @@
           return false;
         }
         
-        $dynano('ajaxEditArea').setContent(response.src);
+        setTimeout(function()
+          {
+            editor_convert_if_needed(response.page_format);
+            $dynano('ajaxEditArea').setContent(response.src);
+          }, aclDisableTransitionFX ? 10 : 750);
       }
     }, true);
 }
@@ -818,73 +857,135 @@
 
 window.ajaxEditorCancel = function()
 {
-  var mb = new MessageBox(MB_YESNO | MB_ICONQUESTION, $lang.get('editor_msg_cancel_confirm_title'), $lang.get('editor_msg_cancel_confirm_body'));
-  mb.onclick['Yes'] = function()
-  {
-    setAjaxLoading();
-    ajaxEditorDestroyModalWindow();
-    editor_open = false;
-    enableUnload();
-    setTimeout('ajaxReset();', 750);
-  }
+  miniPromptMessage({
+      title: $lang.get('editor_msg_cancel_confirm_title'),
+      message: $lang.get('editor_msg_cancel_confirm_body'),
+      buttons: [
+        {
+          text: $lang.get('editor_btn_cancel_confirm'),
+          color: 'red',
+          sprite: [ editor_img_path + '/sprite.png', 16, 16, 0, 16 ],
+          style: {
+            fontWeight: 'bold'
+          },
+          onclick: function()
+          {
+            setAjaxLoading();
+            ajaxEditorDestroyModalWindow();
+            editor_open = false;
+            enableUnload();
+            $dynano('ajaxEditArea').destroyMCE(false);
+            ajaxReset();
+            miniPromptDestroy(this);
+            return false;
+          }
+        },
+        {
+          text: $lang.get('editor_btn_cancel_cancel'),
+          onclick: function()
+          {
+            miniPromptDestroy(this);
+            return false;
+          }
+        }
+      ]
+    });
 }
 
-window.ajaxSetEditorMCE = function()
+window.ajaxSetEditorMCE = function(confirmed)
 {
   if ( editor_loading )
     return false;
   
+  if ( !confirmed )
+  {
+    miniPromptMessage({
+        title: $lang.get('editor_msg_convert_confirm_title'),
+        message: $lang.get('editor_msg_convert_confirm_body'),
+        buttons: [
+          {
+            color: 'blue',
+            text: $lang.get('editor_btn_graphical'),
+            style: {
+              fontWeight: 'bold'
+            },
+            sprite: [ editor_img_path + '/sprite.png', 16, 16, 0, 112 ],
+            onclick: function()
+            {
+              ajaxSetEditorMCE(true);
+              miniPromptDestroy(this);
+              return false;
+            }
+          },
+          {
+            text: $lang.get('etc_cancel'),
+            onclick: function()
+            {
+              miniPromptDestroy(this);
+              return false;
+            }
+          }
+        ]
+      });
+    return false;
+  }
+  
   // Clear out existing buttons
   var span_wiki = $dynano('enano_edit_btn_pt').object;
   var span_mce  = $dynano('enano_edit_btn_mce').object;
-  span_wiki.removeChild(span_wiki.firstChild);
-  span_mce.removeChild(span_mce.firstChild);
-  
-  // Rebuild control
-  var a = document.createElement('a');
-  a.href = '#';
-  a.onclick = function() {
-    ajaxSetEditorPlain();
-    return false;
-  };
-  a.appendChild(document.createTextNode($lang.get('editor_btn_wikitext')));
-  span_wiki.appendChild(a);
-  span_mce.appendChild(document.createTextNode($lang.get('editor_btn_graphical')));
+  span_wiki.style.display = 'inline';
+  span_mce.style.display = 'none';
   
   // Swap editor
-  $dynano('ajaxEditArea').switchToMCE();
-  
-  // Remember the setting
-  createCookie('enano_editor_mode', 'tinymce', 365);
+  $dynano('ajaxEditArea').switchToMCE(true);
 }
 
-window.ajaxSetEditorPlain = function()
+window.ajaxSetEditorPlain = function(confirmed)
 {
   if ( editor_loading )
     return false;
   
+  if ( !confirmed )
+  {
+    miniPromptMessage({
+        title: $lang.get('editor_msg_convert_confirm_title'),
+        message: $lang.get('editor_msg_convert_confirm_body'),
+        buttons: [
+          {
+            color: 'green',
+            text: $lang.get('editor_btn_wikitext'),
+            style: {
+              fontWeight: 'bold'
+            },
+            sprite: [ editor_img_path + '/sprite.png', 16, 16, 0, 96 ],
+            onclick: function()
+            {
+              ajaxSetEditorPlain(true);
+              miniPromptDestroy(this);
+              return false;
+            }
+          },
+          {
+            text: $lang.get('etc_cancel'),
+            onclick: function()
+            {
+              miniPromptDestroy(this);
+              return false;
+            }
+          }
+        ]
+      });
+    return false;
+  }
+  
   // Clear out existing buttons
   var span_wiki = $dynano('enano_edit_btn_pt').object;
   var span_mce  = $dynano('enano_edit_btn_mce').object;
-  span_wiki.removeChild(span_wiki.firstChild);
-  span_mce.removeChild(span_mce.firstChild);
-  
-  // Rebuild control
-  span_wiki.appendChild(document.createTextNode($lang.get('editor_btn_wikitext')));
-  var a = document.createElement('a');
-  a.href = '#';
-  a.onclick = function() {
-    ajaxSetEditorMCE();
-    return false;
-  };
-  a.appendChild(document.createTextNode($lang.get('editor_btn_graphical')));
-  span_mce.appendChild(a);
+  span_wiki.style.display = 'none';
+  span_mce.style.display = 'inline';
   
   // Swap editor
-  $dynano('ajaxEditArea').destroyMCE();
-  
-  // Remember the setting
-  createCookie('enano_editor_mode', 'text', 365);
+  $dynano('ajaxEditArea').destroyMCE(true);
 }
 
 var editor_loading = false;
@@ -1003,6 +1104,8 @@
           return false;
         }
         
+        editor_convert_if_needed(response.page_format);
+        
         $dynano('ajaxEditArea').setContent(response.src);
         $dynano('ajaxEditArea').object.used_draft = true;
         
@@ -1018,3 +1121,54 @@
     }, true);
 }
 
+window.editor_convert_if_needed = function(targetformat, noticetitle, noticebody)
+{
+  // Do we need to change the format?
+  var need_to_mce = ( targetformat == 'xhtml' && !$dynano('ajaxEditArea').isMCE() );
+  var need_to_wkt = ( targetformat == 'wikitext' && $dynano('ajaxEditArea').isMCE() );
+  if ( need_to_mce )
+  {
+    $dynano('ajaxEditArea').setContent('');
+    $dynano('ajaxEditArea').switchToMCE(false);
+    
+    // Clear out existing buttons
+    var span_wiki = $dynano('enano_edit_btn_pt').object;
+    var span_mce  = $dynano('enano_edit_btn_mce').object;
+    span_wiki.style.display = 'inline';
+    span_mce.style.display = 'none';
+  }
+  else if ( need_to_wkt )
+  {
+    $dynano('ajaxEditArea').setContent('');
+    $dynano('ajaxEditArea').destroyMCE(false);
+    
+    // Clear out existing buttons
+    var span_wiki = $dynano('enano_edit_btn_pt').object;
+    var span_mce  = $dynano('enano_edit_btn_mce').object;
+    span_wiki.style.display = 'none';
+    span_mce.style.display = 'inline';
+  }
+  if ( need_to_mce || need_to_wkt )
+  {
+    // explain the conversion
+    if ( !noticetitle )
+      noticetitle = 'editor_msg_convert_draft_load_title';
+    if ( !noticebody )
+      noticebody = 'editor_msg_convert_draft_load_body';
+    
+    miniPromptMessage({
+        title: $lang.get(noticetitle),
+        message: $lang.get(noticebody),
+        buttons: [
+          {
+            text: $lang.get('etc_ok'),
+            onclick: function()
+            {
+              miniPromptDestroy(this);
+              return false;
+            }
+          }
+        ]
+      });
+  }
+}
--- a/includes/clientside/static/functions.js	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/static/functions.js	Mon Feb 16 16:17:25 2009 -0500
@@ -104,6 +104,8 @@
     // The user allowed the editor to be closed. Reset flags and knock out the on-close confirmation.
     editor_open = false;
     enableUnload();
+    // destroy the MCE instance so it can be recreated later
+    $dynano('ajaxEditArea').destroyMCE(false);
   }
   var ajax = ajaxMakeXHR();
   if ( !ajax )
@@ -136,6 +138,8 @@
     // The user allowed the editor to be closed. Reset flags and knock out the on-close confirmation.
     editor_open = false;
     enableUnload();
+    // destroy the MCE instance so it can be recreated later
+    $dynano('ajaxEditArea').destroyMCE(false);
   }
   var ajax = ajaxMakeXHR();
   if ( !ajax )
@@ -448,17 +452,67 @@
   }
 }
 
+/**
+ * Return a DOMElement that uses a sprite image.
+ * @param string Path to sprite image
+ * @param int Width of resulting image
+ * @param int Height of resulting image
+ * @param int X offset
+ * @param int Y offset
+ * @return object HTMLImageElement
+ */
+
+function gen_sprite(path, width, height, xpos, ypos)
+{
+  var image = document.createElement('img');
+  image.src = scriptPath + '/images/spacer.gif';
+  image.width = String(width);
+  image.height = String(height);
+  image.style.backgroundImage = 'url(' + path + ')';
+  image.style.backgroundRepeat = 'no-repeat';
+  xpos = ( xpos == 0 ) ? '0' : '-' + String(xpos);
+  ypos = ( ypos == 0 ) ? '0' : '-' + String(ypos);
+  image.style.backgroundPosition = ypos + 'px ' + xpos + 'px';
+  
+  return image;
+}
+
+/**
+ * The same as gen_sprite but generates HTML instead of a DOMElement.
+ * @param string Path to sprite image
+ * @param int Width of resulting image
+ * @param int Height of resulting image
+ * @param int X offset
+ * @param int Y offset
+ * @return object HTMLImageElement
+ */
+
+function gen_sprite_html(path, width, height, xpos, ypos)
+{
+  var html = '<img src="' + scriptPath + '/images/spacer.gif" width="' + width + '" height="' + height + '" ';
+  xpos = ( xpos == 0 ) ? '0' : '-' + String(xpos);
+  ypos = ( ypos == 0 ) ? '0' : '-' + String(ypos);
+  html += 'style="background-image: url(' + path + '); background-repeat: no-repeat; background-position: ' + ypos + 'px ' + xpos + 'px;"';
+  html += ' />';
+  
+  return html;
+}
+
 function findParentForm(o)
 {
   return get_parent_form(o);
 }
 
-function domObjChangeOpac(opacity, id) {
-    var object = id.style;
-    object.opacity = (opacity / 100);
-    object.MozOpacity = (opacity / 100);
-    object.KhtmlOpacity = (opacity / 100);
-    object.filter = "alpha(opacity=" + opacity + ")";
+function domObjChangeOpac(opacity, id)
+{
+  if ( !id )
+    return false;
+  
+  var object = id.style;
+  object.opacity = (opacity / 100);
+  object.MozOpacity = (opacity / 100);
+  object.KhtmlOpacity = (opacity / 100);
+  object.filter = "alpha(opacity=" + opacity + ")";
 }
 
 function getScrollOffset(el)
--- a/includes/clientside/static/messagebox.js	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/static/messagebox.js	Mon Feb 16 16:17:25 2009 -0500
@@ -524,9 +524,17 @@
               button.input.style[j] = button.style[j];
             }
           }
-          if ( button.image )
+          if ( button.sprite )
           {
-            button.input.className += ' abutton_img';
+            var sprite = gen_sprite(button.sprite[0], button.sprite[1], button.sprite[2], button.sprite[3], button.sprite[4]);
+            sprite.style.position = 'relative';
+            sprite.style.top = '3px';
+            button.input.insertBefore(sprite, button.input.firstChild);
+            insertAfter(button.input, document.createTextNode(' '), sprite);
+          }
+          else if ( button.image )
+          {
+            button.input.className += ' icon';
             button.input.style.backgroundImage = 'url(' + button.image + ')';
           }
           button.input.onclick = function(e)
@@ -572,7 +580,7 @@
         style: {
           fontWeight: 'bold'
         },
-        image: cdnPath + '/images/icons/abort.png',
+        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 0 ],
         onclick: function() {
           miniPromptDestroy(this);
         }
@@ -580,7 +588,7 @@
       {
         text: 'Retry',
         color: 'blue',
-        image: cdnPath + '/images/icons/retry.png',
+        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 16 ],
         onclick: function() {
           miniPromptDestroy(this);
         }
@@ -588,7 +596,7 @@
       {
         text: 'Ignore',
         color: 'green',
-        image: cdnPath + '/images/icons/ignore.png',
+        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 32 ],
         onclick: function() {
           miniPromptDestroy(this);
         }
--- a/includes/clientside/static/template-compiler.js	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/clientside/static/template-compiler.js	Mon Feb 16 16:17:25 2009 -0500
@@ -55,6 +55,8 @@
   code = "'" + code + "'";
   code = code.replace(/\{([A-z0-9_-]+)\}/ig, "' + this.tpl_strings['$1'] + '");
   code = code.replace(/\{lang:([a-z0-9_]+)\}/g, "' + $lang.get('$1') + '");
+  code = code.replace(/\<!-- IFSET ([A-z0-9_-]+) --\>([\s\S]*?)\<!-- BEGINELSE \1 --\>([\s\S]*?)\<!-- END \1 --\>/ig, "' + ( ( typeof(this.tpl_strings['$1']) == 'string' ) ? '$2' : '$3' ) + '");
+  code = code.replace(/\<!-- IFSET ([A-z0-9_-]+) --\>([\s\S]*?)\<!-- END \1 --\>/ig, "' + ( ( typeof(this.tpl_strings['$1']) == 'string' ) ? '$2' : '' ) + '");
   code = code.replace(/\<!-- BEGIN ([A-z0-9_-]+) --\>([\s\S]*?)\<!-- BEGINELSE \1 --\>([\s\S]*?)\<!-- END \1 --\>/ig, "' + ( ( this.tpl_bool['$1'] == true ) ? '$2' : '$3' ) + '");
   code = code.replace(/\<!-- BEGIN ([A-z0-9_-]+) --\>([\s\S]*?)\<!-- END \1 --\>/ig, "' + ( ( this.tpl_bool['$1'] == true ) ? '$2' : '' ) + '");
   return code;
--- a/includes/comment.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/comment.php	Mon Feb 16 16:17:25 2009 -0500
@@ -81,7 +81,7 @@
       echo enano_json_encode($ret);
       return $ret;
     }
-    if ( getConfig('enable_comments') == '0' )
+    if ( getConfig('enable_comments', '1') == '0' )
     {
       $ret = Array('mode'=>'error','error'=>'Comments are not enabled on this site.');
       echo enano_json_encode($ret);
@@ -182,7 +182,7 @@
         $ret['user_level']['mod'] = USER_LEVEL_MOD;
         $ret['user_level']['admin'] = USER_LEVEL_ADMIN;
         
-        $ret['approval_needed'] = ( getConfig('approve_comments') == '1' );
+        $ret['approval_needed'] = ( getConfig('approve_comments', '0') == '1' );
         $ret['guest_posting'] = getConfig('comments_need_login');
         
         if ( $ret['guest_posting'] == '1' && !$session->user_logged_in )
@@ -306,7 +306,7 @@
           $src = $text;
           $sql_text = $db->escape($text);
           $text = RenderMan::render($text);
-          $appr = ( getConfig('approve_comments') == '1' ) ? COMMENT_UNAPPROVED : COMMENT_APPROVED;
+          $appr = ( getConfig('approve_comments', '0') == '1' ) ? COMMENT_UNAPPROVED : COMMENT_APPROVED;
           if ( $appr === COMMENT_APPROVED && $spam_policy === 'moderate' && !$spamcheck )
             $appr = COMMENT_SPAM;
           $time = time();
--- a/includes/common.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/common.php	Mon Feb 16 16:17:25 2009 -0500
@@ -177,6 +177,9 @@
 global $dst_params;
 $dst_params = array(0, 0, 0, 0, 60);
 
+// Establish HTTPS
+$is_https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
+
 // Divert to CLI loader if running from CLI
 if ( defined('ENANO_CLI') || ( isset($argc) && isset($argv) ) )
 {
--- a/includes/constants.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/constants.php	Mon Feb 16 16:17:25 2009 -0500
@@ -103,6 +103,43 @@
 define('THIRD_SUNDAY', 3);
 define('LAST_SUNDAY', 4);
 
+// Rendering options!
+
+/**
+ * Render using the default engine (Text_Wiki + Enano pre- and post-processing).
+ * @const int
+ */
+
+define('RENDER_WIKI_DEFAULT', 1);
+
+/**
+ * Render using template/wiki hybrid syntax (Enano processing + template variable/logic access)
+ * @const int
+ */
+
+define('RENDER_WIKI_TEMPLATE', 2);
+
+/**
+ * Only render block-level things, like paragraphs, divs, etc.
+ * @const int
+ */
+
+define('RENDER_BLOCKONLY', 4);
+
+/**
+ * Only render inline-level things, like links.
+ * @const int
+ */
+
+define('RENDER_INLINEONLY', 8);
+
+/**
+ * Disable smilies when rendering.
+ * @const int
+ */
+
+define('RENDER_NOSMILIES', 16);
+
 //
 // User types - don't touch these
 //
--- a/includes/functions.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/functions.php	Mon Feb 16 16:17:25 2009 -0500
@@ -206,57 +206,17 @@
 
 function makeUrlComplete($n, $t, $query = false, $escape = false)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  $flags = '';
-
-  if(defined('ENANO_BASE_CLASSES_INITIALIZED'))
-  {
-    $sep = urlSeparator;
-  }
-  else
-  {
-    $sep = (strstr($_SERVER['REQUEST_URI'], '?')) ? '&' : '?';
-  }
-  if ( isset( $_GET['printable'] ) ) {
-    $flags .= $sep . 'printable';
-    $sep = '&';
-  }
-  if ( isset( $_GET['theme'] ) )
-  {
-    $flags .= $sep . 'theme='.$session->theme;
-    $sep = '&';
-  }
-  if ( isset( $_GET['style'] ) )
-  {
-    $flags .= $sep . 'style='.$session->style;
-    $sep = '&';
-  }
-  if ( isset($_GET['lang']) && preg_match('/^[a-z0-9_]+$/', @$_GET['lang']) )
-  {
-    $flags .= $sep . 'lang=' . urlencode($_GET['lang']);
-    $sep = '&';
-  }
-
-  if(defined('ENANO_BASE_CLASSES_INITIALIZED'))
-  {
-    $url = $session->append_sid(contentPath . $paths->nslist[$n] . $t . $flags);
-  }
-  else
-  {
-    // If the path manager hasn't been initted yet, take an educated guess at what the URI should be
-    $url = contentPath . $n . ':' . $t . $flags;
-  }
-  if($query)
-  {
-    if(strstr($url, '?')) $sep =  '&';
-    else $sep = '?';
-    $url = $url . $sep . $query . $flags;
-  }
-
-  $baseprot = 'http' . ( isset($_SERVER['HTTPS']) ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'];
-  $url = $baseprot . $url;
-
-  return ($escape) ? htmlspecialchars($url) : $url;
+  return get_server_url() . makeUrlNS($n, $t, $query, $escape);
+}
+
+/**
+ * Returns an http:// URL for this server.
+ * @return string
+ */
+
+function get_server_url()
+{
+  return 'http' . ( $GLOBALS['is_https'] ) . '://' . $_SERVER['HTTP_HOST'];
 }
 
 /**
@@ -277,7 +237,7 @@
   {
     $logged_in = true;
   }
-  return $logged_in && getConfig('main_page_alt_enable', '0') == '1' ? getConfig('main_page_alt', getConfig('main_page')) : getConfig('main_page');
+  return $logged_in && getConfig('main_page_alt_enable', '0') == '1' ? getConfig('main_page_alt', getConfig('main_page', 'Main_Page')) : getConfig('main_page', 'Main_Page');
 }
 
 /**
@@ -439,7 +399,7 @@
 
   $ns_prefix = ( isset($paths->nslist[ $namespace ]) ) ? $paths->nslist[ $namespace ] : $namespace . substr($paths->nslist['Special'], -1);
   $page_id_key = $ns_prefix . $page_id;
-  if ( isset($paths->pages[$page_id_key]) )
+  if ( isPage($page_id_key) )
   {
     $page_data = $paths->pages[$page_id_key];
   }
@@ -1509,69 +1469,51 @@
  * @param string $bits the text to compress, should be only 1s and 0s
  * @return string
  */
-
+ 
 function compress_bitfield($bits)
 {
-  $crc32 = crc32($bits);
-  $bits .= '0';
-  $start_pos = 0;
-  $current = substr($bits, 1, 1);
-  $last    = substr($bits, 0, 1);
-  $chunk_size = 1;
-  $len = strlen($bits);
-  $crc = $len;
-  $crcval = 0;
-  for ( $i = 1; $i < $len; $i++ )
-  {
-    $current = substr($bits, $i, 1);
-    $last    = substr($bits, $i - 1, 1);
-    $next    = substr($bits, $i + 1, 1);
-    // Are we on the last character?
-    if($current == $last && $i+1 < $len)
-      $chunk_size++;
-    else
+  if ( !preg_match('/^[01]+$/', $bits) )
+    return false;
+  
+  $current = intval($bits{0});
+  $clen = 0;
+  $out = '';
+  for ( $i = 0; $i < strlen($bits); $i++ )
+  {
+    $cbit = intval($bits{$i});
+    if ( $cbit !== $current || $clen == 127 || $i == strlen($bits) - 1 )
     {
-      if($i+1 == $len && $current == $next)
+      if ( $i == strlen($bits) - 1 && $cbit === $current )
       {
-        // This character completes a chunk
-        $chunk_size++;
-        $i++;
-        $chunk = substr($bits, $start_pos, $chunk_size);
-        $chunklen = strlen($chunk);
-        $newchunk = $last . '[' . $chunklen . ']';
-        $newlen   = strlen($newchunk);
-        $bits = substr($bits, 0, $start_pos) . $newchunk . substr($bits, $i, $len);
-        $chunk_size = 1;
-        $i = $start_pos + $newlen;
-        $start_pos = $i;
-        $len = strlen($bits);
-        $crcval = $crcval + $chunklen;
+        $clen++;
       }
-      else
+      // write chunk
+      $byte = $clen;
+      if ( $current === 1 )
+        $byte |= 0x80;
+      $out .= chr($byte);
+      
+      if ( $i == strlen($bits) - 1 && $cbit !== $current )
       {
-        // Last character completed a chunk
-        $chunk = substr($bits, $start_pos, $chunk_size);
-        $chunklen = strlen($chunk);
-        $newchunk = $last . '[' . $chunklen . '],';
-        $newlen   = strlen($newchunk);
-        $bits = substr($bits, 0, $start_pos) . $newchunk . substr($bits, $i, $len);
-        $chunk_size = 1;
-        $i = $start_pos + $newlen;
-        $start_pos = $i;
-        $len = strlen($bits);
-        $crcval = $crcval + $chunklen;
+        $out .= ( $cbit === 1 ) ? chr(0x81) : chr(0x1);
       }
+      
+      // reset
+      $current = intval($cbit);
+      $clen = 0;
     }
-  }
-  if($crc != $crcval)
-  {
-    echo __FUNCTION__.'(): ERROR: length check failed, this is a bug in the algorithm<br />Debug info: aiming for a CRC val of '.$crc.', got '.$crcval;
-    return false;
-  }
-  $compressed = 'cbf:len='.$crc.';crc='.dechex($crc32).';data='.$bits.'|end';
-  return $compressed;
+    $clen++;
+  }
+  $crc = dechex(crc32($out));
+  while ( strlen($crc) < 8 )
+    $crc = "0$crc";
+  return "cbf2:{$crc}" . hexencode($out, '', '');
 }
 
+// test case
+// $bf = '0111100010000000000000000000000100000000000000001110000000000000000101100000010100001100010000000000000000000000000000111111111111111111111100100001000000000000000000000000000000000000';
+// die('<pre>Original:  ' . " $bf\nCompressed: " . compress_bitfield($bf) . "\nProcessed:  ".uncompress_bitfield(compress_bitfield($bf)).'</pre>');
+
 /**
  * Uncompresses a bitfield compressed with compress_bitfield()
  * @param string $bits the compressed bitfield
@@ -1580,6 +1522,47 @@
 
 function uncompress_bitfield($bits)
 {
+  if ( substr($bits, 0, 4) == 'cbf:' )
+  {
+    return uncompress_bitfield_old($bits);
+  }
+  if ( substr($bits, 0, 5) != 'cbf2:' )
+  {
+    echo __FUNCTION__.'(): ERROR: Invalid stream';
+    return false;
+  }
+  $bits = substr($bits, 5);
+  $crc = substr($bits, 0, 8);
+  $bits = substr($bits, 8);
+  $bits = hexdecode($bits);
+  if ( dechex(crc32($bits)) !== $crc )
+  {
+    echo __FUNCTION__."(): ERROR: CRC failed";
+    return false;
+  }
+  $out = '';
+  for ( $i = 0; $i < strlen($bits); $i++ )
+  {
+    $byte = ord($bits{$i});
+    $char = $byte & 0x80 ? '1' : '0';
+    $byte &= ~0x80;
+    for ( $j = 0; $j < $byte; $j++ )
+    {
+      $out .= $char;
+    }
+  }
+  return $out;
+}
+
+/**
+ * Decompressor for old-format bitfields.
+ * @param string
+ * @return string
+ * @access private
+ */
+
+function uncompress_bitfield_old($bits)
+{
   if(substr($bits, 0, 4) != 'cbf:')
   {
     echo __FUNCTION__.'(): ERROR: Invalid stream';
@@ -4640,7 +4623,7 @@
   // eliminate comments
   $json = preg_replace(array(
           // eliminate single line comments in '// ...' form
-          '#^\s*//(.+)$#m',
+          '#^\s*//(.*)$#m',
           // eliminate multi-line comments in '/* ... */' form, at start of string
           '#^\s*/\*(.+)\*/#Us',
           // eliminate multi-line comments in '/* ... */' form, at end of string
@@ -5011,6 +4994,4 @@
   return false;
 }
 
-//die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
-
 ?>
--- a/includes/namespaces/default.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/namespaces/default.php	Mon Feb 16 16:17:25 2009 -0500
@@ -353,6 +353,17 @@
                   'restore_onclick' => 'ajaxEditor(\''.$this->revision_id.'\'); return false;',
                 )) . '
             </div>';
+      $q = $db->sql_query('SELECT page_format FROM ' . table_prefix . "logs WHERE log_id = {$this->revision_id};");
+      if ( !$q )
+        $db->_die();
+      
+      list($page_format) = $db->fetchrow_num();
+      $db->free_result();
+    }
+    else
+    {
+      $pathskey = $paths->nslist[ $this->namespace ] . $paths->page_id;
+      $page_format = $paths->pages[$pathskey]['page_format'];
     }
     
     if ( $redir_enabled )
@@ -368,7 +379,17 @@
     
     if ( $incl_inner_headers )
     {
-      $text = '?>' . RenderMan::render($text);
+      if ( $page_format === 'wikitext' )
+      {
+        $text = '?>' . RenderMan::render($text);
+      }
+      else
+      {
+        // Page format is XHTML. This means we want to disable functionality that MCE takes care of, while still retaining
+        // the ability to wikilink, the ability to use images, etc. Basically, RENDER_INLINEONLY disables all behavior in
+        // the rendering engine/Text_Wiki that conflicts with MCE.
+        $text = '?>' . RenderMan::render($text, RENDER_WIKI_DEFAULT | RENDER_INLINEONLY);
+      }
     }
     else
     {
--- a/includes/namespaces/user.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/namespaces/user.php	Mon Feb 16 16:17:25 2009 -0500
@@ -270,7 +270,7 @@
         foreach ( $comments as $comment )
         {
           $c_page_id = $paths->nslist[ $comment['namespace'] ] . sanitize_page_id($comment['page_id']);
-          if ( isset($paths->pages[ $c_page_id ]) )
+          if ( isPage($c_page_id) )
           {
             $parser->assign_bool(array(
               'page_exists' => true
--- a/includes/pageprocess.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/pageprocess.php	Mon Feb 16 16:17:25 2009 -0500
@@ -229,7 +229,7 @@
         $this->page_exists = true;
       }
     }
-    if ( isset($paths->pages[$pathskey]) )
+    if ( isPage($pathskey) )
     {
       if ( $this->send_headers )
       {
@@ -305,46 +305,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    // Send as regular page
-    if ( $this->send_headers )
-    {
-      $template->init_vars($this);
-    }
-    
-    $text = $this->fetch_text();
-    
-    if ( $text == 'err_no_text_rows' )
-    {
-      $this->err_no_rows();
-      return false;
-    }
-    else
-    {
-      $redirect = ( isset($_GET['redirect']) ) ? $_GET['redirect'] : 'YES YOU IDIOT';
-      if ( preg_match('/^#redirect \[\[([^\]]+)\]\]/i', $text, $match) && $redirect != 'no' )
-      {
-        // Redirect page!
-        $page_to = sanitize_page_id($match[1]);
-        $page_id_data = RenderMan::strToPageID($page_to);
-        if ( count($this->redirect_stack) >= 3 )
-        {
-          $this->render( (!$strict_no_headers), '<div class="usermessage"><b>' . $lang->get('page_err_redirects_exceeded') . '</b></div>' );
-        }
-        else
-        {
-          $result = $this->_handle_redirect($page_id_data[0], $page_id_data[1]);
-          if ( $result !== true )
-          {
-            // There was some error during the redirect process - usually an infinite redirect
-            $this->render( (!$strict_no_headers), '<div class="usermessage"><b>' . $result . '</b></div>' );
-          }
-        }
-      }
-      else
-      {
-        $this->render( (!$strict_no_headers) );
-      }
-    }
+    $this->ns->send_from_db();
   }
   
   /**
@@ -365,7 +326,7 @@
       return '';
     }
     $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-    if ( isset($paths->pages[$pathskey]) )
+    if ( isPage($pathskey) )
     {
       if ( isset($paths->pages[$pathskey]['password']) )
       {
@@ -383,10 +344,11 @@
    * @param string The new text for the page
    * @param string A summary of edits made to the page.
    * @param bool If true, the edit is marked as a minor revision
+   * @param string Page format - wikitext or xhtml. REQUIRED, and new in 1.1.6.
    * @return bool True on success, false on failure. When returning false, it will push errors to the PageProcessor error stack; read with $page->pop_error()
    */
   
-  function update_page($text, $edit_summary = false, $minor_edit = false)
+  function update_page($text, $edit_summary = false, $minor_edit = false, $page_format)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
@@ -455,6 +417,13 @@
       return false;
     }
     
+    // Page format check
+    if ( !in_array($page_format, array('xhtml', 'wikitext')) )
+    {
+      $this->raise_error("format \"$page_format\" not one of [xhtml, wikitext]");
+      return false;
+    }
+    
     //
     // Protection validated; update page content
     //
@@ -468,8 +437,8 @@
     $date_string = enano_date('d M Y h:i a');
     
     // Insert log entry
-    $sql = 'INSERT INTO ' . table_prefix . "logs ( time_id, date_string, log_type, action, page_id, namespace, author, page_text, edit_summary, minor_edit )\n"
-         . "  VALUES ( $time, '$date_string', 'page', 'edit', '{$this->page_id}', '{$this->namespace}', '$author', '$text', '$edit_summary', $minor_edit );";
+    $sql = 'INSERT INTO ' . table_prefix . "logs ( time_id, date_string, log_type, action, page_id, namespace, author, page_text, edit_summary, minor_edit, page_format )\n"
+         . "  VALUES ( $time, '$date_string', 'page', 'edit', '{$this->page_id}', '{$this->namespace}', '$author', '$text', '$edit_summary', $minor_edit, '$page_format' );";
     if ( !$db->sql_query($sql) )
     {
       $this->raise_error($db->get_error());
@@ -492,6 +461,20 @@
       return false;
     }
     
+    // Set page_format
+    $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
+    if ( $paths->pages[ $pathskey ]['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.)
+      if ( !$db->sql_query('UPDATE ' . table_prefix . "pages SET page_format = '$page_format' WHERE urlname = '$this->page_id' AND namespace = '$this->namespace';") )
+      {
+        $this->raise_error($db->get_error());
+        return false;
+      }
+      $paths->update_metadata_cache();
+    }
+    
     // Rebuild the search index
     $paths->rebuild_page_index($this->page_id, $this->namespace);
     
@@ -673,7 +656,7 @@
         
         // Rolling back the deletion of a page that was since created?
         $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-        if ( isset($paths->pages[$pathskey]) )
+        if ( isPage($pathskey) )
           return array(
               'success' => false,
               // This is a clean Christian in-joke.
@@ -875,7 +858,7 @@
     
     // Retrieve page metadata
     $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
-    if ( !isset($paths->pages[$pathskey]) )
+    if ( !isPage($pathskey) )
     {
       return array(
         'success' => false,
@@ -1038,7 +1021,7 @@
       return $lang->get('page_err_redirect_infinite_loop');
     }
     $page_id_key = $paths->nslist[ $namespace ] . sanitize_page_id($page_id);
-    if ( !isset($paths->pages[$page_id_key]) )
+    if ( !isPage($page_id_key) )
     {
       return $lang->get('page_err_redirect_to_nonexistent');
     }
--- a/includes/pageutils.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/pageutils.php	Mon Feb 16 16:17:25 2009 -0500
@@ -50,7 +50,7 @@
   public static function getsource($page, $password = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!isset($paths->pages[$page]))
+    if(!isPage($page))
     {
       return '';
     }
@@ -117,7 +117,7 @@
     if(!$session->get_permissions('edit_page'))
       return 'Access to edit pages is denied.';
     
-    if(!isset($paths->pages[$pname]))
+    if(!isPage($pname))
     {
       $create = PageUtils::createPage($page_id, $namespace);
       if ( $create != 'good' )
@@ -195,7 +195,7 @@
     }
     
     $pname = $paths->nslist[$namespace] . $page_id;
-    if(isset($paths->pages[$pname]))
+    if(isPage($pname))
     {
       // echo '<b>Notice:</b> PageUtils::createPage: Page already exists<br />';
       return 'Page already exists';
@@ -346,7 +346,7 @@
       return 'DNE';
     }
     
-    if ( isset($paths->pages[$pname]['password']) )
+    if ( isPage($pname['password']) )
     {
       $password_exists = ( !empty($paths->pages[$pname]['password']) && $paths->pages[$pname]['password'] !== sha1('') );
       if ( $password_exists && $password !== $paths->pages[$pname]['password'] )
@@ -572,7 +572,7 @@
     $text = RenderMan::preprocess_text($text);
     $name = $session->user_logged_in ? RenderMan::preprocess_text($session->username) : RenderMan::preprocess_text($name);
     $subj = RenderMan::preprocess_text($subject);
-    if(getConfig('approve_comments')=='1') $appr = '0'; else $appr = '1';
+    if(getConfig('approve_comments', '0')=='1') $appr = '0'; else $appr = '1';
     $q = 'INSERT INTO ' . table_prefix.'comments(page_id,namespace,subject,comment_data,name,user_id,approved,time) VALUES(\'' . $page_id . '\',\'' . $namespace . '\',\'' . $subj . '\',\'' . $text . '\',\'' . $name . '\',' . $session->user_id . ',' . $appr . ','.time().')';
     $e = $db->sql_query($q);
     if(!$e) die('alert(unescape(\''.rawurlencode('Error inserting comment data: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'))');
@@ -801,7 +801,7 @@
       {
         $_ob .= '<h3>' . $lang->get('comment_postform_title') . '</h3>';
         $_ob .= $lang->get('comment_postform_blurb');
-        if(getConfig('approve_comments')=='1') $_ob .= ' ' . $lang->get('comment_postform_blurb_unapp');
+        if(getConfig('approve_comments', '0')=='1') $_ob .= ' ' . $lang->get('comment_postform_blurb_unapp');
         if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
         {
           $_ob .= ' ' . $lang->get('comment_postform_blurb_captcha');
@@ -1192,7 +1192,7 @@
     
     $pname = $paths->nslist[$namespace] . sanitize_page_id($page_id);
     
-    if ( !isset($paths->pages[$pname]) )
+    if ( !isPage($pname) )
     {
       return 'The page does not exist.';
     }
--- a/includes/paths.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/paths.php	Mon Feb 16 16:17:25 2009 -0500
@@ -173,7 +173,7 @@
     else
     {
       $e = $db->sql_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
-                          . '  delvote_ips,wiki_mode,password FROM '.table_prefix.'pages ORDER BY name;');
+                          . '  delvote_ips,wiki_mode,password,page_format FROM '.table_prefix.'pages ORDER BY name;');
       
       if( !$e )
       {
@@ -250,7 +250,8 @@
           'comments_on' => 1,
           'protected' => 1,
           'delvotes' => 0,
-          'delvote_ips' => ''
+          'delvote_ips' => '',
+          'page_format' => getConfig('default_page_format', 'wikitext')
         );
       $this->external_api_page = true;
       $code = $plugins->setHook('paths_external_api_page');
@@ -322,16 +323,17 @@
       if ( !is_array($this->cpage) )
       {
         $this->cpage = Array(
-          'name'=>$page_name,
-          'urlname'=>$this->page,
-          'namespace'=>'Article',
-          'special'=>0,
-          'visible'=>0,
-          'comments_on'=>1,
-          'protected'=>0,
-          'delvotes'=>0,
-          'delvote_ips'=>'',
-          'wiki_mode'=>2,
+          'name' => $page_name,
+          'urlname' => $this->page,
+          'namespace' => 'Article',
+          'special' => 0,
+          'visible' => 0,
+          'comments_on' => 1,
+          'protected' => 0,
+          'delvotes' => 0,
+          'delvote_ips' => '',
+          'wiki_mode' => 2,
+          'page_format' => getConfig('default_page_format', 'wikitext')
           );
       }
       // Look for a namespace prefix in the urlname, and assign a different namespace, if necessary
@@ -608,7 +610,7 @@
       return false;
     
     $e = $db->sql_unbuffered_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
-                          . '  delvote_ips,wiki_mode,password FROM '.table_prefix.'pages ORDER BY name;');
+                          . '  delvote_ips,wiki_mode,password,page_format FROM '.table_prefix.'pages ORDER BY name;');
     if ( !$e )
       $db->_die();
     
--- a/includes/plugins.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/plugins.php	Mon Feb 16 16:17:25 2009 -0500
@@ -87,6 +87,7 @@
         $this->load_list[] = $row['plugin_filename'];
       }
     }
+    $this->load_list = array_unique($this->load_list);
     
     $this->loaded_plugins = $this->get_plugin_list($this->load_list);
     
@@ -648,9 +649,9 @@
       'success' => true
     );
     
-    endswitch;
+    $this->reimport_plugin_strings($filename, $plugin_list);
     
-    $this->reimport_plugin_strings($filename, $plugin_list);
+    endswitch;
     
     $cache->purge('plugins');
     $cache->purge('page_meta');
--- a/includes/render.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/render.php	Mon Feb 16 16:17:25 2009 -0500
@@ -42,79 +42,14 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
-    $perms =& $session;
-    
-    if ( $page_id != $paths->page_id || $namespace != $paths->namespace )
-    {
-      unset($perms);
-      unset($perms); // PHP <5.1.5 Zend bug
-      $perms = $session->fetch_page_acl($page_id, $namespace);
-      if ( !$perms )
-      {
-        $session->init_permissions();
-        $perms = $session->fetch_page_acl($page_id, $namespace);
-      };
-    }
-    
-    if(!$perms->get_permissions('read'))
-      return 'Access denied ('.$paths->nslist[$namespace].$page_id.')';
-    
-    if($namespace != 'Template' && ($wiki == 0 || $render == false))
-    {
-      if(!$perms->get_permissions('view_source'))
-      {
-        return 'Access denied ('.$paths->nslist[$namespace].$page_id.')';
-      }
-    }
-    
-    $q = $db->sql_query('SELECT page_text,char_tag FROM '.table_prefix.'page_text WHERE page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\';');
-    if ( !$q )
-    {
-      $db->_die('Method called was: RenderMan::getPage(\''.$page_id.'\', \''.$namespace.'\');.');
-    }
-    if ( $db->numrows() < 1 )
-    {
-      return false;
-    }
-    $row = $db->fetchrow();
-    $db->free_result();
+    $page = new PageProcessor($page_id, $namespace);
+    $text = $page->fetch_text();
     
-    $message = $row['page_text'];
-    $chartag = $row['char_tag'];
-    unset($row); // Free some memory
+    if ( !$render )
+      return $text;
     
-    if ( preg_match("#^\#redirect \[\[([^\]\r\n\a\t]+?)\]\]#", $message, $m) && $redir && ( !isset($_GET['redirect']) || ( isset($_GET['redirect']) && $_GET['redirect'] != 'no' ) ) )
-    {
-      $old = $paths->cpage;
-      $a = RenderMan::strToPageID($m[1]);
-      $a[0] = str_replace(' ', '_', $a[0]);
-      
-      $pageid = str_replace(' ', '_', $paths->nslist[$a[1]] . $a[0]);
-      $paths->page = $pageid;
-      $paths->cpage = $paths->pages[$pageid];
-      //die('<pre>'.print_r($paths->cpage,true).'</pre>');
-      
-      unset($template);
-      unset($GLOBALS['template']);
-      
-      $GLOBALS['template'] = new template();
-      global $template;
-      
-      $template->template(); // Tear down and rebuild the template parser
-      $template->load_theme($session->theme, $session->style);
-      
-      $data = '<div><small>(Redirected from <a href="'.makeUrlNS($old['namespace'], $old['urlname_nons'], 'redirect=no', true).'">'.$old['name'].'</a>)</small></div>'.RenderMan::getPage($a[0], $a[1], $wiki, $smilies, $filter_links, false /* Enforces a maximum of one redirect */);
-      
-      return $data;
-    }
-    else if(preg_match('#^\#redirect \[\[(.+?)\]\]#', $message, $m) && isset($_GET['redirect']) && $_GET['redirect'] == 'no')
-    {
-      preg_match('#^\#redirect \[\[(.+)\]\]#', $message, $m);
-      $m[1] = str_replace(' ', '_', $m[1]);
-      $message = preg_replace('#\#redirect \[\[(.+)\]\]#', '<nowiki><div class="mdg-infobox"><table border="0" width="100%" cellspacing="0" cellpadding="0"><tr><td valign="top"><img alt="Cute wet-floor icon" src="'.scriptPath.'/images/redirector.png" /></td><td valign="top" style="padding-left: 10px;"><b>This page is a <i>redirector</i>.</b><br />This means that this page will not show its own content by default. Instead it will display the contents of the page it redirects to.<br /><br />To create a redirect page, make the <i>first characters</i> in the page content <tt>#redirect [[Page_ID]]</tt>. For more information, see the Enano <a href="http://enanocms.org/Help:Wiki_formatting">Wiki formatting guide</a>.<br /><br />This page redirects to <a href="'.makeUrl($m[1]).'">'.$paths->pages[$m[1]]['name'].'</a>.</td></tr></table></div><br /><hr style="margin-left: 1em; width: 200px;" /></nowiki>', $message);
-    }
-    $session->disallow_password_grab();
-    return ($render) ? RenderMan::render($message, $wiki, $smilies, $filter_links) : $message;
+    $text = self::render($text, $wiki, $smilies, $filter_links);
+    return $text;
   }
   
   public static function getTemplate($id, $parms)
@@ -183,7 +118,7 @@
         if ( isset($prefixlist[$match[1]]) )
         {
           $new_id = $paths->nslist[ $prefixlist[$match[1]] ] . sanitize_page_id($match[2]);
-          if ( !isset($paths->pages[$new_id]) )
+          if ( !isPage($new_id) )
           {
             return "[[$new_id]]";
           }
@@ -215,43 +150,36 @@
     return $text;
   }
   
-  public static function render($text, $wiki = 1, $smilies = true, $filter_links = true)
+  /**
+   * Renders a glob of text. Note that this is PHP-safe, so if returned text (or rather, "?>" . $returned) has PHP it can be eval'ed.
+   * @param string Text to render
+   * @param int Render parameters - see constants.php
+   * @return string Rendered text
+   */
+  
+  public static function render($text, $flags = RENDER_WIKI_DEFAULT, $smilies = true)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if($smilies)
+    
+    if ( !$smilies )
+      $flags |= RENDER_NOSMILIES;
+    
+    if ( $flags & ~RENDER_NOSMILIES )
     {
       $text = RenderMan::smilieyize($text);
     }
-    if($wiki == 1)
+    if ( $flags & RENDER_WIKI_DEFAULT )
     {
-      $text = RenderMan::next_gen_wiki_format($text);
+      $text = RenderMan::next_gen_wiki_format($text, $flags);
     }
-    elseif($wiki == 2)
+    else if ( $flags & RENDER_WIKI_TEMPLATE )
     {
       $text = $template->tplWikiFormat($text);
-    }
+    }           
     return $text;
   }
   
-  public static function PlainTextRender($text, $wiki = 1, $smilies = false, $filter_links = true)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if($smilies)
-    {
-      $text = RenderMan::smilieyize($text);
-    }
-    if($wiki == 1)
-    {
-      $text = RenderMan::next_gen_wiki_format($text, true);
-    }
-    elseif($wiki == 2)
-    {
-      $text = $template->tplWikiFormat($text);
-    }
-    return $text;
-  }
-  
-  public static function next_gen_wiki_format($text, $plaintext = false, $filter_links = true, $do_params = false)
+  private static function next_gen_wiki_format($text, $flags = 0)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
@@ -291,76 +219,75 @@
       $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text);
     }
     
-    preg_match_all('/<lang (?:code|id)="([a-z0-9_-]+)">([\w\W]+?)<\/lang>/', $text, $langmatch);
-    foreach ( $langmatch[0] as $i => $match )
+    if ( !($flags & RENDER_BLOCKONLY) )
     {
-      if ( $langmatch[1][$i] == $lang->lang_code )
-      {
-        $text = str_replace_once($match, $langmatch[2][$i], $text);
-      }
-      else
+      preg_match_all('/<lang (?:code|id)="([a-z0-9_-]+)">([\w\W]+?)<\/lang>/', $text, $langmatch);
+      foreach ( $langmatch[0] as $i => $match )
       {
-        $text = str_replace_once($match, '', $text);
+        if ( $langmatch[1][$i] == $lang->lang_code )
+        {
+          $text = str_replace_once($match, $langmatch[2][$i], $text);
+        }
+        else
+        {
+          $text = str_replace_once($match, '', $text);
+        }
       }
-    }
     
-    $code = $plugins->setHook('render_wikiformat_pre');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
+      $code = $plugins->setHook('render_wikiformat_pre');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
     
     //$template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
-    $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
-    $i = 0;
-    while ( preg_match($template_regex, $text) )
-    {
-      $i++;
-      if ( $i == 5 )
-        break;
-      $text = RenderMan::include_templates($text);
-    }
-    
-    $code = $plugins->setHook('render_wikiformat_posttemplates');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    
-    if ( !$plaintext )
-    {
+      $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
+      $i = 0;
+      while ( preg_match($template_regex, $text) )
+      {
+        $i++;
+        if ( $i == 5 )
+          break;
+        $text = RenderMan::include_templates($text);
+      }
+      
+      $code = $plugins->setHook('render_wikiformat_posttemplates');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+      
       // Process images
       $text = RenderMan::process_image_tags($text, $taglist);
       $text = RenderMan::process_imgtags_stage2($text, $taglist);
     }
     
-    if($do_params)
-    {
-      preg_match_all('#\(_([0-9]+)_\)#', $text, $matchlist);
-      foreach($matchlist[1] as $m)
-      {
-        $text = str_replace('(_'.$m.'_)', $paths->getParam((int)$m), $text);
-      }
-    }
-    
     // Before shipping it out to the renderer, replace spaces in between headings and paragraphs:
     $text = preg_replace('/<\/(h[0-9]|div|p)>([\s]+)<(h[0-9]|div|p)( .+?)?>/i', '</\\1><\\3\\4>', $text);
     
     $text = process_tables($text);
-    $text = RenderMan::parse_internal_links($text);
+    
+    if ( !($flags & RENDER_BLOCKONLY) )
+      $text = RenderMan::parse_internal_links($text);
     
     $wiki = Text_Wiki::singleton('Mediawiki');
-    if($plaintext)
+    $wiki->setRenderConf('Xhtml', 'wikilink', 'view_url', contentPath);
+    $wiki->setRenderConf('Xhtml', 'Url', 'css_descr', 'external');
+    if ( $flags & RENDER_BLOCKONLY )
     {
-      $wiki->setRenderConf('Plain', 'wikilink', 'view_url', contentPath);
-      $result = $wiki->transform($text, 'Plain');
+      $wiki->disableRule('Freelink');
+      $wiki->disableRule('Url');
+      $wiki->disableRule('Toc');
+      $wiki->disableRule('Image');
     }
-    else
+    else if ( $flags & RENDER_INLINEONLY )
     {
-      $wiki->setRenderConf('Xhtml', 'wikilink', 'view_url', contentPath);
-      $wiki->setRenderConf('Xhtml', 'Url', 'css_descr', 'external');
-      $result = $wiki->transform($text, 'Xhtml');
+      foreach ( array('code', 'html', 'raw', 'include', 'embed', 'horiz', 'break', 'blockquote', 'list', 'newline', 'paragraph', 'revise', 'tighten') as $rule )
+      {
+        $wiki->disableRule($rule);
+      }
     }
+    $result = $wiki->transform($text, 'Xhtml');
     
     // HTML fixes
     $result = preg_replace('#<tr>([\s]*?)<\/tr>#is', '', $result);
@@ -377,10 +304,13 @@
     $result = str_replace("<p></div></p>", "</div>", $result);
     $result = str_replace("<p></table></p>", "</table>", $result);
     
-    $code = $plugins->setHook('render_wikiformat_post');
-    foreach ( $code as $cmd )
+    if ( !($flags & RENDER_BLOCKONLY) )
     {
-      eval($cmd);
+      $code = $plugins->setHook('render_wikiformat_post');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
     }
     
     // Reinsert <nowiki> sections
@@ -406,82 +336,6 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     return RenderMan::next_gen_wiki_format($message, $plaintext, $filter_links, $do_params);
-    
-    $random_id = md5( time() . mt_rand() );
-    
-    // Strip out <nowiki> sections
-    $nw = preg_match_all('#<nowiki>(.*?)<\/nowiki>#is', $message, $nowiki);
-    
-    if(!$plaintext)
-    {
-    
-      //return '<pre>'.print_r($nowiki,true).'</pre>';
-      
-      for($i=0;$i<sizeof($nowiki[1]);$i++)
-      {
-        $message = str_replace('<nowiki>'.$nowiki[1][$i].'</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $message);
-      }
-      
-      $message = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '\\1', $message);
-      
-      //return '<pre>'.htmlspecialchars($message).'</pre>';
-      
-      $message = RenderMan::process_image_tags($message);
-    
-    }
-    
-    if($do_params)
-    {
-      preg_match_all('#\(_([0-9]+)_\)#', $message, $matchlist);
-      foreach($matchlist[1] as $m)
-      {
-        $message = str_replace('(_'.$m.'_)', $paths->getParam((int)$m), $message);
-      }
-    }
-    
-    $message = RenderMan::include_templates($message);
-    
-    // Reinsert <nowiki> sections
-    for($i=0;$i<$nw;$i++)
-    {
-      $message = str_replace('{NOWIKI:'.$random_id.':'.$i.'}', '<nowiki>'.$nowiki[1][$i].'</nowiki>', $message);
-    }
-    
-    $message = process_tables($message);
-    //if($message2 != $message) return '<pre>'.htmlspecialchars($message2).'</pre>';
-    //$message = str_replace(array('<table>', '</table>'), array('<nowiki><table>', '</table></nowiki>'), $message);
-    
-    $wiki =& Text_Wiki::singleton('Mediawiki');
-    if($plaintext)
-    {
-      $wiki->setRenderConf('Plain', 'wikilink', 'view_url', contentPath);
-      $result = $wiki->transform($message, 'Plain');
-    } else {
-      $wiki->setRenderConf('Xhtml', 'wikilink', 'view_url', contentPath);
-      $wiki->setRenderConf('Xhtml', 'Url', 'css_descr', 'external');
-      $result = $wiki->transform($message, 'Xhtml');
-    }
-    
-    // HTML fixes
-    $result = preg_replace('#<tr>([\s]*?)<\/tr>#is', '', $result);
-    $result = preg_replace('#<p>([\s]*?)<\/p>#is', '', $result);
-    $result = preg_replace('#<br />([\s]*?)<table#is', '<table', $result);
-    $result = str_replace("<pre><code>\n", "<pre><code>", $result);
-    $result = preg_replace("/<p><table([^>]*?)><\/p>/", "<table\\1>", $result);
-    $result = str_replace("<br />\n</td>", "\n</td>", $result);
-    $result = str_replace("<p><tr>", "<tr>", $result);
-    $result = str_replace("<tr><br />", "<tr>", $result);
-    $result = str_replace("</tr><br />", "</tr>", $result);
-    $result = str_replace("</table></p>", "</table>", $result);
-    $result = str_replace("</table><br />", "</table>", $result);
-    $result = preg_replace('/<\/table>$/', "</table><br /><br />", $result);
-    $result = str_replace("<p></div></p>", "</div>", $result);
-    $result = str_replace("<p></table></p>", "</table>", $result);
-    
-    $result = str_replace('<nowiki>',  '&lt;nowiki&gt;',  $result);
-    $result = str_replace('</nowiki>', '&lt;/nowiki&gt;', $result);
-    
-    return $result;
   }
   
   public static function destroy_javascript($message, $_php = false)
@@ -520,6 +374,201 @@
   }
   
   /**
+   * Reverse-renders a blob of text (converts it from XHTML back to wikitext) by using parser hints and educated guesses.
+   * @param string XHTML
+   * @return string Wikitext
+   */
+  
+  public static function reverse_render($text)
+  {
+    // convert \r\n to \n
+    $text = str_replace("\r\n", "\n", $text);
+    
+    // Separate certain block level elements onto their own lines. This tidies up the tag
+    // soup that TinyMCE sometimes produces.
+    $block_elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'table', 'ul', 'pre');
+    $block_elements = implode('|', $block_elements);
+    $regex = "#(</(?:$block_elements)>)\n?<($block_elements)(>| .+?>)#i";
+    $text = preg_replace($regex, "$1\n\n<$2$3", $text);
+    
+    $text = self::reverse_process_parser_hints($text);
+    $text = self::reverse_process_headings($text);
+    $text = self::reverse_process_lists($text);
+    $text = self::reverse_process_tables($text);
+    
+    // Lastly, strip out paragraph tags.
+    $text = preg_replace('|^ *<p>(.+?)</p> *$|m', "\\1", $text);
+    
+    return $text;
+  }
+  
+  public static function reverse_process_parser_hints($text)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    if ( !preg_match_all('|<!--#([a-z0-9_]+)(?: (.+?))?-->([\w\W]*?)<!--#/\\1-->|s', $text, $matches) )
+      return $text;
+    
+    foreach ( $matches[0] as $i => $match )
+    {
+      $tag =& $matches[1][$i];
+      $attribs =& $matches[2][$i];
+      $inner =& $matches[3][$i];
+      
+      $attribs = self::reverse_process_hint_attribs($attribs);
+      switch($tag)
+      {
+        case 'smiley':
+        case 'internallink':
+        case 'imagelink':
+          if ( isset($attribs['code']) )
+          {
+            $text = str_replace($match, $attribs['code'], $text);
+          }
+          else if ( isset($attribs['src']) )
+          {
+            $text = str_replace($match, $attribs['src'], $text);
+          }
+          break;
+      }
+    }
+    
+    return $text;
+  }
+  
+  public static function reverse_process_hint_attribs($attribs)
+  {
+    $return = array();
+    if ( !preg_match_all('/([a-z0-9_-]+)="([^"]+?)"/', $attribs, $matches) )
+      return array();
+    
+    foreach ( $matches[0] as $i => $match )
+    {
+      $name =& $matches[1][$i];
+      $value =& $matches[2][$i];
+      
+      $value = base64_decode($value);
+      
+      $return[$name] = $value;
+    }
+    
+    return $return;
+  }
+  
+  /**
+   * Escapes a string so that it's safe to use as an attribute in a parser hint.
+   * @param string
+   * @return string
+   */
+  
+  public static function escape_parser_hint_attrib($text)
+  {
+    return base64_encode($text);
+  }
+  
+  public static function reverse_process_headings($text)
+  {
+    if ( !preg_match_all('|^<h([1-6])(?: id="toc[0-9]+")?>(.*?)</h\\1>$|m', $text, $matches) )
+      return $text;
+    
+    foreach ( $matches[0] as $i => $match )
+    {
+      // generate heading tag
+      $heading_size = intval($matches[1][$i]);
+      $eq = '';
+      for ( $j = 0; $j < $heading_size; $j++ )
+        $eq .= '=';
+      
+      $heading =& $matches[2][$i];
+      
+      $tag = "$eq $heading $eq";
+      $text = str_replace($match, $tag, $text);
+    }
+    
+    return $text;
+  }
+  
+  public static function reverse_process_lists($text)
+  {
+    if ( !preg_match('!(</?(?:ul|ol|li)>)!', $text) )
+      return $text;
+    
+    $split = preg_split('!(</?(?:ul|ol|li)>)!', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+    
+    $stack_height = 0;
+    $current_list = '';
+    $old_current_list = '';
+    $spaces = '';
+    $marker = '*';
+    $list_id = 0;
+    $just_terminated = false;
+    foreach ( $split as $tag )
+    {
+      switch($tag) 
+      {
+        case '<ul>':
+        case '<ol>':
+          $stack_height++;
+          $just_terminated = false;
+          if ( $stack_height > 1 )
+            $spaces .= $marker;
+          
+          $marker = ( $tag == 'ol' ) ? '#' : '*';
+          if ( $stack_height > 1 )
+            $current_list .= "\n";
+          
+          break;
+        case '</ul>':
+        case '</ol>':
+          $stack_height--;
+          $spaces = substr($spaces, 1);
+          
+          if ( $stack_height == 0 )
+          {
+            // rotate
+            $text = str_replace_once("{$old_current_list}{$tag}", trim($current_list), $text);
+            $current_list = '';
+            $old_current_list = '';
+          }
+          $just_terminated = true;
+          break;
+        case '<li>':
+          if ( $stack_height < 1 )
+            break;
+          
+          $current_list .= "{$spaces}{$marker} ";
+          break;
+        case '</li>':
+          if ( $stack_height < 1 )
+            break;
+          
+          if ( !$just_terminated )
+            $current_list .= "\n";
+          
+          $just_terminated = false;
+          break;
+        default:
+          if ( $stack_height > 0 )
+          {
+            $current_list .= trim($tag);
+          }
+          break;
+      }
+      if ( $stack_height > 0 )
+      {
+        $old_current_list .= $tag;
+      }
+    }
+    
+    return $text;
+  }
+  
+  public static function reverse_process_tables($text)
+  {
+    return $text;
+  }
+  
+  /**
    * 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.
@@ -567,7 +616,8 @@
       }
       else
       {
-        $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+        $omatch = self::escape_parser_hint_attrib($match);
+        $link = "<!--#internallink src=\"$omatch\" --><a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a><!--#/internallink-->";
       }
       
       $text = str_replace($match, $link, $text);
@@ -596,7 +646,8 @@
       }
       else
       {
-        $link = "<a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a>";
+        $omatch = self::escape_parser_hint_attrib($match);
+        $link = "<!--#internallink src=\"$omatch\" --><a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a><!--#/internallink-->";
       }
       
       $text = str_replace($match, $link, $text);
@@ -775,6 +826,9 @@
       eval($cmd);
     }
     
+    // gently apply some reverse-processing to allow Text_Wiki to do magic with TOCs and stuff
+    $text = self::reverse_process_headings($text);
+    
     // Reinsert <nowiki> sections
     for($i=0;$i<$nw;$i++)
     {
@@ -844,43 +898,31 @@
       ':-['     => 'face-embarassed.png',
       ':['      => 'face-embarassed.png'
       );
-    /*
-    $keys = array_keys($smileys);
-    foreach($keys as $k)
-    {
-      $regex1 = '#([\W]+)'.preg_quote($k).'([\s\n\r\.]+)#s';
-      $regex2 = '\\1<img alt="'.$k.'" title="'.$k.'" src="'.scriptPath.'/images/smilies/'.$smileys[$k].'" style="border: 0;" />\\2';
-      $text = preg_replace($regex1, $regex2, $text);
-    }                                                                      
-    */
     
     // Strip out <nowiki> sections
     //return '<pre>'.htmlspecialchars($text).'</pre>';
     $nw = preg_match_all('#<nowiki>(.*?)<\/nowiki>#is', $text, $nowiki);
     
-    for($i=0;$i<sizeof($nowiki[1]);$i++)
+    for ( $i = 0; $i < count($nowiki[1]); $i++ )
     {
-      $text = str_replace('<nowiki>'.$nowiki[1][$i].'</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $text);
+      $text = str_replace('<nowiki>' . $nowiki[1][$i] . '</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $text);
     }
     
-    $keys = array_keys($smileys);
-    foreach($keys as $k)
+    foreach ( $smileys as $smiley => $smiley_path )
     {
-      $t = hexencode($k, ' ', '');
-      $t = trim($t);
-      $t = explode(' ', $t);
-      $s = '';
-      foreach($t as $b)
-      {
-        $s.='&#x'.$b.';';
-      }
-      $pfx = ( $complete_urls ) ? 'http' . ( isset($_SERVER['HTTPS']) ? 's' : '' ) . '://'.$_SERVER['HTTP_HOST'] : '';
-      $text = str_replace(' '.$k, ' <nowiki><img title="'.$s.'" alt="'.$s.'" src="'.$pfx.scriptPath.'/images/smilies/'.$smileys[$k].'" style="border: 0;" /></nowiki>', $text);
+      $hex_smiley = hexencode($smiley, '&#x', ';');
+      $pfx = ( $complete_urls ) ? get_server_url() : '';
+      $text = str_replace(' ' . $smiley,
+          ' <!--#smiley code="' . self::escape_parser_hint_attrib($smiley) . '"--><nowiki>
+           <!-- The above is a reverse-parser hint -->
+             <img title="' . $hex_smiley . '" alt="' . $hex_smiley . '" src="' . $pfx . scriptPath . '/images/smilies/' . $smiley_path . '"
+              style="border: 0;" />
+           </nowiki><!--#/smiley-->', $text);
     }
     //*/
     
     // Reinsert <nowiki> sections
-    for($i=0;$i<$nw;$i++)
+    for ( $i = 0; $i < $nw; $i++ )
     {
       $text = str_replace('{NOWIKI:'.$random_id.':'.$i.'}', '<nowiki>'.$nowiki[1][$i].'</nowiki>', $text);
     }
@@ -888,39 +930,6 @@
     return $text;
   }
   
-  /*
-   * **** DEPRECATED ****
-   * Replaces some critical characters in a string with MySQL-safe equivalents
-   * @param $text string the text to escape
-   * @return array key 0 is the escaped text, key 1 is the character tag
-   * /
-   
-  public static function escape_page_text($text)
-  {
-    $char_tag = md5(microtime() . mt_rand());
-    $text = str_replace("'",  "{APOS:$char_tag}",  $text);
-    $text = str_replace('"',  "{QUOT:$char_tag}",  $text);
-    $text = str_replace("\\", "{SLASH:$char_tag}", $text);
-    return Array($text, $char_tag);
-  }
-  */
-  
-  /* **** DEPRECATED ****
-   * Reverses the result of RenderMan::escape_page_text().
-   * @param $text string the text to unescape
-   * @param $char_tag string the character tag
-   * @return string
-   * /
-   
-  public static function unescape_page_text($text, $char_tag)
-  {
-    $text = str_replace("{APOS:$char_tag}",  "'",  $text);
-    $text = str_replace("{QUOT:$char_tag}",  '"',  $text);
-    $text = str_replace("{SLASH:$char_tag}", "\\", $text);
-    return $text;
-  }
-  */
-  
   /**
    * Generates a summary of the differences between two texts, and formats it as XHTML.
    * @param $str1 string the first block of text
@@ -1025,7 +1034,7 @@
               break;
             }
             // not the height, so see if a plugin took this over
-            // this hook requires plugins to return true if they modified anythin
+            // this hook requires plugins to return true if they modified anything
             $code = $plugins->setHook('img_tag_parse_params');
             foreach ( $code as $cmd )
             {
@@ -1041,7 +1050,7 @@
       
       if ( !isPage( $paths->nslist['File'] . $filename ) )
       {
-        $text = str_replace($full_tag, '[[' . makeUrlNS('File', $filename) . ']]', $text);
+        $text = str_replace($full_tag, '[[' . $paths->nslist['File'] . $filename . ']]', $text);
         continue;
       }
       
@@ -1081,7 +1090,8 @@
       
       $img_tag .= '/>';
       
-      $complete_tag = '';
+      $s_full_tag = self::escape_parser_hint_attrib($full_tag);
+      $complete_tag = '<!--#imagelink src="' . $s_full_tag . '" -->';
       
       if ( !empty($scale_type) && !$raw_display )
       {
@@ -1131,12 +1141,12 @@
         $complete_tag .= '</a>';
       }
       
-      $complete_tag .= "\n\n";
+      $complete_tag .= "<!--#/imagelink-->";
       $taglist[$i] = $complete_tag;
       
+      /*
       $pos = strpos($text, $full_tag);
       
-      /*
       while(true)
       {
         $check1 = substr($text, $pos, 3);
--- a/includes/rijndael.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/rijndael.php	Mon Feb 16 16:17:25 2009 -0500
@@ -98,9 +98,9 @@
   
   static function trace($error)
   {
-    //$bt = debug_backtrace();
+    // $bt = debug_backtrace();
     echo("$error\n");
-    //echo(print_r($bt, true));
+    // echo '<pre>' . htmlspecialchars(print_r($bt, true)) . '</pre>';
     
     exit();
   }
--- a/includes/search.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/search.php	Mon Feb 16 16:17:25 2009 -0500
@@ -256,7 +256,7 @@
             // Is this search term present in the page's title? If so, give extra points
             preg_match("/^ns=$ns_list;pid=(.+)$/", $match, $piecesparts);
             $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
-            if ( isset($paths->pages[$pathskey]) )
+            if ( isPage($pathskey) )
             {
               $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
               if ( $test_func($paths->pages[$pathskey]['name'], $row['word']) || $test_func($paths->pages[$pathskey]['urlname_nons'], $row['word']) )
@@ -298,7 +298,7 @@
           // Is this search term present in the page's title? If so, give extra points
           preg_match("/^ns=$ns_list;pid=(.+)$/", $pages, $piecesparts);
           $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
-          if ( isset($paths->pages[$pathskey]) )
+          if ( isPage($pathskey) )
           {
             $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
             if ( $test_func($paths->pages[$pathskey]['name'], $row['word']) || $test_func($paths->pages[$pathskey]['urlname_nons'], $row['word']) )
@@ -397,7 +397,7 @@
         // Is this search term present in the page's title? If so, give extra points
         preg_match("/^ns=$ns_list;pid=(.+)$/", $id, $piecesparts);
         $pathskey = $paths->nslist[ $piecesparts[1] ] . sanitize_page_id($piecesparts[2]);
-        if ( isset($paths->pages[$pathskey]) )
+        if ( isPage($pathskey) )
         {
           $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
           foreach ( array_merge($query_phrase['any'], $query_phrase['req']) as $term )
--- a/includes/sessions.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/sessions.php	Mon Feb 16 16:17:25 2009 -0500
@@ -716,13 +716,17 @@
       $this->sql('SELECT password,\'\' AS password_salt,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix."users\n"
                . "  WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '$username_db';");
     }
-    if($db->numrows() < 1)
+    if ( $db->numrows() < 1 )
     {
       // This wasn't logged in <1.0.2, dunno how it slipped through
-      if($level > USER_LEVEL_MEMBER)
-        $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+      if ( $level > USER_LEVEL_MEMBER )
+        $this->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
+                   . '  (\'security\', \'admin_auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', '
+                      . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
       else
-        $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
+        $this->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary) VALUES\n"
+                   . '  (\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', '
+                      . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
       
       // Do we also need to increment the lockout countdown?
       if ( @$policy != 'disable' && !defined('IN_ENANO_INSTALL') )
@@ -783,7 +787,7 @@
         $success = true;
       }
     }
-    else if ( $row['old_encryption'] == 2 || defined('ENANO_UPGRADE_USE_AES_PASSWORDS') )
+    else if ( $row['old_encryption'] == 2 || ( defined('ENANO_UPGRADE_USE_AES_PASSWORDS') ) )
     {
       // Our password field uses the 1.0RC1-1.1.5 encryption format
       $real_pass = $aes->decrypt($row['password'], $this->private_key);
@@ -920,7 +924,7 @@
     $salt = '';
     for ( $i = 0; $i < 32; $i++ )
     {
-      $salt .= chr(mt_rand(32, 127));
+      $salt .= chr(mt_rand(32, 126));
     }
     
     // Session key
@@ -946,7 +950,7 @@
     {
       // Stash it in a cookie
       // For now, make the cookie last forever, we can change this in 1.1.x
-      setcookie( 'sid', $session_key, time()+15552000, scriptPath.'/', null, ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ) );
+      setcookie( 'sid', $session_key, time()+15552000, scriptPath.'/', null, $GLOBALS['is_https']);
       $_COOKIE['sid'] = $session_key;
     }
     // $keyhash is stored in the database, this is for compatibility with the older DB structure
@@ -1354,10 +1358,6 @@
       if($this->user_logged_in)
       {
         $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
-        // See if we can get rid of the cached decrypted session key
-        $key_bin = hex2bin($this->sid);
-        $key_hash = sha1($key_bin . '::' . $this->private_key);
-        aes_decrypt_cache_destroy($key_hash);
         // Completely destroy our session
         if($this->auth_level > USER_LEVEL_CHPREF)
         {
@@ -1811,8 +1811,6 @@
       }
     }
     
-    $password = $aes->encrypt($password, $this->private_key, ENC_HEX);
-    
     // Require the account to be activated?
     switch(getConfig('account_activation'))
     {
@@ -1836,7 +1834,7 @@
     $actkey = sha1 ( microtime() . mt_rand() );
 
     // We good, create the user
-    $this->sql('INSERT INTO '.table_prefix.'users ( username, password, email, real_name, theme, style, reg_time, account_active, activation_key, user_level, user_coppa, user_registration_ip ) VALUES ( \''.$username.'\', \''.$password.'\', \''.$email.'\', \''.$real_name.'\', \''.$template->default_theme.'\', \''.$template->default_style.'\', '.time().', '.$active.', \''.$actkey.'\', '.USER_LEVEL_CHPREF.', ' . $coppa_col . ', \'' . $ip . '\' );');
+    $this->sql('INSERT INTO '.table_prefix.'users ( username, email, real_name, theme, style, reg_time, account_active, activation_key, user_level, user_coppa, user_registration_ip ) VALUES ( \''.$username.'\', \''.$email.'\', \''.$real_name.'\', \''.$template->default_theme.'\', \''.$template->default_style.'\', '.time().', '.$active.', \''.$actkey.'\', '.USER_LEVEL_CHPREF.', ' . $coppa_col . ', \'' . $ip . '\' );');
     
     // Get user ID and create users_extra entry
     $q = $this->sql('SELECT user_id FROM '.table_prefix."users WHERE username='$username';");
@@ -1848,6 +1846,9 @@
       $this->sql('INSERT INTO '.table_prefix.'users_extra(user_id) VALUES(' . $user_id . ');');
     }
     
+    // Set the password
+    $this->set_password($user_id, $password);
+    
     // Config option added, 1.1.5
     if ( getConfig('userpage_grant_acl', '1') == '1' )             
     {
@@ -1879,8 +1880,8 @@
     // Require the account to be activated?
     if ( $coppa )
     {
-      $this->admin_activation_request($username);
-      $this->send_coppa_mail($username,$email);
+      $this->admin_activation_request($user_orig);
+      $this->send_coppa_mail($user_orig, $email);
     }
     else
     {
@@ -1890,27 +1891,28 @@
         default:
           break;
         case 'user':
-          $a = $this->send_activation_mail($username);
+          $a = $this->send_activation_mail($user_orig);
           if(!$a)
           {
-            $this->admin_activation_request($username);
+            $this->admin_activation_request($user_orig);
             return $lang->get('user_reg_err_actmail_failed') . ' ' . $a;
           }
           break;
         case 'admin':
-          $this->admin_activation_request($username);
+          $this->admin_activation_request($user_orig);
           break;
       }
     }
     
     // Leave some data behind for the hook
-    $code = $plugins->setHook('user_registered'); // , Array('username'=>$username));
+    $code = $plugins->setHook('user_registered');
     foreach ( $code as $cmd )
     {
       eval($cmd);
     }
     
-    // $this->register_session($username, $password);
+    // Uncomment to automatically log the user in (WARNING: commented out for a reason - doesn't consider activation and other things)
+    // $this->register_session($user_orig, $password);
     return 'success';
   }
   
@@ -1924,9 +1926,6 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
-    $q = $this->sql('SELECT username,email FROM '.table_prefix.'users WHERE user_id=2 OR user_level=' . USER_LEVEL_ADMIN . ' ORDER BY user_id ASC;');
-    $un = $db->fetchrow();
-    $admin_user = $un['username'];
     $q = $this->sql('SELECT username,activation_key,account_active,email FROM '.table_prefix.'users WHERE username=\''.$db->escape($u).'\';');
     $r = $db->fetchrow();
     if ( empty($r['email']) )
@@ -1935,16 +1934,24 @@
     $aklink = makeUrlComplete('Special', 'ActivateAccount/'.str_replace(' ', '_', $u).'/'. ( ( is_string($actkey) ) ? $actkey : $r['activation_key'] ) );
     $message = $lang->get('user_reg_activation_email', array(
         'activation_link' => $aklink,
-        'admin_user' => $admin_user,
         'username' => $u
       ));
       
-    if(getConfig('smtp_enabled') == '1')
+    if ( getConfig('smtp_enabled') == '1' )
     {
       $result = smtp_send_email($r['email'], $lang->get('user_reg_activation_email_subject'), preg_replace("#(?<!\r)\n#s", "\n", $message), getConfig('contact_email'));
-      if($result == 'success') $result = true;
-      else { echo $result; $result = false; }
-    } else {
+      if ( $result == 'success' )
+      {
+        $result = true;
+      }
+      else
+      {
+        echo $result;
+        $result = false;
+      }
+    }
+    else
+    {
       $result = mail($r['email'], $lang->get('user_reg_activation_email_subject'), preg_replace("#(?<!\r)\n#s", "\n", $message), 'From: '.getConfig('contact_email'));
     }
     return $result;
--- a/includes/tagcloud.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/tagcloud.php	Mon Feb 16 16:17:25 2009 -0500
@@ -148,7 +148,7 @@
         $newline = ( $inc == 5 ) ? "<br />" : '';
         ( $inc == 5 ) ? $inc = 0 : null;
         $url = makeUrlNS('Special', 'TagCloud/' . htmlspecialchars($word));
-        $popstring = ( $popularity == 1 ) ? $lang->get('pagetools_tagcloug_tip_popularity_one') : $lang->get('pagetools_tagcloug_tip_popularity_plural', array('popularity' => $popularity));
+        $popstring = ( $popularity == 1 ) ? $lang->get('pagetools_tagcloud_tip_popularity_one') : $lang->get('pagetools_tagcloud_tip_popularity_plural', array('popularity' => $popularity));
         $html[] = "<span class='tc_word_{$span_class} tc_{$span_class}_index_{$index}'><a href='$url' title='$popstring'>$word</a></span>"; // $newline";
       }
     }
--- a/includes/template.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/template.php	Mon Feb 16 16:17:25 2009 -0500
@@ -436,7 +436,7 @@
     
     require(ENANO_ROOT . "/themes/{$this->theme}/theme.cfg");
     
-    if ( $local_page_exists && isset($paths->pages[$local_page]) )
+    if ( $local_page_exists && isPage($local_page) )
     {
       $local_cdata =& $paths->pages[$local_page];
     }
@@ -603,7 +603,7 @@
     
     // Page toolbar
     // Comments button
-    if ( $perms->get_permissions('read') && getConfig('enable_comments')=='1' && $local_cdata['comments_on'] == 1 )
+    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.'\';');
@@ -1151,7 +1151,7 @@
       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')=='1') ? str_replace("\n", "\\\n", RenderMan::render(getConfig('wiki_edit_notice_text'))) : '' ) . '\';
+      var editNotice = \'' . ( (getConfig('wiki_edit_notice', '0')=='1') ? str_replace("\n", "\\\n", RenderMan::render(getConfig('wiki_edit_notice_text'))) : '' ) . '\';
       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 . '\';
@@ -2405,7 +2405,7 @@
       eval($cmd);
     }
     
-    if(count($ob) > 0 || getConfig('powered_btn') == '1') $sb_links = '<div style="text-align: center; padding: 5px 0;">'. ( ( getConfig('powered_btn') == '1' ) ? $this->fading_button : '' ) . implode('<br />', $ob).'</div>';
+    if(count($ob) > 0 || getConfig('powered_btn', '1') == '1') $sb_links = '<div style="text-align: center; padding: 5px 0;">'. ( ( getConfig('powered_btn', '1') == '1' ) ? $this->fading_button : '' ) . implode('<br />', $ob).'</div>';
     else $sb_links = '';
     
     $this->sidebar_widget('Links', $sb_links);
--- a/includes/wikiengine/Parse/Mediawiki/Heading.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/includes/wikiengine/Parse/Mediawiki/Heading.php	Mon Feb 16 16:17:25 2009 -0500
@@ -52,7 +52,7 @@
     * 
     */
     
-    var $regex = '/^(={2,6})(.*?)\1(?:\s|$)/m';
+    var $regex = '/^(={2,6})(.*?)\1(?:[\t ]|$)/m';
     
     var $conf = array(
         'id_prefix' => 'toc'
--- a/index.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/index.php	Mon Feb 16 16:17:25 2009 -0500
@@ -251,7 +251,7 @@
           <input type="submit" name="_cancel"  value="' . $lang->get('editor_btn_cancel') . '" />
         </form>
       ';
-      if ( getConfig('wiki_edit_notice') == '1' )
+      if ( getConfig('wiki_edit_notice', '0') == '1' )
       {
         $notice = getConfig('wiki_edit_notice_text');
         echo RenderMan::render($notice);
--- a/install/includes/payload.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/install/includes/payload.php	Mon Feb 16 16:17:25 2009 -0500
@@ -433,7 +433,7 @@
 
 function stg_lang_import()
 {
-  global $db, $languages;
+  global $db, $languages, $do_langimport;
   
   define('IN_ENANO_UPGRADE_POST', 1);
   
@@ -460,7 +460,7 @@
         // generate full path
         $language_file = ENANO_ROOT . "/language/{$languages[$row['lang_code']]['dir']}/$language_file.json";
         // setting the second parameter to bool(true) causes it to skip existing strings
-        if ( !$lang_local->import($language_file, true) )
+        if ( !$lang_local->import($language_file, ( !$do_langimport )) )
           // on failure, report failure to libenanoinstall
           return false;
       }
--- a/install/upgrade.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/install/upgrade.php	Mon Feb 16 16:17:25 2009 -0500
@@ -21,6 +21,11 @@
 $enano_versions['1.0'] = array('1.0', '1.0.1', '1.0.2b1', '1.0.2', '1.0.3', '1.0.4', '1.0.5');
 $enano_versions['1.1'] = array('1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5', '1.1.6');
 
+// If true, this will do a full langimport instead of only adding new strings.
+// Will probably be left on, but some change probably needs to be made to mark
+// strings as customized in the DB.
+$do_langimport = true;
+
 // Turn on every imaginable API hack to make common load on older databases
 define('IN_ENANO_UPGRADE', 1);
 define('IN_ENANO_MIGRATION', 1);
@@ -38,7 +43,8 @@
 require_once('includes/common.php');
 @ini_set('display_errors', 'on');
 
-define('ENANO_UPGRADE_USE_AES_PASSWORDS', in_array(enano_version(), array('1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '1.0.5', '1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5')));
+if ( in_array(enano_version(), array('1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '1.0.5', '1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5')) )
+  define('ENANO_UPGRADE_USE_AES_PASSWORDS', 1);
 
 $ui = new Enano_Installer_UI('Enano upgrader', false);
 
@@ -270,6 +276,15 @@
     <li><?php echo $lang->get('upgrade_confirm_objective_backup_fs', array('dir' => ENANO_ROOT)); ?></li>
     <li><?php echo $lang->get('upgrade_confirm_objective_backup_db', array('dbname' => $dbname)); ?></li>
   </ul>
+  <?php
+  if ( $do_langimport ):
+  ?>
+  <div class="warning-box">
+    <?php echo $lang->get('upgrade_confirm_warning_langimport'); ?>
+  </div>
+  <?php
+  endif;
+  ?>
   <form method="get" action="upgrade.php" style="text-align: center;">
     <input type="hidden" name="auth" value="<?php echo $session->sid_super; ?>" />
     <button name="stage" value="pimpmyenano" class="submit">
--- a/language/english/admin.json	Mon Feb 16 16:04:54 2009 -0500
+++ b/language/english/admin.json	Mon Feb 16 16:17:25 2009 -0500
@@ -361,7 +361,7 @@
       
       // Section: Sessions
       heading_sessions: 'User sessions',
-      hint_sessions_noelev: '<b>Remember:</b> Settings here only affect normal logins - you can\'t change the length of sessions that give you elevated privileges, such as the re-authentication that occurs when you access the administration panel. <a href="http://docs.enanocms.org/Help:Appendix_B">Read about Enano\'s security model</a>.',
+      hint_sessions_noelev: '<b>Remember:</b> Settings here only affect normal logins - you can\'t change the length of sessions that give you elevated privileges, such as the re-authentication that occurs when you access the administration panel. <a href="http://docs.enanocms.org/Help:Appendix_B" onclick="window.open(this.href); return false;">Read about Enano\'s security model</a>.',
       field_short_time: 'Length of short sessions in minutes:',
       field_short_time_hint: 'This is how long a user\'s session will last when they don\'t check the "remember me" checkbox. Short sessions are automatically renewed every time a page is loaded.',
       field_long_time: 'Length of extended sessions in days:',
@@ -508,7 +508,7 @@
     },
     acppm: {
       heading_main: 'Edit page properties',
-      hint: 'This panel allows you to edit various properties of pages that aren\'t visible anywhere else. In addition to renaming pages, you can also change their <a href="http://docs.enanocms.org/Help:2.3">URL string</a> and options such as whether to index the page for searching or bypass Enano\'s template engine.',
+      hint: 'This panel allows you to edit various properties of pages that aren\'t visible anywhere else. In addition to renaming pages, you can also change their <a href="http://docs.enanocms.org/Help:2.3" onclick="window.open(this.href); return false;">URL string</a> and options such as whether to index the page for searching or bypass Enano\'s template engine.',
       err_page_not_found: 'No pages matching that search string could be found.',
       msg_results_ambiguous_title: 'Ambiguous search results',
       msg_results_ambiguous_body: 'Multiple pages that matched your search terms were found. Please select the page you wish to modify:',
@@ -544,7 +544,7 @@
       lbl_wikimode_on: 'Enabled',
       lbl_wikimode_off: 'Disabled',
       lbl_wikimode_global: 'Inherit global setting',
-      lbl_wikimode_hint: 'By default, all pages use the Wiki Mode setting defined in General Configuration. You can override that using this field. Be aware that there are advantages and disadvantages to Wiki Mode. For example, Wiki Mode encourages collaboration, but also permits vandalism. See the <a href="http://docs.enanocms.org/Help:2.5">Enano Documentation article on Wiki Mode</a> for more information.',
+      lbl_wikimode_hint: 'By default, all pages use the Wiki Mode setting defined in General Configuration. You can override that using this field. Be aware that there are advantages and disadvantages to Wiki Mode. For example, Wiki Mode encourages collaboration, but also permits vandalism. See the <a href="http://docs.enanocms.org/Help:2.5" onclick="window.open(this.href); return false;">Enano Documentation article on Wiki Mode</a> for more information.',
       lbl_delete_title: 'Delete this page?',
       lbl_delete_hint: 'Remember that deleting pages is always reversible unless you clear the page\'s logs after deleting it.',
       lbl_delete: 'Delete this page when I click Save',
--- a/language/english/core.json	Mon Feb 16 16:04:54 2009 -0500
+++ b/language/english/core.json	Mon Feb 16 16:17:25 2009 -0500
@@ -349,17 +349,21 @@
       msg_discard_confirm: 'Do you really want to discard your changes?',
       msg_confirm_ajax: 'Do you really want to do this?\nYour changes to this page have not been saved. If you continue, your changes will be lost.',
       msg_unload: 'If you do, any changes that you have made to this page will be lost.',
-      msg_revert_confirm_title: 'Do you really want to revert your changes?',
-      msg_revert_confirm_body: 'If you choose to continue, all of the changes you have made to this page will be lost, and the editor will be set with the latest published (not draft) copy of the page.',
-      msg_cancel_confirm_title: 'Do you want to cancel your changes?',
-      msg_cancel_confirm_body: 'Are you sure you want to discard the changes you made? If you continue, your changes cannot be recovered.',
+      msg_revert_confirm_title: 'Revert changes?',
+      msg_revert_confirm_body: 'All of the changes you have made to this page will be lost, and the text will be replaced with the latest published (not draft) version of the page.',
+      msg_cancel_confirm_title: 'Close without saving?',
+      msg_cancel_confirm_body: 'You haven\'t saved your changes to the page. Closing the editor will discard your changes.',
       msg_diff: 'Below is a summarization of the differences between the current revision of the page (left), and your version (right).',
       msg_diff_empty: 'There are no differences between the text in the editor and the current revision of the page.',
       msg_editing_old_revision: 'You are editing an old revision of this page. If you click Save, newer revisions of this page will be undone.',
       msg_have_draft_title: 'A draft copy of this page is available.',
       msg_have_draft_body: '%author% saved a draft version of this page on %time%. You can <a href="#use_draft" onclick="ajaxEditorUseDraft(); return false;">use the draft copy</a>, or edit the current published version (below). If you edit the published version, the draft copy will remain available, but will not reflect your changes. It is recommended that you edit the draft version instead of editing the published version. You can also <a href="#delete_draft" onclick="ajaxEditorDeleteDraft(); return false;">discard the draft revision</a>.',
-      btn_graphical: 'graphical editor',
-      btn_wikitext: 'wikitext editor',
+      msg_convert_confirm_title: 'Convert this page?',
+      msg_convert_confirm_body: 'This will change the format of the page. This may cause some loss of formatting information.',
+      msg_convert_draft_load_title: 'Format changed',
+      msg_convert_draft_load_body: 'The revision that was just loaded is in a different format than the current setting. The editor has been switched to match the format.',
+      btn_graphical: 'Convert to HTML',
+      btn_wikitext: 'Convert to wikitext',
       lbl_edit_summary: 'Brief summary of your changes:',
       lbl_edit_summary_explain: 'Please summarize and briefly explain what you changed on the page.',
       lbl_minor_edit: 'Mark revision as trivial:',
@@ -374,6 +378,9 @@
       btn_closeviewer: 'Close viewer',
       msg_draft_saving: 'Saving...',
       msg_draft_saved: 'Saved at %time%',
+      btn_revert_confirm: 'Revert changes',
+      btn_cancel_confirm: 'Close editor',
+      btn_cancel_cancel: 'Keep editing',
       preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
       msg_save_success_title: 'Changes saved',
       msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
--- a/language/english/install.json	Mon Feb 16 16:04:54 2009 -0500
+++ b/language/english/install.json	Mon Feb 16 16:17:25 2009 -0500
@@ -379,6 +379,7 @@
       confirm_body: 'You are about to upgrade to Enano version <b>%enano_version%</b>. You should make sure that you\'ve done the following before you continue:',
       confirm_objective_backup_fs: 'Back up Enano installation directory (<b>%dir%</b>)',
       confirm_objective_backup_db: 'Back up Enano database, including non-Enano tables if any (<b>%dbname%</b>)',
+      confirm_warning_langimport: '<b>Warning!</b> This release of Enano has some changed strings. If you\'ve customized any language strings, those changes will be lost.',
       confirm_btn_upgrade: 'Pimp my Enano!',
       
       msg_schema_complete_title: 'Database upgrades complete',
--- a/language/english/tools.json	Mon Feb 16 16:04:54 2009 -0500
+++ b/language/english/tools.json	Mon Feb 16 16:17:25 2009 -0500
@@ -116,7 +116,11 @@
       
       // GPL page
       gpl_blurb: 'The following text represents the license that the <a href="%about_url%">Enano</a> content management system is under. To make it easier to read, the text has been wiki-formatted; in no other way has it been changed.',
-      // The following three strings will be used only in non-English languages. A Spanish example is provided here.
+      
+      // !!
+      // !! The following three strings will be used ONLY in non-English
+      // !! languages. A Spanish example is provided here.
+      // !!
       
       // "Version in Spanish"
       gpl_title_native: 'Versión en español',
@@ -135,8 +139,8 @@
       tagcloud_instructions: 'Hover your mouse over a tag to see how many pages have the tag. Click on a tag to see a list of the pages that have it.',
       tagcloud_sidebar_title: 'Tag cloud',
       tagcloud_sidebar_btn_larger: 'Larger version',
-      tagcloug_tip_popularity_one: '1 page',
-      tagcloug_tip_popularity_plural: '%popularity% pages',
+      tagcloud_tip_popularity_one: '1 page',
+      tagcloud_tip_popularity_plural: '%popularity% pages',
       
       // Recent changes
       rc_btn_diff: 'diff',
--- a/language/english/user.json	Mon Feb 16 16:04:54 2009 -0500
+++ b/language/english/user.json	Mon Feb 16 16:17:25 2009 -0500
@@ -205,7 +205,7 @@
   %activation_link%
   
 Sincerely yours,
-%admin_user% and the %config.site_name% administration team',
+The %config.site_name% administration team',
 
       reg_activation_email_coppa: 'Dear parent or legal guardian,
 A child under the username %username% recently registered on our website. The child provided your e-mail address as the one of his or her authorized parent or legal guardian, and to comply with the United States Childrens\' Online Privacy Protection act, we ask that all parents of children ages 13 or under please mail us a written form authorizing their child\'s use of our website.
@@ -283,7 +283,7 @@
       emailpassword_msg_profile_success: 'Profile changed',
       emailpassword_msg_pass_success: 'Password changed',
       emailpassword_msg_need_activ_user: 'Your password and e-mail address have been changed. Since e-mail activation is required on this site, you will need to re-activate your account to continue. An e-mail has been sent to the new e-mail address with an activation link. You must click that link in order to log in again.',
-      emailpassword_msg_need_activ_admin: 'Your password and e-mail address have been changed. Since administrative activation is requires on this site, a request has been sent to the administrators to activate your account for you. You will not be able to use your account until it is activated by an administrator.',
+      emailpassword_msg_need_activ_admin: 'Your password and e-mail address have been changed. Since administrative activation is required on this site, a request has been sent to the administrators to activate your account for you. You will not be able to use your account until it is activated by an administrator.',
       emailpassword_msg_password_changed: 'Your password has been changed, and you will now be redirected back to the user control panel.',
       emailpassword_err_password_no_match: 'The passwords you entered do not match.',
       emailpassword_grp_chpasswd: 'Change password',
--- a/plugins/SpecialAdmin.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/plugins/SpecialAdmin.php	Mon Feb 16 16:17:25 2009 -0500
@@ -524,14 +524,14 @@
           <?php echo $lang->get('acpgc_field_editnotice_info'); ?>
         </td>
         <td class="row1">
-          <input onclick="if(this.checked) document.getElementById('editmsg_text').style.display='block'; else document.getElementById('editmsg_text').style.display='none';" type="checkbox" name="editmsg" id="editmsg" <?php if(getConfig('wiki_edit_notice')=='1') echo('CHECKED '); ?>/>
+          <input onclick="if(this.checked) document.getElementById('editmsg_text').style.display='block'; else document.getElementById('editmsg_text').style.display='none';" type="checkbox" name="editmsg" id="editmsg" <?php if(getConfig('wiki_edit_notice', '0')=='1') echo('CHECKED '); ?>/>
           <label for="editmsg"><?php echo $lang->get('acpgc_field_editnotice'); ?></label>
         </td>
       </tr>
       
       <tr>
         <td class="row2">
-          <textarea <?php if(getConfig('wiki_edit_notice')!='1') echo('style="display:none" '); ?>rows="5" cols="30" name="editmsg_text" id="editmsg_text"><?php echo getConfig('wiki_edit_notice_text'); ?></textarea>
+          <textarea <?php if(getConfig('wiki_edit_notice', '0')!='1') echo('style="display:none" '); ?>rows="5" cols="30" name="editmsg_text" id="editmsg_text"><?php echo getConfig('wiki_edit_notice_text'); ?></textarea>
         </td>
       </tr>
       
@@ -581,7 +581,7 @@
           </label>
         </td>
         <td class="row1">
-          <input name="enable-comments"  id="enable-comments"  type="checkbox" <?php if(getConfig('enable_comments')=='1')  echo('CHECKED '); ?>/>
+          <input name="enable-comments"  id="enable-comments"  type="checkbox" <?php if(getConfig('enable_comments', '1')=='1')  echo('CHECKED '); ?>/>
         </td>
       </tr>
       
@@ -592,7 +592,7 @@
           </label>
         </td>
         <td class="row2">
-          <input name="comment-approval" id="comment-approval" type="checkbox" <?php if(getConfig('approve_comments')=='1') echo('CHECKED '); ?>/>
+          <input name="comment-approval" id="comment-approval" type="checkbox" <?php if(getConfig('approve_comments', '0')=='1') echo('CHECKED '); ?>/>
         </td>
       </tr>
       
@@ -1089,7 +1089,7 @@
       </td>
       <td class="row1">
         <label>
-          <input name="enano_powered_link" type="checkbox" <?php if(getConfig('powered_btn') == '1') echo 'checked="checked"'; ?> />&nbsp;&nbsp;<?php echo $lang->get('acpgc_field_enano_link'); ?>
+          <input name="enano_powered_link" type="checkbox" <?php if(getConfig('powered_btn', '1') == '1') echo 'checked="checked"'; ?> />&nbsp;&nbsp;<?php echo $lang->get('acpgc_field_enano_link'); ?>
         </label>
       </td>
     </tr>
@@ -1323,7 +1323,7 @@
   </p>
   <p>
     <?php echo $lang->get('acpup_field_max_size'); ?>
-    <input name="max_file_size" onkeyup="if(!this.value.match(/^([0-9\.]+)$/ig)) this.value = this.value.substr(0,this.value.length-1);" value="<?php echo getConfig('max_file_size'); ?>" />
+    <input name="max_file_size" onkeyup="if(!this.value.match(/^([0-9\.]+)$/ig)) this.value = this.value.substr(0,this.value.length-1);" value="<?php echo getConfig('max_file_size', '256000'); ?>" />
     <select name="fs_units">
       <option value="1" selected="selected"><?php echo $lang->get('etc_unit_bytes'); ?></option>
       <option value="1024"><?php echo $lang->get('etc_unit_kilobytes_short'); ?></option>
--- a/plugins/SpecialUpdownload.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/plugins/SpecialUpdownload.php	Mon Feb 16 16:17:25 2009 -0500
@@ -70,7 +70,7 @@
     {
       die_friendly($lang->get('upload_err_title'), '<p>' . $lang->get('upload_err_cant_get_file_meta') . '</p>');
     }
-    if ( $file['size'] == 0 || $file['size'] > (int)getConfig('max_file_size') )
+    if ( $file['size'] == 0 || $file['size'] > (int)getConfig('max_file_size', '256000') )
     {
       die_friendly($lang->get('upload_err_title'), '<p>' . $lang->get('upload_err_too_big_or_small') . '</p>');
     }
@@ -165,7 +165,7 @@
     <p><?php 
       // Get the max file size, and format it in a way that is user-friendly
       
-      $fs = getConfig('max_file_size');
+      $fs = getConfig('max_file_size', '256000');
       $fs = (int)$fs;
       if($fs >= 1048576)
       {
--- a/plugins/SpecialUserFuncs.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/plugins/SpecialUserFuncs.php	Mon Feb 16 16:17:25 2009 -0500
@@ -513,7 +513,7 @@
       $template->load_theme($session->theme, $session->style);
       if(isset($_POST['return_to']))
       {
-        $name = ( isset($paths->pages[$_POST['return_to']]['name']) ) ? $paths->pages[$_POST['return_to']]['name'] : $_POST['return_to'];
+        $name = ( isPage($_POST['return_to']['name']) ) ? $paths->pages[$_POST['return_to']]['name'] : $_POST['return_to'];
         $subst = array(
             'username' => $session->username,
             'redir_target' => $name
@@ -601,7 +601,7 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $lang;
   
-  if ( $session->user_logged_in )
+  if ( $session->user_level < USER_LEVEL_ADMIN && $session->user_logged_in )
   {
     $paths->main_page();
   }
@@ -636,17 +636,14 @@
     }
     $db->free_result();
   }
-  if ( $session->user_level < USER_LEVEL_ADMIN && $session->user_logged_in )
-  {
-    $paths->main_page();
-  }
   if(isset($_POST['submit'])) 
   {
     $_GET['coppa'] = ( isset($_POST['coppa']) ) ? $_POST['coppa'] : 'x';
     
     $captcharesult = $session->get_captcha($_POST['captchahash']);
     $session->kill_captcha();
-    if ( strtolower($captcharesult) != strtolower($_POST['captchacode']) )
+    // bypass captcha if logged in (at this point, if logged in, we're admin)
+    if ( !$session->user_logged_in && strtolower($captcharesult) != strtolower($_POST['captchacode']) )
     {
       $s = $lang->get('user_reg_err_captcha');
     }
@@ -656,7 +653,7 @@
       {
         $s = 'Invalid COPPA input';
       }
-      else if ( !empty($terms) && !isset($_POST['tou_agreed']) )
+      else if ( !$session->user_logged_in && !empty($terms) && !isset($_POST['tou_agreed']) )
       {
         $s = $lang->get('user_reg_err_accept_tou');
       }
@@ -752,7 +749,7 @@
       <form name="regform" action="<?php echo makeUrl($paths->page); ?>" method="post" onsubmit="return runEncryption();">
         <div class="tblholder">
           <table border="0" width="100%" cellspacing="1" cellpadding="4">
-            <tr><th class="subhead" colspan="3"><?php echo $lang->get('user_reg_msg_table_subtitle'); ?></th></tr>
+            <tr><th colspan="3"><?php echo $lang->get('user_reg_msg_table_subtitle'); ?></th></tr>
             
             <?php if(isset($_POST['submit'])) echo '<tr><td colspan="3" class="row2" style="color: red;">'.$s.'</td></tr>'; ?>
             
@@ -859,6 +856,9 @@
             ?>
             
             <!-- FIELD: CAPTCHA image -->
+            <?php
+            if ( !$session->user_logged_in ):
+            ?>
             <tr>
               <td class="row1" style="width: 50%;" rowspan="2">
                 <?php echo $lang->get('user_reg_lbl_field_captcha'); ?><br />
@@ -868,10 +868,12 @@
                   <?php echo $lang->get('user_reg_msg_captcha_blind'); ?>
                 </small>
               </td>
-              <td colspan="2" class="row1">
-                <img id="captchaimg" alt="CAPTCHA image" src="<?php echo makeUrlNS('Special', 'Captcha/'.$captchacode); ?>" />
+              <td class="row1">
+                <img id="captchaimg" alt="CAPTCHA image" src="<?php echo makeUrlNS('Special', 'Captcha/'.$captchacode); ?>" /><br />
                 <span id="b_username"></span>
               </td>
+              <td class="row1">
+              </td>
             </tr>
             
             <!-- FIELD: CAPTCHA input field -->
@@ -914,7 +916,8 @@
             </tr>
             
             <?php
-            endif;
+            endif; // !empty($terms)
+            endif; // $session->user_logged_in
             ?>
             
             <!-- FIELD: submit button -->
@@ -1019,10 +1022,13 @@
                 document.getElementById('e_username').innerHTML = '<br /><small>' + $lang.get('user_reg_err_username_invalid') + '</small>';
               }
             }
-            document.getElementById('b_username').innerHTML = '';
-            if(hex_md5(frm.real_name.value) == '5a397df72678128cf0e8147a2befd5f1')
+            if ( document.getElementById('b_username') )
             {
-              document.getElementById('b_username').innerHTML = '<br /><br />Hey...I know you!<br /><img alt="" src="http://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Bill_Gates_2004_cr.jpg/220px-Bill_Gates_2004_cr.jpg" />';
+              document.getElementById('b_username').innerHTML = '';
+              if(hex_md5(frm.real_name.value) == '5a397df72678128cf0e8147a2befd5f1')
+              {
+                document.getElementById('b_username').innerHTML = '<br /><br />Hey...I know you!<br /><img alt="" src="http://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Bill_Gates_2004_cr.jpg/220px-Bill_Gates_2004_cr.jpg" />';
+              }
             }
             
             // Password
--- a/plugins/admin/UserManager.php	Mon Feb 16 16:04:54 2009 -0500
+++ b/plugins/admin/UserManager.php	Mon Feb 16 16:17:25 2009 -0500
@@ -56,6 +56,9 @@
       $q = $db->sql_query('DELETE FROM '.table_prefix."users WHERE user_id=$user_id;");
       if ( !$q )
         $db->_die();
+      $q = $db->sql_query('DELETE FROM '.table_prefix."session_keys WHERE user_id=$user_id;");
+      if ( !$q )
+        $db->_die();
       echo '<div class="info-box">' . $lang->get('acpum_msg_delete_success') . '</div>';
     }
     else
@@ -116,7 +119,7 @@
         $imaddr_msn = "$imaddr_msn@hotmail.com";
       }
       
-      if ( substr($homepage, 0, 7) != 'http://' )
+      if ( !preg_match('#^https?://#', $homepage) )
       {
         $homepage = "http://$homepage";
       }
@@ -149,8 +152,7 @@
           $to_update_users['username'] = $username;
           if ( $password )
           {
-            $password = $session->pk_encrypt($password, ENC_HEX);
-            $to_update_users['password'] = $password;
+            $session->set_password($user_id, $password);
           }
           $to_update_users['email'] = $email;
           $to_update_users['real_name'] = $real_name;
--- a/themes/oxygen/toolbar.tpl	Mon Feb 16 16:04:54 2009 -0500
+++ b/themes/oxygen/toolbar.tpl	Mon Feb 16 16:17:25 2009 -0500
@@ -10,14 +10,20 @@
 <!-- VAR toolbar_button -->
   <li>
     <a title="{TITLE}" {FLAGS}>
-      <!-- BEGINNOT no_image -->
-      <img alt="{TITLE}" src="{IMAGE}" />
-      <!-- END no_image -->
+      <!-- IFSET SPRITE -->
+        {SPRITE}
+      <!-- BEGINELSE SPRITE -->
+        <!-- IFSET IMAGE -->
+          <!-- BEGINNOT no_image -->
+            <img alt="{TITLE}" src="{IMAGE}" />
+          <!-- END no_image -->
+        <!-- END IMAGE -->
+      <!-- END SPRITE -->
       <!-- BEGIN show_title -->
         <!-- BEGIN no_image -->
-        <span class="noimage">{TITLE}</span>
+          <span class="noimage">{TITLE}</span>
         <!-- BEGINELSE no_image -->
-        <span>{TITLE}</span>
+          <span>{TITLE}</span>
         <!-- END no_image -->
       <!-- END show_title -->
     </a>