Merging in changes from 9e205056f825
authorDan
Sat, 20 Oct 2007 21:59:27 -0400
changeset 197 90b7a52bea45
parent 196 54b3e14bf19d (diff)
parent 148 9e205056f825 (current diff)
child 228 b0a4d179be85
child 256 62ee6685ad18
Merging in changes from 9e205056f825
.hgtags
--- a/.hgtags	Sat Sep 29 09:43:46 2007 -0400
+++ b/.hgtags	Sat Oct 20 21:59:27 2007 -0400
@@ -4,3 +4,5 @@
 ca9118d9c0f2be22407860f41523f47b2862b34a rebrand
 6f0bbf88c3251ca597cb76ac8b59a1ee61d6dd3d rebrand
 0b5244001799fa29e83bf06c5f14eb69350f171c rebrand
+42c6c83b8a004163c9cc2d85f3c8eada3b73adf6 rebrand
+d53cc29308f4f4b97fc6d054e9e0855f37137409 rebrand
--- a/ajax.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/ajax.php	Sat Oct 20 21:59:27 2007 -0400
@@ -33,35 +33,50 @@
     define('ENANO_ROOT', dirname($filename));
     require(ENANO_ROOT.'/includes/functions.php');
     require(ENANO_ROOT.'/includes/dbal.php');
+    require(ENANO_ROOT.'/includes/json.php');
     $db = new mysql();
     $db->connect();
     
-    // should be connected now
+    // result is sent using JSON
+    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+    $return = Array(
+        'mode' => 'success',
+        'users_real' => Array()
+      );
+    
+    // should be connected to the DB now
     $name = (isset($_GET['name'])) ? $db->escape($_GET['name']) : false;
     if ( !$name )
     {
-      die('userlist = new Array(); errorstring=\'Invalid URI\'');
+      $return = array(
+        'mode' => 'error',
+        'error' => 'Invalid URI'
+      );
+      die( $json->encode($return) );
     }
-    $q = $db->sql_query('SELECT username,user_id FROM '.table_prefix.'users WHERE lcase(username) LIKE lcase(\'%'.$name.'%\');');
+    $allowanon = ( isset($_GET['allowanon']) && $_GET['allowanon'] == '1' ) ? '' : ' AND user_id > 1';
+    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE lcase(username) LIKE lcase(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
     if ( !$q )
     {
-      die('userlist = new Array(); errorstring=\'MySQL error selecting username data: '.addslashes(mysql_error()).'\'');
+      $return = array(
+        'mode' => 'error',
+        'error' => 'MySQL error selecting username data: '.addslashes(mysql_error())
+      );
+      die( $json->encode($return) );
     }
-    if($db->numrows() < 1)
-    {
-      die('userlist = new Array(); errorstring=\'No usernames found\';');
-    }
-    echo 'var errorstring = false; userlist = new Array();';
     $i = 0;
     while($r = $db->fetchrow())
     {
-      echo "userlist[$i] = '".addslashes($r['username'])."'; ";
+      $return['users_real'][] = $r['username'];
       $i++;
     }
     $db->free_result();
     
     // all done! :-)
     $db->close();
+    
+    echo $json->encode( $return );
+    
     exit;
   }
  
@@ -101,7 +116,7 @@
       }
       else
       {
-        echo 'Error saving the page: '.$e;
+        echo '<p>Error saving the page: '.$e.'</p>';
       }
       break;
     case "protect":
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cron.php	Sat Oct 20 21:59:27 2007 -0400
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.0.2 (Coblynau)
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+//
+// cron.php - Maintenance tasks that should be run periodically
+//
+
+// The cron script is triggered by way of an image. This is a 1x1px transparent GIF.
+define('ENANO_GIF_SPACER', base64_decode('R0lGODlhAQABAIAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgABACwAAAAAAQABAAACAkwBADs='));
+
+// Don't need a page to load, all we should need is the Enano API
+$_GET['title'] = 'Enano:Cron';
+require('includes/common.php');
+
+global $db, $session, $paths, $template, $plugins; // Common objects
+
+// Hope now that plugins are loaded :-)
+$last_run = ( $x = getConfig('cron_last_run') ) ? $x : 0;
+$time = strval(time());
+setConfig('cron_last_run', $time);
+
+global $cron_tasks;
+
+foreach ( $cron_tasks as $interval => $tasks )
+{
+  $last_run_threshold = time() - ( $interval * 3600 );
+  if ( $last_run_threshold >= $last_run )
+  {
+    foreach ( $tasks as $task )
+    {
+      @call_user_func($task);
+    }
+  }
+}
+
+header('Pragma: no-cache');
+header('Cache-control: no-cache');
+header('Expires: Thu, 1 Jan 1970 00:00:01 GMT');
+header('Content-type: image/gif');
+
+echo ENANO_GIF_SPACER;
+
+?>
--- a/includes/captcha.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/captcha.php	Sat Oct 20 21:59:27 2007 -0400
@@ -1,7 +1,8 @@
 <?php
+
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * captcha.php - visual confirmation system used during registration
  *
--- a/includes/clientside/jsres.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/jsres.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * jsres.php - the Enano client-side runtime, a.k.a. AJAX on steroids
  *
@@ -40,7 +40,7 @@
 {
   echo "/*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * [Aggressively compressed] Javascript client code
  * Copyright (C) 2006-2007 Dan Fuhry
  * Enano is Free Software, licensed under the GNU General Public License; see http://enanocms.org/ for details.
--- a/includes/clientside/sbedit.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/sbedit.js	Sat Oct 20 21:59:27 2007 -0400
@@ -123,3 +123,71 @@
     });
 }
 
+function ajaxRenameSidebarStage1(parent, id)
+{
+  var oldname = parent.firstChild.nodeValue;
+  parent.removeChild(parent.firstChild);
+  parent.ondblclick = function() {};
+  parent._idcache = id;
+  var input = document.createElement('input');
+  input.type = 'text';
+  input.sbedit_id = id;
+  input.oldvalue = oldname;
+  input.onkeyup = function(e)
+  {
+    if ( typeof(e) != 'object' )
+      return false;
+    if ( !e.keyCode )
+      return false;
+    if ( e.keyCode == 13 )
+    {
+      ajaxRenameSidebarStage2(this);
+    }
+    if ( e.keyCode == 27 )
+    {
+      ajaxRenameSidebarCancel(this);
+    }
+  };
+  input.onblur = function()
+  {
+    ajaxRenameSidebarCancel(this);
+  };
+  input.value = oldname;
+  input.style.fontSize = '7pt';
+  parent.appendChild(input);
+  input.focus();
+}
+
+function ajaxRenameSidebarStage2(input)
+{
+  var newname = input.value;
+  var id = input.sbedit_id;
+  var parent = input.parentNode;
+  parent.removeChild(input);
+  parent.appendChild(document.createTextNode(newname));
+  parent.ondblclick = function() { ajaxRenameSidebarStage1(this, this._idcache); return false; };
+  var img = document.createElement('img');
+  img.src = scriptPath + '/images/loading.gif';
+  parent.appendChild(img);
+  newname = ajaxEscape(newname);
+  ajaxPost(makeUrlNS('Special', 'EditSidebar', 'ajax&noheaders&action=rename&id='+id), 'newname=' +newname, function()
+    {
+      if ( ajax.readyState == 4 )
+      {
+        parent.removeChild(img);
+        if ( ajax.responseText != 'GOOD' )
+          new messagebox(MB_OK|MB_ICONSTOP, 'Error renaming block', ajax.responseText);
+      }
+    });
+}
+
+function ajaxRenameSidebarCancel(input)
+{
+  var newname = input.oldvalue;
+  var id = input.sbedit_id;
+  var parent = input.parentNode;
+  parent.removeChild(input);
+  parent.appendChild(document.createTextNode(newname));
+  parent.ondblclick = function() { ajaxRenameSidebarStage1(this, this._idcache); return false; };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/SpryEffects.js	Sat Oct 20 21:59:27 2007 -0400
@@ -0,0 +1,29 @@
+// Spry.Effect.js - version 0.38 - Spry Pre-Release 1.6
+//
+// Copyright (c) 2007. Adobe Systems Incorporated.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name of Adobe Systems Incorporated nor the names of its
+//     contributors may be used to endorse or promote products derived from this
+//     software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('h b;j(!b)b={};b.15=1;b.1J=2;j(!b.c)b.c={};b.c.4Z={43:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+(19/r)*1e},4q:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+((-1g.5W((19/r)*1g.5V)/2)+0.5)*1e},6a:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+1g.4C(19/r,2)*1e},7G:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+1g.50(19/r)*1e},3I:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+1g.50((-1g.5W((19/r)*1g.5V)/2)+0.5)*1e},5Z:p(19,1c,1e,r){j(19>r)q 1e+1c;h 1F=19/r;q 1c+1g.50(1-1g.4C((1F-1),2))*1e},7J:p(19,1c,1e,r){j(19>r)q 1e+1c;q 1c+(0.5+1g.7E(17*19/r)/2)*1e},7D:p(19,1c,1e,r){j(19>r)q 1e+1c;h 1F=19/r;q 1c+(5*1g.4C(1F,3)-6.4*1g.4C(1F,2)+2*1F)*1e}};18(h 51 3K b.c.4Z){b[51]=b.c.4Z[51]}b.c.3V=p(){8.2R=[]};b.c.3V.w.4W=p(k,d){h a={};a.k=b.c.1x(k);a.d=d;18(h i=0;i<8.2R.M;i++)j(8.5U(8.2R[i],a))q 8.2R[i].J;q D};b.c.3V.w.6e=p(J,k,d){j(!8.4W(k,d)){h 2C=8.2R.M;8.2R[2C]={};h 4F=8.2R[2C];4F.J=J;4F.k=b.c.1x(k);4F.d=d}};b.c.3V.w.5U=p(3U,32){j(3U.k!=32.k)q D;h 52=b.c.m.6b(3U.d,32.d);j(52){j(1n 32.d.3A==\'p\')3U.d.3A=32.d.3A;j(1n 32.d.3o==\'p\')3U.d.3o=32.d.3o}q 52};h 4R=B b.c.3V;j(!b.c.m)b.c.m={};b.c.m.1D=p(61){7V(\'b.c 7W: \'+61)};b.c.m.21=p(J){b.c.m.1D(\'7S \'+J+\' 7R 7N\\\'t 7M 7w 7P a 5o p 7Q. \'+"\\n"+\'7Y 7r b 7c 7f 7e.\');q D};b.c.m.1G=p(){8.x=0;8.y=0;8.N="1Q"};b.c.m.2d=p(){8.A=0;8.C=0;8.N="1Q"};b.c.m.3m=p(5R){h 1H=5R.7h(16);j(1H.M==1)1H="0"+1H;q 1H};b.c.m.2e=p(64){q Y(64,16)};b.c.m.3j=p(5Q,5J,5I){h 3m=b.c.m.3m;h 5L=3m(5Q);h 5O=3m(5J);h 5N=3m(5I);4i=5L.7a(5O,5N).6i();4i=\'#\'+4i;q 4i};b.c.m.5k=p(2K){j(2K.7j(/^#[0-7s-f]{3}$/i)){h 3x=2K.6l(\'\');h 2K=\'#\';18(h i=1;i<3x.M;i++){2K+=3x[i]+\'\'+3x[i]}}q 2K};b.c.m.5f=p(4k){j(4k.25(\'-\')==-1){q 4k}h 44=4k.6l(\'-\');h 53=O;h 4y=\'\';18(h i=0;i<44.M;i++){j(44[i].M>0){j(53){4y=44[i];53=D}H{h s=44[i];4y+=s.7q(0).6i()+s.2s(1)}}}q 4y};b.c.m.1N=p(1i){h 1H=D;j(1n 1i==\'58\'&&1i.M>0&&1i.33("%")>0)1H=O;q 1H};b.c.m.1k=p(1i){h 1H=0;38{1H=4a(1i.2s(0,1i.33("%")))}37(e){b.c.m.1D(\'b.c.m.1k: \'+e)}q 1H};b.c.m.2A=p(1i){h 1H=0;j(1n 1i==\'5T\')q 1i;h 4j=1i.33("1Q");j(4j==-1)4j=1i.M;38{1H=Y(1i.2s(0,4j),10)}37(e){}q 1H};b.c.m.6r=p(2X){j(2X){h 3e=2X.7l;71(3e){j(3e.5b==1)q 3e;3e=3e.7k}}q z};b.c.m.6A=p(3L,4Y){j(!3L||3L.5b!=1||!4Y)q;j(3L.7m()){h 2g=3L.7Z(\'7n\');h 6h=2g.M;18(h i=0;i<6h;i++){h 4X=2g[i];h 4T=b.c.4b(4X);4Y.7O([4X,4T.A,4T.C])}}};b.c.m.6b=p(35,3p){j(35==z&&3p==z)q O;j(35!=z&&3p!=z){h 4S=0;h 4U=0;18(h 8x 3K 35)4S++;18(h 8y 3K 3p)4U++;j(4S!=4U)q D;18(h 1y 3K 35){h 4V=1n 35[1y];h 6f=1n 3p[1y];j(4V!=6f||(4V!=\'3D\'&&35[1y]!=3p[1y]))q D}q O}q D};b.c.m.23=p(54,k,d){j(!d)h d={};d.1t=54;h 40=4R.4W(k,d);j(!40){40=B b.c[54](k,d);4R.6e(40,k,d)}40.2J();q O};j(!b.m)b.m={};b.m.2y=p(){8.2m=[];8.3Y=0};b.m.2y.w.2N=p(36){j(!36)q;h 2C=8.2m.M;18(h i=0;i<2C;i++)j(8.2m[i]==36)q;8.2m[2C]=36};b.m.2y.w.8n=p(36){j(!36)q;18(h i=0;i<8.2m.M;i++){j(8.2m[i]==36){8.2m.88(i,1);3P}}};b.m.2y.w.2t=p(3X,55){j(!3X)q;j(!8.3Y){h 2C=8.2m.M;18(h i=0;i<2C;i++){h 3y=8.2m[i];j(3y){j(1n 3y=="p")3y(3X,8,55);H j(3y[3X])3y[3X](8,55)}}}};b.m.2y.w.69=p(){j(--8.3Y<0){8.3Y=0;b.c.m.1D("82 69() 1s!\\n")}};b.m.2y.w.84=p(){++8.3Y};b.c.1x=p(2Q){h k=2Q;j(1n 2Q=="58")k=3f.70(2Q);j(k==z)b.c.m.1D(\'8c "\'+2Q+\'" 8j 4p.\');q k};b.c.1a=p(k,1y){h 1i;h 5e=b.c.m.5f(1y);38{j(k.11)1i=k.11[5e];j(!1i){j(3f.4J&&3f.4J.2x){h 5d=3f.4J.2x(k,z);1i=5d?5d.8m(1y):z}H j(k.57){1i=k.57[5e]}}}37(e){b.c.m.1D(\'b.c.1a: \'+e)}q 1i==\'8d\'?z:1i};b.c.1h=p(k,1y,1i){38{k.11[b.c.m.5f(1y)]=1i}37(e){b.c.m.1D(\'b.c.1h: \'+e)}};b.c.2Z=p(k,1y,3i){h 1z=3i?3i:k;h 3k=b.c.1a(1z,\'1R\');h 4E=b.c.1a(1z,\'2l\');j(3k==\'1Z\'){b.c.1h(1z,\'2l\',\'3d\');b.c.1h(1z,\'1R\',\'3T\');j(47.56)1z.5M()}h 6q=b.c.1a(k,1y);j(3k==\'1Z\'){b.c.1h(1z,\'1R\',\'1Z\');b.c.1h(1z,\'2l\',4E)}q 6q};b.c.2L=p(k){h 1F=b.c.1a(k,\'1L\');j(!1F||1F==\'5o\'){k.11.1L=\'6g\';j(47.56){k.11.1b=0;k.11.1f=0}}};b.c.5i=p(k){h 3E=b.c.1a(k,\'1R\');j(3E&&3E.3C()==\'1Z\')q O;h 3B=b.c.1a(k,\'2l\');j(3B&&3B.3C()==\'3d\')q O;q D};b.c.2o=p(k){h 3E=b.c.1a(k,\'1R\');j(3E&&3E.3C()==\'1Z\')b.c.1h(k,\'1R\',\'3T\');h 3B=b.c.1a(k,\'2l\');j(3B&&3B.3C()==\'3d\')b.c.1h(k,\'2l\',\'8h\')};b.c.3l=p(k){h 3z=b.c.1a(k,\'3z\');j(!3z||(3z.3C()!=\'3d\'&&3z.3C()!=\'81\')){h 5c=0;h 5g=/2M 7.0/.1j(27.26)&&/5n 5D/.1j(27.26);j(5g)5c=b.c.2n(k).C;b.c.1h(k,\'3z\',\'3d\');j(5g)b.c.1h(k,\'C\',5c+\'1Q\')}};b.c.65=p(k){h 6j=k.6t.M;18(h i=6j-1;i>=0;i--){h 2X=k.6t[i];j(2X.5b==3&&!/\\S/.1j(2X.8I))38{k.8z(2X)}37(e){b.c.m.1D(\'b.c.65: \'+e)}}};b.c.2x=p(k){q/2M/.1j(27.26)?k.57:3f.4J.2x(k,z)};b.c.4b=p(k){h 1T=B b.c.m.2d;h 1d=z;j(k.11.A&&/1Q/i.1j(k.11.A))1T.A=Y(k.11.A,10);H{1d=b.c.2x(k);h 1W=1d&&1d.A&&/1Q/i.1j(1d.A);j(1W)1T.A=Y(1d.A,10);j(!1W||1T.A==0)1T.A=k.8p}j(k.11.C&&/1Q/i.1j(k.11.C))1T.C=Y(k.11.C,10);H{j(!1d)1d=b.c.2x(k);h 1W=1d&&1d.C&&/1Q/i.1j(1d.C);j(1W)1T.C=Y(1d.C,10);j(!1W||1T.C==0)1T.C=k.8t}q 1T};b.c.2n=p(k,3i){h 1z=3i?3i:k;h 3k=b.c.1a(1z,\'1R\');h 4E=b.c.1a(1z,\'2l\');j(3k==\'1Z\'){b.c.1h(1z,\'2l\',\'3d\');b.c.1h(1z,\'1R\',\'3T\');j(47.56)1z.5M()}h 1T=b.c.4b(k);j(3k==\'1Z\'){b.c.1h(1z,\'1R\',\'1Z\');b.c.1h(1z,\'2l\',4E)}q 1T};b.c.5w=p(k){h o=b.c.1a(k,"1E");j(1n o==\'3D\'||o==z)o=1.0;q o};b.c.4G=p(2Q){q b.c.1a(2Q,"4e-2K")};b.c.3W=p(e,1y){h i=Y(b.c.1a(e,1y),10);j(5F(i))q 0;q i};b.c.48=p(k){h 1L=B b.c.m.1G;h 1d=z;j(k.11.1f&&/1Q/i.1j(k.11.1f))1L.x=Y(k.11.1f,10);H{1d=b.c.2x(k);h 1W=1d&&1d.1f&&/1Q/i.1j(1d.1f);j(1W)1L.x=Y(1d.1f,10);j(!1W||1L.x==0)1L.x=k.8g}j(k.11.1b&&/1Q/i.1j(k.11.1b))1L.y=Y(k.11.1b,10);H{j(!1d)1d=b.c.2x(k);h 1W=1d&&1d.1b&&/1Q/i.1j(1d.1b);j(1W)1L.y=Y(1d.1b,10);j(!1W||1L.y==0)1L.y=k.8a}q 1L};b.c.87=b.c.48;b.c.X=p(d){b.m.2y.1s(8);8.1t=\'X\';8.k=z;8.2D=0;8.8E=\'1Z\';8.1u=D;8.49=z;8.2f=0;j(!d)h d={};j(d.F)8.E=D;H 8.E=b.15;h 1q=8;j(d.3A!=z)8.2N({3Q:p(){38{1q.d.3A(1q.k,1q)}37(e){b.c.m.1D(\'b.c.X.w.2J: 3A 5Y: \'+e)}}});j(d.3o!=z)8.2N({3J:p(){38{1q.d.3o(1q.k,1q)}37(e){b.c.m.1D(\'b.c.X.w.29: 3o 5Y: \'+e)}}});8.d={r:1O,F:D,u:b.43,3c:16};8.5X(d);j(d.u)8.59(d.u);j(d.v)8.5a(d.v)};b.c.X.w=B b.m.2y();b.c.X.w.1v=b.m.X;b.c.X.w.1Y=O;b.c.X.w.5X=p(d){j(!d)q;18(h 1y 3K d)8.d[1y]=d[1y]};b.c.X.w.59=p(u){j(1n u==\'5T\'||u=="1"||u=="2")7d(Y(u,10)){5S 1:u=b.43;3P;5S 2:u=b.4q;3P;7g:b.c.m.1D(\'4Q u\')}H j(1n u==\'58\'){j(1n 47[u]==\'p\')u=47[u];H j(1n b[u]==\'p\')u=b[u];H b.c.m.1D(\'4Q u\')}8.d.u=u;j(1n 8.G!=\'3D\'){h l=8.G.M;18(h i=0;i<l;i++)8.G[i].J.59(u)}};b.c.X.w.6o=p(r){8.d.r=r;j(1n 8.G!=\'3D\'){h l=8.G.M;18(h i=0;i<l;i++){8.G[i].J.6o(r)}}};b.c.X.w.5a=p(v){8.d.3c=Y(1O/v,10);8.d.v=v;j(1n 8.G!=\'3D\'){h l=8.G.M;18(h i=0;i<l;i++){8.G[i].J.5a(v)}}};b.c.X.w.2J=p(4K){j(!8.k)q;j(3Z.M==0)4K=D;j(8.1u)8.4z();8.2W();h 4H=B 78();8.2D=4H.76();j(8.k.6Z)8.k=3f.70(8.k.6Z);j(8.2f!=0&&8.d.F){j(8.2f<1&&1n 8.d.u==\'p\'){h 4o=0;h 5h=8.d.r;h 2J=0;h 29=1;h 72=0;8.2f=1g.77(8.2f*1O)/1O;h 4p=D;h 3F=0;71(!4p){j(72++>8.d.r)3P;h 3S=4o+((5h-4o)/2);3F=1g.77(8.d.u(3S,1,-1,8.d.r)*1O)/1O;j(3F==8.2f){8.2D-=3S;4p=O}j(3F<8.2f){5h=3S;29=3F}H{4o=3S;2J=3F}}}8.2f=0}8.2t(\'3Q\',8);j(4K==D){h 1q=8;8.49=5x(p(){1q.4D()},8.d.3c)}8.1u=O};b.c.X.w.4A=p(){j(8.49){8e(8.49);8.49=z}8.2D=0};b.c.X.w.29=p(){8.4A();8.2t(\'3J\',8);8.1u=D};b.c.X.w.4z=p(){h 14=8.2I();j(8.2D>0&&14<8.d.r)8.2f=8.d.u(14,0,1,8.d.r);8.4A();8.2t(\'5G\',8);8.1u=D};b.c.X.w.4D=p(){h 1u=O;8.2t(\'4L\',8);h 75=8.2I();j(1n 8.d.u!=\'p\'){b.c.m.1D(\'4Q u\');q}8.3r();j(75>8.d.r){1u=D;8.29()}q 1u};b.c.X.w.2I=p(){j(8.2D>0){h 4H=B 78();q(4H.76()-8.2D)}q 0};b.c.X.w.K=p(){j(!8.E){8.E=b.15;q}j(8.d.F==O){j(8.E==b.15){8.E=b.1J;8.2t(\'5H\',8)}H j(8.E==b.1J){8.E=b.15}}};b.c.X.w.2W=p(){j(8.d&&8.d.F)8.K()};b.c.X.w.3r=p(){};b.c.X.w.4L=p(6X){j(6X!=8)8.2t(\'4L\',8)};b.c.1A=p(k,Z,13,d){8.4P=D;j(3Z.M==3){d=13;13=Z;Z=b.c.48(k);8.4P=O}b.c.X.1s(8,d);8.1t=\'1A\';8.k=b.c.1x(k);j(!8.k)q;j(Z.N!=13.N)b.c.m.1D(\'b.c.1A: 6H N (\'+Z.N+\', \'+13.N+\')\');8.N=Z.N;8.3H=4a(Z.x);8.46=4a(13.x);8.3s=4a(Z.y);8.42=4a(13.y)};b.c.1A.w=B b.c.X();b.c.1A.w.1v=b.c.1A;b.c.1A.w.3r=p(){h 1f=0;h 1b=0;h P=1g.P;h 14=8.2I();j(8.E==b.15){1f=P(8.d.u(14,8.3H,8.46-8.3H,8.d.r));1b=P(8.d.u(14,8.3s,8.42-8.3s,8.d.r))}H j(8.E==b.1J){1f=P(8.d.u(14,8.46,8.3H-8.46,8.d.r));1b=P(8.d.u(14,8.42,8.3s-8.42,8.d.r))}8.k.11.1f=1f+8.N;8.k.11.1b=1b+8.N};b.c.1A.w.2W=p(){j(8.d&&8.d.F)8.K();j(8.4P==O){h Z=b.c.48(8.k);8.3H=Z.x;8.3s=Z.y;8.8v=8.3H-8.46;8.8B=8.3s-8.42}};b.c.1P=p(k,T,12,d){8.5B=D;j(3Z.M==3){d=12;12=T;T=b.c.2n(k);8.5B=O}b.c.X.1s(8,d);8.1t=\'1P\';8.k=b.c.1x(k);j(!8.k)q;k=8.k;j(T.N!=12.N){b.c.m.1D(\'b.c.1P: 6H N (\'+T.N+\', \'+12.N+\')\');q D}8.N=T.N;h 1m=b.c.2n(k);8.2b=1m.A;8.2r=1m.C;8.1K=T.A;8.1M=T.C;8.28=12.A;8.2c=12.C;8.2g=B 63();j(8.d.1S){b.c.2L(8.k);h 1l=b.c.3W;8.6I=1l(k,\'4s-1b-A\');8.6J=1l(k,\'4s-4O-A\');8.6T=1l(k,\'4s-1f-A\');8.6S=1l(k,\'4s-4N-A\');8.6U=1l(k,\'4m-1b\');8.6V=1l(k,\'4m-4O\');8.6W=1l(k,\'4m-1f\');8.6R=1l(k,\'4m-4N\');8.6Q=1l(k,\'3n-1b\');8.6L=1l(k,\'3n-4O\');8.6K=1l(k,\'3n-4N\');8.6M=1l(k,\'3n-1f\');8.8i=1l(k,\'1f\');8.8b=1l(k,\'1b\')}j(8.d.2G)b.c.m.6A(k,8.2g);8.4M=1.0;h 2z=b.c.1a(8.k,\'80-5r\');j(2z&&/74\\s*$/.1j(2z))8.4M=85(2z);h 4c=b.c.m.1N;j(4c(8.1K)){h 6v=b.c.m.1k(8.1K);8.1K=1m.A*(6v/W)}j(4c(8.1M)){h 6u=b.c.m.1k(8.1M);8.1M=1m.C*(6u/W)}j(4c(8.28)){h 6w=b.c.m.1k(8.28);8.28=1m.A*(6w/W)}j(4c(8.2c)){h 6x=b.c.m.1k(8.2c);8.2c=1m.C*(6x/W)}8.2o=b.c.5i(8.k)};b.c.1P.w=B b.c.X();b.c.1P.w.1v=b.c.1P;b.c.1P.w.3r=p(){h A=0;h C=0;h 2z=0;h E=0;h P=1g.P;h 14=8.2I();j(8.E==b.15){A=P(8.d.u(14,8.1K,8.28-8.1K,8.d.r));C=P(8.d.u(14,8.1M,8.2c-8.1M,8.d.r));E=1}H j(8.E==b.1J){A=P(8.d.u(14,8.28,8.1K-8.28,8.d.r));C=P(8.d.u(14,8.2c,8.1M-8.2c,8.d.r));E=-1}h 2a=A/8.2b;2z=8.4M*2a;h 1w=8.k.11;j(A<0)A=0;j(C<0)C=0;1w.A=A+8.N;1w.C=C+8.N;j(1n 8.d.1S!=\'3D\'&&8.d.1S==O){h 1l=b.c.3W;h 79=1l(8.k,\'1b\');h 6C=1l(8.k,\'1f\');h 73=1l(8.k,\'3n-1b\');h 6D=1l(8.k,\'3n-1f\');h 39=2a;h 34=C/8.2r;h 6P=P(8.6I*34);h 6O=P(8.6J*34);h 6N=P(8.6T*39);h 6y=P(8.6S*39);h 6z=P(8.6U*34);h 6Y=P(8.6V*34);h 6B=P(8.6W*39);h 6G=P(8.6R*39);h 5t=P(8.6Q*34);h 6F=P(8.6L*34);h 6E=P(8.6K*39);h 5l=P(8.6M*39);1w.7X=6P+8.N;1w.7H=6O+8.N;1w.8C=6N+8.N;1w.7i=6y+8.N;1w.8s=6z+8.N;1w.8F=6Y+8.N;1w.8o=6B+8.N;1w.8f=6G+8.N;1w.8k=5t+8.N;1w.89=6F+8.N;1w.8D=5l+8.N;1w.8A=6E+8.N;1w.1f=P(6C+6D-5l)+8.N;1w.1b=P(79+73-5t)+8.N}j(8.d.2G){18(h i=0;i<8.2g.M;i++){8.2g[i][0].11.A=2a*8.2g[i][1]+8.N;8.2g[i][0].11.C=2a*8.2g[i][2]+8.N}8.k.11.2z=2z+\'74\'}j(8.2o){b.c.2o(8.k);8.2o=D}};b.c.1P.w.2W=p(){j(8.d&&8.d.F)8.K();j(8.5B==O){h T=b.c.4b(k);8.1K=T.A;8.1M=T.C;8.86=8.1K-8.28;8.8H=8.1M-8.2c}};b.c.24=p(k,2h,2v,d){8.5v=D;j(3Z.M==3){d=2v;2v=2h;2h=b.c.5w(k);8.5v=O}b.c.X.1s(8,d);8.1t=\'24\';8.k=b.c.1x(k);j(!8.k)q;j(/2M/.1j(27.26)&&(!8.k.8w))b.c.1h(8.k,\'83\',\'1\');8.2h=2h;8.2v=2v;8.2o=b.c.5i(8.k)};b.c.24.w=B b.c.X();b.c.24.w.1v=b.c.24;b.c.24.w.3r=p(){h 1E=0;h 14=8.2I();j(8.E==b.15)1E=8.d.u(14,8.2h,8.2v-8.2h,8.d.r);H j(8.E==b.1J)1E=8.d.u(14,8.2v,8.2h-8.2v,8.d.r);j(1E<0)1E=0;j(/2M/.1j(27.26)){h 4d=b.c.1a(8.k,\'4w\');j(4d){4d=4d.5E(/4r\\(1E=[0-9]{1,3}\\)/g,\'\')}8.k.11.4w=4d+"4r(1E="+1g.P(1E*W)+")"}H 8.k.11.1E=1E;j(8.2o){b.c.2o(8.k);8.2o=D}};b.c.24.w.2W=p(){j(8.d&&8.d.F)8.K();j(8.5v==O){8.2h=b.c.5w(k);8.8G=8.2h-8.2v}};b.c.2E=p(k,1U,2w,d){8.5u=D;j(3Z.M==3){d=2w;2w=1U;1U=b.c.4G(k);8.5u=O}b.c.X.1s(8,d);8.1t=\'2E\';8.k=b.c.1x(k);j(!8.k)q;8.1U=1U;8.2w=2w;8.3g=b.c.m.2e(1U.2u(1,2));8.3w=b.c.m.2e(1U.2u(3,2));8.3h=b.c.m.2e(1U.2u(5,2));8.3R=b.c.m.2e(2w.2u(1,2));8.3M=b.c.m.2e(2w.2u(3,2));8.3N=b.c.m.2e(2w.2u(5,2))};b.c.2E.w=B b.c.X();b.c.2E.w.1v=b.c.2E;b.c.2E.w.3r=p(){h 4h=0;h 4g=0;h 4f=0;h P=1g.P;h 14=8.2I();j(8.E==b.15){4h=P(8.d.u(14,8.3g,8.3R-8.3g,8.d.r));4g=P(8.d.u(14,8.3w,8.3M-8.3w,8.d.r));4f=P(8.d.u(14,8.3h,8.3N-8.3h,8.d.r))}H j(8.E==b.1J){4h=P(8.d.u(14,8.3R,8.3g-8.3R,8.d.r));4g=P(8.d.u(14,8.3M,8.3w-8.3M,8.d.r));4f=P(8.d.u(14,8.3N,8.3h-8.3N,8.d.r))}8.k.11.7F=b.c.m.3j(4h,4g,4f)};b.c.2E.w.2W=p(){j(8.d&&8.d.F)8.K();j(8.5u==O){8.1U=b.c.4G(k);8.3g=b.c.m.2e(1U.2u(1,2));8.3w=b.c.m.2e(1U.2u(3,2));8.3h=b.c.m.2e(1U.2u(5,2));8.7b=8.3g-8.3R;8.7v=8.3w-8.3M;8.7u=8.3h-8.3N}};b.c.U=p(d){b.c.X.1s(8,d);8.1t=\'U\';8.G=B 63();8.1C=-1;h 62=p(J,45){8.J=J;8.45=45;8.1u=D};8.5s=62};b.c.U.w=B b.c.X();b.c.U.w.1v=b.c.U;b.c.U.w.5x=p(3c){h l=8.G.M;8.d.3c=3c;18(h i=0;i<l;i++){8.G[i].J.5x(3c)}};b.c.U.w.4D=p(){h 1u=O;h 3q=D;h 5y=D;h 5A=D;j((8.1C==-1&&8.E==b.15)||(8.1C==8.G.M&&8.E==b.1J))8.5z();h 2J=8.E==b.15?0:8.G.M-1;h 29=8.E==b.15?8.G.M:-1;h 3O=8.E==b.15?1:-1;18(h i=2J;i!=29;i+=3O){j(8.G[i].1u==O){5y=8.G[i].J.4D();j(5y==D&&i==8.1C){8.G[i].1u=D;5A=O}}}j(5A==O)3q=8.5z();j(3q==O){8.29();1u=D;18(h i=0;i<8.G.M;i++)8.G[i].1u=D;8.1C=8.E==b.15?8.G.M:-1}q 1u};b.c.U.w.5z=p(){h 3q=D;h 3O=8.E==b.15?1:-1;h 29=8.E==b.15?8.G.M:-1;8.1C+=3O;j((8.1C>(8.G.M-1)&&8.E==b.15)||(8.1C<0&&8.E==b.1J))3q=O;H 18(h i=8.1C;i!=29;i+=3O){j((i>8.1C&&8.E==b.15||i<8.1C&&8.E==b.1J)&&8.G[i].45=="5K")3P;8.G[i].J.2J(O);8.G[i].1u=O;8.1C=i}q 3q};b.c.U.w.5m=p(){j(!8.E){8.E=b.15;q}j(8.d.F==O){j(8.E==b.15){8.E=b.1J;8.2t(\'5H\',8);8.1C=8.G.M}H j(8.E==b.1J){8.E=b.15;8.1C=-1}}H{j(8.E==b.15)8.1C=-1;H j(8.E==b.1J)8.1C=8.G.M}};b.c.U.w.K=p(){8.5m();18(h i=0;i<8.G.M;i++){j(8.G[i].J.d&&(8.G[i].J.d.F!=z))j(8.G[i].J.d.F==O)8.G[i].J.K()}};b.c.U.w.4z=p(){18(h i=0;i<8.G.M;i++)j(8.G[i].J.1u)8.G[i].J.4z();h 14=8.2I();j(8.2D>0&&14<8.d.r)8.2f=8.d.u(14,0,1,8.d.r);8.4A();8.2t(\'5G\',8);8.1u=D};b.c.U.w.1V=p(J){J.2N(8);8.G[8.G.M]=B 8.5s(J,"5K");j(8.G.M==1){8.k=J.k}};b.c.U.w.2k=p(J){j(8.G.M==0||8.G[8.G.M-1].45!=\'5P\')J.2N(8);8.G[8.G.M]=B 8.5s(J,"5P");j(8.G.M==1){8.k=J.k}};b.c.U.w.2W=p(){8.5m()};b.c.30=p(k,d){j(!8.1Y)q b.c.m.21(\'30\');b.c.U.1s(8,d);8.1t=\'30\';h k=b.c.1x(k);8.k=k;j(!8.k)q;h R=1O;h 1r=0.0;h 1p=W.0;h K=D;h u=b.3I;h v=60;h 1I=0;j(/2M/.1j(27.26))1I=Y(b.c.2Z(8.k,\'4w\').5E(/4r\\(1E=([0-9]{1,3})\\)/g,\'$1\'),10);H 1I=Y(b.c.2Z(8.k,\'1E\')*W,10);j(5F(1I))1I=W;j(d){j(d.r!=z)R=d.r;j(d.L!=z){j(b.c.m.1N(d.L))1r=b.c.m.1k(d.L)*1I/W;H 1r=d.L}j(d.I!=z){j(b.c.m.1N(d.I))1p=b.c.m.1k(d.I)*1I/W;H 1p=d.I}j(d.F!=z)K=d.F;j(d.u!=z)u=d.u;j(d.v!=z)v=d.v;H 8.d.u=u}1r=1r/W.0;1p=1p/W.0;d={r:R,F:K,u:u,L:1r,I:1p,v:v};h 4x=B b.c.24(k,1r,1p,d);8.1V(4x)};b.c.30.w=B b.c.U();b.c.30.w.1v=b.c.30;b.c.2Y=p(k,d){j(!8.1Y)q b.c.m.21(\'2Y\');b.c.U.1s(8,d);8.1t=\'2Y\';h k=b.c.1x(k);8.k=k;j(!8.k)q;h R=1O;h K=D;h V=b.5Z;h v=60;h 2F=D;b.c.3l(k);h 1m=b.c.2n(k);h 4l=1m.C;h 4I=0;h 4u=d?d.L:1m.C;h 4t=d?d.I:0;h 2j=D;j(d){j(d.r!=z)R=d.r;j(d.L!=z){j(b.c.m.1N(d.L))4l=b.c.m.1k(d.L)*1m.C/W;H 4l=b.c.m.2A(d.L)}j(d.I!=z){j(b.c.m.1N(d.I))4I=b.c.m.1k(d.I)*1m.C/W;H 4I=b.c.m.2A(d.I)}j(d.F!=z)K=d.F;j(d.u!=z)V=d.u;j(d.v!=z)v=d.v;j(d.1S!=z)2j=d.1S}h T=B b.c.m.2d;T.A=1m.A;T.C=4l;h 12=B b.c.m.2d;12.A=1m.A;12.C=4I;d={r:R,F:K,u:V,2G:2F,1S:2j,L:4u,I:4t,v:v};h 6s=B b.c.1P(k,T,12,d);8.1V(6s)};b.c.2Y.w=B b.c.U();b.c.2Y.w.1v=b.c.2Y;b.c.2O=p(k,d){j(!8.1Y)q b.c.m.21(\'2O\');b.c.U.1s(8,d);8.1t=\'2O\';h R=1O;h 1B="#8u";h K=D;h V=b.4q;h v=60;h k=b.c.1x(k);8.k=k;j(!8.k)q;h 1o=b.c.4G(k);j(1o=="8q")1o="#8r";j(d){j(d.r!=z)R=d.r;j(d.L!=z)1o=d.L;j(d.I!=z)1B=d.I;j(d.F!=z)K=d.F;j(d.u!=z)V=d.u;j(d.v!=z)v=d.v}j(1o.25(\'3j\')!=-1)h 1o=b.c.m.3j(Y(1o.2s(1o.25(\'(\')+1,1o.25(\',\')),10),Y(1o.2s(1o.25(\',\')+1,1o.33(\',\')),10),Y(1o.2s(1o.33(\',\')+1,1o.25(\')\')),10));j(1B.25(\'3j\')!=-1)h 1B=b.c.m.3j(Y(1B.2s(1B.25(\'(\')+1,1B.25(\',\')),10),Y(1B.2s(1B.25(\',\')+1,1B.33(\',\')),10),Y(1B.2s(1B.33(\',\')+1,1B.25(\')\')),10));h 1o=b.c.m.5k(1o);h 1B=b.c.m.5k(1B);8.6m=b.c.1a(k,\'4e-5j\');d={r:R,F:K,u:V,v:v};h 6k=B b.c.2E(k,1o,1B,d);8.1V(6k);8.2N({3Q:p(J){b.c.1h(J.k,\'4e-5j\',\'1Z\')},3J:p(J){b.c.1h(J.k,\'4e-5j\',J.6m);j(J.E==b.15&&J.d.6n)b.c.1h(k,\'4e-2K\',J.d.6n)}})};b.c.2O.w=B b.c.U();b.c.2O.w.1v=b.c.2O;b.c.2P=p(k,d){j(!8.1Y)q b.c.m.21(\'2P\');b.c.U.1s(8,d);8.1t=\'2P\';h k=b.c.1x(k);8.k=k;j(!8.k)q;h R=1O;h K=D;h V=b.4q;h v=60;h 31=D;h 2i=b.c.m.6r(k);h E=-1;j(/2M 7.0/.1j(27.26)&&/5n 5D/.1j(27.26))b.c.2L(k);b.c.3l(k);j(/2M 6.0/.1j(27.26)&&/5n 5D/.1j(27.26)){h 1F=b.c.1a(k,\'1L\');j(1F&&(1F==\'5o\'||1F==\'8l\')){b.c.1h(k,\'1L\',\'6g\');b.c.1h(k,\'1b\',\'\');b.c.1h(k,\'1f\',\'\')}}j(2i){b.c.2L(2i);b.c.3l(2i);h 68=b.c.2n(2i,k);b.c.1h(2i,\'A\',68.A+\'1Q\')}h 22=b.c.2n(k);h 2S=B b.c.m.2d();h 1X=B b.c.m.2d();2S.A=1X.A=22.A;2S.C=1X.C=22.C;j(!8.d.I){j(!d)d={};d.I=\'0%\'}j(d&&d.67!==z&&d.67===O)31=O;j(d.r!=z)R=d.r;j(d.L!=z){j(31){j(b.c.m.1N(d.L))22.A=2S.A*b.c.m.1k(d.L)/W;H 22.A=b.c.m.2A(d.L)}H{j(b.c.m.1N(d.L))22.C=2S.C*b.c.m.1k(d.L)/W;H 22.C=b.c.m.2A(d.L)}}j(d.I!=z){j(31){j(b.c.m.1N(d.I))1X.A=2S.A*b.c.m.1k(d.I)/W;H 1X.A=b.c.m.2A(d.I)}H{j(b.c.m.1N(d.I))1X.C=2S.C*b.c.m.1k(d.I)/W;H 1X.C=b.c.m.2A(d.I)}}j(d.F!=z)K=d.F;j(d.u!=z)V=d.u;j(d.v!=z)v=d.v;d={r:R,u:V,2G:D,F:K,v:v};h 5r=B b.c.1P(k,22,1X,d);8.2k(5r);j((22.A<1X.A&&31)||(22.C<1X.C&&!31))E=1;h Z=B b.c.m.1G();h 13=B b.c.m.1G();13.x=Z.x=b.c.3W(2i,\'1f\');13.y=Z.y=b.c.3W(2i,\'1b\');13.N=Z.N;j(31)13.x=Y(Z.x+E*(22.A-1X.A),10);H 13.y=Y(Z.y+E*(22.C-1X.C),10);j(E==1){h 3x=Z;h Z=13;h 13=3x}d={r:R,u:V,F:K,L:Z,I:13,v:v};h 66=B b.c.1A(2i,Z,13,d);8.2k(66)};b.c.2P.w=B b.c.U();b.c.2P.w.1v=b.c.2P;b.c.2q=p(k,d){j(!k)q;j(!8.1Y)q b.c.m.21(\'2q\');b.c.U.1s(8,d);8.1t=\'2q\';h R=1O;h K=D;h 2F=O;h 5q=D;h 5p=O;h 2j=D;h V=b.6a;h v=60;h k=b.c.1x(k);8.k=k;j(!8.k)q;b.c.3l(k);h 4v=b.c.2n(k);h 2b=4v.A;h 2r=4v.C;h 2a=(2b==0)?1:2r/2b;h T=B b.c.m.2d;T.A=0;T.C=0;h 12=B b.c.m.2d;12.A=2b;12.C=2r;h 4u=d?d.L:4v.A;h 4t=d?d.I:0;h 2p=b.c.m.2A;j(d){j(d.5C!=z)5p=d.5C;j(d.r!=z)R=d.r;j(d.1S!=z)2j=d.1S;j(d.2G!=z)2F=d.2G;j(d.L!=z){j(b.c.m.1N(d.L)){T.A=2b*(b.c.m.1k(d.L)/W);T.C=2r*(b.c.m.1k(d.L)/W)}H{j(5q){T.C=2p(d.L);T.A=2p(d.L)/2a}H{T.A=2p(d.L);T.C=2a*2p(d.L)}}}j(d.I!=z){j(b.c.m.1N(d.I)){12.A=2b*(b.c.m.1k(d.I)/W);12.C=2r*(b.c.m.1k(d.I)/W)}H{j(5q){12.C=2p(d.I);12.A=2p(d.I)/2a}H{12.A=2p(d.I);12.C=2a*2p(d.I)}}}j(d.F!=z)K=d.F;j(d.u!=z)V=d.u;j(d.v!=z)v=d.v}d={r:R,F:K,u:V,2G:2F,1S:2j,v:v};h 3G=B b.c.1P(k,T,12,d);8.2k(3G);j(5p){b.c.2L(k);h Q=B b.c.m.1G();Q.x=Y(b.c.2Z(k,"1f"),10);Q.y=Y(b.c.2Z(k,"1b"),10);j(!Q.x)Q.x=0;j(!Q.y)Q.y=0;d={r:R,F:K,u:V,L:4u,I:4t,v:v};h Z=B b.c.m.1G;Z.x=Q.x+(2b-T.A)/2.0;Z.y=Q.y+(2r-T.C)/2.0;h 13=B b.c.m.1G;13.x=Q.x+(2b-12.A)/2.0;13.y=Q.y+(2r-12.C)/2.0;h 3v=B b.c.1A(k,Z,13,d);8.2k(3v)}};b.c.2q.w=B b.c.U();b.c.2q.w.1v=b.c.2q;b.c.2H=p(k,d){j(!8.1Y)q b.c.m.21(\'2H\');b.c.U.1s(8,d);8.d.E=D;j(8.d.F)8.d.F=D;8.1t=\'2H\';h k=b.c.1x(k);8.k=k;j(!8.k)q;h R=W;h V=b.43;h v=60;h 3u=4;j(d){j(d.r!=z)3u=1g.4n(8.d.r/R)-1;j(d.v!=z)v=d.v;j(d.u!=z)V=d.u}b.c.2L(k);h Q=B b.c.m.1G();Q.x=Y(b.c.1a(k,"1f"),10);Q.y=Y(b.c.1a(k,"1b"),10);j(!Q.x)Q.x=0;j(!Q.y)Q.y=0;h 41=B b.c.m.1G;41.x=Q.x;41.y=Q.y;h 3b=B b.c.m.1G;3b.x=Q.x+20;3b.y=Q.y+0;h 3t=B b.c.m.1G;3t.x=Q.x+ -20;3t.y=Q.y+0;d={r:1g.4n(R/2),F:D,v:v,u:V};h J=B b.c.1A(k,41,3b,d);8.1V(J);d={r:R,F:D,v:v,u:V};h 6d=B b.c.1A(k,3b,3t,d);h 6c=B b.c.1A(k,3t,3b,d);18(h i=0;i<3u;i++){j(i%2==0)8.1V(6d);H 8.1V(6c)}h 1F=(3u%2==0)?3b:3t;d={r:1g.4n(R/2),F:D,v:v,u:V};h J=B b.c.1A(k,1F,41,d);8.1V(J)};b.c.2H.w=B b.c.U();b.c.2H.w.1v=b.c.2H;b.c.2H.w.K=p(){};b.c.2U=p(k,d){j(!8.1Y)q b.c.m.21(\'2U\');j(!d)d={};j(!d.I)d.I=\'0%\';j(!d.L)d.L=\'W%\';d.5C=D;b.c.2q.1s(8,k,d);8.1t=\'2U\'};b.c.2U.w=B b.c.2q();b.c.2U.w.1v=b.c.2U;b.c.2B=p(k,d){j(!8.1Y)q b.c.m.21(\'2B\');b.c.U.1s(8,d);8.d.E=D;j(8.d.F)8.d.F=D;h k=b.c.1x(k);h 1I=0;8.k=k;j(!8.k)q;8.1t=\'2B\';h R=W;h 1r=W.0;h 1p=0.0;h K=D;h V=b.43;h v=60;j(/2M/.1j(27.26))1I=Y(b.c.2Z(8.k,\'4w\').5E(/4r\\(1E=([0-9]{1,3})\\)/g,\'$1\'),10);H 1I=Y(b.c.2Z(8.k,\'1E\')*W,10);j(5F(1I)){1I=W}j(d){j(d.L!=z){j(b.c.m.1N(d.L))1r=b.c.m.1k(d.L)*1I/W;H 1r=d.L}j(d.I!=z){j(b.c.m.1N(d.I))1p=b.c.m.1k(d.I)*1I/W;H 1p=d.I}j(d.u!=z)V=d.u;j(d.v!=z)v=d.v}d={r:R,F:K,u:V,v:v};1r=1r/W.0;1p=1p/W.0;h 4x=B b.c.24(k,1r,1p,d);h 6p=B b.c.24(k,1p,1r,d);h 3u=Y(8.d.r/7o,10);18(h i=0;i<3u;i++){8.1V(4x);8.1V(6p)}};b.c.2B.w=B b.c.U();b.c.2B.w.1v=b.c.2B;b.c.2B.w.K=p(){};b.c.3a=p(k,d){j(!8.1Y)q b.c.m.21(\'3a\');b.c.U.1s(8,d);h k=b.c.1x(k);8.k=k;j(!8.k)q;8.1t=\'3a\';h K=D;h 2F=D;h R=1O;h V=b.3I;h v=60;b.c.2L(k);j(d){j(d.F!=z)K=d.F;j(d.r!=z)R=d.r;j(d.u!=z)V=d.u;j(d.v!=z)v=d.v}h 1m=b.c.4b(k);h 1K=1m.A;h 1M=1m.C;d={r:R,F:K,u:V,v:v};h 1r=1.0;h 1p=0.0;h 4B=B b.c.24(k,1r,1p,d);8.2k(4B);h Z=b.c.48(k);h 13=B b.c.m.1G;13.x=1K/2.0*-1.0;13.y=1M/2.0*-1.0;d={r:R,F:K,u:V,L:Z,I:13,v:v};h 3v=B b.c.1A(k,Z,13,d);8.2k(3v);h 1q=8;8.2N({3Q:p(){j(1q.E==b.1J){1q.k.11.1R=\'3T\'}},3J:p(){j(1q.E==b.15){1q.k.11.1R=\'1Z\'}}})};b.c.3a.w=B b.c.U;b.c.3a.w.1v=b.c.3a;b.c.2T=p(k,d){j(!8.1Y)q b.c.m.21(\'2T\');b.c.U.1s(8,d);h k=b.c.1x(k);8.k=k;j(!8.k)q;h R=1O;h v=60;h V=b.3I;h E=b.15;h K=D;8.1t=\'2T\';b.c.2L(k);j(d){j(d.r!=z)R=d.r;j(d.F!=z)K=d.F;j(d.v!=z)v=d.v;j(d.u!=z)V=d.u;j(d.7p!=z)E=-1}h Q=B b.c.m.1G();Q.x=Y(b.c.1a(k,"1f"),10);Q.y=Y(b.c.1a(k,"1b"),10);j(!Q.x)Q.x=0;j(!Q.y)Q.y=0;h Z=B b.c.m.1G;Z.x=Q.x+0;Z.y=Q.y+0;h 13=B b.c.m.1G;13.x=Q.x+0;13.y=Q.y+(E*7t);d={L:Z,I:13,r:R,F:K,u:V,v:v};h 3v=B b.c.1A(k,d.L,d.I,d);8.2k(3v);h 1r=1.0;h 1p=0.0;d={r:R,F:K,u:V,v:v};h 4B=B b.c.24(k,1r,1p,d);8.2k(4B);h 1q=8;8.2N({3Q:p(){1q.k.11.1R=\'3T\'},3J:p(){j(1q.E==b.15){1q.k.11.1R=\'1Z\'}}})};b.c.2T.w=B b.c.U();b.c.2T.w.1v=b.c.2T;b.c.2V=p(k,d){j(!8.1Y)q b.c.m.21(\'2V\');b.c.U.1s(8,d);h k=b.c.1x(k);8.k=k;j(!8.k)q;8.1t=\'2V\';h R=1O;h K=D;h 2F=O;h 2j=D;h V=b.3I;h v=v;b.c.3l(k);h 1m=b.c.2n(k);h 1K=1m.A;h 1M=1m.C;h 28=1K;h 2c=1M/5;h T=B b.c.m.2d;T.A=1K;T.C=1M;h 12=B b.c.m.2d;12.A=28;12.C=2c;j(d){j(d.r!=z)R=1g.4n(d.r/2);j(d.F!=z)K=d.F;j(d.1S!=z)2j=d.1S;j(d.v!=z)v=d.v;j(d.u!=z)V=d.u}d={r:R,F:K,2G:2F,1S:2j,u:V,v:v};h 3G=B b.c.1P(k,T,12,d);8.1V(3G);T.A=12.A;T.C=12.C;12.A=\'0%\';h 3G=B b.c.1P(k,T,12,d);8.1V(3G)};b.c.2V.w=B b.c.U();b.c.2V.w.1v=b.c.2V;b.c.7T=p(k,d){q b.c.m.23(\'30\',k,d)};b.c.7U=p(k,d){q b.c.m.23(\'2Y\',k,d)};b.c.7L=p(k,d){q b.c.m.23(\'2O\',k,d)};b.c.7K=p(k,d){q b.c.m.23(\'2P\',k,d)};b.c.7B=p(k,d){q b.c.m.23(\'2q\',k,d)};b.c.7C=p(k,d){q b.c.m.23(\'2H\',k,d)};b.c.7A=p(k,d){q b.c.m.23(\'2U\',k,d)};b.c.7z=p(k,d){q b.c.m.23(\'2B\',k,d)};b.c.7x=p(k,d){q b.c.m.23(\'3a\',k,d)};b.c.7y=p(k,d){q b.c.m.23(\'2T\',k,d)};b.c.7I=p(k,d){q b.c.m.23(\'2V\',k,d)};',62,541,'||||||||this|||Spry|Effect|options||||var||if|element||Utils|||function|return|duration|||transition|fps|prototype|||null|width|new|height|false|direction|toggle|effectsArray|else|to|effect|doToggle|from|length|units|true|floor|startOffsetPosition|durationInMilliseconds||fromRect|Cluster|kindOfTransition|100|Animator|parseInt|fromPos||style|toRect|toPos|elapsed|forwards|||for|time|getStyleProp|top|begin|computedStyle|change|left|Math|setStyleProp|value|test|getPercentValue|intProp|originalRect|typeof|fromColor|toOpacity|self|fromOpacity|call|name|isRunning|constructor|elStyle|getElement|prop|refElement|Move|toColor|currIdx|showError|opacity|pos|Position|result|originalOpacity|backwards|startWidth|position|startHeight|isPercentValue|1000|Size|px|display|useCSSBox|dimensions|startColor|addNextEffect|tryComputedStyle|toDim|notStaticAnimator|none||showInitError|fromDim|DoEffect|Opacity|indexOf|userAgent|navigator|stopWidth|stop|propFactor|originalWidth|stopHeight|Rectangle|hexToInt|cancelRemaining|childImages|startOpacity|firstChildElt|fullCSSBox|addParallelEffect|visibility|observers|getDimensionsRegardlessOfDisplayState|enforceVisible|pixelValue|Grow|originalHeight|substring|notifyObservers|substr|stopOpacity|stopColor|getComputedStyle|Notifier|fontSize|getPixelValue|Pulsate|len|startMilliseconds|Color|doScaleContent|scaleContent|Shake|getElapsedMilliseconds|start|color|makePositioned|MSIE|addObserver|Highlight|Slide|ele|effects|initDim|DropOut|Squish|Fold|prepareStart|node|Blind|getStylePropRegardlessOfDisplayState|Fade|slideHorizontally|effectB|lastIndexOf|heightFactor|optionsA|observer|catch|try|widthFactor|Puff|rightPos|interval|hidden|childCurr|document|startRedColor|startBlueColor|displayElement|rgb|displayOrig|makeClipping|intToHex|margin|finish|optionsB|allEffectsDidRun|animate|startY|leftPos|steps|moveEffect|startGreenColor|tmp|obs|overflow|setup|propVisible|toLowerCase|undefined|propDisplay|middle|sizeEffect|startX|fifthTransition|onPostEffect|in|startEltIn|stopGreenColor|stopBlueColor|step|break|onPreEffect|stopRedColor|half|block|effectA|Registry|intPropStyle|methodName|suppressNotifications|arguments|ef|centerPos|stopY|linearTransition|oStringList|kind|stopX|window|getPosition|timer|Number|getDimensions|isPercent|tmpval|background|blueColor|greenColor|redColor|compositeColorHex|unitIndex|stringToCamelize|fromHeightPx|padding|ceil|startTime|found|sinusoidalTransition|alpha|border|optionTo|optionFrom|dimRect|filter|fadeEffect|camelizedString|cancel|stopFlagReset|opacityEffect|pow|drawEffect|visibilityOrig|eff|getBgColor|currDate|toHeightPx|defaultView|withoutTimer|onStep|fontFactor|right|bottom|dynamicFromPos|unknown|SpryRegistry|objectCountA|dimensionsCurr|objectCountB|typeA|getRegisteredEffect|imgCurr|targetImagesOut|Transitions|sqrt|trans|compare|isFirstEntry|effectName|data|opera|currentStyle|string|setTransition|setFps|nodeType|heightCache|css|camelized|camelize|needsCache|stopTime|isInvisible|image|longColorVersion|margin_left|toggleCluster|Windows|static|growFromCenter|calcHeight|size|ClusteredEffect|margin_top|dynamicStartColor|dynamicStartOpacity|getOpacity|setInterval|baseEffectIsStillRunning|initNextEffectsRunning|evalNextEffectsRunning|dynamicFromRect|growCenter|NT|replace|isNaN|onCancel|onToggle|blueInt|greenInt|queue|redHex|focus|blueHex|greenHex|parallel|redInt|integerNum|case|number|effectsAreTheSame|PI|cos|setOptions|callback|circleTransition||msg|_ClusteredEffect|Array|hexStr|cleanWhitespace|move|horizontal|childRect|enableNotifications|squareTransition|optionsAreIdentical|effectToLeft|effectToRight|addEffect|typeB|relative|imageCnt|toUpperCase|childCountInit|highlightEffect|split|restoreBackgroundImage|restoreColor|setDuration|appearEffect|styleProp|getFirstChildElement|blindEffect|childNodes|startHeightPercent|startWidthPercent|stopWidthPercent|stopHeightPercent|border_right|padding_top|fetchChildImages|padding_left|origLeft|origMarginLeft|margin_right|margin_bottom|padding_right|Conflicting|startFromBorder_top|startFromBorder_bottom|startFromMargin_right|startFromMargin_bottom|startFromMargin_left|border_left|border_bottom|border_top|startFromMargin_top|startFromPadding_right|startFromBorder_right|startFromBorder_left|startFromPadding_top|startFromPadding_bottom|startFromPadding_left|el|padding_bottom|id|getElementById|while|emergency|origMarginTop|em|timeElapsed|getTime|round|Date|origTop|concat|redColorRange|Effects|switch|documentation|migration|default|toString|borderRightWidth|match|nextSibling|firstChild|hasChildNodes|img|200|dropIn|charAt|read|9a|160|blueColorRange|greenColorRange|accessed|DoPuff|DoDropOut|DoPulsate|DoSquish|DoGrow|DoShake|growSpecificTransition|sin|backgroundColor|squarerootTransition|borderBottomWidth|DoFold|pulsateTransition|DoSlide|DoHighlight|be|can|push|as|anymore|class|The|DoFade|DoBlind|alert|ERR|borderTopWidth|Please|getElementsByTagName|font|scroll|Unbalanced|zoom|disableNotifications|parseFloat|widthRange|getOffsetPosition|splice|marginBottom|offsetTop|startTop|Element|auto|clearInterval|paddingRight|offsetLeft|visible|startLeft|not|marginTop|fixed|getPropertyValue|removeObserver|paddingLeft|offsetWidth|transparent|ffff99|paddingTop|offsetHeight|ffffff|rangeMoveX|hasLayout|propA|propB|removeChild|marginRight|rangeMoveY|borderLeftWidth|marginLeft|repeat|paddingBottom|opacityRange|heightRange|nodeValue'.split('|'),0,{}))
--- a/includes/clientside/static/acl.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/acl.js	Sat Oct 20 21:59:27 2007 -0400
@@ -4,6 +4,9 @@
 var aclPermList = false;
 var aclDataCache = false;
 
+// Can be set to true by slow themes (St. Patty)
+var aclDisableTransitionFX = false;
+
 function ajaxOpenACLManager(page_id, namespace)
 {
   if(IE)
@@ -125,7 +128,7 @@
   usrsel = document.createElement('input');
   usrsel.type = 'text';
   usrsel.name = 'username';
-  usrsel.onkeyup = function() { ajaxUserNameComplete(this); };
+  usrsel.onkeyup = function() { new AutofillUsername(this, undefined, true); };
   usrsel.id = 'userfield_' + aclManagerID;
   try {
     usrsel.setAttribute("autocomplete","off");
@@ -494,7 +497,7 @@
 
 function __aclBuildWizardWindow()
 {
-  darken();
+  darken(aclDisableTransitionFX);
   box = document.createElement('div');
   box.style.width = '640px'
   box.style.height = '440px';
@@ -570,7 +573,16 @@
   
   body = document.getElementsByTagName('body')[0];
   body.appendChild(box);
-  setTimeout("document.getElementById('"+aclManagerID+"').style.display = 'block'; opacity('"+aclManagerID+"', 0, 100, 500); opacity('"+aclManagerID + '_panel'+"', 0, 100, 500);", 1000);
+  if ( aclDisableTransitionFX )
+  {
+    document.getElementById(aclManagerID).style.display = 'block';
+    changeOpac(100, aclManagerID);
+    changeOpac(100, aclManagerID + '_panel');
+  }
+  else
+  {
+    setTimeout("document.getElementById('"+aclManagerID+"').style.display = 'block'; opacity('"+aclManagerID+"', 0, 100, 500); opacity('"+aclManagerID + '_panel'+"', 0, 100, 500);", 1000);
+  }
 }
 
 function killACLManager()
@@ -578,8 +590,16 @@
   el = document.getElementById(aclManagerID);
   if(el)
   {
-    opacity(aclManagerID, 100, 0, 500);
-    setTimeout('var el = document.getElementById(aclManagerID); el.parentNode.removeChild(el); enlighten();', 750);
+    if ( aclDisableTransitionFX )
+    {
+      enlighten(true);
+      el.parentNode.removeChild(el);
+    }
+    else
+    {
+      opacity(aclManagerID, 100, 0, 500);
+      setTimeout('var el = document.getElementById(aclManagerID); el.parentNode.removeChild(el); enlighten();', 750);
+    }
   }
 }
 
--- a/includes/clientside/static/ajax.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/ajax.js	Sat Oct 20 21:59:27 2007 -0400
@@ -822,6 +822,33 @@
   window.location = loc;
 }
 
+var navto_ns;
+var navto_pg;
+var navto_ul;
+
+function ajaxLoginNavTo(namespace, page_id, min_level)
+{
+  // IE <6 pseudo-compatibility
+  if ( KILL_SWITCH )
+    return true;
+  navto_pg = page_id;
+  navto_ns = namespace;
+  navto_ul = min_level;
+  if ( auth_level < min_level )
+  {
+    ajaxPromptAdminAuth(function(k) {
+      ENANO_SID = k;
+      auth_level = navto_ul;
+      var loc = makeUrlNS(navto_ns, navto_pg);
+      if ( (ENANO_SID + ' ').length > 1 )
+        window.location = loc;
+    }, min_level);
+    return false;
+  }
+  var loc = makeUrlNS(navto_ns, navto_pg);
+  window.location = loc;
+}
+
 function ajaxAdminUser(username)
 {
   // IE <6 pseudo-compatibility
@@ -1142,7 +1169,6 @@
       keepalive_interval = setInterval('ajaxPingServer();', 600000);
     var span = document.getElementById('keepalivestat');
     span.firstChild.nodeValue = 'Turn off keep-alive';
-    ajaxPingServer();
   }
   else
   {
@@ -1155,6 +1181,6 @@
 
 function aboutKeepAlive()
 {
-  new messagebox(MB_OK|MB_ICONINFORMATION, 'About the keep-alive feature', 'Keep-alive is a new Enano feature that keeps your administrative session from timing out while you are using the administration panel. This feature can be useful if you are editing a large page or doing something in the administration interface that will take longer than 15 minutes.<br /><br />For security reasons, Enano mandates that high-privilege logins last only 15 minutes, with the time being reset each time a page is loaded (or, more specifically, each time the session API is started). The consequence of this is that if you are performing an action in the administration panel that takes more than 15 minutes, your session may be terminated. The keep-alive feature attempts to relieve this by sending a "ping" to the server every 10 minutes.<br /><br />Please note that keep-alive state is determined by a cookie. Thus, if you log out and then back in as a different administrator, keep-alive will use the same setting that was used when you were logged in as the first administrative user. In the same way, if you log into the administration panel under your account from another computer, keep-alive will be set to "off".');
+  new messagebox(MB_OK|MB_ICONINFORMATION, 'About the keep-alive feature', 'Keep-alive is a new Enano feature that keeps your administrative session from timing out while you are using the administration panel. This feature can be useful if you are editing a large page or doing something in the administration interface that will take longer than 15 minutes.<br /><br />For security reasons, Enano mandates that high-privilege logins last only 15 minutes, with the time being reset each time a page is loaded (or, more specifically, each time the session API is started). The consequence of this is that if you are performing an action in the administration panel that takes more than 15 minutes, your session may be terminated. The keep-alive feature attempts to relieve this by sending a "ping" to the server every 10 minutes.<br /><br />Please note that keep-alive state is determined by a cookie. Thus, if you log out and then back in as a different administrator, keep-alive will use the same setting that was used when you were logged in as the first administrative user. In the same way, if you log into the administration panel under your account from another computer, keep-alive will be set to "off".<br /><br /><b>For more information:</b><br /><a href="http://docs.enanocms.org/Help:Appendix_B" onclick="window.open(this.href); return false;">Overview of Enano'+"'"+'s security model');
 }
 
--- a/includes/clientside/static/autocomplete.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/autocomplete.js	Sat Oct 20 21:59:27 2007 -0400
@@ -160,7 +160,24 @@
         thediv.id = id;
         unObj.onblur = function() { destroyUsernameDropdowns(); }
         
-        eval(ajax.responseText);
+        var response = String(ajax.responseText) + ' ';
+        if ( response.substr(0,1) != '{' )
+        {
+          new messagebox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
+          return false;
+        }
+        
+        response = parseJSON(response);
+        var errorstring = false;
+        if ( response.mode == 'error' )
+        {
+          errorstring = response.error;
+        }
+        else
+        {
+          var userlist = response.users_real;
+        }
+        
         if(errorstring)
         {
           html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/autofill.js	Sat Oct 20 21:59:27 2007 -0400
@@ -0,0 +1,512 @@
+/**
+ * Javascript auto-completion for form fields.
+ */
+ 
+var af_current = false;
+ 
+function AutofillUsername(parent, event, allowanon)
+{
+  // if this is IE, use the old code
+  if ( IE )
+  {
+    ajaxUserNameComplete(parent);
+    return false;
+  }
+  if ( parent.afobj )
+  {
+    parent.afobj.go();
+    return true;
+  }
+  
+  parent.autocomplete = 'off';
+  parent.setAttribute('autocomplete', 'off');
+  
+  this.repeat = false;
+  this.event = event;
+  this.box_id = false;
+  this.boxes = new Array();
+  this.state = false;
+  this.allowanon = ( allowanon ) ? true : false;
+  
+  if ( !parent.id )
+    parent.id = 'afuser_' + Math.floor(Math.random() * 1000000);
+  
+  this.field_id = parent.id;
+  
+  // constants
+  this.KEY_UP    = 38;
+  this.KEY_DOWN  = 40;
+  this.KEY_ESC   = 27;
+  this.KEY_TAB   = 9;
+  this.KEY_ENTER = 13;
+  
+  // response cache
+  this.responses = new Object();
+  
+  // ajax placeholder
+  this.process_dataset = function(resp_json)
+  {
+    // window.console.info('Processing the following dataset.');
+    // window.console.debug(resp_json);
+    var autofill = this;
+    
+    if ( typeof(autofill.event) == 'object' )
+    {
+      if ( autofill.event.keyCode )
+      {
+        if ( autofill.event.keyCode == autofill.KEY_ENTER && autofill.boxes.length < 1 && !autofill.box_id )
+        {
+          // user hit enter after accepting a suggestion - submit the form
+          var frm = findParentForm($(autofill.field_id).object);
+          frm._af_acting = false;
+          frm.submit();
+          // window.console.info('Submitting form');
+          return false;
+        }
+        if ( autofill.event.keyCode == autofill.KEY_UP || autofill.event.keyCode == autofill.KEY_DOWN || autofill.event.keyCode == autofill.KEY_ESC || autofill.event.keyCode == autofill.KEY_TAB || autofill.event.keyCode == autofill.KEY_ENTER )
+        {
+          autofill.keyhandler();
+          // window.console.info('Control key detected, called keyhandler and exiting');
+          return true;
+        }
+      }
+    }
+    
+    if ( this.box_id )
+    {
+      this.destroy();
+      // window.console.info('already have a box open - destroying and exiting');
+      //return false;
+    }
+    
+    var users = new Array();
+    for ( var i = 0; i < resp_json.users_real.length; i++ )
+    {
+      try
+      {
+        var user = resp_json.users_real[i].toLowerCase();
+        var inp  = $(autofill.field_id).object.value;
+        inp = inp.toLowerCase();
+        if ( user.indexOf(inp) > -1 )
+        {
+          users.push(resp_json.users_real[i]);
+        }
+      }
+      catch(e)
+      {
+        users.push(resp_json.users_real[i]);
+      }
+    }
+
+    // This was used ONLY for debugging the DOM and list logic    
+    // resp_json.users = resp_json.users_real;
+    
+    // construct table
+    var div = document.createElement('div');
+    div.className = 'tblholder';
+    div.style.clip = 'rect(0px,auto,auto,0px)';
+    div.style.maxHeight = '200px';
+    div.style.overflow = 'auto';
+    div.style.zIndex = '9999';
+    var table = document.createElement('table');
+    table.border = '0';
+    table.cellSpacing = '1';
+    table.cellPadding = '3';
+    
+    var tr = document.createElement('tr');
+    var th = document.createElement('th');
+    th.appendChild(document.createTextNode('Username suggestions'));
+    tr.appendChild(th);
+    table.appendChild(tr);
+    
+    if ( users.length < 1 )
+    {
+      var tr = document.createElement('tr');
+      var td = document.createElement('td');
+      td.className = 'row1';
+      td.appendChild(document.createTextNode('No suggestions'));
+      td.afobj = autofill;
+      tr.appendChild(td);
+      table.appendChild(tr);
+    }
+    else
+      
+      for ( var i = 0; i < users.length; i++ )
+      {
+        var user = users[i];
+        var tr = document.createElement('tr');
+        var td = document.createElement('td');
+        td.className = ( i == 0 ) ? 'row2' : 'row1';
+        td.appendChild(document.createTextNode(user));
+        td.afobj = autofill;
+        td.style.cursor = 'pointer';
+        td.onclick = function()
+        {
+          this.afobj.set(this.firstChild.nodeValue);
+        }
+        tr.appendChild(td);
+        table.appendChild(tr);
+      }
+      
+    // Finalize div
+    var tb_top    = $(autofill.field_id).Top();
+    var tb_height = $(autofill.field_id).Height();
+    var af_top    = tb_top + tb_height - 9;
+    var tb_left   = $(autofill.field_id).Left();
+    var af_left   = tb_left;
+    
+    div.style.position = 'absolute';
+    div.style.left = af_left + 'px';
+    div.style.top  = af_top  + 'px';
+    div.style.width = '200px';
+    div.style.fontSize = '7pt';
+    div.style.fontFamily = 'Trebuchet MS, arial, helvetica, sans-serif';
+    div.id = 'afuserdrop_' + Math.floor(Math.random() * 1000000);
+    div.appendChild(table);
+    
+    autofill.boxes.push(div.id);
+    autofill.box_id = div.id;
+    if ( users.length > 0 )
+      autofill.state = users[0];
+    
+    var body = document.getElementsByTagName('body')[0];
+    body.appendChild(div);
+    
+    autofill.repeat = true;
+  }
+  
+  // perform ajax call
+  this.fetch_and_process = function()
+  {
+    af_current = this;
+    var processResponse = function()
+    {
+      if ( ajax.readyState == 4 )
+      {
+        var afobj = af_current;
+        af_current = false;
+        // parse the JSON response
+        var response = String(ajax.responseText) + ' ';
+        if ( response.substr(0,1) != '{' )
+        {
+          new messagebox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
+          return false;
+        }
+        if ( $(afobj.field_id).object.value.length < 3 )
+          return false;
+        var resp_json = parseJSON(response);
+        var resp_code = $(afobj.field_id).object.value.toLowerCase().substr(0, 3);
+        afobj.responses[resp_code] = resp_json;
+        afobj.process_dataset(resp_json);
+      }
+    }
+    var usernamefragment = ajaxEscape($(this.field_id).object.value);
+    ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse);
+  }
+  
+  this.go = function()
+  {
+    if ( document.getElementById(this.field_id).value.length < 3 )
+    {
+      this.destroy();
+      return false;
+    }
+    
+    if ( af_current )
+      return false;
+    
+    var resp_code = $(this.field_id).object.value.toLowerCase().substr(0, 3);
+    if ( this.responses.length < 1 || ! this.responses[ resp_code ] )
+    {
+      // window.console.info('Cannot find dataset ' + resp_code + ' in cache, sending AJAX request');
+      this.fetch_and_process();
+    }
+    else
+    {
+      // window.console.info('Using cached dataset: ' + resp_code);
+      var resp_json = this.responses[ resp_code ];
+      this.process_dataset(resp_json);
+    }
+    document.getElementById(this.field_id).onkeyup = function(event)
+    {
+      this.afobj.event = event;
+      this.afobj.go();
+    }
+    document.getElementById(this.field_id).onkeydown = function(event)
+    {
+      var form = findParentForm(this);
+      if ( typeof(event) != 'object' )
+        var event = window.event;
+      if ( typeof(event) == 'object' )
+      {
+        if ( event.keyCode == this.afobj.KEY_ENTER && this.afobj.boxes.length < 1 && !this.afobj.box_id )
+        {
+          // user hit enter after accepting a suggestion - submit the form
+          form._af_acting = false;
+          return true;
+        }
+      }
+      form._af_acting = true;
+    }
+  }
+  
+  this.keyhandler = function()
+  {
+    var key = this.event.keyCode;
+    if ( key == this.KEY_ENTER && !this.repeat )
+    {
+      var form = findParentForm($(this.field_id).object);
+        form._af_acting = false;
+      return true;
+    }
+    switch(key)
+    {
+      case this.KEY_UP:
+        this.focus_up();
+        break;
+      case this.KEY_DOWN:
+        this.focus_down();
+        break;
+      case this.KEY_ESC:
+        this.destroy();
+        break;
+      case this.KEY_TAB:
+        this.destroy();
+        break;
+      case this.KEY_ENTER:
+        this.set();
+        break;
+    }
+    
+    var form = findParentForm($(this.field_id).object);
+      form._af_acting = false;
+  }
+  
+  this.get_state_td = function()
+  {
+    var div = document.getElementById(this.box_id);
+    if ( !div )
+      return false;
+    if ( !this.state )
+      return false;
+    var table = div.firstChild;
+    for ( var i = 1; i < table.childNodes.length; i++ )
+    {
+      // the table is DOM-constructed so no cruddy HTML hacks :-)
+      var child = table.childNodes[i];
+      var tn = child.firstChild.firstChild;
+      if ( tn.nodeValue == this.state )
+        return child.firstChild;
+    }
+    return false;
+  }
+  
+  this.focus_down = function()
+  {
+    var state_td = this.get_state_td();
+    if ( !state_td )
+      return false;
+    if ( state_td.parentNode.nextSibling )
+    {
+      // Ooh boy, DOM stuff can be so complicated...
+      // <tr>  -->  <tr>
+      // <td>       <td>
+      // user       user
+      
+      var newstate = state_td.parentNode.nextSibling.firstChild.firstChild.nodeValue;
+      if ( !newstate )
+        return false;
+      this.state = newstate;
+      state_td.className = 'row1';
+      state_td.parentNode.nextSibling.firstChild.className = 'row2';
+      
+      // Exception - automatically scroll around if the item is off-screen
+      var height = $(this.box_id).Height();
+      var top = $(this.box_id).object.scrollTop;
+      var scroll_bottom = height + top;
+      
+      var td_top = $(state_td.parentNode.nextSibling.firstChild).Top() - $(this.box_id).Top();
+      var td_height = $(state_td.parentNode.nextSibling.firstChild).Height();
+      var td_bottom = td_top + td_height;
+      
+      if ( td_bottom > scroll_bottom )
+      {
+        var scrollY = td_top - height + 2*td_height - 7;
+        // window.console.debug(scrollY);
+        $(this.box_id).object.scrollTop = scrollY;
+        /*
+        var newtd = state_td.parentNode.nextSibling.firstChild;
+        var a = document.createElement('a');
+        var id = 'autofill' + Math.floor(Math.random() * 100000);
+        a.name = id;
+        a.id = id;
+        newtd.appendChild(a);
+        window.location.hash = '#' + id;
+        */
+        
+        // In firefox, scrolling like that makes the field get unfocused
+        $(this.field_id).object.focus();
+      }
+    }
+    else
+    {
+      return false;
+    }
+  }
+  
+  this.focus_up = function()
+  {
+    var state_td = this.get_state_td();
+    if ( !state_td )
+      return false;
+    if ( state_td.parentNode.previousSibling && state_td.parentNode.previousSibling.firstChild.tagName != 'TH' )
+    {
+      // Ooh boy, DOM stuff can be so complicated...
+      // <tr>  <--  <tr>
+      // <td>       <td>
+      // user       user
+      
+      var newstate = state_td.parentNode.previousSibling.firstChild.firstChild.nodeValue;
+      if ( !newstate )
+      {
+        return false;
+      }
+      this.state = newstate;
+      state_td.className = 'row1';
+      state_td.parentNode.previousSibling.firstChild.className = 'row2';
+      
+      // Exception - automatically scroll around if the item is off-screen
+      var top = $(this.box_id).object.scrollTop;
+      
+      var td_top = $(state_td.parentNode.previousSibling.firstChild).Top() - $(this.box_id).Top();
+      
+      if ( td_top < top )
+      {
+        $(this.box_id).object.scrollTop = td_top - 10;
+        /*
+        var newtd = state_td.parentNode.previousSibling.firstChild;
+        var a = document.createElement('a');
+        var id = 'autofill' + Math.floor(Math.random() * 100000);
+        a.name = id;
+        a.id = id;
+        newtd.appendChild(a);
+        window.location.hash = '#' + id;
+        */
+        
+        // In firefox, scrolling like that makes the field get unfocused
+        $(this.field_id).object.focus();
+      }
+    }
+    else
+    {
+      $(this.box_id).object.scrollTop = 0;
+      return false;
+    }
+  }
+  
+  this.destroy = function()
+  {
+    this.repeat = false;
+    var body = document.getElementsByTagName('body')[0];
+    var div = document.getElementById(this.box_id);
+    if ( !div )
+      return false;
+    setTimeout('var body = document.getElementsByTagName("body")[0]; body.removeChild(document.getElementById("'+div.id+'"));', 20);
+    // hackish workaround for divs that stick around past their welcoming period
+    for ( var i = 0; i < this.boxes.length; i++ )
+    {
+      var div = document.getElementById(this.boxes[i]);
+      if ( div )
+        setTimeout('var body = document.getElementsByTagName("body")[0]; var div = document.getElementById("'+div.id+'"); if ( div ) body.removeChild(div);', 20);
+      delete(this.boxes[i]);
+    }
+    this.box_id = false;
+    this.state = false;
+  }
+  
+  this.set = function(val)
+  {
+    var ta = document.getElementById(this.field_id);
+    if ( val )
+      ta.value = val;
+    else if ( this.state )
+      ta.value = this.state;
+    this.destroy();
+  }
+  
+  this.sleep = function()
+  {
+    if ( this.box_id )
+    {
+      var div = document.getElementById(this.box_id);
+      div.style.display = 'none';
+    }
+    var el = $(this.field_id).object;
+    var fr = findParentForm(el);
+    el._af_acting = false;
+  }
+  
+  this.wake = function()
+  {
+    if ( this.box_id )
+    {
+      var div = document.getElementById(this.box_id);
+      div.style.display = 'block';
+    }
+  }
+  
+  parent.onblur = function()
+  {
+    af_current = this.afobj;
+    window.setTimeout('if ( af_current ) af_current.sleep(); af_current = false;', 50);
+  }
+  
+  parent.onfocus = function()
+  {
+    af_current = this.afobj;
+    window.setTimeout('if ( af_current ) af_current.wake(); af_current = false;', 50);
+  }
+  
+  parent.afobj = this;
+  var frm = findParentForm(parent);
+  if ( frm.onsubmit )
+  {
+    frm.orig_onsubmit = frm.onsubmit;
+    frm.onsubmit = function(e)
+    {
+      if ( this._af_acting )
+        return false;
+      this.orig_onsubmit(e);
+    }
+  }
+  else
+  {
+    frm.onsubmit = function()
+    {
+      if ( this._af_acting )
+        return false;
+    }
+  }
+  
+  if ( parent.value.length < 3 )
+  {
+    this.destroy();
+    return false;
+  }
+}
+
+function findParentForm(o)
+{
+  if ( o.tagName == 'FORM' )
+    return o;
+  while(true)
+  {
+    o = o.parentNode;
+    if ( !o )
+      return false;
+    if ( o.tagName == 'FORM' )
+      return o;
+  }
+  return false;
+}
+
--- a/includes/clientside/static/enano-lib-basic.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/enano-lib-basic.js	Sat Oct 20 21:59:27 2007 -0400
@@ -264,6 +264,7 @@
   'admin-menu.js',
   'ajax.js',
   'autocomplete.js',
+  'autofill.js',
   'base64.js',
   'dropdown.js',
   'faders.js',
@@ -283,6 +284,7 @@
   'flyin.js',
   'paginate.js',
   'pwstrength.js',
+  'SpryEffects.js',
   'loader.js'
 ];
 
--- a/includes/clientside/static/faders.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/faders.js	Sat Oct 20 21:59:27 2007 -0400
@@ -93,18 +93,29 @@
   var y = getScrollOffset();
   if(document.getElementById('messageBox')) return;
   darken(true);
+  if ( aclDisableTransitionFX )
+  {
+    document.getElementById('specialLayer_darkener').style.zIndex = '5';
+  }
   var master_div = document.createElement('div');
+  master_div.style.zIndex = '6';
   var mydiv = document.createElement('div');
   mydiv.style.width = '400px';
   mydiv.style.height = '200px';
   w = getWidth();
   h = getHeight();
-  //master_div.style.left = (w / 2) - 200+'px';
-  //master_div.style.top = (h / 2) + y - 120+'px';
-  master_div.style.top = '-10000px';
-  master_div.style.position = ( IE ) ? 'absolute' : 'fixed';
-  z = getHighestZ(); // document.getElementById('specialLayer_darkener').style.zIndex;
-  mydiv.style.zIndex = parseInt(z) + 1;
+  if ( aclDisableTransitionFX )
+  {
+    master_div.style.left = ((w / 2) - 200)+'px';
+    master_div.style.top = ((h / 2) + y - 120)+'px';
+    master_div.style.position = 'absolute';
+  }
+  else
+  {
+    master_div.style.top = '-10000px';
+    master_div.style.position = ( IE ) ? 'absolute' : 'fixed';
+  }
+  z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex : getHighestZ();
   mydiv.style.backgroundColor = '#FFFFFF';
   mydiv.style.padding = '10px';
   mydiv.style.marginBottom = '1px';
@@ -115,11 +126,13 @@
   buttondiv.style.width = '400px';
   w = getWidth();
   h = getHeight();
-  // buttondiv.style.left = (w / 2) - 200+'px';
-  // buttondiv.style.top = (h / 2) + y + 101+'px';
-  // buttondiv.style.position = ( IE ) ? 'absolute' : 'fixed';
-  z = getHighestZ(); // document.getElementById('specialLayer_darkener').style.zIndex;
-  buttondiv.style.zIndex = parseInt(z) + 1;
+  if ( aclDisableTransitionFX )
+  {
+    //buttondiv.style.left = ((w / 2) - 200)+'px';
+    //buttondiv.style.top = ((h / 2) + y + 101)+'px';
+  }
+  //buttondiv.style.position = ( IE ) ? 'absolute' : 'fixed';
+  z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex : getHighestZ();
   buttondiv.style.backgroundColor = '#C0C0C0';
   buttondiv.style.padding = '10px';
   buttondiv.style.textAlign = 'right';
@@ -265,7 +278,8 @@
   
   body.appendChild(master_div);
   
-  setTimeout('mb_runFlyIn();', 100);
+  if ( !aclDisableTransitionFX )
+    setTimeout('mb_runFlyIn();', 100);
   
   this.onclick = new Array();
   this.onbeforeclick = new Array();
@@ -293,9 +307,19 @@
   
   var mydiv = document.getElementById('messageBox');
   var maindiv = mydiv.parentNode;
-  var to = fly_out_top(maindiv, true, false);
   
-  setTimeout("var mbdiv = document.getElementById('messageBox'); mbdiv.parentNode.removeChild(mbdiv.nextSibling); mbdiv.parentNode.removeChild(mbdiv); enlighten(true);", to);
+  if ( aclDisableTransitionFX )
+  {
+    var mbdiv = document.getElementById('messageBox');
+    mbdiv.parentNode.removeChild(mbdiv.nextSibling);
+    mbdiv.parentNode.removeChild(mbdiv);
+    enlighten(true);
+  }
+  else
+  {
+    var to = fly_out_top(maindiv, true, false);
+    setTimeout("var mbdiv = document.getElementById('messageBox'); mbdiv.parentNode.removeChild(mbdiv.nextSibling); mbdiv.parentNode.removeChild(mbdiv); enlighten(true);", to);
+  }
   if(typeof mb.onclick[val] == 'function')
   {
     o = mb.onclick[val];
--- a/includes/clientside/static/loader.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/loader.js	Sat Oct 20 21:59:27 2007 -0400
@@ -1,27 +1,8 @@
 // Some final stuff - loader routines, etc.
 
-var __tmpEnanoStartup9843275;
-  
-function enanoStartup(e) {
-  if ( !e )
-  {
-    // Delay initting sliders until images are loaded
-    if ( typeof(window.onload) == 'function' )
-      __tmpEnanoStartup9843275 = window.onload;
-    else
-      __tmpEnanoStartup9843275 = function(){};
-    window.onload = function(e){__tmpEnanoStartup9843275(e);initSliders();};
-  }
-  else
-  {
-    initSliders();
-  }
-}
-
 function mdgInnerLoader(e)
 {
   jws.startup();
-  enanoStartup(e);
   if(window.location.hash == '#comments') ajaxComments();
   window.onkeydown=isKeyPressed;
   window.onkeyup=function(e) { isKeyPressed(e); };
@@ -34,6 +15,7 @@
   {
     dbx_set_key();
   }
+  initSliders();
   runOnloadHooks(e);
 }
 if(window.onload) var ld = window.onload;
--- a/includes/clientside/static/misc.js	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/clientside/static/misc.js	Sat Oct 20 21:59:27 2007 -0400
@@ -301,6 +301,7 @@
 var ajax_auth_prompt_cache = false;
 var ajax_auth_mb_cache = false;
 var ajax_auth_level_cache = false;
+var ajax_auth_error_string = false;
 
 function ajaxPromptAdminAuth(call_on_ok, level)
 {
@@ -336,7 +337,14 @@
         response = parseJSON(response);
         var level = ajax_auth_level_cache;
         var form_html = '';
-        if ( level > USER_LEVEL_MEMBER )
+        var shown_error = false;
+        if ( ajax_auth_error_string )
+        {
+          shown_error = true;
+          form_html += '<div class="error-box-mini" id="ajax_auth_error">' + ajax_auth_error_string + '</div>';
+          ajax_auth_error_string = false;
+        }
+        else if ( level > USER_LEVEL_MEMBER )
         {
           form_html += 'Please re-enter your login details, to verify your identity.<br /><br />';
         }
@@ -350,7 +358,7 @@
             </tr> \
             <tr> \
               <td colspan="2" style="text-align: center;"> \
-                <br /><small>Trouble logging in? Try the <a href="'+makeUrlNS('Special', 'Login/' + title)+'">full login form</a>.<br />';
+                <br /><small>Trouble logging in? Try the <a href="'+makeUrlNS('Special', 'Login/' + title, 'level=' + level)+'">full login form</a>.<br />';
        if ( level <= USER_LEVEL_MEMBER )
        {
          form_html += ' \
@@ -377,6 +385,19 @@
         }
         $('ajaxlogin_pass').object.onblur = function(e) { if ( !shift ) $('messageBox').object.nextSibling.firstChild.focus(); };
         $('ajaxlogin_pass').object.onkeypress = function(e) { if ( !e && IE ) return true; if ( e.keyCode == 13 ) $('messageBox').object.nextSibling.firstChild.click(); };
+        /*
+        ## This causes the background image to disappear under Fx 2
+        if ( shown_error )
+        {
+          // fade to #FFF4F4
+          var fader = new Spry.Effect.Highlight('ajax_auth_error', {duration: 1000, from: '#FFF4F4', to: '#805600', restoreColor: '#805600', finish: function()
+              {
+                var fader = new Spry.Effect.Highlight('ajax_auth_error', {duration: 3000, from: '#805600', to: '#FFF4F4', restoreColor: '#FFF4F4'});
+                fader.start();
+          }});
+          fader.start();
+        }
+        */
       }
     });
 }
@@ -488,8 +509,20 @@
             }
             break;
           case 'error':
-            alert(response.error);
-            ajaxAuthLoginInnerSetup();
+            if ( response.error == 'The username and/or password is incorrect.' )
+            {
+              ajax_auth_error_string = response.error;
+              mb_current_obj.updateContent('');
+              document.getElementById('messageBox').style.backgroundColor = '#C0C0C0';
+              var mb_parent = document.getElementById('messageBox').parentNode;
+              new Spry.Effect.Shake(mb_parent, {duration: 1500}).start();
+              setTimeout("document.getElementById('messageBox').style.backgroundColor = '#FFF'; ajaxAuthLoginInnerSetup();", 2500);
+            }
+            else
+            {
+              alert(response.error);
+              ajaxAuthLoginInnerSetup();
+            }
             break;
           default:
             alert(ajax.responseText);
--- a/includes/comment.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/comment.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/common.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/common.php	Sat Oct 20 21:59:27 2007 -0400
@@ -23,7 +23,7 @@
   exit;
 }
 
-$version = '1.0.2b1';
+$version = '1.0.2';
 
 function microtime_float()
 {
@@ -68,6 +68,9 @@
 if ( file_exists( ENANO_ROOT . '/_nightly.php') )
   require(ENANO_ROOT.'/_nightly.php');
 
+// List of scheduled tasks
+$cron_tasks = array();
+
 // Start including files. LOTS of files. Yeah!
 require_once(ENANO_ROOT.'/includes/constants.php');
 dc_here('Enano CMS '.$version.' (dev) - debug window<br />Powered by debugConsole');
--- a/includes/constants.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/constants.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * constants.php - important defines used Enano-wide
  *
@@ -37,6 +37,7 @@
 define('PAGE_GRP_CATLINK', 1);
 define('PAGE_GRP_TAGGED', 2);
 define('PAGE_GRP_NORMAL', 3);
+define('PAGE_GRP_REGEX', 4);
 
 //
 // User types - don't touch these
--- a/includes/dbal.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/dbal.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/email.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/email.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/functions.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/functions.php	Sat Oct 20 21:59:27 2007 -0400
@@ -1789,6 +1789,26 @@
 
 function sanitize_html($html, $filter_php = true)
 {
+  // Random seed for substitution
+  $rand_seed = md5( sha1(microtime()) . mt_rand() );
+  
+  // Strip out comments that are already escaped
+  preg_match_all('/&lt;!--(.*?)--&gt;/', $html, $comment_match);
+  $i = 0;
+  foreach ( $comment_match[0] as $comment )
+  {
+    $html = str_replace_once($comment, "{HTMLCOMMENT:$i:$rand_seed}", $html);
+    $i++;
+  }
+  
+  // Strip out code sections that will be postprocessed by Text_Wiki
+  preg_match_all(';^<code(\s[^>]*)?>((?:(?R)|.)*?)\n</code>(\s|$);msi', $html, $code_match);
+  $i = 0;
+  foreach ( $code_match[0] as $code )
+  {
+    $html = str_replace_once($code, "{TW_CODE:$i:$rand_seed}", $html);
+    $i++;
+  }
 
   $html = preg_replace('#<([a-z]+)([\s]+)([^>]+?)'.htmlalternatives('javascript:').'(.+?)>(.*?)</\\1>#is', '&lt;\\1\\2\\3javascript:\\59&gt;\\60&lt;/\\1&gt;', $html);
   $html = preg_replace('#<([a-z]+)([\s]+)([^>]+?)'.htmlalternatives('javascript:').'(.+?)>#is', '&lt;\\1\\2\\3javascript:\\59&gt;', $html);
@@ -1802,6 +1822,8 @@
   $tag_whitelist = array_keys ( setupAttributeWhitelist() );
   if ( !$filter_php )
     $tag_whitelist[] = '?php';
+  // allow HTML comments
+  $tag_whitelist[] = '!--';
   $len = strlen($html);
   $in_quote = false;
   $quote_char = '';
@@ -1862,8 +1884,12 @@
       }
       else
       {
+        // If not filtering PHP, don't bother to strip
         if ( $tag_name == '?php' && !$filter_php )
           continue;
+        // If this is a comment, likewise skip this "tag"
+        if ( $tag_name == '!--' )
+          continue;
         $f = fixTagAttributes( $attribs_only, $tag_name );
         $s = ( empty($f) ) ? '' : ' ';
 
@@ -1891,15 +1917,28 @@
     }
 
   }
-
+  
   // Vulnerability from ha.ckers.org/xss.html:
   // <script src="http://foo.com/xss.js"
   // <
   // The rule is so specific because everything else will have been filtered by now
   $html = preg_replace('/<(script|iframe)(.+?)src=([^>]*)</i', '&lt;\\1\\2src=\\3&lt;', $html);
 
-  // Unstrip comments
-  $html = preg_replace('/&lt;!--([^>]*?)--&gt;/i', '', $html);
+  // Restore stripped comments
+  $i = 0;
+  foreach ( $comment_match[0] as $comment )
+  {
+    $html = str_replace_once("{HTMLCOMMENT:$i:$rand_seed}", $comment, $html);
+    $i++;
+  }
+  
+  // Restore stripped code
+  $i = 0;
+  foreach ( $code_match[0] as $code )
+  {
+    $html = str_replace_once("{TW_CODE:$i:$rand_seed}", $code, $html);
+    $i++;
+  }
 
   return $html;
 
@@ -2705,7 +2744,7 @@
 function sanitize_tag($tag)
 {
   $tag = strtolower($tag);
-  $tag = preg_replace('/[^\w _-]+/', '', $tag);
+  $tag = preg_replace('/[^\w _@\$%\^&-]+/', '', $tag);
   $tag = trim($tag);
   return $tag;
 }
@@ -2757,7 +2796,7 @@
   $strip_tags = implode('|', $strip_tags);
   
   // Strip out the tags and replace with placeholders
-  preg_match_all("#<($strip_tags)(.*?)>(.*?)</($strip_tags)>#is", $html, $matches);
+  preg_match_all("#<($strip_tags)([ ]+.*?)?>(.*?)</($strip_tags)>#is", $html, $matches);
   $seed = md5(microtime() . mt_rand()); // Random value used for placeholders
   for ($i = 0;$i < sizeof($matches[1]); $i++)
   {
@@ -2765,7 +2804,7 @@
   }
   
   // Optimize (but don't obfuscate) Javascript
-  preg_match_all('/<script(.*?)>(.+?)<\/script>/is', $html, $jscript);
+  preg_match_all('/<script([ ]+.*?)?>(.*?)(\]\]>)?<\/script>/is', $html, $jscript);
   
   // list of Javascript reserved words - from about.com
   $reserved_words = array('abstract', 'as', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'continue', 'const', 'debugger', 'default', 'delete', 'do',
@@ -2780,6 +2819,8 @@
   {
     $js =& $jscript[2][$i];
     
+    // echo('<pre>' . "-----------------------------------------------------------------------------\n" . htmlspecialchars($js) . '</pre>');
+    
     // for line optimization, explode it
     $particles = explode("\n", $js);
     
@@ -3127,6 +3168,20 @@
   return $score;
 }
 
+/**
+ * Registers a task that will be run every X hours. Scheduled tasks should always be scheduled at runtime - they are not stored in the DB.
+ * @param string Function name to call, or array(object, string method)
+ * @param int Interval between runs, in hours. Defaults to 24.
+ */
+
+function register_cron_task($func, $hour_interval = 24)
+{
+  global $cron_tasks;
+  if ( !isset($cron_tasks[$hour_interval]) )
+    $cron_tasks[$hour_interval] = array();
+  $cron_tasks[$hour_interval][] = $func;
+}
+
 //die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
 
 ?>
--- a/includes/graphs.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/graphs.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/js-compressor.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/js-compressor.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * Javascript compression library - used to compact the client-side Javascript code (all 72KB of it!) to save some bandwidth
  *
--- a/includes/pageprocess.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/pageprocess.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * pageprocess.php - intelligent retrieval of pages
  * Copyright (C) 2006-2007 Dan Fuhry
  *
--- a/includes/pageutils.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/pageutils.php	Sat Oct 20 21:59:27 2007 -0400
@@ -15,18 +15,27 @@
 class PageUtils {
   
   /**
-   * List possible username completions
+   * Tell if a username is used or not.
    * @param $name the name to check for
-   * @return array
+   * @return string
    */
   
   function checkusername($name)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE username=\''.$db->escape(rawurldecode($name)).'\'');
-    if(!$q) die(mysql_error());
-    if($db->numrows() < 1) { $db->free_result(); return('good'); }
-    else { $db->free_result(); return('bad'); }
+    $q = $db->sql_query('SELECT username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape(rawurldecode($name)) . '\'');
+    if ( !$q )
+    {
+      die(mysql_error());
+    }
+    if ( $db->numrows() < 1)
+    {
+      $db->free_result(); return('good');
+    }
+    else
+    {
+      $db->free_result(); return('bad');
+    }
   }
   
   /**
@@ -57,10 +66,10 @@
     $pid = RenderMan::strToPageID($page);
     if($pid[1] == 'Special' || $pid[1] == 'Admin')
     {
-      die('This type of page ('.$paths->nslist[$pid[1]].') cannot be edited because the page source code is not stored in the database.');
+      die('This type of page (' . $paths->nslist[$pid[1]] . ') cannot be edited because the page source code is not stored in the database.');
     }
     
-    $e = $db->sql_query('SELECT page_text,char_tag FROM '.table_prefix.'page_text WHERE page_id=\''.$pid[0].'\' AND namespace=\''.$pid[1].'\'');
+    $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'page_text WHERE page_id=\'' . $pid[0] . '\' AND namespace=\'' . $pid[1] . '\'');
     if ( !$e )
     {
       $db->_die('The page text could not be selected.');
@@ -123,7 +132,7 @@
         return $r;
       }
       
-      $fname = 'page_'.$pid[1].'_'.$paths->pages[$page]['urlname_nons'];
+      $fname = 'page_' . $pid[1] . '_' . $paths->pages[$page]['urlname_nons'];
       @call_user_func($fname);
       
     }
@@ -147,7 +156,7 @@
         return $r;
       }
       
-      $fname = 'page_'.$pid[1].'_'.$pid[0];
+      $fname = 'page_' . $pid[1] . '_' . $pid[0];
       if ( !function_exists($fname) )
       {
         $title = 'Page backend not found';
@@ -190,12 +199,17 @@
                <p>You have requested a page that doesn\'t exist yet.';
         if($session->get_permissions('create_page')) echo ' You can <a href="'.makeUrl($paths->page, 'do=edit', true).'" onclick="ajaxEditor(); return false;">create this page</a>, or return to the <a href="'.makeUrl(getConfig('main_page')).'">homepage</a>.';
         else echo ' Return to the <a href="'.makeUrl(getConfig('main_page')).'">homepage</a>.</p>';
-        if($session->get_permissions('history_rollback')) {
-          $e = $db->sql_query('SELECT * FROM '.table_prefix.'logs WHERE action=\'delete\' AND page_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\''.$pid[1].'\' ORDER BY time_id DESC;');
-          if(!$e) $db->_die('The deletion log could not be selected.');
-          if($db->numrows() > 0) {
+        if ( $session->get_permissions('history_rollback') )
+        {
+          $e = $db->sql_query('SELECT * FROM ' . table_prefix.'logs WHERE action=\'delete\' AND page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $pid[1] . '\' ORDER BY time_id DESC;');
+          if ( !$e )
+          {
+            $db->_die('The deletion log could not be selected.');
+          }
+          if ($db->numrows() > 0 )
+          {
             $r = $db->fetchrow();
-            echo '<p>This page also appears to have some log entries in the database - it seems that it was deleted on '.$r['date_string'].'. You can probably <a href="'.makeUrl($paths->page, 'do=rollback&amp;id='.$r['time_id']).'" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;">roll back</a> the deletion.</p>';
+            echo '<p>This page also appears to have some log entries in the database - it seems that it was deleted on ' . $r['date_string'] . '. You can probably <a href="'.makeUrl($paths->page, 'do=rollback&amp;id=' . $r['time_id']) . '" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">roll back</a> the deletion.</p>';
           }
           $db->free_result();
         }
@@ -233,15 +247,16 @@
         return $text;
       }
       
-      if($hist_id) {
-        $e = $db->sql_query('SELECT page_text,date_string,char_tag FROM '.table_prefix.'logs WHERE page_id=\''.$paths->pages[$page]['urlname_nons'].'\' AND namespace=\''.$pid[1].'\' AND log_type=\'page\' AND action=\'edit\' AND time_id='.$db->escape($hist_id).'');
+      if ( $hist_id )
+      {
+        $e = $db->sql_query('SELECT page_text,date_string,char_tag FROM ' . table_prefix.'logs WHERE page_id=\'' . $paths->pages[$page]['urlname_nons'] . '\' AND namespace=\'' . $pid[1] . '\' AND log_type=\'page\' AND action=\'edit\' AND time_id=' . $db->escape($hist_id) . '');
         if($db->numrows() < 1)
         {
           $db->_die('There were no rows in the text table that matched the page text query.');
         }
         $r = $db->fetchrow();
         $db->free_result();
-        $message = '<div class="info-box" style="margin-left: 0; margin-top: 5px;"><b>Notice:</b><br />The page you are viewing was archived on '.$r['date_string'].'.<br /><a href="'.makeUrl($page).'" onclick="ajaxReset(); return false;">View current version</a>  |  <a href="'.makeUrl($page, 'do=rollback&amp;id='.$hist_id).'" onclick="ajaxRollback(\''.$hist_id.'\')">Restore this version</a></div><br />'.RenderMan::render($r['page_text']);
+        $message = '<div class="info-box" style="margin-left: 0; margin-top: 5px;"><b>Notice:</b><br />The page you are viewing was archived on ' . $r['date_string'] . '.<br /><a href="'.makeUrl($page).'" onclick="ajaxReset(); return false;">View current version</a>  |  <a href="'.makeUrl($page, 'do=rollback&amp;id=' . $hist_id) . '" onclick="ajaxRollback(\'' . $hist_id . '\')">Restore this version</a></div><br />'.RenderMan::render($r['page_text']);
         
         if( !$paths->pages[$page]['special'] )
         {
@@ -252,7 +267,7 @@
           display_page_headers();
         }
         
-        eval('?>'.$message);
+        eval('?>' . $message);
         
         if( !$paths->pages[$page]['special'] )
         {
@@ -286,7 +301,7 @@
 
         // This is it, this is what all of Enano has been working up to...
         
-        eval('?>'.$message);
+        eval('?>' . $message);
         
         if( !$paths->pages[$page]['special'] )
         {
@@ -322,8 +337,9 @@
     
     if(!isset($paths->pages[$pname]))
     {
-      if(!PageUtils::createPage($page_id, $namespace))
-        return 'The page did not exist, and I was not able to create it. Permissions problem?';
+      $create = PageUtils::createPage($page_id, $namespace);
+      if ( $create != 'good' )
+        return 'The page did not exist, and I was not able to create it. The reported error was: ' . $create;
       $paths->page_exists = true;
     }
     
@@ -337,10 +353,10 @@
     $msg = $db->escape($message);
     
     $minor = $minor ? 'true' : 'false';
-    $q='INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \''.$paths->cpage['urlname_nons'].'\', \''.$paths->namespace.'\', \''.$msg.'\', \''.$uid.'\', \''.$session->username.'\', \''.$db->escape(htmlspecialchars($summary)).'\', '.$minor.');';
+    $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $paths->cpage['urlname_nons'] . '\', \'' . $paths->namespace . '\', \'' . $msg . '\', \'' . $uid . '\', \'' . $session->username . '\', \'' . $db->escape(htmlspecialchars($summary)) . '\', ' . $minor . ');';
     if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
     
-    $q = 'UPDATE '.table_prefix.'page_text SET page_text=\''.$msg.'\',char_tag=\''.$uid.'\' WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';';
+    $q = 'UPDATE ' . table_prefix.'page_text SET page_text=\'' . $msg . '\',char_tag=\'' . $uid . '\' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';';
     $e = $db->sql_query($q);
     if(!$e) $db->_die('Enano was unable to save the page contents. Your changes have been lost <tt>:\'(</tt>.');
       
@@ -362,32 +378,38 @@
     if(in_array($namespace, Array('Special', 'Admin')))
     {
       // echo '<b>Notice:</b> PageUtils::createPage: You can\'t create a special page in the database<br />';
-      return false; // Can't create a special page
+      return 'You can\'t create a special page in the database';
     }
     
     if(!isset($paths->nslist[$namespace]))
     {
       // echo '<b>Notice:</b> PageUtils::createPage: Couldn\'t look up the namespace<br />';
-      return false; // Couldn't look up namespace
+      return 'Couldn\'t look up the namespace';
     }
     
     $pname = $paths->nslist[$namespace] . $page_id;
     if(isset($paths->pages[$pname]))
     {
       // echo '<b>Notice:</b> PageUtils::createPage: Page already exists<br />';
-      return false; // Page already exists
+      return 'Page already exists';
     }
     
     if(!$session->get_permissions('create_page'))
     {
       // echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create pages<br />';
-      return false; // Access denied
+      return 'Not authorized to create pages';
     }
     
     if($session->user_level < USER_LEVEL_ADMIN && $namespace == 'System')
     {
       // echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create system messages<br />';
-      return false; // Not authorized to create system messages
+      return 'Not authorized to create system messages';
+    }
+    
+    if ( substr($page_id, 0, 8) == 'Project:' )
+    {
+      // echo '<b>Notice:</b> PageUtils::createPage: Prefix "Project:" is reserved<br />';
+      return 'The prefix "Project:" is reserved for a parser shortcut; if a page was created using this prefix, it would not be possible to link to it.';
     }
     
     $page_id = dirtify_page_id($page_id);
@@ -398,7 +420,7 @@
     if(!preg_match($regex, $page))
     {
       //echo '<b>Notice:</b> PageUtils::createPage: Name contains invalid characters<br />';
-      return false; // Name contains invalid characters
+      return 'Name contains invalid characters';
     }
     
     $page_id = sanitize_page_id( $page_id );
@@ -421,16 +443,15 @@
     
     $paths->add_page($page_data);
     
-    $qa = $db->sql_query('INSERT INTO '.table_prefix.'pages(name,urlname,namespace,visible,protected,delvote_ips) VALUES(\''.$db->escape($name).'\', \''.$db->escape($page_id).'\', \''.$namespace.'\', '. ( $visible ? '1' : '0' ) .', '.$prot.', \'' . $db->escape(serialize($ips)) . '\');');
-    $qb = $db->sql_query('INSERT INTO '.table_prefix.'page_text(page_id,namespace) VALUES(\''.$db->escape($page_id).'\', \''.$namespace.'\');');
-    $qc = $db->sql_query('INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'create\', \''.$session->username.'\', \''.$db->escape($page_id).'\', \''.$namespace.'\');');
+    $qa = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace,visible,protected,delvote_ips) VALUES(\'' . $db->escape($name) . '\', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\', '. ( $visible ? '1' : '0' ) .', ' . $prot . ', \'' . $db->escape(serialize($ips)) . '\');');
+    $qb = $db->sql_query('INSERT INTO ' . table_prefix.'page_text(page_id,namespace) VALUES(\'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
+    $qc = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'create\', \'' . $session->username . '\', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
     
     if($qa && $qb && $qc)
-      return true;
+      return 'good';
     else
     {
-      echo $db->get_error();
-      return false;
+      return $db->get_error();
     }
   }
   
@@ -450,31 +471,41 @@
     $wiki = ( ( $paths->pages[$pname]['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $paths->pages[$pname]['wiki_mode'] == 1) ? true : false;
     $prot = ( ( $paths->pages[$pname]['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $paths->pages[$pname]['protected'] == 1) ? true : false;
     
-    if(!$session->get_permissions('protect')) return('Insufficient access rights');
-    if(!$wiki) return('Page protection only has an effect when Wiki Mode is enabled.');
-    if(!preg_match('#^([0-9]+){1}$#', (string)$level)) return('Invalid $level parameter.');
-    
-    if($reason!='NO_REASON') {
-      switch($level)
-      {
-        case 0:
-          $q = 'INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'unprot\', \''.$session->username.'\', \''.$page_id.'\', \''.$namespace.'\', \''.$db->escape(htmlspecialchars($reason)).'\');';
-          break;
-        case 1:
-          $q = 'INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'prot\', \''.$session->username.'\', \''.$page_id.'\', \''.$namespace.'\', \''.$db->escape(htmlspecialchars($reason)).'\');';
-          break;
-        case 2:
-          $q = 'INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'semiprot\', \''.$session->username.'\', \''.$page_id.'\', \''.$namespace.'\', \''.$db->escape(htmlspecialchars($reason)).'\');';
-          break;
-        default:
-          return 'PageUtils::protect(): Invalid value for $level';
-          break;
-      }
-      if(!$db->sql_query($q)) $db->_die('The log entry for the page protection could not be inserted.');
+    if ( !$session->get_permissions('protect') )
+    {
+      return('Insufficient access rights');
+    }
+    if ( !$wiki )
+    {
+      return('Page protection only has an effect when Wiki Mode is enabled.');
+    }
+    if ( !preg_match('#^([0-9]+){1}$#', (string)$level) )
+    {
+      return('Invalid $level parameter.');
     }
     
-    $q = $db->sql_query('UPDATE '.table_prefix.'pages SET protected='.$_POST['level'].' WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
-    if(!$q) $db->_die('The pages table was not updated.');
+    switch($level)
+    {
+      case 0:
+        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'unprot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
+        break;
+      case 1:
+        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'prot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
+        break;
+      case 2:
+        $q = 'INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,page_id,namespace,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'semiprot\', \'' . $session->username . '\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\');';
+        break;
+      default:
+        return 'PageUtils::protect(): Invalid value for $level';
+        break;
+    }
+    if(!$db->sql_query($q)) $db->_die('The log entry for the page protection could not be inserted.');
+    
+    $q = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=' . $level . ' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+    if ( !$q )
+    {
+      $db->_die('The pages table was not updated.');
+    }
     
     return('good');
   }
@@ -499,8 +530,8 @@
     $wiki = ( ( $paths->pages[$pname]['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $paths->pages[$pname]['wiki_mode'] == 1) ? true : false;
     $prot = ( ( $paths->pages[$pname]['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $paths->pages[$pname]['protected'] == 1) ? true : false;
     
-    $q = 'SELECT time_id,date_string,page_id,namespace,author,edit_summary,minor_edit FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' ORDER BY time_id DESC;';
-    if(!$db->sql_query($q)) $db->_die('The history data for the page "'.$paths->cpage['name'].'" could not be selected.');
+    $q = 'SELECT time_id,date_string,page_id,namespace,author,edit_summary,minor_edit FROM ' . table_prefix.'logs WHERE log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' ORDER BY time_id DESC;';
+    if(!$db->sql_query($q)) $db->_die('The history data for the page "' . $paths->cpage['name'] . '" could not be selected.');
     echo 'History of edits and actions<h3>Edits:</h3>';
     $numrows = $db->numrows();
     if($numrows < 1) echo 'No history entries in this category.';
@@ -551,29 +582,38 @@
           $s1 = '';
           $s2 = '';
         }
-        if($ticker > 1)        echo '<td class="'.$cls.'" style="padding: 0;"><input '.$s1.'name="diff1" type="radio" value="'.$r['time_id'].'" id="diff1_'.$r['time_id'].'" class="clsDiff1Radio" onclick="selectDiff1Button(this);" /></td>'."\n"; else echo '<td class="'.$cls.'"></td>';
-        if($ticker < $numrows) echo '<td class="'.$cls.'" style="padding: 0;"><input '.$s2.'name="diff2" type="radio" value="'.$r['time_id'].'" id="diff2_'.$r['time_id'].'" class="clsDiff2Radio" onclick="selectDiff2Button(this);" /></td>'."\n"; else echo '<td class="'.$cls.'"></td>';
+        if($ticker > 1)        echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s1 . 'name="diff1" type="radio" value="' . $r['time_id'] . '" id="diff1_' . $r['time_id'] . '" class="clsDiff1Radio" onclick="selectDiff1Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
+        if($ticker < $numrows) echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s2 . 'name="diff2" type="radio" value="' . $r['time_id'] . '" id="diff2_' . $r['time_id'] . '" class="clsDiff2Radio" onclick="selectDiff2Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
         
         // Date and time
-        echo '<td class="'.$cls.'">'.$r['date_string'].'</td class="'.$cls.'">'."\n";
+        echo '<td class="' . $cls . '">' . $r['date_string'] . '</td class="' . $cls . '">'."\n";
         
         // User
-        if($session->get_permissions('mod_misc') && preg_match('#^([0-9]*){1,3}\.([0-9]*){1,3}\.([0-9]*){1,3}\.([0-9]*){1,3}$#', $r['author'])) $rc = ' style="cursor: pointer;" title="Click cell background for reverse DNS info" onclick="ajaxReverseDNS(this, \''.$r['author'].'\');"';
-        else $rc = '';
-        echo '<td class="'.$cls.'"'.$rc.'><a href="'.makeUrlNS('User', $r['author']).'" ';
-        if(!isPage($paths->nslist['User'] . $r['author'])) echo 'class="wikilink-nonexistent"';
-        echo '>'.$r['author'].'</a></td class="'.$cls.'">'."\n";
+        if ( $session->get_permissions('mod_misc') && is_valid_ip($r['author']) )
+        {
+          $rc = ' style="cursor: pointer;" title="Click cell background for reverse DNS info" onclick="ajaxReverseDNS(this, \'' . $r['author'] . '\');"';
+        }
+        else
+        {
+          $rc = '';
+        }
+        echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', $r['author']).'" ';
+        if ( !isPage($paths->nslist['User'] . $r['author']) )
+        {
+          echo 'class="wikilink-nonexistent"';
+        }
+        echo '>' . $r['author'] . '</a></td class="' . $cls . '">'."\n";
         
         // Edit summary
-        echo '<td class="'.$cls.'">'.$r['edit_summary'].'</td>'."\n";
+        echo '<td class="' . $cls . '">' . $r['edit_summary'] . '</td>'."\n";
         
         // Minor edit
-        echo '<td class="'.$cls.'" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>'."\n";
         
         // Actions!
-        echo '<td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'oldid='.$r['time_id']).'" onclick="ajaxHistView(\''.$r['time_id'].'\'); return false;">View revision</a></td>'."\n";
-        echo '<td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/'.$r['author']).'">View user contribs</a></td>'."\n";
-        echo '<td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id='.$r['time_id']).'" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;">Revert to this revision</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'oldid=' . $r['time_id']) . '" onclick="ajaxHistView(\'' . $r['time_id'] . '\'); return false;">View revision</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">View user contribs</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['time_id']) . '" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">Revert to this revision</a></td>'."\n";
         
         echo '</tr>'."\n"."\n";
         
@@ -588,8 +628,8 @@
     }
     $db->free_result();
     echo '<h3>Other changes:</h3>';
-    $q = 'SELECT time_id,action,date_string,page_id,namespace,author,edit_summary,minor_edit FROM '.table_prefix.'logs WHERE log_type=\'page\' AND action!=\'edit\' AND page_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\''.$paths->namespace.'\' ORDER BY time_id DESC;';
-    if(!$db->sql_query($q)) $db->_die('The history data for the page "'.$paths->cpage['name'].'" could not be selected.');
+    $q = 'SELECT time_id,action,date_string,page_id,namespace,author,edit_summary,minor_edit FROM ' . table_prefix.'logs WHERE log_type=\'page\' AND action!=\'edit\' AND page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $paths->namespace . '\' ORDER BY time_id DESC;';
+    if(!$db->sql_query($q)) $db->_die('The history data for the page "' . $paths->cpage['name'] . '" could not be selected.');
     if($db->numrows() < 1) echo 'No history entries in this category.';
     else {
       
@@ -603,34 +643,34 @@
         echo '<tr>';
         
         // Date and time
-        echo '<td class="'.$cls.'">'.$r['date_string'].'</td class="'.$cls.'">';
+        echo '<td class="' . $cls . '">' . $r['date_string'] . '</td class="' . $cls . '">';
         
         // User
-        echo '<td class="'.$cls.'"><a href="'.makeUrlNS('User', $r['author']).'" ';
+        echo '<td class="' . $cls . '"><a href="'.makeUrlNS('User', $r['author']).'" ';
         if(!isPage($paths->nslist['User'] . $r['author'])) echo 'class="wikilink-nonexistent"';
-        echo '>'.$r['author'].'</a></td class="'.$cls.'">';
+        echo '>' . $r['author'] . '</a></td class="' . $cls . '">';
         
         
         // Minor edit
-        echo '<td class="'.$cls.'" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>';
+        echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>';
         
         // Action taken
-        echo '<td class="'.$cls.'">';
+        echo '<td class="' . $cls . '">';
         // Some of these are sanitized at insert-time. Others follow the newer Enano policy of stripping HTML at runtime.
-        if    ($r['action']=='prot')     echo 'Protected page</td><td class="'.$cls.'">Reason: '.$r['edit_summary'];
-        elseif($r['action']=='unprot')   echo 'Unprotected page</td><td class="'.$cls.'">Reason: '.$r['edit_summary'];
-        elseif($r['action']=='semiprot') echo 'Semi-protected page</td><td class="'.$cls.'">Reason: '.$r['edit_summary'];
-        elseif($r['action']=='rename')   echo 'Renamed page</td><td class="'.$cls.'">Old title: '.htmlspecialchars($r['edit_summary']);
-        elseif($r['action']=='create')   echo 'Created page</td><td class="'.$cls.'">';
-        elseif($r['action']=='delete')   echo 'Deleted page</td><td class="'.$cls.'">Reason: '.$r['edit_summary'];
-        elseif($r['action']=='reupload') echo 'Uploaded new file version</td><td class="'.$cls.'">Reason: '.htmlspecialchars($r['edit_summary']);
+        if    ($r['action']=='prot')     echo 'Protected page</td><td class="' . $cls . '">Reason: ' . $r['edit_summary'];
+        elseif($r['action']=='unprot')   echo 'Unprotected page</td><td class="' . $cls . '">Reason: ' . $r['edit_summary'];
+        elseif($r['action']=='semiprot') echo 'Semi-protected page</td><td class="' . $cls . '">Reason: ' . $r['edit_summary'];
+        elseif($r['action']=='rename')   echo 'Renamed page</td><td class="' . $cls . '">Old title: '.htmlspecialchars($r['edit_summary']);
+        elseif($r['action']=='create')   echo 'Created page</td><td class="' . $cls . '">';
+        elseif($r['action']=='delete')   echo 'Deleted page</td><td class="' . $cls . '">Reason: ' . $r['edit_summary'];
+        elseif($r['action']=='reupload') echo 'Uploaded new file version</td><td class="' . $cls . '">Reason: '.htmlspecialchars($r['edit_summary']);
         echo '</td>';
         
         // Actions!
-        echo '<td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/'.$r['author']).'">View user contribs</a></td>';
-        echo '<td class="'.$cls.'" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id='.$r['time_id']).'" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;">Revert action</a></td>';
+        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">View user contribs</a></td>';
+        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['time_id']) . '" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">Revert action</a></td>';
         
-        //echo '(<a href="#" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;">rollback</a>) <i>'.$r['date_string'].'</i> '.$r['author'].' (<a href="'.makeUrl($paths->nslist['User'].$r['author']).'">Userpage</a>, <a href="'.makeUrl($paths->nslist['Special'].'Contributions/'.$r['author']).'">Contrib</a>): ';
+        //echo '(<a href="#" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">rollback</a>) <i>' . $r['date_string'] . '</i> ' . $r['author'] . ' (<a href="'.makeUrl($paths->nslist['User'].$r['author']).'">Userpage</a>, <a href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">Contrib</a>): ';
         
         if($r['minor_edit']) echo '<b> - minor edit</b>';
         echo '<br />';
@@ -654,71 +694,157 @@
   function rollback($id)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!$session->get_permissions('history_rollback')) return('You are not authorized to perform rollbacks.');
-    if(!preg_match('#^([0-9]+)$#', (string)$id)) return('The value "id" on the query string must be an integer.');
-    $e = $db->sql_query('SELECT log_type,action,date_string,page_id,namespace,page_text,char_tag,author,edit_summary FROM '.table_prefix.'logs WHERE time_id='.$id.';');
-    if(!$e) $db->_die('The rollback data could not be selected.');
+    if ( !$session->get_permissions('history_rollback') )
+    {
+      return('You are not authorized to perform rollbacks.');
+    }
+    if ( !preg_match('#^([0-9]+)$#', (string)$id) )
+    {
+      return('The value "id" on the query string must be an integer.');
+    }
+    $e = $db->sql_query('SELECT log_type,action,date_string,page_id,namespace,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id=' . $id . ';');
+    if ( !$e )
+    {
+      $db->_die('The rollback data could not be selected.');
+    }
     $rb = $db->fetchrow();
     $db->free_result();
-    switch($rb['log_type']) {
+    
+    if ( $rb['log_type'] == 'page' && $rb['action'] != 'delete' )
+    {
+      $pagekey = $paths->nslist[$rb['namespace']] . $rb['page_id'];
+      if ( !isset($paths->pages[$pagekey]) )
+      {
+        return "Page doesn't exist";
+      }
+      $pagedata =& $paths->pages[$pagekey];
+      $protected = false;
+      // Special case: is the page protected? if so, check for even_when_protected permissions
+      if($pagedata['protected'] == 2)
+      {
+        // The page is semi-protected, determine permissions
+        if($session->user_logged_in && $session->reg_time + 60*60*24*4 < time()) 
+        {
+          $protected = false;
+        }
+        else
+        {
+          $protected = true;
+        }
+      }
+      else
+      {
+        $protected = ( $pagedata['protected'] == 1 );
+      }
+      
+      $perms = $session->fetch_page_acl($rb['page_id'], $rb['namespace']);
+      
+      if ( $protected && !$perms->get_permissions('even_when_protected') )
+      {
+        return "Because this page is protected, you need moderator rights to roll back changes.";
+      }
+    }
+    else
+    {
+      $perms =& $session;
+    }
+    
+    switch($rb['log_type'])
+    {
       case "page":
-        switch($rb['action']) {
+        switch($rb['action'])
+        {
           case "edit":
+            if ( !$perms->get_permissions('edit_page') )
+              return "You don't have permission to edit pages, so rolling back edits can't be allowed either.";
             $t = $db->escape($rb['page_text']);
-            $e = $db->sql_query('UPDATE '.table_prefix.'page_text SET page_text=\''.$t.'\',char_tag=\''.$rb['char_tag'].'\' WHERE page_id=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\'');
-            if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            else return('The page "'.$paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been rolled back to the state it was in on '.$rb['date_string'].'.');
+            $e = $db->sql_query('UPDATE ' . table_prefix.'page_text SET page_text=\'' . $t . '\',char_tag=\'' . $rb['char_tag'] . '\' WHERE page_id=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
+            if ( !$e )
+            {
+              return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
+            }
+            else
+            {
+              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been rolled back to the state it was in on ' . $rb['date_string'] . '.';
+            }
             break;
           case "rename":
+            if ( !$perms->get_permissions('rename') )
+              return "You don't have permission to rename pages, so rolling back renames can't be allowed either.";
             $t = $db->escape($rb['edit_summary']);
-            $e = $db->sql_query('UPDATE '.table_prefix.'pages SET name=\''.$t.'\' WHERE urlname=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\'');
-            if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            else return('The page "'.$paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been rolled back to the name it had ("'.$rb['edit_summary'].'") before '.$rb['date_string'].'.');
+            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET name=\'' . $t . '\' WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
+            if ( !$e )
+            {
+              return "An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
+            }
+            else
+            {
+              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been rolled back to the name it had ("' . $rb['edit_summary'] . '") before ' . $rb['date_string'] . '.';
+            }
             break;
           case "prot":
-            $e = $db->sql_query('UPDATE '.table_prefix.'pages SET protected=0 WHERE urlname=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\'');
-            if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            else return('The page "'.$paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at '.$rb['date_string'].'.');
+            if ( !$perms->get_permissions('protect') )
+              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
+            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=0 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
+            if ( !$e )
+              return "An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
+            else
+              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at ' . $rb['date_string'] . '.';
             break;
           case "semiprot":
-            $e = $db->sql_query('UPDATE '.table_prefix.'pages SET protected=0 WHERE urlname=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\'');
-            if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            else return('The page "'.$paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at '.$rb['date_string'].'.');
+            if ( !$perms->get_permissions('protect') )
+              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
+            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=0 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
+            if ( !$e )
+              return "An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
+            else
+              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at ' . $rb['date_string'] . '.';
             break;
           case "unprot":
-            $e = $db->sql_query('UPDATE '.table_prefix.'pages SET protected=1 WHERE urlname=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\'');
-            if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            else return('The page "'.$paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been protected according to the log created at '.$rb['date_string'].'.');
+            if ( !$perms->get_permissions('protect') )
+              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
+            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=1 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
+            if ( !$e )
+              return "An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
+            else
+              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been protected according to the log created at ' . $rb['date_string'] . '.';
             break;
           case "delete":
-            if(!$session->get_permissions('history_rollback_extra')) return('Administrative privileges are required for page undeletion.');
-            if(isset($paths->pages[$paths->cpage['urlname']])) return('You cannot raise a dead page that is alive.');
+            if ( !$perms->get_permissions('history_rollback_extra') )
+              return 'Administrative privileges are required for page undeletion.';
+            if ( isset($paths->pages[$paths->cpage['urlname']]) )
+              return 'You cannot raise a dead page that is alive.';
             $name = str_replace('_', ' ', $rb['page_id']);
-            $e = $db->sql_query('INSERT INTO '.table_prefix.'pages(name,urlname,namespace) VALUES( \''.$name.'\', \''.$rb['page_id'].'\',\''.$rb['namespace'].'\' )');if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            $e = $db->sql_query('SELECT page_text,char_tag FROM '.table_prefix.'logs WHERE page_id=\''.$rb['page_id'].'\' AND namespace=\''.$rb['namespace'].'\' AND log_type=\'page\' AND action=\'edit\' ORDER BY time_id DESC;'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
+            $e = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace) VALUES( \'' . $name . '\', \'' . $rb['page_id'] . '\',\'' . $rb['namespace'] . '\' )');if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
+            $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'logs WHERE page_id=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\' AND log_type=\'page\' AND action=\'edit\' ORDER BY time_id DESC;'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
             $r = $db->fetchrow();
-            $e = $db->sql_query('INSERT INTO '.table_prefix.'page_text(page_id,namespace,page_text,char_tag) VALUES(\''.$rb['page_id'].'\',\''.$rb['namespace'].'\',\''.$db->escape($r['page_text']).'\',\''.$r['char_tag'].'\')'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            return('The page "'.$name.'" has been undeleted according to the log created at '.$rb['date_string'].'.');
+            $e = $db->sql_query('INSERT INTO ' . table_prefix.'page_text(page_id,namespace,page_text,char_tag) VALUES(\'' . $rb['page_id'] . '\',\'' . $rb['namespace'] . '\',\'' . $db->escape($r['page_text']) . '\',\'' . $r['char_tag'] . '\')'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".mysql_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
+            return 'The page "' . $name . '" has been undeleted according to the log created at ' . $rb['date_string'] . '.';
             break;
           case "reupload":
-            if(!$session->get_permissions('history_rollbacks_extra')) return('Administrative privileges are required for file rollbacks.');
+            if ( !$session->get_permissions('history_rollbacks_extra') )
+            {
+              return 'Administrative privileges are required for file rollbacks.';
+            }
             $newtime = time();
             $newdate = date('d M Y h:i a');
-            if(!$db->sql_query('UPDATE '.table_prefix.'logs SET time_id='.$newtime.',date_string=\''.$newdate.'\' WHERE time_id='.$id)) return('Error during query: '.mysql_error());
-            if(!$db->sql_query('UPDATE '.table_prefix.'files SET time_id='.$newtime.' WHERE time_id='.$id)) return('Error during query: '.mysql_error());
-            return('The file has been rolled back to the version uploaded on '.date('d M Y h:i a', (int)$id).'.');
+            if(!$db->sql_query('UPDATE ' . table_prefix.'logs SET time_id=' . $newtime . ',date_string=\'' . $newdate . '\' WHERE time_id=' . $id))
+              return 'Error during query: '.mysql_error();
+            if(!$db->sql_query('UPDATE ' . table_prefix.'files SET time_id=' . $newtime . ' WHERE time_id=' . $id))
+              return 'Error during query: '.mysql_error();
+            return 'The file has been rolled back to the version uploaded on '.date('d M Y h:i a', (int)$id).'.';
             break;
           default:
-            return('Rollback of the action "'.$rb['action'].'" is not yet supported.');
+            return('Rollback of the action "' . $rb['action'] . '" is not yet supported.');
             break;
         }
         break;
       case "security":
       case "login":
-        return('A '.$rb['log_type'].'-related log entry cannot be rolled back.');
+        return('A ' . $rb['log_type'] . '-related log entry cannot be rolled back.');
         break;
       default:
-        return('Unknown log entry type: "'.$rb['log_type'].'"');
+        return('Unknown log entry type: "' . $rb['log_type'] . '"');
     }
   }
   
@@ -749,9 +875,9 @@
     $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';
-    $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().')';
+    $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: '.mysql_error().'\n\nQuery:\n'.$q).'\'))');
+    if(!$e) die('alert(unescape(\''.rawurlencode('Error inserting comment data: '.mysql_error().'\n\nQuery:\n' . $q) . '\'))');
     else $_ob .= '<div class="info-box">Your comment has been posted.</div>';
     return PageUtils::comments($page_id, $namespace, false, Array(), $_ob);
   }
@@ -781,15 +907,15 @@
       case "delete":
         if(isset($flags['id']))
         {
-          $q = 'DELETE FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND comment_id='.intval($flags['id']).' LIMIT 1;';
+          $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id='.intval($flags['id']).' LIMIT 1;';
         } else {
           $n = $db->escape($flags['name']);
           $s = $db->escape($flags['subj']);
           $t = $db->escape($flags['text']);
-          $q = 'DELETE FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND name=\''.$n.'\' AND subject=\''.$s.'\' AND comment_data=\''.$t.'\' LIMIT 1;';
+          $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
         }
         $e=$db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n'.$q).'\'));');
+        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n' . $q) . '\'));');
         break;
       case "approve":
         if(isset($flags['id']))
@@ -799,20 +925,20 @@
           $n = $db->escape($flags['name']);
           $s = $db->escape($flags['subj']);
           $t = $db->escape($flags['text']);
-          $where = 'name=\''.$n.'\' AND subject=\''.$s.'\' AND comment_data=\''.$t.'\'';
+          $where = 'name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\'';
         }
-        $q = 'SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND '.$where.' LIMIT 1;';
+        $q = 'SELECT approved FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ' LIMIT 1;';
         $e = $db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error selecting approval status: '.mysql_error().'\n\nQuery:\n'.$q).'\'));');
+        if(!$e) die('alert(unesape(\''.rawurlencode('Error selecting approval status: '.mysql_error().'\n\nQuery:\n' . $q) . '\'));');
         $r = $db->fetchrow();
         $db->free_result();
         $a = ( $r['approved'] ) ? '0' : '1';
-        $q = 'UPDATE '.table_prefix.'comments SET approved='.$a.' WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND '.$where.';';
+        $q = 'UPDATE ' . table_prefix.'comments SET approved=' . $a . ' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ';';
         $e=$db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n'.$q).'\'));');
+        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n' . $q) . '\'));');
         if($a=='1') $v = 'Unapprove';
         else $v = 'Approve';
-        echo 'document.getElementById("mdgApproveLink'.$_GET['id'].'").innerHTML="'.$v.'";';
+        echo 'document.getElementById("mdgApproveLink'.intval($_GET['id']).'").innerHTML="' . $v . '";';
         break;
       }
     }
@@ -824,31 +950,31 @@
     
     $tpl = $template->makeParser('comment.tpl');
     
-    $e = $db->sql_query('SELECT * FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND approved=0;');
+    $e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=0;');
     if(!$e) $db->_die('The comment text data could not be selected.');
     $num_unapp = $db->numrows();
     $db->free_result();
-    $e = $db->sql_query('SELECT * FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND approved=1;');
+    $e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=1;');
     if(!$e) $db->_die('The comment text data could not be selected.');
     $num_app = $db->numrows();
     $db->free_result();
     $lq = $db->sql_query('SELECT c.comment_id,c.subject,c.name,c.comment_data,c.approved,c.time,c.user_id,u.user_level,u.signature
-                  FROM '.table_prefix.'comments AS c
-                  LEFT JOIN '.table_prefix.'users AS u
+                  FROM ' . table_prefix.'comments AS c
+                  LEFT JOIN ' . table_prefix.'users AS u
                     ON c.user_id=u.user_id
-                  WHERE page_id=\''.$page_id.'\'
-                  AND namespace=\''.$namespace.'\' ORDER BY c.time ASC;');
+                  WHERE page_id=\'' . $page_id . '\'
+                  AND namespace=\'' . $namespace . '\' ORDER BY c.time ASC;');
     if(!$lq) _die('The comment text data could not be selected. '.mysql_error());
     $_ob .= '<h3>Article Comments</h3>';
     $n = ( $session->get_permissions('mod_comments')) ? $db->numrows() : $num_app;
-    if($n==1) $s = 'is '.$n.' comment'; else $s = 'are '.$n.' comments';
+    if($n==1) $s = 'is ' . $n . ' comment'; else $s = 'are ' . $n . ' comments';
     if($n < 1)
     {
       $_ob .= '<p>There are currently no comments on this '.strtolower($namespace).'';
       if($namespace != 'Article') $_ob .= ' page';
       $_ob .= '.</p>';
-    } else $_ob .= '<p>There '.$s.' on this article.';
-    if($session->get_permissions('mod_comments') && $num_unapp > 0) $_ob .= ' <span style="color: #D84308">'.$num_unapp.' of those are unapproved.</span>';
+    } else $_ob .= '<p>There ' . $s . ' on this article.';
+    if($session->get_permissions('mod_comments') && $num_unapp > 0) $_ob .= ' <span style="color: #D84308">' . $num_unapp . ' of those are unapproved.</span>';
     elseif(!$session->get_permissions('mod_comments') && $num_unapp > 0) { $u = ($num_unapp == 1) ? "is $num_unapp comment" : "are $num_unapp comments"; $_ob .= ' However, there ' . $u . ' awating approval.'; }
     $_ob .= '</p>';
     $list = 'list = { ';
@@ -859,7 +985,8 @@
       $i++;
       $strings = Array();
       $bool = Array();
-      if($session->get_permissions('mod_comments') || $row['approved']) {
+      if ( $session->get_permissions('mod_comments') || $row['approved'] )
+      {
         $list .= $i . ' : { \'comment\' : unescape(\''.rawurlencode($row['comment_data']).'\'), \'name\' : unescape(\''.rawurlencode($row['name']).'\'), \'subject\' : unescape(\''.rawurlencode($row['subject']).'\'), }, ';
         
         // Comment ID (used in the Javascript apps)
@@ -905,10 +1032,10 @@
         if($session->get_permissions('edit_comments'))
         {
           // Edit link
-          $strings['EDIT_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=editcomment&amp;id='.$row['comment_id']).'" id="editbtn_'.$i.'">edit</a>';
+          $strings['EDIT_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=editcomment&amp;id=' . $row['comment_id']) . '" id="editbtn_' . $i . '">edit</a>';
         
           // Delete link
-          $strings['DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=deletecomment&amp;id='.$row['comment_id']).'">delete</a>';
+          $strings['DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=deletecomment&amp;id=' . $row['comment_id']) . '">delete</a>';
         }
         else
         {
@@ -920,19 +1047,19 @@
         }
         
         // Send PM link
-        $strings['SEND_PM_LINK'] = ( $session->user_logged_in && $row['user_id'] > 0 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/To/'.$row['name']).'">Send private message</a><br />' : '';
+        $strings['SEND_PM_LINK'] = ( $session->user_logged_in && $row['user_id'] > 0 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/To/' . $row['name']) . '">Send private message</a><br />' : '';
         
         // Add Buddy link
-        $strings['ADD_BUDDY_LINK'] = ( $session->user_logged_in && $row['user_id'] > 0 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/FriendList/Add/'.$row['name']).'">Add to buddy list</a>' : '';
+        $strings['ADD_BUDDY_LINK'] = ( $session->user_logged_in && $row['user_id'] > 0 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/FriendList/Add/' . $row['name']) . '">Add to buddy list</a>' : '';
         
         // Mod links
         $applink = '';
-        $applink .= '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=approve&amp;id='.$row['comment_id']).'" id="mdgApproveLink'.$i.'">';
+        $applink .= '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=approve&amp;id=' . $row['comment_id']) . '" id="mdgApproveLink' . $i . '">';
         if($row['approved']) $applink .= 'Unapprove';
         else $applink .= 'Approve';
         $applink .= '</a>';
         $strings['MOD_APPROVE_LINK'] = $applink; unset($applink);
-        $strings['MOD_DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=delete&amp;id='.$row['comment_id']).'">Delete</a>';
+        $strings['MOD_DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=delete&amp;id=' . $row['comment_id']) . '">Delete</a>';
         
         // Signature
         $strings['SIGNATURE'] = '';
@@ -959,19 +1086,19 @@
         $_ob .= '<h3>Got something to say?</h3>If you have comments or suggestions on this article, you can shout it out here.';
         if(getConfig('approve_comments')=='1') $_ob .= '  Before your comment will be visible to the public, a moderator will have to approve it.';
         if(getConfig('comments_need_login') == '1' && !$session->user_logged_in) $_ob .= ' Because you are not logged in, you will need to enter a visual confirmation before your comment will be posted.';
-        $sn = $session->user_logged_in ? $session->username . '<input name="name" id="mdgScreenName" type="hidden" value="'.$session->username.'" />' : '<input name="name" id="mdgScreenName" type="text" size="35" />';
+        $sn = $session->user_logged_in ? $session->username . '<input name="name" id="mdgScreenName" type="hidden" value="' . $session->username . '" />' : '<input name="name" id="mdgScreenName" type="text" size="35" />';
         $_ob .= '  <a href="#" id="mdgCommentFormLink" style="display: none;" onclick="document.getElementById(\'mdgCommentForm\').style.display=\'block\';this.style.display=\'none\';return false;">Leave a comment...</a>
         <div id="mdgCommentForm">
         <h3>Comment form</h3>
         <form action="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=postcomment').'" method="post" style="margin-left: 1em">
         <table border="0">
-        <tr><td>Your name or screen name:</td><td>'.$sn.'</td></tr>
+        <tr><td>Your name or screen name:</td><td>' . $sn . '</td></tr>
         <tr><td>Comment subject:</td><td><input name="subj" id="mdgSubject" type="text" size="35" /></td></tr>';
         if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
         {
           $session->kill_captcha();
           $captcha = $session->make_captcha();
-          $_ob .= '<tr><td>Visual confirmation:<br /><small>Please enter the code you see on the right.</small></td><td><img src="'.makeUrlNS('Special', 'Captcha/'.$captcha).'" alt="Visual confirmation" style="cursor: pointer;" onclick="this.src = \''.makeUrlNS("Special", "Captcha/".$captcha).'/\'+Math.floor(Math.random() * 100000);" /><input name="captcha_id" id="mdgCaptchaID" type="hidden" value="'.$captcha.'" /><br />Code: <input name="captcha_input" id="mdgCaptchaInput" type="text" size="10" /><br /><small><script type="text/javascript">document.write("If you can\'t read the code, click on the image to generate a new one.");</script><noscript>If you can\'t read the code, please refresh this page to generate a new one.</noscript></small></td></tr>';
+          $_ob .= '<tr><td>Visual confirmation:<br /><small>Please enter the code you see on the right.</small></td><td><img src="'.makeUrlNS('Special', 'Captcha/' . $captcha) . '" alt="Visual confirmation" style="cursor: pointer;" onclick="this.src = \''.makeUrlNS("Special", "Captcha/".$captcha).'/\'+Math.floor(Math.random() * 100000);" /><input name="captcha_id" id="mdgCaptchaID" type="hidden" value="' . $captcha . '" /><br />Code: <input name="captcha_input" id="mdgCaptchaInput" type="text" size="10" /><br /><small><script type="text/javascript">document.write("If you can\'t read the code, click on the image to generate a new one.");</script><noscript>If you can\'t read the code, please refresh this page to generate a new one.</noscript></small></td></tr>';
         }
         $_ob .= '
         <tr><td valign="top">Comment text:<br />(most HTML will be stripped)</td><td><textarea name="text" id="mdgCommentArea" rows="10" cols="40"></textarea></td></tr>
@@ -981,7 +1108,7 @@
         </div>';
       }
     } else {
-      $_ob .= '<h3>Got something to say?</h3><p>You need to be logged in to post comments. <a href="'.makeUrlNS('Special', 'Login/'.$pname.'%2523comments').'">Log in</a></p>';
+      $_ob .= '<h3>Got something to say?</h3><p>You need to be logged in to post comments. <a href="'.makeUrlNS('Special', 'Login/' . $pname . '%2523comments').'">Log in</a></p>';
     }
     $list .= '};';
     echo 'document.getElementById(\'ajaxEditContainer\').innerHTML = unescape(\''. rawurlencode($_ob) .'\');
@@ -1052,7 +1179,7 @@
     if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
     {
       if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM '.table_prefix.'comments c, '.table_prefix.'users u WHERE comment_data=\''.$old_text.'\' AND subject=\''.$old_subject.'\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND u.user_id=c.user_id;';
+      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
       $s = $db->sql_query($q);
       if(!$s) _die('SQL error during safety check: '.mysql_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
       $r = $db->fetchrow($s);
@@ -1061,13 +1188,13 @@
     }
     $s = RenderMan::preprocess_text($subject);
     $t = RenderMan::preprocess_text($text);
-    $sql  = 'UPDATE '.table_prefix.'comments SET subject=\''.$s.'\',comment_data=\''.$t.'\' WHERE comment_data=\''.$old_text.'\' AND subject=\''.$old_subject.'\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\'';
+    $sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
     $result = $db->sql_query($sql);
     if($result)
     {
       return 'result="GOOD";
-                      list['.$id.'][\'subject\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $s))))).'\');
-                      list['.$id.'][\'comment\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t))))).'\'); id = '.$id.';
+                      list[' . $id . '][\'subject\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $s))))).'\');
+                      list[' . $id . '][\'comment\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t))))).'\'); id = ' . $id . ';
       s = unescape(\''.rawurlencode($s).'\');
       t = unescape(\''.str_replace('%5Cn', '<br \\/>', rawurlencode(RenderMan::render(str_replace('{{EnAnO:Newline}}', "\n", stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t)))))).'\');';
     }
@@ -1075,7 +1202,7 @@
     {
       return 'result="BAD"; error=unescape("'.rawurlencode('Enano encountered a problem whilst saving the comment.
       Performed SQL:
-      '.$sql.'
+      ' . $sql . '
     
       Error returned by MySQL: '.mysql_error()).'");';
     }
@@ -1101,7 +1228,7 @@
     if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
     {
       if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM '.table_prefix.'comments c, '.table_prefix.'users u WHERE comment_id='.$id.' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND u.user_id=c.user_id;';
+      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
       $s = $db->sql_query($q);
       if(!$s) _die('SQL error during safety check: '.mysql_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
       $r = $db->fetchrow($s);
@@ -1110,13 +1237,13 @@
     }
     $s = RenderMan::preprocess_text($subject);
     $t = RenderMan::preprocess_text($text);
-    $sql  = 'UPDATE '.table_prefix.'comments SET subject=\''.$s.'\',comment_data=\''.$t.'\' WHERE comment_id='.$id.' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\'';
+    $sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
     $result = $db->sql_query($sql);
     if($result)
     return 'good';
     else return 'Enano encountered a problem whilst saving the comment.
     Performed SQL:
-    '.$sql.'
+    ' . $sql . '
     
     Error returned by MySQL: '.mysql_error();
   }
@@ -1148,16 +1275,16 @@
     if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
     {
       if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM '.table_prefix.'comments c, '.table_prefix.'users u WHERE comment_data=\''.$t.'\' AND subject=\''.$s.'\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND u.user_id=c.user_id;';
+      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $t . '\' AND subject=\'' . $s . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
       $s = $db->sql_query($q);
       if(!$s) _die('SQL error during safety check: '.mysql_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
       $r = $db->fetchrow($s);
       if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
       $db->free_result();
     }
-    $q = 'DELETE FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND name=\''.$n.'\' AND subject=\''.$s.'\' AND comment_data=\''.$t.'\' LIMIT 1;';
+    $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
     $e=$db->sql_query($q);
-    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n'.$q).'\'));');
+    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n' . $q) . '\'));');
     return('good');
   }
   
@@ -1182,16 +1309,16 @@
     if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
     {
       if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM '.table_prefix.'comments c, '.table_prefix.'users u WHERE comment_id='.$id.' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND u.user_id=c.user_id;';
+      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
       $s = $db->sql_query($q);
       if(!$s) _die('SQL error during safety check: '.mysql_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
       $r = $db->fetchrow($s);
       if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
       $db->free_result();
     }
-    $q = 'DELETE FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\' AND comment_id='.$id.' LIMIT 1;';
+    $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id=' . $id . ' LIMIT 1;';
     $e=$db->sql_query($q);
-    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n'.$q).'\'));');
+    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.mysql_error().'\n\nQuery:\n' . $q) . '\'));');
     return('good');
   }
   
@@ -1218,19 +1345,19 @@
     }
     if( ( $session->get_permissions('rename') && ( ( $prot && $session->get_permissions('even_when_protected') ) || !$prot ) ) && ( $paths->namespace != 'Special' && $paths->namespace != 'Admin' ))
     {
-      $e = $db->sql_query('INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'rename\', \''.$db->escape($paths->cpage['urlname_nons']).'\', \''.$paths->namespace.'\', \''.$db->escape($session->username).'\', \''.$db->escape($paths->cpage['name']).'\')');
+      $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'rename\', \'' . $db->escape($paths->cpage['urlname_nons']) . '\', \'' . $paths->namespace . '\', \'' . $db->escape($session->username) . '\', \'' . $db->escape($paths->cpage['name']) . '\')');
       if ( !$e )
       {
         $db->_die('The page title could not be updated.');
       }
-      $e = $db->sql_query('UPDATE '.table_prefix.'pages SET name=\''.$db->escape($name).'\' WHERE urlname=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\';');
+      $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET name=\'' . $db->escape($name) . '\' WHERE urlname=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\';');
       if ( !$e )
       {
         $db->_die('The page title could not be updated.');
       }
       else
       {
-        return('The page "'.$paths->pages[$pname]['name'].'" has been renamed to "'.$name.'". You are encouraged to leave a comment explaining your action.' . "\n\n" . 'You will see the change take effect the next time you reload this page.');
+        return('The page "' . $paths->pages[$pname]['name'] . '" has been renamed to "' . $name . '". You are encouraged to leave a comment explaining your action.' . "\n\n" . 'You will see the change take effect the next time you reload this page.');
       }
     }
     else
@@ -1250,18 +1377,18 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     if(!$session->get_permissions('clear_logs')) die('Administrative privileges are required to flush logs, you loser.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'logs WHERE page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\';');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'logs WHERE page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\';');
     if(!$e) $db->_die('The log entries could not be deleted.');
     
     // If the page exists, make a backup of it in case it gets spammed/vandalized
     // If not, the admin's probably deleting a trash page
     if ( isset($paths->pages[ $paths->nslist[$namespace] . $page_id ]) )
     {
-      $e = $db->sql_query('SELECT page_text,char_tag FROM '.table_prefix.'page_text WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
+      $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
       if(!$e) $db->_die('The current page text could not be selected; as a result, creating the backup of the page failed. Please make a backup copy of the page by clicking Edit this page and then clicking Save Changes.');
       $row = $db->fetchrow();
       $db->free_result();
-      $q='INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \''.$page_id.'\', \''.$namespace.'\', \''.$db->escape($row['page_text']).'\', \''.$row['char_tag'].'\', \''.$session->username.'\', \''."Automatic backup created when logs were purged".'\', '.'false'.');';
+      $q='INSERT INTO ' . table_prefix.'logs(log_type,action,time_id,date_string,page_id,namespace,page_text,char_tag,author,edit_summary,minor_edit) VALUES(\'page\', \'edit\', '.time().', \''.date('d M Y h:i a').'\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $db->escape($row['page_text']) . '\', \'' . $row['char_tag'] . '\', \'' . $session->username . '\', \''."Automatic backup created when logs were purged".'\', '.'false'.');';
       if(!$db->sql_query($q)) $db->_die('The history (log) entry could not be inserted into the logs table.');
     }
     return('The logs for this page have been cleared. A backup of this page has been added to the logs table so that this page can be restored in case of vandalism or spam later.');
@@ -1285,17 +1412,17 @@
       return 'Invalid reason for deletion passed';
     }
     if(!$perms->get_permissions('delete_page')) return('Administrative privileges are required to delete pages, you loser.');
-    $e = $db->sql_query('INSERT INTO '.table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'delete\', \''.$page_id.'\', \''.$namespace.'\', \''.$session->username.'\', \'' . $db->escape(htmlspecialchars($reason)) . '\')');
+    $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,edit_summary) VALUES('.time().', \''.date('d M Y h:i a').'\', \'page\', \'delete\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $session->username . '\', \'' . $db->escape(htmlspecialchars($reason)) . '\')');
     if(!$e) $db->_die('The page log entry could not be inserted.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'categories WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\'');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
     if(!$e) $db->_die('The page categorization entries could not be deleted.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'comments WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\'');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
     if(!$e) $db->_die('The page comments could not be deleted.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'page_text WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\'');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
     if(!$e) $db->_die('The page text entry could not be deleted.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'pages WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\'');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'pages WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
     if(!$e) $db->_die('The page entry could not be deleted.');
-    $e = $db->sql_query('DELETE FROM '.table_prefix.'files WHERE page_id=\''.$page_id.'\'');
+    $e = $db->sql_query('DELETE FROM ' . table_prefix.'files WHERE page_id=\'' . $page_id . '\'');
     if(!$e) $db->_die('The file entry could not be deleted.');
     return('This page has been deleted. Note that there is still a log of edits and actions in the database, and anyone with admin rights can raise this page from the dead unless the log is cleared. If the deleted file is an image, there may still be cached thumbnails of it in the cache/ directory, which is inaccessible to users.');
   }
@@ -1360,7 +1487,7 @@
     
     $cv++;
     
-    $q = 'UPDATE '.table_prefix.'pages SET delvotes='.$cv.',delvote_ips=\''.$ips.'\' WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\'';
+    $q = 'UPDATE ' . table_prefix.'pages SET delvotes=' . $cv . ',delvote_ips=\'' . $ips . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
     $w = $db->sql_query($q);
     
     return 'Your vote to have this page deleted has been cast.'."\nYou are encouraged to leave a comment explaining the reason for your vote.";
@@ -1377,7 +1504,7 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     if(!$session->get_permissions('vote_reset')) die('You need moderator rights in order to do this, stinkin\' hacker.');
-    $q = 'UPDATE '.table_prefix.'pages SET delvotes=0,delvote_ips=\'' . $db->escape(serialize(array('ip'=>array(),'u'=>array()))) . '\' WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\'';
+    $q = 'UPDATE ' . table_prefix.'pages SET delvotes=0,delvote_ips=\'' . $db->escape(serialize(array('ip'=>array(),'u'=>array()))) . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
     $e = $db->sql_query($q);
     if(!$e) $db->_die('The number of delete votes was not reset.');
     else return('The number of votes for having this page deleted has been reset to zero.');
@@ -1393,14 +1520,17 @@
   {
     $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
     
-    $dir = './themes/'.$_GET['id'].'/css/';
+    if ( !preg_match('/^([a-z0-9_-]+)$/', $_GET['id']) )
+      return $json->encode(false);
+    
+    $dir = './themes/' . $_GET['id'] . '/css/';
     $list = Array();
     // Open a known directory, and proceed to read its contents
     if (is_dir($dir)) {
       if ($dh = opendir($dir)) {
         while (($file = readdir($dh)) !== false) {
-          if(preg_match('#^(.*?)\.css$#is', $file) && $file != '_printable.css') { // _printable.css should be included with every theme
-                                                                                    // it should be a copy of the original style, but
+          if ( preg_match('#^(.*?)\.css$#is', $file) && $file != '_printable.css' ) // _printable.css should be included with every theme
+          {                                                                         // it should be a copy of the original style, but
                                                                                     // mostly black and white
                                                                                     // Note to self: document this
             $list[] = substr($file, 0, strlen($file)-4);
@@ -1440,7 +1570,7 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     ob_start();
     $_ob = '';
-    $e = $db->sql_query('SELECT category_id FROM '.table_prefix.'categories WHERE page_id=\''.$paths->cpage['urlname_nons'].'\' AND namespace=\''.$paths->namespace.'\'');
+    $e = $db->sql_query('SELECT category_id FROM ' . table_prefix.'categories WHERE page_id=\'' . $paths->cpage['urlname_nons'] . '\' AND namespace=\'' . $paths->namespace . '\'');
     if(!$e) jsdie('Error selecting category information for current page: '.mysql_error());
     $cat_current = Array();
     while($r = $db->fetchrow())
@@ -1492,10 +1622,10 @@
          $is_prot = true;
       $prot = ( $is_prot ) ? ' disabled="disabled" ' : '';
       $prottext = ( $is_prot ) ? ' <img alt="(protected)" width="16" height="16" src="'.scriptPath.'/images/lock16.png" />' : '';
-      echo 'catlist['.$i.'] = \''.$cat_info[$i]['urlname_nons'].'\';';
-      $_ob .= '<span class="catCheck"><input '.$prot.' name="'.$cat_info[$i]['urlname_nons'].'" id="mdgCat_'.$cat_info[$i]['urlname_nons'].'" type="checkbox"';
+      echo 'catlist[' . $i . '] = \'' . $cat_info[$i]['urlname_nons'] . '\';';
+      $_ob .= '<span class="catCheck"><input ' . $prot . ' name="' . $cat_info[$i]['urlname_nons'] . '" id="mdgCat_' . $cat_info[$i]['urlname_nons'] . '" type="checkbox"';
       if(isset($cat_info[$i]['member'])) $_ob .= ' checked="checked"';
-      $_ob .= '/>  <label for="mdgCat_'.$cat_info[$i]['urlname_nons'].'">'.$cat_info[$i]['name'].$prottext.'</label></span><br />';
+      $_ob .= '/>  <label for="mdgCat_' . $cat_info[$i]['urlname_nons'] . '">' . $cat_info[$i]['name'].$prottext.'</label></span><br />';
     }
     
     $disabled = ( sizeof($cat_info) < 1 ) ? 'disabled="disabled"' : '';
@@ -1550,9 +1680,9 @@
       if(!$auth)
       {
         // Find out if the page is currently in the category
-        $q = $db->sql_query('SELECT * FROM '.table_prefix.'categories WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
+        $q = $db->sql_query('SELECT * FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
         if(!$q)
-          return 'MySQL error: '.$db->get_error();
+          return 'MySQL error: ' . $db->get_error();
         if($db->numrows() > 0)
         {
           $auth = true;
@@ -1560,13 +1690,13 @@
         }
         $db->free_result();
       }
-      if(isset($which_cats[$cat_all[$i]['urlname_nons']]) && $which_cats[$cat_all[$i]['urlname_nons']] == true /* for clarity ;-) */ && $auth ) $rowlist[] = '(\''.$page_id.'\', \''.$namespace.'\', \''.$cat_all[$i]['urlname_nons'].'\')';
+      if(isset($which_cats[$cat_all[$i]['urlname_nons']]) && $which_cats[$cat_all[$i]['urlname_nons']] == true /* for clarity ;-) */ && $auth ) $rowlist[] = '(\'' . $page_id . '\', \'' . $namespace . '\', \'' . $cat_all[$i]['urlname_nons'] . '\')';
     }
     if(sizeof($rowlist) > 0)
     {
       $val = implode(',', $rowlist);
-      $q = 'INSERT INTO '.table_prefix.'categories(page_id,namespace,category_id) VALUES' . $val . ';';
-      $e = $db->sql_query('DELETE FROM '.table_prefix.'categories WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
+      $q = 'INSERT INTO ' . table_prefix.'categories(page_id,namespace,category_id) VALUES' . $val . ';';
+      $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
       if(!$e) $db->_die('The old category data could not be deleted.');
       $e = $db->sql_query($q);
       if(!$e) $db->_die('The new category data could not be inserted.');
@@ -1574,7 +1704,7 @@
     }
     else
     {
-      $e = $db->sql_query('DELETE FROM '.table_prefix.'categories WHERE page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
+      $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
       if(!$e) $db->_die('The old category data could not be deleted.');
       return('GOOD');
     }
@@ -1592,9 +1722,15 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     if(!$session->get_permissions('set_wiki_mode')) return('Insufficient access rights');
-    if(!isset($level) || (isset($level) && !preg_match('#^([0-2]){1}$#', (string)$level))) return('Invalid mode string');
-    $q = $db->sql_query('UPDATE '.table_prefix.'pages SET wiki_mode='.$level.' WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
-    if(!$q) return('Error during update query: '.mysql_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
+    if ( !isset($level) || ( isset($level) && !preg_match('#^([0-2]){1}$#', (string)$level) ) )
+    {
+      return('Invalid mode string');
+    }
+    $q = $db->sql_query('UPDATE ' . table_prefix.'pages SET wiki_mode=' . $level . ' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+    if ( !$q )
+    {
+      return('Error during update query: '.mysql_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
+    }
     return('GOOD');
   }
   
@@ -1618,11 +1754,23 @@
       return 'Access is denied';
     if(!isset($pass)) return('Password was not set on URL');
     $p = $pass;
-    if(!preg_match('#([0-9a-f]){40,40}#', $p)) $p = sha1($p);
-    if($p=='da39a3ee5e6b4b0d3255bfef95601890afd80709') $p = '';
-    $e = $db->sql_query('UPDATE '.table_prefix.'pages SET password=\''.$p.'\' WHERE urlname=\''.$page_id.'\' AND namespace=\''.$namespace.'\';');
-    if(!$e) die('PageUtils::setpass(): Error during update query: '.mysql_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
-    if($p=='') return('The password for this page has been disabled.');
+    if ( !preg_match('#([0-9a-f]){40,40}#', $p) )
+    {
+      $p = sha1($p);
+    }
+    if ( $p == 'da39a3ee5e6b4b0d3255bfef95601890afd80709' )
+      // sha1('') = da39a3ee5e6b4b0d3255bfef95601890afd80709
+      $p = '';
+    $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET password=\'' . $p . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+    if ( !$e )
+    {
+      die('PageUtils::setpass(): Error during update query: '.mysql_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
+    }
+    // Is the new password blank?
+    if ( $p == '' )
+    {
+      return('The password for this page has been disabled.');
+    }
     else return('The password for this page has been set.');
   }
   
@@ -1654,7 +1802,7 @@
    
   function scrollBox($text, $height = 250)
   {
-    return '<div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: '.(string)intval($height).'px; overflow: auto; margin: 1em 0 1em 1em;">'.$text.'</div>';
+    return '<div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: '.(string)intval($height).'px; overflow: auto; margin: 1em 0 1em 1em;">' . $text . '</div>';
   }
   
   /**
@@ -1675,8 +1823,8 @@
        !preg_match('#^([0-9]+)$#', (string)$id2  )) return 'SQL injection attempt';
     // OK we made it through security
     // Safest way to make sure we don't end up with the revisions in wrong columns is to make 2 queries
-    if(!$q1 = $db->sql_query('SELECT page_text,char_tag,author,edit_summary FROM '.table_prefix.'logs WHERE time_id='.$id1.' AND log_type=\'page\' AND action=\'edit\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';')) return 'MySQL error: '.mysql_error();
-    if(!$q2 = $db->sql_query('SELECT page_text,char_tag,author,edit_summary FROM '.table_prefix.'logs WHERE time_id='.$id2.' AND log_type=\'page\' AND action=\'edit\' AND page_id=\''.$page_id.'\' AND namespace=\''.$namespace.'\';')) return 'MySQL error: '.mysql_error();
+    if(!$q1 = $db->sql_query('SELECT page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id=' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: '.mysql_error();
+    if(!$q2 = $db->sql_query('SELECT page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id=' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: '.mysql_error();
     $row1 = $db->fetchrow($q1);
     $db->free_result($q1);
     $row2 = $db->fetchrow($q2);
@@ -1718,8 +1866,8 @@
     $parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false;
     $page_id =& $parms['page_id'];
     $namespace =& $parms['namespace'];
-    $page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\''.$db->escape($page_id).'\' AND a.namespace=\''.$db->escape($namespace).'\'';
-    $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\'';
+    $page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\'';
+    $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\'';
     //die(print_r($page_id,true));
     $template->load_theme();
     // $perms_obj = $session->fetch_page_acl($page_id, $namespace);
@@ -1741,7 +1889,7 @@
       {
         case 'listgroups':
           $return['groups'] = Array();
-          $q = $db->sql_query('SELECT group_id,group_name FROM '.table_prefix.'groups ORDER BY group_name ASC;');
+          $q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups ORDER BY group_name ASC;');
           while($row = $db->fetchrow())
           {
             $return['groups'][] = Array(
@@ -1751,7 +1899,7 @@
           }
           $db->free_result();
           $return['page_groups'] = Array();
-          $q = $db->sql_query('SELECT pg_id,pg_name FROM '.table_prefix.'page_groups ORDER BY pg_name ASC;');
+          $q = $db->sql_query('SELECT pg_id,pg_name FROM ' . table_prefix.'page_groups ORDER BY pg_name ASC;');
           if ( !$q )
             return Array(
               'mode' => 'error',
@@ -1775,18 +1923,18 @@
           switch($parms['target_type'])
           {
             case ACL_TYPE_USER:
-              $q = $db->sql_query('SELECT a.rules,u.user_id FROM '.table_prefix.'users AS u
-                  LEFT JOIN '.table_prefix.'acl AS a
+              $q = $db->sql_query('SELECT a.rules,u.user_id FROM ' . table_prefix.'users AS u
+                  LEFT JOIN ' . table_prefix.'acl AS a
                     ON a.target_id=u.user_id
                   WHERE a.target_type='.ACL_TYPE_USER.'
-                    AND u.username=\''.$db->escape($parms['target_id']).'\'
-                    '.$page_where_clause.';');
+                    AND u.username=\'' . $db->escape($parms['target_id']) . '\'
+                    ' . $page_where_clause . ';');
               if(!$q)
                 return(Array('mode'=>'error','error'=>mysql_error()));
               if($db->numrows() < 1)
               {
                 $return['type'] = 'new';
-                $q = $db->sql_query('SELECT user_id FROM '.table_prefix.'users WHERE username=\''.$db->escape($parms['target_id']).'\';');
+                $q = $db->sql_query('SELECT user_id FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';');
                 if(!$q)
                   return(Array('mode'=>'error','error'=>mysql_error()));
                 if($db->numrows() < 1)
@@ -1822,18 +1970,18 @@
               }
               break;
             case ACL_TYPE_GROUP:
-              $q = $db->sql_query('SELECT a.rules,g.group_name,g.group_id FROM '.table_prefix.'groups AS g
-                  LEFT JOIN '.table_prefix.'acl AS a
+              $q = $db->sql_query('SELECT a.rules,g.group_name,g.group_id FROM ' . table_prefix.'groups AS g
+                  LEFT JOIN ' . table_prefix.'acl AS a
                     ON a.target_id=g.group_id
                   WHERE a.target_type='.ACL_TYPE_GROUP.'
                     AND g.group_id=\''.intval($parms['target_id']).'\'
-                    '.$page_where_clause.';');
+                    ' . $page_where_clause . ';');
               if(!$q)
                 return(Array('mode'=>'error','error'=>mysql_error()));
               if($db->numrows() < 1)
               {
                 $return['type'] = 'new';
-                $q = $db->sql_query('SELECT group_id,group_name FROM '.table_prefix.'groups WHERE group_id=\''.intval($parms['target_id']).'\';');
+                $q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups WHERE group_id=\''.intval($parms['target_id']).'\';');
                 if(!$q)
                   return(Array('mode'=>'error','error'=>mysql_error()));
                 if($db->numrows() < 1)
@@ -1881,8 +2029,8 @@
           {
             return Array('mode'=>'error','error'=>'Editing access control lists is disabled in the administration demo.');
           }
-          $q = $db->sql_query('DELETE FROM '.table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
-            '.$page_where_clause_lite.';');
+          $q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
+            ' . $page_where_clause_lite . ';');
           if(!$q)
             return Array('mode'=>'error','error'=>mysql_error());
           $rules = $session->perm_to_string($parms['perms']);
@@ -1893,10 +2041,10 @@
                 'error' => 'Supplied rule list has a length of zero'
               );
           }
-          $q = ($page_id && $namespace) ? 'INSERT INTO '.table_prefix.'acl ( target_type, target_id, page_id, namespace, rules )
-                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \''.$db->escape($page_id).'\', \''.$db->escape($namespace).'\', \''.$db->escape($rules).'\' )' :
-                                          'INSERT INTO '.table_prefix.'acl ( target_type, target_id, rules )
-                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \''.$db->escape($rules).'\' )';
+          $q = ($page_id && $namespace) ? 'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, page_id, namespace, rules )
+                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($page_id) . '\', \'' . $db->escape($namespace) . '\', \'' . $db->escape($rules) . '\' )' :
+                                          'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, rules )
+                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($rules) . '\' )';
           if(!$db->sql_query($q)) return Array('mode'=>'error','error'=>mysql_error());
           return Array(
               'mode' => 'success',
@@ -1912,8 +2060,8 @@
           {
             return Array('mode'=>'error','error'=>'Editing access control lists is disabled in the administration demo.');
           }
-          $q = $db->sql_query('DELETE FROM '.table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
-            '.$page_where_clause_lite.';');
+          $q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
+            ' . $page_where_clause_lite . ';');
           if(!$q)
             return Array('mode'=>'error','error'=>mysql_error());
           return Array(
@@ -2057,7 +2205,7 @@
         }
         $type  = ( $response['target_type'] == ACL_TYPE_GROUP ) ? 'group' : 'user';
         $scope = ( $response['page_id'] ) ? ( $response['namespace'] == '__PageGroup' ? 'this group of pages' : 'this page' ) : 'this entire site';
-        echo 'This panel allows you to edit what the '.$type.' "'.$response['target_name'].'" can do on <b>'.$scope.'</b>. Unless you set a permission to "Deny", these permissions may be overridden by other rules.';
+        echo 'This panel allows you to edit what the ' . $type . ' "' . $response['target_name'] . '" can do on <b>' . $scope . '</b>. Unless you set a permission to "Deny", these permissions may be overridden by other rules.';
         echo $formstart;
         $parser = $template->makeParserText( $response['template']['acl_field_begin'] );
         echo $parser->run();
--- a/includes/paths.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/paths.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /**
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * paths.php - The part of Enano that actually manages content. Everything related to page handling and namespaces is in here.
  *
@@ -855,14 +855,35 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
-    $page_id = $db->escape(sanitize_page_id($page_id));
+    static $cache = array();
+    
+    if ( count($cache) == 0 )
+    {
+      foreach ( $this->nslist as $key => $_ )
+      {
+        $cache[$key] = array();
+      }
+    }
+    
     if ( !isset($this->nslist[$namespace]) )
-      die('$paths->get_page_groups(): HACKING ATTEMPT');
+      die('$paths->get_page_groups(): HACKING ATTEMPT: namespace "'. htmlspecialchars($namespace) .'" doesn\'t exist');
+    
+    $page_id_unescaped = $paths->nslist[$namespace] .
+                         dirtify_page_id($page_id);
+    $page_id_str       = $paths->nslist[$namespace] .
+                         sanitize_page_id($page_id);
+    
+    $page_id = $db->escape(sanitize_page_id($page_id));
+    
+    if ( isset($cache[$namespace][$page_id]) )
+    {
+      return $cache[$namespace][$page_id];
+    }
     
     $group_list = array();
     
     // What linked categories have this page?
-    $q = $db->sql_query('SELECT g.pg_id FROM '.table_prefix.'page_groups AS g
+    $q = $db->sql_unbuffered_query('SELECT g.pg_id, g.pg_type, g.pg_target FROM '.table_prefix.'page_groups AS g
   LEFT JOIN '.table_prefix.'categories AS c
     ON ( ( c.category_id = g.pg_target AND g.pg_type = ' . PAGE_GRP_CATLINK . ' ) OR c.category_id IS NULL )
   LEFT JOIN '.table_prefix.'page_group_members AS m
@@ -872,47 +893,32 @@
   WHERE
     ( c.page_id=\'' . $page_id . '\' AND c.namespace=\'' . $namespace . '\' ) OR
     ( t.page_id=\'' . $page_id . '\' AND t.namespace=\'' . $namespace . '\' ) OR
-    ( m.page_id=\'' . $page_id . '\' AND m.namespace=\'' . $namespace . '\' );');
+    ( m.page_id=\'' . $page_id . '\' AND m.namespace=\'' . $namespace . '\' ) OR
+    ( g.pg_type = ' . PAGE_GRP_REGEX . ' );');
     if ( !$q )
       $db->_die();
     
     while ( $row = $db->fetchrow() )
     {
-      $group_list[] = $row['pg_id'];
+      if ( $row['pg_type'] == PAGE_GRP_REGEX )
+      {
+        //echo "&lt;debug&gt; matching page " . htmlspecialchars($page_id_unescaped) . " against regex <tt>" . htmlspecialchars($row['pg_target']) . "</tt>.";
+        if ( @preg_match($row['pg_target'], $page_id_unescaped) || @preg_match($row['pg_target'], $page_id_str) )
+        {
+          //echo "..matched";
+          $group_list[] = $row['pg_id'];
+        }
+        //echo "<br />";
+      }
+      else
+      {
+        $group_list[] = $row['pg_id'];
+      }
     }
     
     $db->free_result();
     
-    /*
-    // Static-page groups
-    $q = $db->sql_query('SELECT g.pg_id FROM '.table_prefix.'page_groups AS g
-                           LEFT JOIN '.table_prefix.'page_group_members AS m
-                             ON ( g.pg_id = m.pg_id )
-                           WHERE m.page_id=\'' . $page_id . '\' AND m.namespace=\'' . $namespace . '\'
-                           GROUP BY g.pg_id;');
-    
-    if ( !$q )
-      $db->_die();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      $group_list[] = $row['pg_id'];
-    }
-    
-    // Tag groups
-    
-    $q = $db->sql_query('SELECT g.pg_id FROM '.table_prefix.'page_groups AS g
-                           LEFT JOIN '.table_prefix.'tags AS t
-                             ON ( t.tag_name = g.pg_target AND pg_type = ' . PAGE_GRP_TAGGED . ' )
-                           WHERE t.page_id = \'' . $page_id . '\' AND t.namespace = \'' . $namespace . '\';');
-    if ( !$q )
-      $db->_die();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      $group_list[] = $row['pg_id'];
-    }
-    */
+    $cache[$namespace][$page_id] = $group_list;
     
     return $group_list;
     
--- a/includes/plugins.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/plugins.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/render.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/render.php	Sat Oct 20 21:59:27 2007 -0400
@@ -248,6 +248,12 @@
       $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text);
     }
     
+    $code = $plugins->setHook('render_wikiformat_pre');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
     if ( !$plaintext )
     {
       // Process images
@@ -264,7 +270,8 @@
       }
     }
     
-    $template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
+    //$template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
+    $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
     $i = 0;
     while ( preg_match($template_regex, $text) )
     {
@@ -290,10 +297,26 @@
       $result = $wiki->transform($text, 'Xhtml');
     }
     
-    // if ( !$plaintext )
-    // {
-    //   $result = RenderMan::process_imgtags_stage2($result, $taglist);
-    // }
+    // 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><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);
+    
+    $code = $plugins->setHook('render_wikiformat_post');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
     
     // Reinsert <nowiki> sections
     for($i=0;$i<$nw;$i++)
@@ -311,7 +334,8 @@
     
   }
   
-  function wikiFormat($message, $filter_links = true, $do_params = false, $plaintext = false) {
+  function wikiFormat($message, $filter_links = true, $do_params = false, $plaintext = false)
+  {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     return RenderMan::next_gen_wiki_format($message, $plaintext, $filter_links, $do_params);
@@ -384,6 +408,8 @@
     $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);
@@ -460,8 +486,8 @@
       list($page_id, $namespace) = RenderMan::strToPageID($matches[1][$i]);
       $pid_clean = $paths->nslist[$namespace] . sanitize_page_id($page_id);
       
-      $url = makeUrl($matches[1][$i], false, true);
-      $inner_text = htmlspecialchars(get_page_title($pid_clean));
+      $url = makeUrl($pid_clean, false, true);
+      $inner_text = ( isPage($pid_clean) ) ? htmlspecialchars(get_page_title($pid_clean)) : htmlspecialchars($matches[1][$i]);
       $quot = '"';
       $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"';
       
@@ -473,46 +499,6 @@
     return $text;
   }
   
-  /* *
-   * Replaces template inclusions with the templates
-   * @param string $message The text to format
-   * @return string
-   * /
-   
-  function old_include_templates($message)
-  {
-    $random_id = md5( time() . mt_rand() );
-    preg_match_all('#\{\{(.+?)\}\}#s', $message, $matchlist);
-    foreach($matchlist[1] as $m)
-    {
-      $mn = $m;
-      // Strip out wikilinks and re-add them after the explosion (because of the "|")
-      preg_match_all('#\[\[(.+?)\]\]#i', $m, $linklist);
-      //echo '<pre>'.print_r($linklist, true).'</pre>';
-      for($i=0;$i<sizeof($linklist[1]);$i++)
-      {
-        $mn = str_replace('[['.$linklist[1][$i].']]', '{WIKILINK:'.$random_id.':'.$i.'}', $mn);
-      }
-      
-      $ar = explode('|', $mn);
-      
-      for($j=0;$j<sizeof($ar);$j++)
-      {
-        for($i=0;$i<sizeof($linklist[1]);$i++)
-        {
-          $ar[$j] = str_replace('{WIKILINK:'.$random_id.':'.$i.'}', '[['.$linklist[1][$i].']]', $ar[$j]);
-        }
-      }
-      
-      $tp = $ar[0];
-      unset($ar[0]);
-      $tp = str_replace(' ', '_', $tp);
-      $message = str_replace('{{'.$m.'}}', RenderMan::getTemplate($tp, $ar), $message);
-    }
-    return $message;
-  }
-  */
-  
   /**
    * Parses a partial template tag in wikitext, and return an array with the parameters.
    * @param string The portion of the template tag that contains the parameters.
@@ -528,16 +514,26 @@
   
   function parse_template_vars($input)
   {
-    $input = explode("\n", trim( $input ));
+    if ( !preg_match('/^(\|[ ]*([A-z0-9_]+)([ ]*)=([ ]*)(.+?))*$/is', trim($input)) )
+    {
+      $using_pipes = false;
+      $input = explode("\n", trim( $input ));
+    }
+    else
+    {
+      $using_pipes = true;
+      $input = substr($input, 1);
+      $input = explode("|", trim( $input ));
+    }
     $parms = Array();
     $current_line = '';
     $current_parm = '';
     foreach ( $input as $num => $line )
     {
-      if ( preg_match('/^([ ]*?)([A-z0-9_]+?)([ ]*?)=([ ]*?)(.+?)$/i', $line, $matches) )
+      if ( preg_match('/^[ ]*([A-z0-9_]+)([ ]*)=([ ]*)(.+?)$/is', $line, $matches) )
       {
-        $parm =& $matches[2];
-        $text =& $matches[5];
+        $parm =& $matches[1];
+        $text =& $matches[4];
         if ( $parm == $current_parm )
         {
           $current_line .= $text;
@@ -570,6 +566,7 @@
   
   /**
    * Processes all template tags within a block of wikitext.
+   * Updated in 1.0.2 to also parse template tags in the format of {{Foo |a = b |b = c |c = therefore, a}}
    * @param string The text to process
    * @return string Formatted text
    * @example
@@ -578,16 +575,18 @@
      parm1 = Foo
      parm2 = Bar
      }}';
-   $text = include_templates($text);
+   $text = RenderMan::include_templates($text);
    * </code>
    */
   
   function include_templates($text)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    $template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
+    // $template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
+    $template_regex = "/\{\{(.+)(((\n|[ ]*\|)[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
     if ( $count = preg_match_all($template_regex, $text, $matches) )
     {
+      //die('<pre>' . print_r($matches, true) . '</pre>');
       for ( $i = 0; $i < $count; $i++ )
       {
         $matches[1][$i] = sanitize_page_id($matches[1][$i]);
@@ -595,10 +594,9 @@
         if ( !empty($parmsection) )
         {
           $parms = RenderMan::parse_template_vars($parmsection);
-          foreach ( $parms as $j => $parm )
-          {
-            $parms[$j] = $parm;
-          }
+          if ( !is_array($parms) )
+            // Syntax error
+            $parms = array();
         }
         else
         {
@@ -710,6 +708,7 @@
       ':-/'     => 'face-plain.png',
       ':joke:'  => 'face-plain.png',
       ']:-&gt;' => 'face-devil-grin.png',
+      ']:->'    => 'face-devil-grin.png',
       ':kiss:'  => 'face-kiss.png',
       ':-P'     => 'face-tongue-out.png',
       ':P'      => 'face-tongue-out.png',
--- a/includes/search.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/search.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * search.php - algorithm used to search pages
  *
--- a/includes/sessions.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/sessions.php	Sat Oct 20 21:59:27 2007 -0400
@@ -585,13 +585,12 @@
     $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';');
     if($db->numrows() < 1)
     {
-      return "The username and/or password is incorrect.\n$db->latest_query";
       // 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().', \''.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().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
-        
+      return "The username and/or password is incorrect.";  
     }
     $row = $db->fetchrow();
     
@@ -708,8 +707,15 @@
     
     // Retrieve the real password from the database
     $this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';');
-    if($db->numrows() < 1)
-      return 'The username and/or password is incorrect.';
+    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().', \''.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().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
+      return "The username and/or password is incorrect.";  
+    }
     $row = $db->fetchrow();
     
     // Check to see if we're logging in using a temporary password
@@ -2117,13 +2123,30 @@
       return false;
     }
     
+    // cache of permission objects (to save RAM and SQL queries)
+    static $objcache = array();
+    
+    if ( count($objcache) == 0 )
+    {
+      foreach ( $paths->nslist as $key => $_ )
+      {
+        $objcache[$key] = array();
+      }
+    }
+    
+    if ( isset($objcache[$namespace][$page_id]) )
+    {
+      return $objcache[$namespace][$page_id];
+    }
+    
     //if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
     //{
     //  // Page does not exist
     //  return false;
     //}
     
-    $object = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
+    $objcache[$namespace][$page_id] = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
+    $object =& $objcache[$namespace][$page_id];
     
     return $object;
     
--- a/includes/stats.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/stats.php	Sat Oct 20 21:59:27 2007 -0400
@@ -1,7 +1,8 @@
 <?php
+
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * stats.php - handles statistics for pages (disablable in the admin CP)
  *
--- a/includes/tagcloud.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/tagcloud.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/includes/template.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/template.php	Sat Oct 20 21:59:27 2007 -0400
@@ -625,8 +625,7 @@
     
     $this->tpl_bool['stupid_mode'] = false;
     
-    if($paths->page == $paths->nslist['Special'].'Administration') $this->tpl_bool['in_admin'] = true;
-    else $this->tpl_bool['in_admin'] = false;
+    $this->tpl_bool['in_admin'] = ( ( $paths->cpage['urlname_nons'] == 'Administration' && $paths->namespace == 'Special' ) || $paths->namespace == 'Admin' );
     
     $p = ( isset($_GET['printable']) ) ? '/printable' : '';
     
@@ -784,7 +783,13 @@
     dc_here('template: generating and sending the page header');
     if(!defined('ENANO_HEADERS_SENT'))
       define('ENANO_HEADERS_SENT', '');
-    if(!$this->no_headers) echo ( $simple ) ? $this->process_template('simple-header.tpl') : $this->process_template('header.tpl');
+    if ( !$this->no_headers )
+    {
+      $header = ( $simple ) ?
+        $this->process_template('simple-header.tpl') :
+        $this->process_template('header.tpl');
+      echo $header;
+    }
     if ( !$simple && $session->user_logged_in && $session->unread_pms > 0 )
     {
       echo $this->notify_unread_pms();
@@ -871,78 +876,294 @@
     else return '';
   }
   
-  function process_template($file) {
+  /**
+   * Compiles and executes a template based on the current variables and booleans. Loads
+   * the theme and initializes variables if needed. This mostly just calls child functions.
+   * @param string File to process
+   * @return string
+   */
+  
+  function process_template($file)
+  {
     global $db, $session, $paths, $template, $plugins; // Common objects
     if(!defined('ENANO_TEMPLATE_LOADED'))
     {
       $this->load_theme();
       $this->init_vars();
     }
-    eval($this->compile_template($file));
-    return $tpl_code;
+    
+    $compiled = $this->compile_template($file);
+    return eval($compiled);
   }
   
-  function extract_vars($file) {
+  /**
+   * Loads variables from the specified template file. Returns an associative array containing the variables.
+   * @param string Template file to process (elements.tpl)
+   * @return array
+   */
+  
+  function extract_vars($file)
+  {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!$this->theme)
+    
+    // Sometimes this function gets called before the theme is loaded
+    // This is a bad coding practice so this function will always be picky.
+    if ( !$this->theme )
     {
       die('$template->extract_vars(): theme not yet loaded, so we can\'t open template files yet...this is a bug and should be reported.<br /><br />Backtrace, most recent call first:<pre>'.enano_debug_print_backtrace(true).'</pre>');
     }
-    if(!is_file(ENANO_ROOT . '/themes/'.$this->theme.'/'.$file)) die('Cannot find '.$file.' file for style "'.$this->theme.'", exiting');
-    $text = file_get_contents(ENANO_ROOT . '/themes/'.$this->theme.'/'.$file);
+    
+    // Full pathname of template file
+    $tpl_file_fullpath = ENANO_ROOT . '/themes/' . $this->theme . '/' . $file;
+    
+    // Make sure the template even exists
+    if ( !is_file($tpl_file_fullpath) )
+    {
+      die_semicritical('Cannot find template file',
+                       '<p>The template parser was asked to load the file "' . htmlspecialchars($filename) . '", but that file couldn\'t be found in the directory for
+                           the current theme.</p>
+                        <p>Additional debugging information:<br />
+                           <b>Theme currently in use: </b>' . $this->theme . '<br />
+                           <b>Requested file: </b>' . $file . '
+                           </p>');
+    }
+    // Retrieve file contents
+    $text = file_get_contents($tpl_file_fullpath);
+    if ( !$text )
+    {
+      return false;
+    }
+    
+    // Get variables, regular expressions FTW
     preg_match_all('#<\!-- VAR ([A-z0-9_-]*) -->(.*?)<\!-- ENDVAR \\1 -->#is', $text, $matches);
+    
+    // Initialize return values
     $tplvars = Array();
-    for($i=0;$i<sizeof($matches[1]);$i++)
+    
+    // Loop through each match, setting $tplvars[ $first_subpattern ] to $second_subpattern
+    for ( $i = 0; $i < sizeof($matches[1]); $i++ )
     {
-      $tplvars[$matches[1][$i]] = $matches[2][$i];
+      $tplvars[ $matches[1][$i] ] = $matches[2][$i];
     }
+    
+    // All done!
     return $tplvars;
   }
-  function compile_template($text) {
+  
+  /**
+   * Compiles a block of template code.
+   * @param string The text to process
+   * @return string
+   */
+  
+  function compile_tpl_code($text)
+  {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!is_file(ENANO_ROOT . '/themes/'.$this->theme.'/'.$text)) die('Cannot find '.$text.' file for style, exiting');
-    $n = $text;
-    $tpl_filename = ENANO_ROOT . '/cache/' . $this->theme . '-' . str_replace('/', '-', $n) . '.php';
-    if(!is_file(ENANO_ROOT . '/themes/'.$this->theme.'/'.$text)) die('Cannot find '.$text.' file for style, exiting');
-    if(file_exists($tpl_filename) && getConfig('cache_thumbs')=='1')
+    // A random seed used to salt tags
+    $seed = md5 ( microtime() . mt_rand() );
+    
+    // Strip out PHP sections
+    preg_match_all('/<\?php(.+?)\?>/is', $text, $php_matches);
+    
+    foreach ( $php_matches[0] as $i => $match )
+    {
+      // Substitute the PHP section with a random tag
+      $tag = "{PHP:$i:$seed}";
+      $text = str_replace_once($match, $tag, $text);
+    }
+    
+    // Escape slashes and single quotes in template code
+    $text = str_replace('\\', '\\\\', $text);
+    $text = str_replace('\'', '\\\'', $text);
+    
+    // Initialize the PHP compiled code
+    $text = 'ob_start(); echo \''.$text.'\'; $tpl_code = ob_get_contents(); ob_end_clean(); return $tpl_code;';
+    
+    ##
+    ## Main rules
+    ##
+    
+    //
+    // Conditionals
+    //
+    
+    $keywords = array('BEGIN', 'BEGINNOT', 'IFSET', 'IFPLUGIN');
+    $code = $plugins->setHook('template_compile_logic_keyword');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
+    $keywords = implode('|', $keywords);
+    
+    // Matches
+    //          1     2                               3                 4   56                       7     8
+    $regexp = '/(<!-- ('. $keywords .') ([A-z0-9_-]+) -->)(.*)((<!-- BEGINELSE \\3 -->)(.*))?(<!-- END \\3 -->)/isU';
+    
+    /*
+    The way this works is: match all blocks using the standard form with a different keyword in the block each time,
+    and replace them with appropriate PHP logic. Plugin-extensible now. :-)
+    
+    The while-loop is to bypass what is apparently a PCRE bug. It's hackish but it works. Properly written plugins should only need
+    to compile templates (using this method) once for each time the template file is changed.
+    */
+    while ( preg_match($regexp, $text) )
     {
-      include($tpl_filename);
-      $text = file_get_contents(ENANO_ROOT . '/themes/'.$this->theme.'/'.$text);
-      if(isset($md5) && $md5 == md5($text)) {
+      preg_match_all($regexp, $text, $matches);
+      for ( $i = 0; $i < count($matches[0]); $i++ )
+      {
+        $start_tag =& $matches[1][$i];
+        $type =& $matches[2][$i];
+        $test =& $matches[3][$i];
+        $particle_true  =& $matches[4][$i];
+        $else_tag =& $matches[6][$i];
+        $particle_else =& $matches[7][$i];
+        $end_tag =& $matches[8][$i];
+        
+        switch($type)
+        {
+          case 'BEGIN':
+            $cond = "isset(\$this->tpl_bool['$test']) && \$this->tpl_bool['$test']";
+            break;
+          case 'BEGINNOT':
+            $cond = "!isset(\$this->tpl_bool['$test']) || ( isset(\$this->tpl_bool['$test']) && !\$this->tpl_bool['$test'] )";
+            break;
+          case 'IFPLUGIN':
+            $cond = "getConfig('plugin_$test') == '1'";
+            break;
+          case 'IFSET':
+            $cond = "isset(\$this->tpl_strings['$test'])";
+            break;
+          default:
+            $code = $plugins->setHook('template_compile_logic_cond');
+            foreach ( $code as $cmd )
+            {
+              eval($cmd);
+            }
+            break;
+        }
+        
+        if ( !isset($cond) || ( isset($cond) && !is_string($cond) ) )
+          continue;
+        
+        $tag_complete = <<<TPLCODE
+        ';
+        /* START OF CONDITION: $type ($test) */
+        if ( $cond )
+        {
+          echo '$particle_true';
+        /* ELSE OF CONDITION: $type ($test) */
+        }
+        else
+        {
+          echo '$particle_else';
+        /* END OF CONDITION: $type ($test) */
+        }
+        echo '
+TPLCODE;
+        
+        $text = str_replace_once($matches[0][$i], $tag_complete, $text);
+        
+      }
+    }
+    
+    // For debugging ;-)
+    // die("<pre>&lt;?php\n" . htmlspecialchars($text."\n\n".print_r($matches,true)) . "\n\n?&gt;</pre>");
+    
+    //
+    // Data substitution/variables
+    //
+    
+    // System messages
+    $text = preg_replace('/<!-- SYSMSG ([A-z0-9\._-]+?) -->/is', '\' . $this->tplWikiFormat($pages->sysMsg(\'\\1\')) . \'', $text);
+    
+    // Template variables
+    $text = preg_replace('/\{([A-z0-9_-]+?)\}/is', '\' . $this->tpl_strings[\'\\1\'] . \'', $text);
+    
+    // Reinsert PHP
+    
+    foreach ( $php_matches[1] as $i => $match )
+    {
+      // Substitute the random tag with the "real" PHP code
+      $tag = "{PHP:$i:$seed}";
+      $text = str_replace_once($tag, "'; $match echo '", $text);
+    }
+    
+    // echo('<pre>' . htmlspecialchars($text) . '</pre>');
+    
+    return $text;  
+    
+  }
+  
+  /**
+   * Compiles the contents of a given template file, possibly using a cached copy, and returns the compiled code.
+   * @param string Filename of template (header.tpl)
+   * @return string
+   */
+  
+  function compile_template($filename)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // Full path to template file
+    $tpl_file_fullpath = ENANO_ROOT . '/themes/' . $this->theme . '/' . $filename;
+    
+    // Make sure the file exists
+    if ( !is_file($tpl_file_fullpath) )
+    {
+      die_semicritical('Cannot find template file',
+                       '<p>The template parser was asked to load the file "' . htmlspecialchars($filename) . '", but that file couldn\'t be found in the directory for
+                           the current theme.</p>
+                        <p>Additional debugging information:<br />
+                           <b>Theme currently in use: </b>' . $this->theme . '<br />
+                           <b>Requested file: </b>' . $file . '
+                           </p>');
+    }
+    
+    // Check for cached copy
+    // This will make filenames in the pattern of theme-file.tpl.php
+    $cache_file = ENANO_ROOT . '/cache/' . $this->theme . '-' . str_replace('/', '-', $filename) . '.php';
+    
+    // Only use cached copy if caching is enabled
+    //   (it is enabled by default I think)
+    if ( file_exists($cache_file) && getConfig('cache_thumbs') == '1' )
+    {
+      // Cache files are auto-generated, but otherwise are normal PHP files
+      include($cache_file);
+      
+      // Fetch content of the ORIGINAL
+      $text = file_get_contents($tpl_file_fullpath);
+      
+      // $md5 will be set by the cached file
+      // This makes sure that a cached copy of the template is used only if its MD5
+      // matches the MD5 of the file that the compiled file was compiled from.
+      if ( isset($md5) && $md5 == md5($text) )
+      {
         return str_replace('\\"', '"', $tpl_text);
       }
     }
-    $text = file_get_contents(ENANO_ROOT . '/themes/'.$this->theme.'/'.$n);
     
+    // We won't use the cached copy here
+    $text = file_get_contents($tpl_file_fullpath);
+    
+    // This will be used later when writing the cached file
     $md5 = md5($text);
     
-    $seed = md5 ( microtime() . mt_rand() );
-    preg_match_all("/<\?php(.*?)\?>/is", $text, $m);
-    //die('<pre>'.htmlspecialchars(print_r($m, true)).'</pre>');
-    for($i = 0; $i < sizeof($m[1]); $i++)
+    // Preprocessing and checks complete - compile the code
+    $text = $this->compile_tpl_code($text);
+    
+    // Perhaps caching is enabled and the admin has changed the template?
+    if ( is_writable( ENANO_ROOT . '/cache/' ) && getConfig('cache_thumbs') == '1' )
     {
-      $text = str_replace("<?php{$m[1][$i]}?>", "{PHPCODE:{$i}:{$seed}}", $text);
-    }
-    //die('<pre>'.htmlspecialchars($text).'</pre>');
-    $text = 'ob_start(); echo \''.str_replace('\'', '\\\'', $text).'\'; $tpl_code = ob_get_contents(); ob_end_clean();';
-    $text = preg_replace('#<!-- BEGIN (.*?) -->#is', '\'; if(isset($this->tpl_bool[\'\\1\']) && $this->tpl_bool[\'\\1\']) { echo \'', $text);
-    $text = preg_replace('#<!-- IFSET (.*?) -->#is', '\'; if(isset($this->tpl_strings[\'\\1\'])) { echo \'', $text);
-    $text = preg_replace('#<!-- IFPLUGIN (.*?) -->#is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { echo \'', $text);
-    $text = preg_replace('#<!-- SYSMSG (.*?) -->#is', '\'; echo $template->tplWikiFormat($paths->sysMsg(\'\\1\')); echo \'', $text);
-    $text = preg_replace('#<!-- BEGINNOT (.*?) -->#is', '\'; if(!$this->tpl_bool[\'\\1\']) { echo \'', $text);
-    $text = preg_replace('#<!-- BEGINELSE (.*?) -->#is', '\'; } else { echo \'', $text);
-    $text = preg_replace('#<!-- END (.*?) -->#is', '\'; } echo \'', $text);
-    $text = preg_replace('#\{([A-z0-9]*)\}#is', '\'.$this->tpl_strings[\'\\1\'].\'', $text);
-    for($i = 0; $i < sizeof($m[1]); $i++)
-    {
-      $text = str_replace("{PHPCODE:{$i}:{$seed}}", "'; {$m[1][$i]} echo '", $text);
-    }
-    if(is_writable(ENANO_ROOT.'/cache/') && getConfig('cache_thumbs')=='1')
-    {
-      //die($tpl_filename);
-      $h = fopen($tpl_filename, 'w');
-      if(!$h) return $text;
-      $t = addslashes($text);
+      $h = fopen($cache_file, 'w');
+      if ( !$h )
+      {
+        // Couldn't open the file - silently ignore and return
+        return $text;
+      }
+      
+      // Escape the compiled code so it can be eval'ed
+      $text_escaped = addslashes($text);
       $notice = <<<EOF
 
 /*
@@ -951,37 +1172,34 @@
  */
 
 EOF;
-      fwrite($h, '<?php ' . $notice . ' $md5 = \''.$md5.'\'; $tpl_text = \''.$t.'\'; ?>');
+      // This is really just a normal PHP file that sets a variable or two and exits.
+      // $tpl_text actually will contain the compiled code
+      fwrite($h, '<?php ' . $notice . ' $md5 = \'' . $md5 . '\'; $tpl_text = \'' . $text_escaped . '\'; ?>');
       fclose($h);
     }
+    
     return $text; //('<pre>'.htmlspecialchars($text).'</pre>');
   }
   
-  function compile_template_text($text) {
-    $seed = md5 ( microtime() . mt_rand() );
-    preg_match_all("/<\?php(.*?)\?>/is", $text, $m);
-    //die('<pre>'.htmlspecialchars(print_r($m, true)).'</pre>');
-    for($i = 0; $i < sizeof($m[1]); $i++)
-    {
-      $text = str_replace("<?php{$m[1][$i]}?>", "{PHPCODE:{$i}:{$seed}}", $text);
-    }
-    //die('<pre>'.htmlspecialchars($text).'</pre>');
-    $text = 'ob_start(); echo \''.str_replace('\'', '\\\'', $text).'\'; $tpl_code = ob_get_contents(); ob_end_clean(); return $tpl_code;';
-    $text = preg_replace('#<!-- BEGIN (.*?) -->#is', '\'; if(isset($this->tpl_bool[\'\\1\']) && $this->tpl_bool[\'\\1\']) { echo \'', $text);
-    $text = preg_replace('#<!-- IFSET (.*?) -->#is', '\'; if(isset($this->tpl_strings[\'\\1\'])) { echo \'', $text);
-    $text = preg_replace('#<!-- IFPLUGIN (.*?) -->#is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { echo \'', $text);
-    $text = preg_replace('#<!-- SYSMSG (.*?) -->#is', '\'; echo $template->tplWikiFormat($paths->sysMsg(\'\\1\')); echo \'', $text);
-    $text = preg_replace('#<!-- BEGINNOT (.*?) -->#is', '\'; if(!$this->tpl_bool[\'\\1\']) { echo \'', $text);
-    $text = preg_replace('#<!-- BEGINELSE (.*?) -->#is', '\'; } else { echo \'', $text);
-    $text = preg_replace('#<!-- END (.*?) -->#is', '\'; } echo \'', $text);
-    $text = preg_replace('#\{([A-z0-9]*)\}#is', '\'.$this->tpl_strings[\'\\1\'].\'', $text);
-    for($i = 0; $i < sizeof($m[1]); $i++)
-    {
-      $text = str_replace("{PHPCODE:{$i}:{$seed}}", "'; {$m[1][$i]} echo '", $text);
-    }
-    return $text; //('<pre>'.htmlspecialchars($text).'</pre>');
+  
+  /**
+   * Compiles (parses) some template code with the current master set of variables and booleans.
+   * @param string Text to process
+   * @return string
+   */
+  
+  function compile_template_text($text)
+  {
+    // this might do something else in the future, possibly cache large templates
+    return $this->compile_tpl_code($text);
   }
   
+  /**
+   * For convenience - compiles AND parses some template code.
+   * @param string Text to process
+   * @return string
+   */
+  
   function parse($text)
   {
     $text = $this->compile_template_text($text);
@@ -1004,7 +1222,18 @@
   // So you can implement custom logic into your sidebar if you wish.
   // "Real" PHP support coming soon :-D
   
-  function tplWikiFormat($message, $filter_links = false, $filename = 'elements.tpl') {
+  /**
+   * Takes a blob of HTML with the specially formatted template-oriented wikitext and formats it. Does not use eval().
+   * This function butchers every coding standard in Enano and should eventually be rewritten. The fact is that the
+   * code _works_ and does a good job of checking for errors and cleanly complaining about them.
+   * @param string Text to process
+   * @param bool Ignored for backwards compatibility
+   * @param string File to get variables for sidebar data from
+   * @return string
+   */
+  
+  function tplWikiFormat($message, $filter_links = false, $filename = 'elements.tpl')
+  {
     global $db, $session, $paths, $template, $plugins; // Common objects
     $filter_links = false;
     $tplvars = $this->extract_vars($filename);
@@ -1029,83 +1258,93 @@
     
     // Conditionals
     
-    preg_match_all('#\{if ([A-Za-z0-9_ &\|\!-]*)\}(.*?)\{\/if\}#is', $message, $links);
+    preg_match_all('#\{if ([A-Za-z0-9_ \(\)&\|\!-]*)\}(.*?)\{\/if\}#is', $message, $links);
     
-    for($i=0;$i<sizeof($links[1]);$i++)
+    // Temporary exception from coding standards - using tab length of 4 here for clarity
+    for ( $i = 0; $i < sizeof($links[1]); $i++ )
     {
-      $message = str_replace('{if '.$links[1][$i].'}'.$links[2][$i].'{/if}', '{CONDITIONAL:'.$i.':'.$random_id.'}', $message);
-      
-      // Time for some manual parsing...
-      $chk = false;
-      $current_id = '';
-      $prn_level = 0;
-      // Used to keep track of where we are in the conditional
-      // Object of the game: turn {if this && ( that OR !something_else )} ... {/if} into if( ( isset($this->tpl_bool['that']) && $this->tpl_bool['that'] ) && ...
-      // Method of attack: escape all variables, ignore all else. Non-valid code is filtered out by a regex above.
-      $in_var_now = true;
-      $in_var_last = false;
-      $current_var = '';
-      $current_var_start_pos = 0;
-      $current_var_end_pos   = 0;
-      $j = -1;
-      $links[1][$i] = $links[1][$i] . ' ';
-      $d = strlen($links[1][$i]);
-      while($j < $d)
-      {
-        $j++;
-        $in_var_last = $in_var_now;
+        $condition =& $links[1][$i];
+        $message = str_replace('{if '.$condition.'}'.$links[2][$i].'{/if}', '{CONDITIONAL:'.$i.':'.$random_id.'}', $message);
         
-        $char = substr($links[1][$i], $j, 1);
-        $in_var_now = ( preg_match('#^([A-z0-9_]*){1}$#', $char) ) ? true : false;
-        if(!$in_var_last && $in_var_now)
-        {
-          $current_var_start_pos = $j;
-        }
-        if($in_var_last && !$in_var_now)
-        {
-          $current_var_end_pos = $j;
-        }
-        if($in_var_now)
+        // Time for some manual parsing...
+        $chk = false;
+        $current_id = '';
+        $prn_level = 0;
+        // Used to keep track of where we are in the conditional
+        // Object of the game: turn {if this && ( that OR !something_else )} ... {/if} into if( ( isset($this->tpl_bool['that']) && $this->tpl_bool['that'] ) && ...
+        // Method of attack: escape all variables, ignore all else. Non-valid code is filtered out by a regex above.
+        $in_var_now = true;
+        $in_var_last = false;
+        $current_var = '';
+        $current_var_start_pos = 0;
+        $current_var_end_pos     = 0;
+        $j = -1;
+        $condition = $condition . ' ';
+        $d = strlen($condition);
+        while($j < $d)
         {
-          $current_var .= $char;
-          continue;
+            $j++;
+            $in_var_last = $in_var_now;
+            
+            $char = substr($condition, $j, 1);
+            $in_var_now = ( preg_match('#^([A-z0-9_]*){1}$#', $char) ) ? true : false;
+            if(!$in_var_last && $in_var_now)
+            {
+                $current_var_start_pos = $j;
+            }
+            if($in_var_last && !$in_var_now)
+            {
+                $current_var_end_pos = $j;
+            }
+            if($in_var_now)
+            {
+                $current_var .= $char;
+                continue;
+            }
+            // OK we are not inside of a variable. That means that we JUST hit the end because the counter ($j) will be advanced to the beginning of the next variable once processing here is complete.
+            if($char != ' ' && $char != '(' && $char != ')' && $char != 'A' && $char != 'N' && $char != 'D' && $char != 'O' && $char != 'R' && $char != '&' && $char != '|' && $char != '!' && $char != '<' && $char != '>' && $char != '0' && $char != '1' && $char != '2' && $char != '3' && $char != '4' && $char != '5' && $char != '6' && $char != '7' && $char != '8' && $char != '9')
+            {
+                // XSS attack! Bail out
+                $errmsg    = '<p><b>Error:</b> Syntax error (possibly XSS attack) caught in template code:</p>';
+                $errmsg .= '<pre>';
+                $errmsg .= '{if '.htmlspecialchars($condition).'}';
+                $errmsg .= "\n    ";
+                for ( $k = 0; $k < $j; $k++ )
+                {
+                    $errmsg .= " ";
+                }
+                // Show position of error
+                $errmsg .= '<span style="color: red;">^</span>';
+                $errmsg .= '</pre>';
+                $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $errmsg, $message);
+                continue 2;
+            }
+            if($current_var != '')
+            {
+                $cd = '( isset($this->tpl_bool[\''.$current_var.'\']) && $this->tpl_bool[\''.$current_var.'\'] )';
+                $cvt = substr($condition, 0, $current_var_start_pos) . $cd . substr($condition, $current_var_end_pos, strlen($condition));
+                $j = $j + strlen($cd) - strlen($current_var);
+                $current_var = '';
+                $condition = $cvt;
+                $d = strlen($condition);
+            }
         }
-        // OK we are not inside of a variable. That means that we JUST hit the end because the counter ($j) will be advanced to the beginning of the next variable once processing here is complete.
-        if($char != ' ' && $char != '(' && $char != ')' && $char != 'A' && $char != 'N' && $char != 'D' && $char != 'O' && $char != 'R' && $char != '&' && $char != '|' && $char != '!' && $char != '<' && $char != '>' && $char != '0' && $char != '1' && $char != '2' && $char != '3' && $char != '4' && $char != '5' && $char != '6' && $char != '7' && $char != '8' && $char != '9')
-        {
-          // XSS attack! Bail out
-          echo '<p><b>Error:</b> Syntax error (possibly XSS attack) caught in template code:</p>';
-          echo '<pre>';
-          echo '{if '.$links[1][$i].'}';
-          echo "\n    ";
-          for($k=0;$k<$j;$k++) echo " ";
-          echo '<span style="color: red;">^</span>';
-          echo '</pre>';
-          continue 2;
-        }
-        if($current_var != '')
+        $condition = substr($condition, 0, strlen($condition)-1);
+        $condition = '$chk = ( '.$condition.' ) ? true : false;';
+        eval($condition);
+        
+        if($chk)
         {
-          $cd = '( isset($this->tpl_bool[\''.$current_var.'\']) && $this->tpl_bool[\''.$current_var.'\'] )';
-          $cvt = substr($links[1][$i], 0, $current_var_start_pos) . $cd . substr($links[1][$i], $current_var_end_pos, strlen($links[1][$i]));
-          $j = $j + strlen($cd) - strlen($current_var);
-          $current_var = '';
-          $links[1][$i] = $cvt;
-          $d = strlen($links[1][$i]);
+            if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
+            else $c = $links[2][$i];
+            $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
         }
-      }
-      $links[1][$i] = substr($links[1][$i], 0, strlen($links[1][$i])-1);
-      $links[1][$i] = '$chk = ( '.$links[1][$i].' ) ? true : false;';
-      eval($links[1][$i]);
-      
-      if($chk) { // isset($this->tpl_bool[$links[1][$i]]) && $this->tpl_bool[$links[1][$i]]
-        if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
-        else $c = $links[2][$i];
-        $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-      } else {
-        if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
-        else $c = '';
-        $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
-      }
+        else
+        {
+            if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
+            else $c = '';
+            $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
+        }
     }
     
     preg_match_all('#\{!if ([A-Za-z_-]*)\}(.*?)\{\/if\}#is', $message, $links);
@@ -1174,26 +1413,26 @@
     // $message = preg_replace('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?) ([^\]]+)\\]#', '<a href="\\1://\\2">\\3</a><br style="display: none;" />', $message);
     // $message = preg_replace('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?)\\]#', '<a href="\\1://\\2">\\1://\\2</a><br style="display: none;" />', $message);
     
-    preg_match_all('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?)\\ ([^\]]+)]#', $message, $ext_link);
+    preg_match_all('/\[((https?|ftp|irc):\/\/([^@\]"\':]+)?((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?) ([^\]]+)\]/is', $message, $ext_link);
     
     for ( $i = 0; $i < count($ext_link[0]); $i++ )
     {
       $text_parser->assign_vars(Array(  
-          'HREF'  => "{$ext_link[1][$i]}://{$ext_link[2][$i]}",
+          'HREF'  => $ext_link[1][$i],
           'FLAGS' => '',
-          'TEXT'  => $ext_link[3][$i]
+          'TEXT'  => $ext_link[16][$i]
         ));
       $message = str_replace($ext_link[0][$i], $text_parser->run(), $message);
     }
     
-    preg_match_all('#\[(http|ftp|irc):\/\/([a-z0-9\/:_\.\?&%\#@_\\\\-]+?)\\]#', $message, $ext_link);
+    preg_match_all('/\[((https?|ftp|irc):\/\/([^@\]"\':]+)?((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?)\]/is', $message, $ext_link);
     
     for ( $i = 0; $i < count($ext_link[0]); $i++ )
     {
       $text_parser->assign_vars(Array(  
-          'HREF'  => "{$ext_link[1][$i]}://{$ext_link[2][$i]}",
+          'HREF'  => $ext_link[1][$i],
           'FLAGS' => '',
-          'TEXT'  => htmlspecialchars("{$ext_link[1][$i]}://{$ext_link[2][$i]}")
+          'TEXT'  => htmlspecialchars($ext_link[1][$i])
         ));
       $message = str_replace($ext_link[0][$i], $text_parser->run(), $message);
     }
@@ -1234,7 +1473,7 @@
   function username_field($name, $value = false)
   {
     $randomid = md5( time() . microtime() . mt_rand() );
-    $text = '<input name="'.$name.'" onkeyup="ajaxUserNameComplete(this)" autocomplete="off" type="text" size="30" id="userfield_'.$randomid.'"';
+    $text = '<input name="'.$name.'" onkeyup="new AutofillUsername(this);" autocomplete="off" type="text" size="30" id="userfield_'.$randomid.'"';
     if($value) $text .= ' value="'.$value.'"';
     $text .= ' />';
     return $text;
--- a/includes/wikiengine/Tables.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/includes/wikiengine/Tables.php	Sat Oct 20 21:59:27 2007 -0400
@@ -1,8 +1,8 @@
 <?php
 
-/**
+/*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -422,6 +422,7 @@
 	 * @return array
 	 */
 	function setupAttributeWhitelist() {
+    global $db, $session, $paths, $template, $plugins;
 		$common = array( 'id', 'class', 'lang', 'dir', 'title', 'style' );
 		$block = array_merge( $common, array( 'align' ) );
 		$tablealign = array( 'align', 'char', 'charoff', 'valign' );
@@ -570,6 +571,14 @@
       # XHTML stuff
       'acronym'    => $common
 			);
+    
+    // custom tags can be added by plugins
+    $code = $plugins->setHook('html_attribute_whitelist');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
 		return $whitelist;
 	}
   
--- a/index.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/index.php	Sat Oct 20 21:59:27 2007 -0400
@@ -1,8 +1,8 @@
 <?php
 
-/**
+/*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * @Version 1.0.2 (Coblynau)
+ * Version 1.0.2 (Coblynau)
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -141,6 +141,11 @@
           <input type="submit" name="_cancel" value="Cancel" />
         </form>
       ';
+      if ( getConfig('wiki_edit_notice') == '1' )
+      {
+        $notice = getConfig('wiki_edit_notice_text');
+        echo RenderMan::render($notice);
+      }
       $template->footer();
       break;
     case 'viewsource':
--- a/install.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/install.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  * install.php - handles everything related to installation and initial configuration
  *
@@ -15,7 +15,7 @@
  
 @include('config.php');
 if( ( defined('ENANO_INSTALLED') || defined('MIDGET_INSTALLED') ) && ((isset($_GET['mode']) && ($_GET['mode']!='finish' && $_GET['mode']!='css')) || !isset($_GET['mode']))) {
-  $_GET['title'] = 'Enano:WhoCaresWhatThisIs';
+  $_GET['title'] = 'Enano:Installation_locked';
   require('includes/common.php');
   die_friendly('Installation locked', '<p>The Enano installer has found a Enano installation in this directory. You MUST delete config.php if you want to re-install Enano.</p><p>If you wish to upgrade an older Enano installation to this version, please use the <a href="upgrade.php">upgrade script</a>.</p>');
   exit;
@@ -23,9 +23,8 @@
 
 define('IN_ENANO_INSTALL', 'true');
 
-define('ENANO_VERSION', '1.0.2');
+define('ENANO_VERSION', '1.1.1');
 // In beta versions, define ENANO_BETA_VERSION here
-define('ENANO_BETA_VERSION', '1');
 
 if(!defined('scriptPath')) {
   $sp = dirname($_SERVER['REQUEST_URI']);
@@ -316,8 +315,7 @@
     <div style="text-align: center; margin-top: 10px;">
       <img alt="[ Enano CMS Project logo ]" src="images/enano-artwork/installer-greeting-green.png" style="display: block; margin: 0 auto; padding-left: 100px;" />
       <h2>Welcome to Enano</h2>
-      <h3>version 1.0.2 &ndash; beta 1<br />
-      <span style="font-weight: normal;">also affectionately known as "coblynau" <tt>:)</tt></span></h3>
+      <h3>version 1.1.1 &ndash; unstable</h3>
       <?php
       if ( file_exists('./_nightly.php') )
       {
@@ -1066,7 +1064,8 @@
       $schema = str_replace('{{TABLE_PREFIX}}', $_POST['table_prefix'],                          $schema);
       $schema = str_replace('{{VERSION}}',      ENANO_VERSION,                                   $schema);
       $schema = str_replace('{{ADMIN_EMBED_PHP}}', $_POST['admin_embed_php'],                    $schema);
-      $schema = str_replace('{{BETA_VERSION}}', ENANO_BETA_VERSION,                              $schema);
+      // Not anymore!! :-D
+      // $schema = str_replace('{{BETA_VERSION}}', ENANO_BETA_VERSION,                              $schema);
       
       if(isset($_POST['wiki_mode']))
       {
--- a/licenses/index.html	Sat Sep 29 09:43:46 2007 -0400
+++ b/licenses/index.html	Sat Oct 20 21:59:27 2007 -0400
@@ -110,6 +110,7 @@
 <p><a href="bsdlic.html">View the text of this license</a></p>
 <ul>
   <li><a href="http://pajhome.org.uk/">Paul Johnston</a>'s implementations of the MD5 and SHA1 algorithms in Javascript</li>
+  <li><a href="http://labs.adobe.com/technologies/spry/">Adobe Spry</a>, used for some Javascript effects</li>
 </ul>
 
 <h2>The MIT/X License</h2>
--- a/plugins/PrivateMessages.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/PrivateMessages.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:PrivateMessages, which is used to manage private message functions. Also handles buddy lists.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialAdmin.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialAdmin.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,7 +4,7 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Administration, which is the AJAX frontend to the various Admin pagelets. This plugin cannot be disabled.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
@@ -2834,9 +2834,8 @@
     
     if(isset($_GET['action']) && isset($_GET['id']))
     {
-      if(preg_match('#^([0-9]*)$#', $_GET['id']))
+      if(!preg_match('#^([0-9]*)$#', $_GET['id']))
       {
-      } else {
         echo '<div class="warning-box">Error with action: $_GET["id"] was not an integer, aborting to prevent SQL injection</div>';
       }
       switch($_GET['action'])
@@ -2980,7 +2979,7 @@
             echo '<div class="warning-box" style="margin: 10px 0;">$_GET[\'side\'] contained an SQL injection attempt</div>';
             break;
           }
-          $query = $db->sql_query('UPDATE '.table_prefix.'sidebar SET sidebar_id=' . $db->escape($_GET['side']) . ' WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $query = $db->sql_query('UPDATE '.table_prefix.'sidebar SET sidebar_id=' . $db->escape($_GET['side']) . ' WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$query)
           {
             echo $db->get_error();
@@ -2990,7 +2989,7 @@
           echo '<div class="info-box" style="margin: 10px 0;">Item moved.</div>';
           break;
         case 'delete':
-          $query = $db->sql_query('DELETE FROM '.table_prefix.'sidebar WHERE item_id=' . $db->escape($_GET['id']) . ';'); // Already checked for injection attempts ;-)
+          $query = $db->sql_query('DELETE FROM '.table_prefix.'sidebar WHERE item_id=' . intval($_GET['id']) . ';'); // Already checked for injection attempts ;-)
           if(!$query)
           {
             echo $db->get_error();
@@ -3005,7 +3004,7 @@
           echo '<div class="error-box" style="margin: 10px 0;">Item deleted.</div>';
           break;
         case 'disenable';
-          $q = $db->sql_query('SELECT item_enabled FROM '.table_prefix.'sidebar WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $q = $db->sql_query('SELECT item_enabled FROM '.table_prefix.'sidebar WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$q)
           {
             echo $db->get_error();
@@ -3015,7 +3014,22 @@
           $r = $db->fetchrow();
           $db->free_result();
           $e = ( $r['item_enabled'] == 1 ) ? '0' : '1';
-          $q = $db->sql_query('UPDATE '.table_prefix.'sidebar SET item_enabled='.$e.' WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $q = $db->sql_query('UPDATE '.table_prefix.'sidebar SET item_enabled='.$e.' WHERE item_id=' . intval($_GET['id']) . ';');
+          if(!$q)
+          {
+            echo $db->get_error();
+            $template->footer();
+            exit;
+          }
+          if(isset($_GET['ajax']))
+          {
+            ob_end_clean();
+            die('GOOD');
+          }
+          break;
+        case 'rename';
+          $newname = $db->escape($_POST['newname']);
+          $q = $db->sql_query('UPDATE '.table_prefix.'sidebar SET block_name=\''.$newname.'\' WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$q)
           {
             echo $db->get_error();
@@ -3029,7 +3043,7 @@
           }
           break;
         case 'getsource':
-          $q = $db->sql_query('SELECT block_content,block_type FROM '.table_prefix.'sidebar WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $q = $db->sql_query('SELECT block_content,block_type FROM '.table_prefix.'sidebar WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$q)
           {
             echo $db->get_error();
@@ -3045,7 +3059,7 @@
         case 'save':
           if ( defined('ENANO_DEMO_MODE') )
           {
-            $q = $db->sql_query('SELECT block_type FROM '.table_prefix.'sidebar WHERE item_id=' . $db->escape($_GET['id']) . ';');
+            $q = $db->sql_query('SELECT block_type FROM '.table_prefix.'sidebar WHERE item_id=' . intval($_GET['id']) . ';');
             if(!$q)
             {
               echo 'var status=unescape(\''.hexencode($db->get_error()).'\');';
@@ -3061,13 +3075,13 @@
               $_POST['content'] = sanitize_html($_POST['content'], true);
             }
           }
-          $q = $db->sql_query('UPDATE '.table_prefix.'sidebar SET block_content=\''.$db->escape(rawurldecode($_POST['content'])).'\' WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $q = $db->sql_query('UPDATE '.table_prefix.'sidebar SET block_content=\''.$db->escape(rawurldecode($_POST['content'])).'\' WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$q)
           {
             echo 'var status=unescape(\''.hexencode($db->get_error()).'\');';
             exit;
           }
-          $q = $db->sql_query('SELECT block_type,block_content FROM '.table_prefix.'sidebar WHERE item_id=' . $db->escape($_GET['id']) . ';');
+          $q = $db->sql_query('SELECT block_type,block_content FROM '.table_prefix.'sidebar WHERE item_id=' . intval($_GET['id']) . ';');
           if(!$q)
           {
             echo 'var status=unescape(\''.hexencode($db->get_error()).'\');';
@@ -3159,6 +3173,8 @@
           $parser = $template->makeParserText($vars['sidebar_section']);
           $c = $template->tplWikiFormat($row['block_content'], false, 'sidebar-editor.tpl');
           $c = preg_replace('#<a (.*?)>(.*?)</a>#is', '<a href="#" onclick="return false;">\\2</a>', $c);
+          // fix for the "Administration" link that somehow didn't get rendered properly
+          $c = preg_replace("/(^|\n)([ ]*)<a([ ]+.*)?>(.+)<\/a>(<br(.*)\/>)([\r\n]+|$)/isU", '\\1\\2<li><a\\3>\\4</a></li>\\7', $c);
           break;
         case BLOCK_HTML:
           $parser = $template->makeParserText($vars['sidebar_section_raw']);
@@ -3178,7 +3194,7 @@
           $c = ($template->fetch_block($row['block_content'])) ? $template->fetch_block($row['block_content']) : 'Can\'t find plugin block';
           break;
       }
-      $t = $template->tplWikiFormat($row['block_name']);
+      $t = '<span title="Double-click to rename this block" id="sbrename_' . $row['item_id'] . '" ondblclick="ajaxRenameSidebarStage1(this, \''.$row['item_id'].'\'); return false;">' . $template->tplWikiFormat($row['block_name']) . '</span>';
       if($row['item_enabled'] == 0) $t .= ' <span id="disabled_'.$row['item_id'].'" style="color: red;">(disabled)</span>';
       else           $t .= ' <span id="disabled_'.$row['item_id'].'" style="color: red; display: none;">(disabled)</span>';
       $side = ( $row['sidebar_id'] == SIDEBAR_LEFT ) ? SIDEBAR_RIGHT : SIDEBAR_LEFT;
--- a/plugins/SpecialCSS.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialCSS.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:CSS, which is used in template files to reference the style sheet. Disabling or deleting this plugin will result in site instability.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialGroups.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialGroups.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides group moderators and site administrators with the ability to control who is part of their groups. 
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -50,10 +50,10 @@
     {
       die_friendly('Error', '<p>Hacking attempt</p>');
     }
-    $q = $db->sql_query('SELECT group_name,group_type FROM '.table_prefix.'groups WHERE group_id=' . $gid . ';');
+    $q = $db->sql_query('SELECT group_name,group_type,system_group FROM '.table_prefix.'groups WHERE group_id=' . $gid . ';');
     if ( !$q )
     {
-      $db->_die();
+      $db->_die('SpecialGroups.php, line ' . __LINE__);
     }
     $row = $db->fetchrow();
     $db->free_result();
@@ -70,7 +70,7 @@
                            ORDER BY m.is_mod DESC,u.username ASC;');
     if ( !$q )
     {
-      $db->_die();
+      $db->_die('SpecialGroups.php, line ' . __LINE__);
     }
     
     $is_member = false;
@@ -127,11 +127,29 @@
           {
             die_friendly('ERROR', '<p>Hacking attempt</p>');
           }
-          $q = $db->sql_query('UPDATE '.table_prefix.'groups SET group_type=' . intval($_POST['group_state']) . ' WHERE group_id=' . intval( $_POST['group_id']) . ';');
-          if (!$q)
-            $db->_die();
-          $row['group_type'] = $_POST['group_state'];
-          echo '<div class="info-box" style="margin-left: 0;">The group state was updated.</div>';
+          $q = $db->sql_query('SELECT group_type, system_group FROM '.table_prefix.'groups WHERE group_id=' . intval( $_POST['group_id']) . ';');
+          if ( !$q )
+            $db->_die('SpecialGroups.php, line ' . __LINE__);
+          $error = false;
+          if ( $db->numrows() < 1 )
+          {
+            echo '<div class="error-box" style="margin-left: 0;">The group you selected does not exist.</div>';
+            $error = true;
+          }
+          $r = $db->fetchrow();
+          if ( $r['system_group'] == 1 && ( intval($_POST['group_state']) == GROUP_OPEN || intval($_POST['group_state']) == GROUP_REQUEST ) )
+          {
+            echo '<div class="error-box" style="margin-left: 0;">Because this is a system group, you can\'t make it open or allow membership requests.</div>';
+            $error = true;
+          }
+          if ( !$error )
+          {
+            $q = $db->sql_query('UPDATE '.table_prefix.'groups SET group_type=' . intval($_POST['group_state']) . ' WHERE group_id=' . intval( $_POST['group_id']) . ';');
+            if (!$q)
+              $db->_die('SpecialGroups.php, line ' . __LINE__);
+            $row['group_type'] = $_POST['group_state'];
+            echo '<div class="info-box" style="margin-left: 0;">The group state was updated.</div>';
+          }
           break;
         case 'adduser':
           $username = $_POST['add_username'];
@@ -139,7 +157,7 @@
           
           $q = $db->sql_query('SELECT user_id FROM '.table_prefix.'users WHERE username=\'' . $db->escape($username) . '\';');
           if (!$q)
-            $db->_die();
+            $db->_die('SpecialGroups.php, line ' . __LINE__);
           if ($db->numrows() < 1)
           {
             echo '<div class="error-box">The username you entered could not be found.</div>';
@@ -152,7 +170,7 @@
           // Check if the user is already in the group, and if so, only update modship
           $q = $db->sql_query('SELECT member_id,is_mod FROM '.table_prefix.'group_members WHERE user_id=' . $uid . ' AND group_id=' . intval($_POST['group_id']) . ';');
           if ( !$q )
-            $db->_die();
+            $db->_die('SpecialGroups.php, line ' . __LINE__);
           if ( $db->numrows() > 0 )
           {
             $r = $db->fetchrow();
@@ -160,7 +178,7 @@
             {
               $q = $db->sql_query('UPDATE '.table_prefix.'group_members SET is_mod=' . $mod . ' WHERE member_id=' . $r['member_id'] . ';');
               if ( !$q )
-                $db->_die();
+                $db->_die('SpecialGroups.php, line ' . __LINE__);
               foreach ( $members as $i => $member )
               {
                 if ( $member['member_id'] == $r['member_id'] )
@@ -179,7 +197,7 @@
           
           $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id,is_mod) VALUES(' . intval($_POST['group_id']) . ', ' . $uid . ', ' . $mod . ');');
           if (!$q)
-            $db->_die();
+            $db->_die('SpecialGroups.php, line ' . __LINE__);
           echo '<div class="info-box">The user "' . $username . '" has been added to this usergroup.</div>';
           
           $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id)
@@ -195,7 +213,7 @@
                                  ORDER BY m.is_mod DESC,u.username ASC
                                  LIMIT 1;');
           if ( !$q )
-            $db->_die();
+            $db->_die('SpecialGroups.php, line ' . __LINE__);
           
           $r = $db->fetchrow();
           $members[] = $r;
@@ -209,7 +227,7 @@
             {
               $q = $db->sql_query('DELETE FROM '.table_prefix.'group_members WHERE member_id=' . $member['member_id'] . ';');
               if (!$q)
-                $db->_die();
+                $db->_die('SpecialGroups.php, line ' . __LINE__);
               unset($members[$i]);
             }
           }
@@ -223,7 +241,7 @@
               {
                 $q = $db->sql_query('UPDATE '.table_prefix.'group_members SET pending=0 WHERE member_id=' . $member['member_id'] . ';');
                 if (!$q)
-                  $db->_die();
+                  $db->_die('SpecialGroups.php, line ' . __LINE__);
                 $members[] = $member;
                 unset($pending[$i]);
                 continue;
@@ -232,7 +250,7 @@
               {
                 $q = $db->sql_query('DELETE FROM '.table_prefix.'group_members WHERE member_id=' . $member['member_id'] . ';');
                 if (!$q)
-                  $db->_die();
+                  $db->_die('SpecialGroups.php, line ' . __LINE__);
                 unset($pending[$i]);
               }
             }
@@ -246,7 +264,7 @@
     {
       $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id) VALUES(' . $gid . ', ' . $session->user_id . ');');
       if (!$q)
-        $db->_die();
+        $db->_die('SpecialGroups.php, line ' . __LINE__);
       echo '<div class="info-box">You have been added to this group.</div>';
       
       $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id)
@@ -262,7 +280,7 @@
                              ORDER BY m.is_mod DESC,u.username ASC
                              LIMIT 1;');
       if ( !$q )
-        $db->_die();
+        $db->_die('SpecialGroups.php, line ' . __LINE__);
       
       $r = $db->fetchrow();
       $members[] = $r;
@@ -274,7 +292,7 @@
     {
       $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id,pending) VALUES(' . $gid . ', ' . $session->user_id . ', 1);');
       if (!$q)
-        $db->_die();
+        $db->_die('SpecialGroups.php, line ' . __LINE__);
       echo '<div class="info-box">A request has been sent to the moderator(s) of this group to add you.</div>';
     }
     
@@ -305,7 +323,7 @@
               </tr>
               <tr>
                 <td class="row2">Group name:</td>
-                <td class="row1">' . $row['group_name'] . '</td>
+                <td class="row1">' . $row['group_name'] . ( $row['system_group'] == 1 ? ' (system group)' : '' ) . '</td>
               </tr>
               <tr>
                 <td class="row2">Membership status:</td>
--- a/plugins/SpecialPageFuncs.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialPageFuncs.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:CreatePage, which can be used to create new pages. Also adds the About Enano and GNU General Public License pages.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -109,6 +109,17 @@
       
       exit;
     }
+    if ( substr($urlname, 0, 8) == 'Project:' )
+    {
+      $template->header();
+      
+      echo '<h3>The page could not be created.</h3><p>The page title can\'t start with "Project:" because this prefix is reserved for a parser shortcut.</p>';
+      
+      $template->footer();
+      $db->close();
+      
+      exit;
+    }
     
     $tn = $paths->nslist[$_POST['namespace']] . $urlname;
     if ( isset($paths->pages[$tn]) )
@@ -151,13 +162,13 @@
     {
       $db->_die('The page entry could not be inserted.');
     }
-    $q = $db->sql_query('INSERT INTO '.table_prefix.'page_text(page_id,namespace,page_text) VALUES(\''.$urlname.'\', \''.$_POST['namespace'].'\', \''.$db->escape('Please edit this page! <nowiki><script type="text/javascript">ajaxEditor();</script></nowiki>').'\');');
+    $q = $db->sql_query('INSERT INTO '.table_prefix.'page_text(page_id,namespace,page_text) VALUES(\''.$urlname.'\', \''.$_POST['namespace'].'\', \''.'\');');
     if ( !$q )
     {
       $db->_die('The page text entry could not be inserted.');
     }
     
-    header('Location: '.makeUrlNS($_POST['namespace'], sanitize_page_id($p)));
+    header('Location: '.makeUrlNS($_POST['namespace'], sanitize_page_id($p)) . '#do:edit');
     exit;
   }
   $template->header();
--- a/plugins/SpecialSearch.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialSearch.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Search, which is a frontend to the Enano search engine.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -100,18 +100,21 @@
   if(!empty($q))
   {
     // See if any pages directly match the title
-          
-    for ( $i = 0; $i < count ( $paths->pages ) / 2; $i++ )
+    
+    if ( strlen($q) >= 4 )
     {
-      $pg =& $paths->pages[$i];
-      $q_lc = strtolower( str_replace(' ', '_', $q) );
-      $q_tl = strtolower( str_replace('_', ' ', $q) );
-      $p_lc = strtolower($pg['urlname']);
-      $p_tl = strtolower($pg['name']);
-      if ( strstr($p_tl, $q_tl) || strstr($p_lc, $q_lc) && $pg['visible'] == 1 )
+      for ( $i = 0; $i < count ( $paths->pages ) / 2; $i++ )
       {
-        echo '<div class="usermessage">Perhaps you were looking for <b><a href="' . makeUrl($pg['urlname'], false, true) . '">' . htmlspecialchars($pg['name']) . '</a></b>?</div>';
-        break;
+        $pg =& $paths->pages[$i];
+        $q_lc = strtolower( str_replace(' ', '_', $q) );
+        $q_tl = strtolower( str_replace('_', ' ', $q) );
+        $p_lc = strtolower($pg['urlname']);
+        $p_tl = strtolower($pg['name']);
+        if ( strstr($p_tl, $q_tl) || strstr($p_lc, $q_lc) && $pg['visible'] == 1 )
+        {
+          echo '<div class="usermessage">Perhaps you were looking for <b><a href="' . makeUrl($pg['urlname'], false, true) . '">' . htmlspecialchars($pg['name']) . '</a></b>?</div>';
+          break;
+        }
       }
     }
           
--- a/plugins/SpecialUpdownload.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialUpdownload.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the pages Special:UploadFile and Special:DownloadFile. UploadFile is used to upload files to the site, and DownloadFile fetches the file from the database, creates thumbnails if necessary, and sends the file to the user.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  * SpecialUpdownload.php - handles uploading and downloading of user-uploaded files - possibly the most rigorously security-enforcing script in all of Enano, although sessions.php comes in a close second
  *
@@ -59,8 +59,14 @@
     {
       $file = false;
     }
-    if(!is_array($file)) die_friendly('Upload failed', '<p>The server could not retrieve the array $_FILES[\'data\'].</p>');
-    if($file['size'] == 0 || $file['size'] > (int)getConfig('max_file_size')) die_friendly('Upload failed', '<p>The file you uploaded is either too large or 0 bytes in length.</p>');
+    if ( !is_array($file) )
+    {
+      die_friendly('Upload failed', '<p>The server could not retrieve the array $_FILES[\'data\'].</p>');
+    }
+    if ( $file['size'] == 0 || $file['size'] > (int)getConfig('max_file_size') )
+    {
+      die_friendly('Upload failed', '<p>The file you uploaded is either too large or 0 bytes in length.</p>');
+    }
     /*
     $allowed_mime_types = Array(
         'text/plain',
@@ -88,7 +94,7 @@
     */
     $types = fetch_allowed_extensions();
     $ext = substr($file['name'], strrpos($file['name'], '.')+1, strlen($file['name']));
-    if(!isset($types[$ext]) || ( isset($types[$ext]) && !$types[$ext] ) )
+    if ( !isset($types[$ext]) || ( isset($types[$ext]) && !$types[$ext] ) )
     {
       die_friendly('Upload failed', '<p>The file type ".'.$ext.'" is not allowed.</p>');
     }
--- a/plugins/SpecialUserFuncs.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialUserFuncs.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the pages Special:Login, Special:Logout, Special:Register, and Special:Preferences.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/SpecialUserPrefs.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/SpecialUserPrefs.php	Sat Oct 20 21:59:27 2007 -0400
@@ -4,13 +4,13 @@
 Plugin URI: http://enanocms.org/
 Description: Provides the page Special:Preferences.
 Author: Dan Fuhry
-Version: 1.0.1
+Version: 1.0.2
 Author URI: http://enanocms.org/
 */
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0 release candidate 2
+ * Version 1.0.2
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute it and/or modify it under the terms of the GNU General Public License
@@ -100,7 +100,7 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $userprefs_menu_links;
   
-  userprefs_menu_add('Profile/membership', 'Edit e-mail address and password', makeUrlNS('Special', 'Preferences/EmailPassword'));
+  userprefs_menu_add('Profile/membership', 'Edit e-mail address and password', makeUrlNS('Special', 'Preferences/EmailPassword') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/EmailPassword\', '.USER_LEVEL_CHPREF.'); return false;');
   userprefs_menu_add('Profile/membership', 'Edit signature', makeUrlNS('Special', 'Preferences/Signature'));
   userprefs_menu_add('Profile/membership', 'Edit public profile', makeUrlNS('Special', 'Preferences/Profile'));
   userprefs_menu_add('Private messages', 'Inbox', makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'));
@@ -302,11 +302,14 @@
   {
     case 'Home':
       global $email;
-      $user_page = '<a href="' . makeUrlNS('User', str_replace(' ', '_', $session->username)) . '">user page</a> <sup>(<a href="' . makeUrlNS('User', str_replace(' ', '_', $session->username)) . '#do:comments">comments</a>)</sup>';
+      $userpage_id = $paths->nslist['User'] . sanitize_page_id($session->username);
+      $userpage_exists = ( isPage($userpage_id) ) ? '' : ' class="wikilink-nonexistent"';
+      $user_page = '<a href="' . makeUrlNS('User', sanitize_page_id($session->username)) . '"' . $userpage_exists . '>user page</a> <sup>(<a href="' . makeUrlNS('User', str_replace(' ', '_', $session->username)) . '#do:comments">comments</a>)</sup>';
       $site_admin = $email->encryptEmail(getConfig('contact_email'), '', '', 'administrator');
+      $make_one_now = '<a href="' . makeUrlNS('User', sanitize_page_id($session->username)) . '">make one now</a>';
       echo "<h3 style='margin-top: 0;'>$session->username, welcome to your control panel</h3>";
       echo "<p>Here you can make changes to your profile, view statistics on yourself on this site, and set your preferences.</p>
-            <p>If you have not already done so, you are encouraged to make a $user_page and tell the other members of this site a little about yourself.</p>
+            <p>Your $user_page is your free writing space. You can use it to tell the other members of this site a little bit about yourself. If you haven't already made a user page, why not $make_one_now?</p>
             <p>Use the menu at the top to navigate around. If you have any questions, you may contact the $site_admin.";
       break;
     case 'EmailPassword':
--- a/plugins/admin/PageGroups.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/admin/PageGroups.php	Sat Oct 20 21:59:27 2007 -0400
@@ -48,7 +48,12 @@
             echo '<div class="error-box">Please specify at least one page to place in this group.</div>';
             return;
           }
-          if ( $_POST['group_type'] != PAGE_GRP_TAGGED && $_POST['group_type'] != PAGE_GRP_CATLINK && $_POST['group_type'] != PAGE_GRP_NORMAL )
+          if ( $_POST['group_type'] == PAGE_GRP_REGEX && empty($_POST['regex']) )
+          {
+            echo '<div class="error-box">Please specify a regular expression to match page IDs against.</div>';
+            return;
+          }
+          if ( $_POST['group_type'] != PAGE_GRP_TAGGED && $_POST['group_type'] != PAGE_GRP_CATLINK && $_POST['group_type'] != PAGE_GRP_NORMAL && $_POST['group_type'] != PAGE_GRP_REGEX )
           {
             echo '<div class="error-box">Umm, you sent an invalid group type. I\'d put a real error message here but this will only be shown if you try to hack the system.</div>';
             return;
@@ -103,6 +108,14 @@
               if ( !$q )
                 $db->_die();
               break;
+            case PAGE_GRP_REGEX:
+              $name  = $db->escape($_POST['pg_name']);
+              $regex = $db->escape($_POST['regex']);
+              $sql = 'INSERT INTO '.table_prefix.'page_groups(pg_type,pg_name,pg_target) VALUES(' . PAGE_GRP_REGEX . ', \'' . $name . '\', \'' . $regex . '\');';
+              $q = $db->sql_query($sql);
+              if ( !$q )
+                $db->_die();
+              break;
           }
           echo '<div class="info-box">The page group "' . htmlspecialchars($_POST['pg_name']) . '" has been created.</div>';
           break;
@@ -115,6 +128,7 @@
           var pg_normal  = <?php echo PAGE_GRP_NORMAL; ?>;
           var pg_tagged  = <?php echo PAGE_GRP_TAGGED; ?>;
           var pg_catlink = <?php echo PAGE_GRP_CATLINK; ?>;
+          var pg_regex   = <?php echo PAGE_GRP_REGEX; ?>;
           var selection = false;
           // Get selection
           for ( var i = 0; i < selector.childNodes.length; i++ )
@@ -135,7 +149,7 @@
             return true;
           }
           selection = parseInt(selection);
-          if ( selection != pg_normal && selection != pg_tagged && selection != pg_catlink )
+          if ( selection != pg_normal && selection != pg_tagged && selection != pg_catlink && selection != pg_regex )
           {
             alert('Invalid field value');
             return true;
@@ -156,6 +170,10 @@
             document.getElementById('pg_create_title_normal').style.display = 'inline';
             document.getElementById('pg_create_normal_1').style.display = 'block';
             document.getElementById('pg_create_normal_2').style.display = 'block';
+            
+            document.getElementById('pg_create_title_regex').style.display = 'none';
+            document.getElementById('pg_create_regex_1').style.display = 'none';
+            document.getElementById('pg_create_regex_2').style.display = 'none';
           }
           else if ( selection == pg_catlink )
           {
@@ -170,6 +188,10 @@
             document.getElementById('pg_create_title_normal').style.display = 'none';
             document.getElementById('pg_create_normal_1').style.display = 'none';
             document.getElementById('pg_create_normal_2').style.display = 'none';
+            
+            document.getElementById('pg_create_title_regex').style.display = 'none';
+            document.getElementById('pg_create_regex_1').style.display = 'none';
+            document.getElementById('pg_create_regex_2').style.display = 'none';
           }
           else if ( selection == pg_tagged )
           {
@@ -184,6 +206,28 @@
             document.getElementById('pg_create_title_normal').style.display = 'none';
             document.getElementById('pg_create_normal_1').style.display = 'none';
             document.getElementById('pg_create_normal_2').style.display = 'none';
+            
+            document.getElementById('pg_create_title_regex').style.display = 'none';
+            document.getElementById('pg_create_regex_1').style.display = 'none';
+            document.getElementById('pg_create_regex_2').style.display = 'none';
+          }
+          else if ( selection == pg_regex )
+          {
+            document.getElementById('pg_create_title_catlink').style.display = 'none';
+            document.getElementById('pg_create_catlink_1').style.display = 'none';
+            document.getElementById('pg_create_catlink_2').style.display = 'none';
+            
+            document.getElementById('pg_create_title_tagged').style.display = 'none';
+            document.getElementById('pg_create_tagged_1').style.display = 'none';
+            document.getElementById('pg_create_tagged_2').style.display = 'none';
+            
+            document.getElementById('pg_create_title_normal').style.display = 'none';
+            document.getElementById('pg_create_normal_1').style.display = 'none';
+            document.getElementById('pg_create_normal_2').style.display = 'none';
+            
+            document.getElementById('pg_create_title_regex').style.display = 'inline';
+            document.getElementById('pg_create_regex_1').style.display = 'block';
+            document.getElementById('pg_create_regex_2').style.display = 'block';
           }
         
         }
@@ -199,6 +243,10 @@
           document.getElementById('pg_create_tagged_1').style.display = 'none';
           document.getElementById('pg_create_tagged_2').style.display = 'none';
           
+          document.getElementById('pg_create_title_regex').style.display = 'none';
+          document.getElementById('pg_create_regex_1').style.display = 'none';
+          document.getElementById('pg_create_regex_2').style.display = 'none';
+          
           document.getElementById('pg_create_title_normal').style.display = 'inline';
           document.getElementById('pg_create_normal_1').style.display = 'block';
           document.getElementById('pg_create_normal_2').style.display = 'block';
@@ -292,6 +340,7 @@
                 <option value="' . PAGE_GRP_NORMAL  . '" selected="selected">Static group of pages</option>
                 <option value="' . PAGE_GRP_TAGGED  . '">Group of pages with one tag</option>
                 <option value="' . PAGE_GRP_CATLINK . '">Link to category</option>
+                <option value="' . PAGE_GRP_REGEX   . '">Perl-compatible regular expression (advanced)</option>
               </select>
               </td>
             </tr>';
@@ -308,6 +357,9 @@
                 <span id="pg_create_title_catlink">
                   Mirror a category
                 </span>
+                <span id="pg_create_title_regex">
+                  Filter through a regular expression
+                </span>
               </th>
             </tr>';
       
@@ -324,6 +376,14 @@
                 <div id="pg_create_tagged_1">
                   Include pages with this tag:
                 </div>
+                <div id="pg_create_regex_1">
+                  Regular expression:<br />
+                  <small>Be sure to include the starting and ending delimiters and any flags you might need.<br />
+                         These pages might help: <a href="http://us.php.net/manual/en/reference.pcre.pattern.modifiers.php">Pattern modifiers</a> &bull;
+                         <a href="http://us.php.net/manual/en/reference.pcre.pattern.syntax.php">Pattern syntax</a><br />
+                         Examples: <tt>/^(Special|Admin):/i</tt> &bull; <tt>/^Image:([0-9]+)$/</tt><br />
+                         Developers, remember that this will be matched against the full page identifier string. This means that <tt>/^About_Enano$/</tt>
+                         will NOT match the page Special:About_Enano.</small>
               </td>';
             
       echo '  <td class="row1">
@@ -341,6 +401,9 @@
                 <div id="pg_create_catlink_2">
                   ' . $catlist . '
                 </div>
+                <div id="pg_create_regex_2">
+                  <input type="text" name="regex" size="60" /> 
+                </div>
               </td>
             </tr>';
             
@@ -539,6 +602,23 @@
                   echo '<div class="info-box">The affecting tag was updated.</div>';
               }
             }
+            else if ( $_POST['pg_type'] == PAGE_GRP_REGEX )
+            {
+              $target = $_POST['pg_target'];
+              if ( empty($target) )
+              {
+                echo '<div class="error-box">Please enter an expression to match against..</div>';
+              }
+              else
+              {
+                $target = $db->escape($target);
+                $q = $db->sql_query('UPDATE '.table_prefix.'page_groups SET pg_target=\'' . $target . '\' WHERE pg_id=' . $edit_id . ';');
+                if ( !$q )
+                  $db->_die();
+                else
+                  echo '<div class="info-box">The expression to match against was updated.</div>';
+              }
+            }
             else if ( $_POST['pg_type'] == PAGE_GRP_CATLINK )
             {
               $target = $_POST['pg_target'];
@@ -797,6 +877,22 @@
                   </td>
                 </tr>';
           break;
+        case PAGE_GRP_REGEX:
+          echo '<tr>
+                  <td class="row2">
+                    Regular expression to use:<br />
+                    <small>Be sure to include the starting and ending delimiters and any flags you might need.<br />
+                           These pages might help: <a href="http://us.php.net/manual/en/reference.pcre.pattern.modifiers.php">Pattern modifiers</a> &bull;
+                           <a href="http://us.php.net/manual/en/reference.pcre.pattern.syntax.php">Pattern syntax</a><br />
+                           Examples: <tt>/^(Special|Admin):/i</tt> &bull; <tt>/^Image:([0-9]+)$/</tt><br />
+                           Developers, remember that this will be matched against the full page identifier string. This means that <tt>/^About_Enano$/</tt>
+                           will NOT match the page Special:About_Enano.</small>
+                  </td>
+                  <td class="row1">
+                    <input type="text" name="pg_target" value="' . htmlspecialchars($row['pg_target']) . '" size="30" />
+                  </td>
+                </tr>';
+          break;
         case PAGE_GRP_CATLINK:
           
           // Build category list
@@ -911,6 +1007,9 @@
         case PAGE_GRP_NORMAL:
           $type = 'Static set of pages';
           break;
+        case PAGE_GRP_REGEX:
+          $type = 'Regular expression match';
+          break;
       }
       $target = '';
       if ( $row['pg_type'] == PAGE_GRP_TAGGED )
@@ -921,6 +1020,10 @@
       {
         $target = 'Category: ' . htmlspecialchars(get_page_title($paths->nslist['Category'] . sanitize_page_id($row['pg_target'])));
       }
+      else if ( $row['pg_type'] == PAGE_GRP_REGEX )
+      {
+        $target = 'Expression: <tt>' . htmlspecialchars($row['pg_target']) . '</tt>';
+      }
       $btn_edit = '<input type="submit" name="action[edit][' . $row['pg_id'] . ']" value="Edit" />';
       $btn_del = '<input type="submit" name="action[del][' . $row['pg_id'] . ']" value="Delete" />';
       // stupid jEdit bug/hack
--- a/plugins/admin/SecurityLog.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/admin/SecurityLog.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/plugins/admin/UserManager.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/plugins/admin/UserManager.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * Copyright (C) 2006-2007 Dan Fuhry
  *
  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
--- a/schema.sql	Sat Sep 29 09:43:46 2007 -0400
+++ b/schema.sql	Sat Oct 20 21:59:27 2007 -0400
@@ -1,5 +1,5 @@
 -- Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- Version 1.0 (Banshee)
+-- Version 1.0.2 (Coblynau)
 -- Copyright (C) 2006-2007 Dan Fuhry
 
 -- This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
@@ -278,7 +278,7 @@
   ('copyright_notice', '{{COPYRIGHT}}'),
   ('wiki_edit_notice_text', '== Why can I edit this page? ==\n\nEveryone can edit almost any page in this website. This concept is called a wiki. It gives everyone the opportunity to make a change for the best. While some spam and vandalism may occur, it is believed that most contributions will be legitimate and helpful.\n\nFor security purposes, a history of all page edits is kept, and administrators are able to restore vandalized or spammed pages with just a few clicks.'),
   ('cache_thumbs', '{{ENABLE_CACHE}}'),
-  ('max_file_size', '256000'),('enano_version', '{{VERSION}}'),('enano_beta_version', '{{BETA_VERSION}}'),( 'allowed_mime_types', 'cbf:len=168;crc=c3dcad3f;data=0[1],1[4],0[3],1[1],0[2],1[1],0[11],1[1],0[7],1[1],0[9],1[1],0[6],1[3],0[10],1[1],0[2],1[2],0[1],1[1],0[1],1[2],0[6],1[3],0[1],1[1],0[2],1[4],0[1],1[2],0[3],1[1],0[4],1[2],0[26],1[5],0[6],1[2],0[2],1[1],0[4],1[1],0[10],1[2],0[1],1[1],0[6]|end' ),
+  ('max_file_size', '256000'),('enano_version', '{{VERSION}}'),( 'allowed_mime_types', 'cbf:len=168;crc=c3dcad3f;data=0[1],1[4],0[3],1[1],0[2],1[1],0[11],1[1],0[7],1[1],0[9],1[1],0[6],1[3],0[10],1[1],0[2],1[2],0[1],1[1],0[1],1[2],0[6],1[3],0[1],1[1],0[2],1[4],0[1],1[2],0[3],1[1],0[4],1[2],0[26],1[5],0[6],1[2],0[2],1[1],0[4],1[1],0[10],1[2],0[1],1[1],0[6]|end' ),
   ('contact_email', '{{ADMIN_EMAIL}}'),
   ('powered_btn', '1');
 
--- a/themes/oxygen/css/bleu.css	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/oxygen/css/bleu.css	Sat Oct 20 21:59:27 2007 -0400
@@ -6,7 +6,7 @@
  
 /* The basics */
 html,body                          { height: 100%; }
-body                               { margin: 0; padding: 0; background: url(../images/bleu/bg.png); font-family: trebuchet ms, verdana, arial, helvetica, sans-serif; font-size: 9pt; }
+body                               { /* color added in 1.0.2 to fix light text in dark desktop themes */ color: #202020; margin: 0; padding: 0; background: url(../images/bleu/bg.png); font-family: trebuchet ms, verdana, arial, helvetica, sans-serif; font-size: 9pt; }
 .holder                            { border: 1px solid #CCCCCC; padding: 1px; background-color: #FFFFFF; color: #444444 }
 div.pad                            { padding: 10px; }                         
 table#title                        { margin: 0; padding: 0; height: 100px; background-color: #90B0D0; text-align: center; }
--- a/themes/oxygen/footer.tpl	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/oxygen/footer.tpl	Sat Oct 20 21:59:27 2007 -0400
@@ -13,6 +13,8 @@
             <div id="credits">
               <b>{COPYRIGHT}</b><br />
               Website engine powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>&nbsp;&nbsp;|&nbsp;&nbsp;Generated in [[GenTime]]sec
+              <!-- Do not remove this line or scheduled tasks will not run. -->
+              <img alt=" " src="{SCRIPTPATH}/cron.php" width="1" height="1" />
             </div>
           
           </td><td id="mdg-btr"></td></tr>
--- a/themes/printable/css/default.css	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/printable/css/default.css	Sat Oct 20 21:59:27 2007 -0400
@@ -6,7 +6,7 @@
  
 /* The basics */
 html,body                          { height: 100%; }
-body                               { margin: 0; padding: 0; background-color: #FFFFFF; font-family: trebuchet ms, verdana, arial, helvetica, sans-serif; font-size: 9pt; }
+body                               { /* color added in 1.0.2 to fix light text in dark desktop themes */ color: #202020; margin: 0; padding: 0; background-color: #FFFFFF; font-family: trebuchet ms, verdana, arial, helvetica, sans-serif; font-size: 9pt; }
 .holder                            { border: 1px solid #CCCCCC; padding: 1px; background-color: #FFFFFF; color: #444444 }
 div.pad                            { padding: 10px; }                         
 table#title                        { margin: 0; padding: 0; height: 100px; background-color: #90B0D0; text-align: center; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/stpatty/css-extra/ie-fixes-shamrock.css	Sat Oct 20 21:59:27 2007 -0400
@@ -0,0 +1,23 @@
+/*
+ * St. Patty theme for Enano
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This theme is Free Software, available under the terms of the GNU General Public License. See the file "GPL" included with this
+ * package for details.
+ */
+ 
+div#bg {
+  background-image: none;
+}
+div#title {
+  /* background-image: none; */
+}
+div#rap {
+  background-image: url(../images/rap-ie.gif);
+}
+div#sidebar ul li a {
+  margin-bottom: -14px;
+}
+div#pagetools a {
+  padding: 4px 3px;
+}
--- a/themes/stpatty/css-extra/ie-fixes.css	Sat Sep 29 09:43:46 2007 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/*
- * St. Patty theme for Enano
- * Copyright (C) 2007 Dan Fuhry
- *
- * This theme is Free Software, available under the terms of the GNU General Public License. See the file "GPL" included with this
- * package for details.
- */
- 
-div#bg {
-  background-image: none;
-}
-div#title {
-  /* background-image: none; */
-}
-div#rap {
-  background-image: url(../images/rap-ie.gif);
-}
-div#sidebar ul li a {
-  margin-bottom: -14px;
-}
-div#pagetools a {
-  padding: 4px 3px;
-}
--- a/themes/stpatty/css/shamrock.css	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/stpatty/css/shamrock.css	Sat Oct 20 21:59:27 2007 -0400
@@ -16,6 +16,8 @@
 }
 body {
   background-color: #101d14;
+  /* color added in 1.0.2 to fix light text in dark desktop themes */
+  color: #202020;
   background-image: url(../images/bghatching.gif);
   background-repeat: repeat;
   font-family: "Lucida Sans Unicode", sans-serif;
--- a/themes/stpatty/footer.tpl	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/stpatty/footer.tpl	Sat Oct 20 21:59:27 2007 -0400
@@ -11,6 +11,8 @@
                  -->
           <b>{COPYRIGHT}</b><br />
           Powered by <a href="<!-- BEGIN stupid_mode -->http://enanocms.org/<!-- BEGINELSE stupid_mode -->{URL_ABOUT_ENANO}<!-- END stupid_mode -->">Enano</a>  |  <a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.1</a>  |  <a href="http://jigsaw.w3.org/css-validator/validator?uri=referer">Valid CSS</a>  |  [[Stats]]
+          <!-- Do not remove this line or scheduled tasks will not run. -->
+          <img alt=" " src="{SCRIPTPATH}/cron.php" width="1" height="1" />
         </div>
       </div>
     </div>
--- a/themes/stpatty/header.tpl	Sat Sep 29 09:43:46 2007 -0400
+++ b/themes/stpatty/header.tpl	Sat Oct 20 21:59:27 2007 -0400
@@ -9,10 +9,15 @@
     <!-- This script automatically loads the other 15 JS files -->
     <script type="text/javascript" src="{SCRIPTPATH}/includes/clientside/static/enano-lib-basic.js"></script>
     <!--[if lt IE 7]>
-    <link rel="stylesheet" type="text/css" href="{SCRIPTPATH}/themes/{THEME_ID}/css-extra/ie-fixes.css" />
+    <link rel="stylesheet" type="text/css" href="{SCRIPTPATH}/themes/{THEME_ID}/css-extra/ie-fixes-{STYLE_ID}.css" />
     <![endif]-->
     <script type="text/javascript">
     // <![CDATA[
+    
+      // Disable transition effects for the ACL editor
+      // (they're real slow in this theme, at least in fx/opera/IE)
+      var aclDisableTransitionFX = true;
+    
       function ajaxRenameInline()
       {
         // This trick is _so_ vBulletin...
--- a/upgrade.php	Sat Sep 29 09:43:46 2007 -0400
+++ b/upgrade.php	Sat Oct 20 21:59:27 2007 -0400
@@ -2,7 +2,7 @@
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.0.2 (Coblynau)
+ * Version 1.1.1
  * upgrade.php - upgrade script
  * Copyright (C) 2006-2007 Dan Fuhry
  *
@@ -61,7 +61,7 @@
 // Everything related to versions goes here!
 
 // Valid versions to upgrade from
-$valid_versions = Array('1.0b1', '1.0b2', '1.0b3', '1.0b4', '1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.1.1');
+$valid_versions = Array('1.0b1', '1.0b2', '1.0b3', '1.0b4', '1.0RC1', '1.0RC2', '1.0RC3', '1.0', '1.0.1', '1.0.1.1', '1.0.2b1');
 
 // Basically a list of dependencies, which should be resolved automatically
 // If, for example, upgrading from 1.0b1 to 1.0RC1 requires one extra query that would not
@@ -75,9 +75,10 @@
     '1.0RC2' => Array('1.0RC3'),
     '1.0RC3' => Array('1.0'),
     '1.0' => Array('1.0.1'),
-    '1.0.1' => Array('1.0.1.1')
+    '1.0.1' => Array('1.0.1.1'),
+    '1.0.1.1' => Array('1.0.2b1')
   );
-$this_version   = '1.0.2b1';
+$this_version   = '1.0.2';
 $func_list = Array(
     '1.0' => Array('u_1_0_1_update_del_votes'),
     '1.0b4' => Array('u_1_0_RC1_update_user_ids', 'u_1_0_RC1_add_admins_to_group', 'u_1_0_RC1_alter_files_table', 'u_1_0_RC1_destroy_session_cookie', 'u_1_0_RC1_set_contact_email', 'u_1_0_RC1_update_page_text'), // ,
@@ -157,7 +158,7 @@
 $session->start();
 
 $template = new template_nodb();
-$template->load_theme('stpatty', 'shamrock', false);
+$template->load_theme('oxygen', 'bleu', false);
 
 $modestrings = Array(
               'login'      => 'Administrative login',
@@ -525,7 +526,7 @@
     ?>
     
     <div style="text-align: center; margin-top: 10px;">
-      <img alt="[ Enano CMS Project logo ]" src="images/enano-artwork/installer-greeting-green.png" style="display: block; margin: 0 auto; padding-left: 134px;" />
+      <img alt="[ Enano CMS Project logo ]" src="images/enano-artwork/installer-greeting-blue.png" style="display: block; margin: 0 auto; padding-left: 134px;" />
       <h2>Welcome to the Enano upgrade wizard</h2>
       <?php
       if ( file_exists('./_nightly.php') )
@@ -619,10 +620,10 @@
       $schema = file_get_contents('upgrade.sql');
       
       // Strip out and process version blocks
-      preg_match_all('#---BEGIN ([0-9A-z\.\-]*?)---'."\n".'(.*?)'."\n".'---END \\1---#is', $schema, $matches);
+      preg_match_all('#---BEGIN ([0-9A-z\.\-]*?)---'."\n".'((.*?)'."\n)?".'---END \\1---#is', $schema, $matches);
       
       $from_list  =& $matches[1];
-      $query_list =& $matches[2];
+      $query_list =& $matches[3];
       
       foreach($matches[0] as $m)
       {
--- a/upgrade.sql	Sat Sep 29 09:43:46 2007 -0400
+++ b/upgrade.sql	Sat Oct 20 21:59:27 2007 -0400
@@ -3,12 +3,14 @@
 -- ALL NON-SQL LINES, even otherwise blank lines, must start with "--" or they will get sent to MySQL!
 -- Common tasks (version numbers)
 DELETE FROM {{TABLE_PREFIX}}config WHERE config_name='enano_version' OR config_name='enano_beta_version' OR config_name='enano_alpha_version' OR config_name='enano_rc_version';
-INSERT INTO {{TABLE_PREFIX}}config (config_name, config_value) VALUES( 'enano_version', '1.0.2' ),( 'enano_beta_version', '1' );
+INSERT INTO {{TABLE_PREFIX}}config (config_name, config_value) VALUES( 'enano_version', '1.0.2' );
+---BEGIN 1.0.2b1---
+-- This is really optional, but could reduce confusion if regex page groups get truncated for no apparent reason.
+ALTER TABLE {{TABLE_PREFIX}}page_groups MODIFY COLUMN pg_target text DEFAULT NULL;
+---END 1.0.2b1---
 ---BEGIN 1.0.1.1---
--- No changes in this release
 ---END 1.0.1.1---
 ---BEGIN 1.0.1---
--- No changes in this release
 ---END 1.0.1---
 ---BEGIN 1.0---
 -- Fix for obnoxious $_GET issue