Improved and enabled HTML optimization algorithm; enabled gzip compression; added but did not test at all the tag cloud class in includes/tagcloud.php, this is still very preliminary and not ready for any type of production use
authorDan
Wed, 01 Aug 2007 13:39:27 -0400
changeset 80 cb7dde69c301
parent 79 5faff33a6580
child 81 d7fc25acd3f3
Improved and enabled HTML optimization algorithm; enabled gzip compression; added but did not test at all the tag cloud class in includes/tagcloud.php, this is still very preliminary and not ready for any type of production use
ajax.php
includes/clientside/sbedit.js
includes/clientside/static/ajax.js
includes/clientside/static/dropdown.js
includes/common.php
includes/functions.php
includes/paths.php
includes/tagcloud.php
index.php
install.php
plugins/SpecialAdmin.php
plugins/SpecialUpdownload.php
plugins/admin/PageGroups.php
schema.sql
themes/admin/footer.tpl
themes/admin/js/menu.js
themes/oxygen/header.tpl
--- a/ajax.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/ajax.php	Wed Aug 01 13:39:27 2007 -0400
@@ -224,7 +224,7 @@
       $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       
       $ret = array('tags' => array(), 'user_level' => $session->user_level, 'can_add' => $session->get_permissions('tag_create'));
-      $q = $db->sql_query('SELECT t.tag_id, t.tag_name, pg.pg_target IS NULL AS used_in_acl, t.user FROM '.table_prefix.'tags AS t
+      $q = $db->sql_query('SELECT t.tag_id, t.tag_name, pg.pg_target IS NOT NULL AS used_in_acl, t.user FROM '.table_prefix.'tags AS t
         LEFT JOIN '.table_prefix.'page_groups AS pg
           ON ( ( pg.pg_type = ' . PAGE_GRP_TAGGED . ' AND pg.pg_target=t.tag_name ) OR ( pg.pg_type IS NULL AND pg.pg_target IS NULL ) )
         WHERE t.page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND t.namespace=\'' . $db->escape($paths->namespace) . '\';');
@@ -233,21 +233,141 @@
       
       while ( $row = $db->fetchrow() )
       {
-        $can_del = ( 
-          ( $session->get_permissions('tag_delete_own') && $row['user'] == $session->user_id && $session->user_logged_in ) || // User created the tag and can remove own tags
-          ( $session->get_permissions('tag_delete_other') && $row['used_in_acl'] != 1 ) || // User can remove tags and the tag isn't used in an ACL (page group)
-          ( $row['used_in_acl'] == 1 && $session->get_permissions('tag_delete_own') && $session->get_permissions('tag_delete_other') && ( $session->get_permissions('edit_acl') || $session->user_level >= USER_LEVEL_ADMIN ) )
-          );
+        $can_del = true;
+        
+        $perm = ( $row['user'] != $session->user_id ) ?
+                'tag_delete_other' :
+                'tag_delete_own';
+        
+        if ( $row['user'] == 1 && !$session->user_logged_in )
+          // anonymous user trying to delete tag (hardcode blacklisted)
+          $can_del = false;
+          
+        if ( !$session->get_permissions($perm) )
+          $can_del = false;
+        
+        if ( $row['used_in_acl'] == 1 && !$session->get_permissions('edit_acl') && $session->user_level < USER_LEVEL_ADMIN )
+          $can_del = false;
+        
         $ret['tags'][] = array(
           'id' => $row['tag_id'],
           'name' => $row['tag_name'],
-          'can_del' => $can_del
+          'can_del' => $can_del,
+          'acl' => ( $row['used_in_acl'] == 1 )
         );
       }
       
       echo $json->encode($ret);
       
       break;
+    case 'addtag':
+      $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+      $resp = array(
+          'success' => false,
+          'error' => 'No error',
+          'can_del' => ( $session->get_permissions('tag_delete_own') && $session->user_logged_in ),
+          'in_acl' => false
+        );
+      
+      // first of course, are we allowed to tag pages?
+      if ( !$session->get_permissions('tag_create') )
+      {
+        $resp['error'] = 'You are not permitted to tag pages.';
+        die($json->encode($resp));
+      }
+      
+      // sanitize the tag name
+      $tag = sanitize_tag($_POST['tag']);
+      $tag = $db->escape($tag);
+      
+      if ( strlen($tag) < 2 )
+      {
+        $resp['error'] = 'Tags must consist of at least 2 alphanumeric characters.';
+        die($json->encode($resp));
+      }
+      
+      // check if tag is already on page
+      $q = $db->sql_query('SELECT 1 FROM '.table_prefix.'tags WHERE page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND namespace=\'' . $db->escape($paths->namespace) . '\' AND tag_name=\'' . $tag . '\';');
+      if ( !$q )
+        $db->_die();
+      if ( $db->numrows() > 0 )
+      {
+        $resp['error'] = 'This page already has this tag.';
+        die($json->encode($resp));
+      }
+      $db->free_result();
+      
+      // tricky: make sure this tag isn't being used in some page group, and thus adding it could affect page access
+      $can_edit_acl = ( $session->get_permissions('edit_acl') || $session->user_level >= USER_LEVEL_ADMIN );
+      $q = $db->sql_query('SELECT 1 FROM '.table_prefix.'page_groups WHERE pg_type=' . PAGE_GRP_TAGGED . ' AND pg_target=\'' . $tag . '\';');
+      if ( !$q )
+        $db->_die();
+      if ( $db->numrows() > 0 && !$can_edit_acl )
+      {
+        $resp['error'] = 'This tag is used in an ACL page group, and thus can\'t be added to a page by people without administrator privileges.';
+        die($json->encode($resp));
+      }
+      $resp['in_acl'] = ( $db->numrows() > 0 );
+      $db->free_result();
+      
+      // we're good
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'tags(tag_name,page_id,namespace,user) VALUES(\'' . $tag . '\', \'' . $db->escape($paths->cpage['urlname_nons']) . '\', \'' . $db->escape($paths->namespace) . '\', ' . $session->user_id . ');');
+      if ( !$q )
+        $db->_die();
+      
+      $resp['success'] = true;
+      $resp['tag'] = $tag;
+      $resp['tag_id'] = $db->insert_id();
+      
+      echo $json->encode($resp);
+      break;
+    case 'deltag':
+      
+      $tag_id = intval($_POST['tag_id']);
+      if ( empty($tag_id) )
+        die('Invalid tag ID');
+      
+      $q = $db->sql_query('SELECT t.tag_id, t.user, t.page_id, t.namespace, pg.pg_target IS NOT NULL AS used_in_acl FROM '.table_prefix.'tags AS t
+  LEFT JOIN '.table_prefix.'page_groups AS pg
+    ON ( pg.pg_id IS NULL OR ( pg.pg_target = t.tag_name AND pg.pg_type = ' . PAGE_GRP_TAGGED . ' ) )
+  WHERE t.tag_id=' . $tag_id . ';');
+      
+      if ( !$q )
+        $db->_die();
+      
+      if ( $db->numrows() < 1 )
+        die('Could not find a tag with that ID');
+      
+      $row = $db->fetchrow();
+      $db->free_result();
+      
+      if ( $row['page_id'] == $paths->cpage['urlname_nons'] && $row['namespace'] == $paths->namespace )
+        $perms =& $session;
+      else
+        $perms = $session->fetch_page_acl($row['page_id'], $row['namespace']);
+        
+      $perm = ( $row['user'] != $session->user_id ) ?
+                'tag_delete_other' :
+                'tag_delete_own';
+      
+      if ( $row['user'] == 1 && !$session->user_logged_in )
+        // anonymous user trying to delete tag (hardcode blacklisted)
+        die('You are not authorized to delete this tag.');
+        
+      if ( !$perms->get_permissions($perm) )
+        die('You are not authorized to delete this tag.');
+      
+      if ( $row['used_in_acl'] == 1 && !$perms->get_permissions('edit_acl') && $session->user_level < USER_LEVEL_ADMIN )
+        die('You are not authorized to delete this tag.');
+      
+      // We're good
+      $q = $db->sql_query('DELETE FROM '.table_prefix.'tags WHERE tag_id = ' . $tag_id . ';');
+      if ( !$q )
+        $db->_die();
+      
+      echo 'success';
+      
+      break;
     default:
       die('Hacking attempt');
       break;
--- a/includes/clientside/sbedit.js	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/clientside/sbedit.js	Wed Aug 01 13:39:27 2007 -0400
@@ -60,10 +60,10 @@
         var thediv = document.createElement('div');
         //if(!oElm.id) oElm.id = 'autoEditButton_'+Math.floor(Math.random() * 100000);
         oElm = oElm.parentNode;
-        o = fetch_offset(oElm);
-        d = fetch_dimensions(oElm);
-        top = o['top'] + d['h'] + 'px';
-        left = o['left'] + 'px';
+        var magic = $(oElm).Top() + $(oElm).Height();
+        var top = String(magic);
+        top = top + 'px';
+        left = $(oElm).Left() + 'px';
         thediv.style.top = top;
         thediv.style.left = left;
         thediv.style.position = 'absolute';
@@ -107,10 +107,13 @@
         eval(ajax.responseText);
         if(status == 'GOOD')
         {
-          parent = document.getElementById('disabled_'+id).parentNode.parentNode;
+          var _id = 'disabled_' + String(id);
+          var parent = document.getElementById(_id).parentNode.parentNode;
           oElm.parentNode.parentNode.removeChild(oElm.parentNode);
           content = content.replace('%a', unescape('%0A'));
-          parent.firstChild.nextSibling.nextSibling.nextSibling.innerHTML = content; // $content is set in ajax.responseText
+          var obj = ( IE ) ? parent.firstChild.nextSibling.nextSibling : parent.firstChild.nextSibling.nextSibling.nextSibling;
+          if ( obj )
+            obj.innerHTML = content; // $content is set in ajax.responseText
         }
         else
         {
--- a/includes/clientside/static/ajax.js	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/clientside/static/ajax.js	Wed Aug 01 13:39:27 2007 -0400
@@ -893,7 +893,8 @@
             var a = document.createElement('a');
             a.appendChild(document.createTextNode('[X]'));
             a.href = '#';
-            a.onclick = function() { return false; }
+            a._js_tag_id = json.tags[i].id;
+            a.onclick = function() { ajaxDeleteTag(this, this._js_tag_id); return false; }
             catbox.appendChild(a);
           }
           if ( ( i + 1 ) < json.tags.length )
@@ -912,17 +913,28 @@
     });
 }
 
+var addtag_open = false;
+
 function ajaxAddTagStage1()
 {
+  if ( addtag_open )
+    return false;
   var catbox = document.getElementById('mdgCatBox');
   var adddiv = document.createElement('div');
   var text = document.createElement('input');
   var addlink = document.createElement('a');
   addlink.href = '#';
-  addlink.onclick = function() { return false; };
+  addlink.onclick = function() { ajaxAddTagStage2(this.parentNode.firstChild.nextSibling.value, this.parentNode); return false; };
   addlink.appendChild(document.createTextNode('+ Add'));
   text.type = 'text';
   text.size = '15';
+  text.onkeyup = function(e)
+  {
+    if ( e.keyCode == 13 )
+    {
+      ajaxAddTagStage2(this.value, this.parentNode);
+    }
+  }
   
   adddiv.style.margin = '5px 0 0 0';
   adddiv.appendChild(document.createTextNode('Add a tag: '));
@@ -930,6 +942,119 @@
   adddiv.appendChild(document.createTextNode(' '));
   adddiv.appendChild(addlink);
   catbox.appendChild(adddiv);
+  addtag_open = true;
+}
+
+var addtag_nukeme = false;
+
+function ajaxAddTagStage2(tag, nukeme)
+{
+  if ( !addtag_open )
+    return false;
+  if ( addtag_nukeme )
+    return false;
+  addtag_nukeme = nukeme;
+  tag = ajaxEscape(tag);
+  setAjaxLoading();
+  ajaxPost(stdAjaxPrefix + '&_mode=addtag', 'tag=' + tag, function()
+    {
+      if ( ajax.readyState == 4 )
+      {
+        unsetAjaxLoading();
+        var nukeme = addtag_nukeme;
+        addtag_nukeme = false;
+        var resptext = String(ajax.responseText + ' ');
+        resptext = resptext.substr(0, resptext.length-1);
+        if ( resptext.substr(0, 1) != '{' )
+        {
+          alert('Invalid JSON response from server:\n' + resptext);
+          return false;
+        }
+        var json = parseJSON(resptext);
+        var parent = nukeme.parentNode;
+        parent.removeChild(nukeme);
+        addtag_open = false;
+        if ( json.success )
+        {
+          var node = parent.childNodes[1];
+          var insertafter = false;
+          var nukeafter = false;
+          if ( node.nodeValue == 'No tags on this page' )
+          {
+            nukeafter = true;
+          }
+          insertafter = parent.childNodes[ parent.childNodes.length - 3 ];
+          // these need to be inserted in reverse order
+          if ( json.can_del )
+          {
+            var a = document.createElement('a');
+            a.appendChild(document.createTextNode('[X]'));
+            a.href = '#';
+            a._js_tag_id = json.tag_id;
+            a.onclick = function() { ajaxDeleteTag(this, this._js_tag_id); return false; }
+            insertAfter(parent, a, insertafter);
+            insertAfter(parent, document.createTextNode(' '), insertafter);
+          }
+          insertAfter(parent, document.createTextNode(json.tag), insertafter);
+          if ( !nukeafter )
+          {
+            insertAfter(parent, document.createTextNode(', '), insertafter);
+          }
+          if ( nukeafter )
+          {
+            parent.removeChild(insertafter);
+          }
+        }
+        else
+        {
+          alert(json.error);
+        }
+      }
+    });
+}
+
+function ajaxDeleteTag(parentobj, tag_id)
+{
+  var arrDelete = [ parentobj, parentobj.previousSibling, parentobj.previousSibling.previousSibling ];
+  var parent = parentobj.parentNode;
+  var writeNoTags = false;
+  if ( parentobj.previousSibling.previousSibling.previousSibling.nodeValue == ', ' )
+    arrDelete.push(parentobj.previousSibling.previousSibling.previousSibling);
+  else if ( parentobj.previousSibling.previousSibling.previousSibling.nodeValue == 'Page tags: ' )
+    arrDelete.push(parentobj.nextSibling);
+  
+  if ( parentobj.previousSibling.previousSibling.previousSibling.nodeValue == 'Page tags: ' &&
+       parentobj.nextSibling.nextSibling.firstChild )
+    if ( parentobj.nextSibling.nextSibling.firstChild.nodeValue == '(add a tag)')
+      writeNoTags = true;
+    
+  ajaxPost(stdAjaxPrefix + '&_mode=deltag', 'tag_id=' + String(tag_id), function()
+    {
+      if ( ajax.readyState == 4 )
+      {
+        if ( ajax.responseText == 'success' )
+        {
+          for ( var i = 0; i < arrDelete.length; i++ )
+          {
+            try
+            {
+              parent.removeChild(arrDelete[i]);
+            } catch(e) {}
+          }
+          if ( writeNoTags )
+          {
+            var node1 = document.createTextNode('No tags on this page');
+            var node2 = document.createTextNode(' ');
+            insertAfter(parent, node1, parent.firstChild);
+            insertAfter(parent, node2, node1);
+          }
+        }
+        else
+        {
+          alert(ajax.responseText);
+        }
+      }
+    });
 }
 
 function ajaxTagToCat()
--- a/includes/clientside/static/dropdown.js	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/clientside/static/dropdown.js	Wed Aug 01 13:39:27 2007 -0400
@@ -486,9 +486,12 @@
       event = window.event;
     }
     clX = event.clientX;
-    sL  = document.body.scrollLeft;
+    if ( document.body )
+      sL  = document.body.scrollLeft;
+    else
+      sL  = 0;
     mouseX = clX + sL;
-    mouseY = event.clientY + document.body.scrollTop;
+    mouseY = event.clientY + ( document.body ? document.body.scrollTop : 0 );
     return;
   }
   if( typeof(event.clientX) == 'number' )
--- a/includes/common.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/common.php	Wed Aug 01 13:39:27 2007 -0400
@@ -91,6 +91,7 @@
 require_once(ENANO_ROOT.'/includes/json.php');
 require_once(ENANO_ROOT.'/includes/wikiengine/Tables.php');
 require_once(ENANO_ROOT.'/includes/pageprocess.php');
+require_once(ENANO_ROOT.'/includes/tagcloud.php');
 
 strip_magic_quotes_gpc();
 
@@ -172,7 +173,8 @@
     table_prefix.'acl',
     table_prefix.'search_cache',
     table_prefix.'page_groups',
-    table_prefix.'page_group_members'
+    table_prefix.'page_group_members',
+    table_prefix.'tags'
   );
 
 dc_here('common: initializing base classes');
--- a/includes/functions.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/functions.php	Wed Aug 01 13:39:27 2007 -0400
@@ -732,12 +732,9 @@
   if ( $paths->namespace != 'Special' && $paths->namespace != 'Admin' )
   {
     echo '<div class="mdg-comment" style="margin: 10px 0 0 0;">';
-    if ( $session->user_level >= USER_LEVEL_ADMIN )
-    {
-      echo '<div style="float: right;">';
-      echo '(<a href="#" onclick="ajaxCatToTag(); return false;">show page tags</a>)';
-      echo '</div>';
-    }
+    echo '<div style="float: right;">';
+    echo '(<a href="#" onclick="ajaxCatToTag(); return false;">show page tags</a>)';
+    echo '</div>';
     echo '<div id="mdgCatBox">Categories: ';
     
     $where = '( c.page_id=\'' . $db->escape($paths->cpage['urlname_nons']) . '\' AND c.namespace=\'' . $db->escape($paths->namespace) . '\' )';
@@ -2656,6 +2653,158 @@
   return $array;
 }
 
+/**
+ * Sanitizes a page tag.
+ * @param string
+ * @return string
+ */
+
+function sanitize_tag($tag)
+{
+  $tag = strtolower($tag);
+  $tag = preg_replace('/[^\w _-]+/', '', $tag);
+  $tag = trim($tag);
+  return $tag;
+}
+
+/**
+ * Gzips the output buffer.
+ */
+
+function gzip_output()
+{
+  global $do_gzip;
+  
+  //
+  // Compress buffered output if required and send to browser
+  //
+  if ( $do_gzip && function_exists('ob_gzhandler') )
+  {
+    //
+    // Copied from phpBB, which was in turn borrowed from php.net
+    //
+    $gzip_contents = ob_get_contents();
+    ob_end_clean();
+    
+    header('Content-encoding: gzip');
+    $gzip_contents = ob_gzhandler($gzip_contents);
+    echo $gzip_contents;
+  }
+}
+
+/**
+ * Aggressively and hopefully non-destructively optimizes a blob of HTML.
+ * @param string HTML to process
+ * @return string much snaller HTML
+ */
+
+function aggressive_optimize_html($html)
+{
+  $size_before = strlen($html);
+  
+  // kill carriage returns
+  $html = str_replace("\r", "", $html);
+  
+  // Optimize (but don't obfuscate) Javascript
+  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',
+                          'double', 'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import',
+                          'in', 'instanceof', 'int', 'interface', 'is', 'long', 'namespace', 'native', 'new', 'null', 'package', 'private', 'protected', 'public',
+                          'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'use', 'var',
+                          'void', 'volatile', 'while', 'with');
+  
+  $reserved_words = '(' . implode('|', $reserved_words) . ')';
+  
+  for ( $i = 0; $i < count($jscript[0]); $i++ )
+  {
+    $js =& $jscript[2][$i];
+    
+    // for line optimization, explode it
+    $particles = explode("\n", $js);
+    
+    foreach ( $particles as $j => $atom )
+    {
+      // Remove comments
+      $atom = preg_replace('#\/\/(.+)#i', '', $atom);
+      
+      $atom = trim($atom);
+      if ( empty($atom) )
+        unset($particles[$j]);
+      else
+        $particles[$j] = $atom;
+    }
+    
+    $js = implode("\n", $particles);
+    
+    $js = preg_replace('#/\*(.*?)\*/#s', '', $js);
+    
+    // find all semicolons and then linebreaks, and replace with a single semicolon
+    $js = str_replace(";\n", ';', $js);
+    
+    // starting braces
+    $js = preg_replace('/\{([\s]+)/m', '{', $js);
+    $js = str_replace(")\n{", '){', $js);
+    
+    // ending braces (tricky)
+    $js = preg_replace('/\}([^;])/m', '};\\1', $js);
+    
+    // other rules
+    $js = str_replace("};\n", "};", $js);
+    $js = str_replace(",\n", ',', $js);
+    $js = str_replace("[\n", '[', $js);
+    $js = str_replace("]\n", ']', $js);
+    $js = str_replace("\n}", '}', $js);
+    
+    // newlines immediately before reserved words
+    $js = preg_replace("/(\)|;)\n$reserved_words/is", '\\1\\2', $js);
+    
+    // fix for firefox issue
+    $js = preg_replace('/\};([\s]*)(else|\))/i', '}\\2', $js);
+    
+    // apply changes
+    $html = str_replace($jscript[0][$i], "<script{$jscript[1][$i]}>$js</script>", $html);
+  }
+  
+  // Which tags to strip - you can change this if needed
+  $strip_tags = Array('pre', 'script', 'style', 'enano:no-opt');
+  $strip_tags = implode('|', $strip_tags);
+  
+  // Strip out the tags and replace with placeholders
+  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++)
+  {
+    $html = str_replace($matches[0][$i], "{DONT_STRIP_ME_NAKED:$seed:$i}", $html);
+  }
+  
+  // Finally, process the HTML
+  $html = preg_replace("#\n([ ]*)#", " ", $html);
+  
+  // Remove annoying spaces between tags
+  $html = preg_replace("#>([ ][ ]+)<#", "> <", $html);
+  
+  // Re-insert untouchable tags
+  for ($i = 0;$i < sizeof($matches[1]); $i++)
+  {
+    $html = str_replace("{DONT_STRIP_ME_NAKED:$seed:$i}", "<{$matches[1][$i]}{$matches[2][$i]}>{$matches[3][$i]}</{$matches[4][$i]}>", $html);
+  }
+  
+  // Remove <enano:no-opt> blocks (can be used by themes that don't want their HTML optimized)
+  $html = preg_replace('#<(\/|)enano:no-opt(.*?)>#', '', $html);
+  
+  $size_after = strlen($html);
+  
+  // Tell snoopish users what's going on
+  $html = str_replace('<html>', "\n".'<!-- NOTE: Enano has performed an HTML optimization routine on the HTML you see here. This is to enhance page loading speeds.
+     To view the uncompressed source of this page, add the "nocompress" parameter to the URI of this page: index.php?title=Main_Page&nocompress or Main_Page?nocompress'."
+     Size before compression: $size_before bytes
+     Size after compression:  $size_after bytes
+     -->\n<html>", $html);
+  return $html;
+}
+
 //die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
 
 ?>
--- a/includes/paths.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/includes/paths.php	Wed Aug 01 13:39:27 2007 -0400
@@ -845,9 +845,16 @@
     
     // What linked categories have this page?
     $q = $db->sql_query('SELECT g.pg_id 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 . ' )
-                           WHERE c.page_id=\'' . $page_id . '\' AND c.namespace=\'' . $namespace . '\';');
+  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
+    ON ( ( g.pg_id = m.pg_id AND g.pg_type = ' . PAGE_GRP_NORMAL . ' ) OR ( m.pg_id IS NULL ) )
+  LEFT JOIN '.table_prefix.'tags AS t
+    ON ( ( t.tag_name = g.pg_target AND pg_type = ' . PAGE_GRP_TAGGED . ' ) OR t.tag_name IS NULL )
+  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 . '\' );');
     if ( !$q )
       $db->_die();
     
@@ -858,6 +865,7 @@
     
     $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
@@ -873,7 +881,20 @@
       $group_list[] = $row['pg_id'];
     }
     
-    // Tagging ain't implemented yet ;-)
+    // 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'];
+    }
+    */
     
     return $group_list;
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/tagcloud.php	Wed Aug 01 13:39:27 2007 -0400
@@ -0,0 +1,153 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.0.1 (Loch Ness)
+ * 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.
+ */
+
+/**
+ * Class for formatting and displaying tag clouds. Loosely based the reference cloud engine from <http://www.lotsofcode.com/php/tutorials/tag-cloud>.
+ * @package Enano
+ * @subpackage Presentation/UI
+ * @copyright (C) 2007 Dan Fuhry
+ * @license GNU General Public License, version 2 or at your option any later versionc
+ */
+
+class TagCloud
+{
+  
+  /**
+   * The list of words in the cloud.
+   * @var array
+   */
+  
+  var $words = array();
+  
+  /**
+   * Constructor.
+   * @param array Optional. An initial list of words, just a plain old array.
+   */
+  
+  function __construct($words)
+  {
+    if ( count($words) > 0 )
+    {
+      foreach ( $words as $word )
+        $this->add_word($word);
+    }
+  }
+  
+  /**
+   * Adds a word into the word list.
+   * @param string The word to add
+   */
+  
+  function add_word($word)
+  {
+    $word = strtolower($word);
+    
+    if ( isset($this->words[$word]) )
+      $this->words[$word] += 1;
+    else
+      $this->words[$word] = 1;
+  }
+  
+  /**
+   * Returns the total size of the cloud.
+   * @return int
+   */
+  
+  function get_cloud_size()
+  {
+    return array_sum($this->words);
+  }
+  
+  /**
+   * Shuffles the cloud.
+   */
+  
+  function shuffle_cloud()
+  {
+    $keys = array_keys($this->words);
+    if ( !$keys || empty($keys) || !is_array($keys) )
+      return null;
+    
+    shuffle($keys);
+    if ( !$keys || empty($keys) || !is_array($keys) )
+      return null;
+    
+    $temp = $this->words;
+    $this->words = array();
+    foreach ( $keys as $word )
+    {
+      $this->words[$word] = $temp[$word];
+    }
+    
+    unset($temp);
+  }
+  
+  /**
+   * Returns the popularity index (scale class) for a 1-100 number.
+   * @param int
+   * @return int
+   */
+  
+  function get_scale_class($val)
+  {
+    $ret = 0;
+    if ( $val >= 99 )
+      $ret = 1;
+    else if ( $val >= 70 )
+      $ret = 2;
+    else if ( $val >= 60 )
+      $ret = 3;
+    else if ( $val >= 50 )
+      $ret = 4;
+    else if ( $val >= 40 )
+      $ret = 5;
+    else if ( $val >= 30 )
+      $ret = 6;
+    else if ( $val >= 20 )
+      $ret = 7;
+    else if ( $val >= 10 )
+      $ret = 8;
+    else if ( $val >= 5 )
+      $ret = 9;
+    return $ret;
+  }
+  
+  /**
+   * Generates and returns HTML for the cloud.
+   * @return string
+   */
+   
+  function make_html()
+  {
+    $html = array();
+    $max  = max($this->words);
+    $size = $this->get_cloud_size();
+    if ( count($this->words) > 0 )
+    {
+      foreach ( $this->words as $word => $popularity )
+      {
+        $word = htmlspecialchars($word);
+        $percent = ( $popularity / $max ) * 100;
+        $index = $this->get_scale_class($percent);
+        $html[] = "<span class='tc_word tc_index_$index'>$word</span>";
+      }
+    }
+    $html = implode("\n", $html);
+    return $html;
+  }
+   
+  
+}
+
+?>
--- a/index.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/index.php	Wed Aug 01 13:39:27 2007 -0400
@@ -13,17 +13,20 @@
  *
  */
 
-  // Set up gzip encoding before any output is sent
+  // Se t up gzip encoding before any output is sent
   
-  $aggressive_optimize_html = false;
+  $aggressive_optimize_html = true;
   
   global $do_gzip;
-  $do_gzip = false;
+  $do_gzip = true;
   
   if(isset($_SERVER['PATH_INFO'])) $v = $_SERVER['PATH_INFO'];
   elseif(isset($_GET['title'])) $v = $_GET['title'];
   else $v = '';
   
+  if ( isset($_GET['nocompress']) )
+    $aggressive_optimize_html = false;
+  
   error_reporting(E_ALL);
   
   // if(!strstr($v, 'CSS') && !strstr($v, 'UploadFile') && !strstr($v, 'DownloadFile')) // These pages are blacklisted because we can't have debugConsole's HTML output disrupting the flow of header() calls and whatnot
@@ -371,35 +374,7 @@
     $html = ob_get_contents();
     ob_end_clean();
     
-    // Which tags to strip - you can change this if needed
-    $strip_tags = Array('pre', 'script', 'style', 'enano:no-opt');
-    $strip_tags = implode('|', $strip_tags);
-    
-    // Strip out the tags and replace with placeholders
-    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++)
-    {
-      $html = str_replace("<{$matches[1][$i]}{$matches[2][$i]}>{$matches[3][$i]}</{$matches[4][$i]}>", "{DONT_STRIP_ME_NAKED:$seed:$i}", $html);
-    }
-    
-    // Finally, process the HTML
-    $html = preg_replace("#\n([ ]*)#", " ", $html);
-    
-    // Remove annoying spaces between tags
-    $html = preg_replace("#>([ ]*?){2,}<#", "> <", $html);
-    
-    // Re-insert untouchable tags
-    for ($i = 0;$i < sizeof($matches[1]); $i++)
-    {
-      $html = str_replace("{DONT_STRIP_ME_NAKED:$seed:$i}", "<{$matches[1][$i]}{$matches[2][$i]}>{$matches[3][$i]}</{$matches[4][$i]}>", $html);
-    }
-    
-    // Remove <enano:no-opt> blocks (can be used by themes that don't want their HTML optimized)
-    $html = preg_replace('#<(\/|)enano:no-opt(.*?)>#', '', $html);
-    
-    // Tell snoopish users what's going on
-    $html = str_replace('<html>', "\n<!-- NOTE: This HTML document has been Aggressively Optimized(TM) by Enano to make page loading faster. -->\n<html>", $html);
+    $html = aggressive_optimize_html($html);
     
     // Re-enable output buffering to allow the Gzip function (below) to work
     ob_start();
@@ -407,31 +382,8 @@
     // Done, send it to the user
     echo( $html );
   }
-  
-  //
-  // Compress buffered output if required and send to browser
-  //
-  if ( $do_gzip )
-  {
-    //
-    // Copied from phpBB, which was in turn borrowed from php.net
-    //
-    $gzip_contents = ob_get_contents();
-    ob_end_clean();
+
+  $db->close();  
+  gzip_output();
   
-    $gzip_size = strlen($gzip_contents);
-    $gzip_crc = crc32($gzip_contents);
-  
-    $gzip_contents = gzcompress($gzip_contents, 9);
-    $gzip_contents = substr($gzip_contents, 0, strlen($gzip_contents) - 4);
-  
-    header('Content-encoding: gzip');
-    echo "\x1f\x8b\x08\x00\x00\x00\x00\x00";
-    echo $gzip_contents;
-    echo pack('V', $gzip_crc);
-    echo pack('V', $gzip_size);
-  }
-  
-  $db->close();
-
 ?>
--- a/install.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/install.php	Wed Aug 01 13:39:27 2007 -0400
@@ -812,7 +812,7 @@
         <tr><td>Your e-mail address:</td><td><input onkeyup="verify();" name="admin_email" type="text" size="30" /></td><td><img id="s_email" alt="Good/bad icon" src="images/bad.gif" /></td></tr>
         <tr>
           <td>
-            Allow administrative embedding of PHP:<br />
+            Allow administrators to embed PHP code into pages:<br />
             <small><span style="color: #D84308">Do not under any circumstances enable this option without reading these
                    <a href="install.php?mode=pophelp&amp;topic=admin_embed_php"
                       onclick="window.open(this.href, 'pophelpwin', 'width=550,height=400,status=no,toolbars=no,toolbar=no,address=no,scroll=yes'); return false;"
@@ -1010,7 +1010,7 @@
       {
         echo 'Dropping existing Enano tables...';
         // Our list of tables included in Enano
-        $tables = Array( 'mdg_categories', 'mdg_comments', 'mdg_config', 'mdg_logs', 'mdg_page_text', 'mdg_session_keys', 'mdg_pages', 'mdg_users', 'mdg_users_extra', 'mdg_themes', 'mdg_buddies', 'mdg_banlist', 'mdg_files', 'mdg_privmsgs', 'mdg_sidebar', 'mdg_hits', 'mdg_search_index', 'mdg_groups', 'mdg_group_members', 'mdg_acl', 'mdg_search_cache' );
+        $tables = Array( 'mdg_categories', 'mdg_comments', 'mdg_config', 'mdg_logs', 'mdg_page_text', 'mdg_session_keys', 'mdg_pages', 'mdg_users', 'mdg_users_extra', 'mdg_themes', 'mdg_buddies', 'mdg_banlist', 'mdg_files', 'mdg_privmsgs', 'mdg_sidebar', 'mdg_hits', 'mdg_search_index', 'mdg_groups', 'mdg_group_members', 'mdg_acl', 'mdg_search_cache', 'mdg_tags', 'mdg_page_groups', 'mdg_page_group_members' );
         $tables = implode(', ', $tables);
         $tables = str_replace('mdg_', $_POST['table_prefix'], $tables);
         $query_of_death = 'DROP TABLE '.$tables.';';
--- a/plugins/SpecialAdmin.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/plugins/SpecialAdmin.php	Wed Aug 01 13:39:27 2007 -0400
@@ -1888,7 +1888,7 @@
     }
   }
   elseif(isset($_POST['install'])) {
-    $q = 'SELECT * FROM '.table_prefix.'themes;';
+    $q = 'SELECT theme_id FROM '.table_prefix.'themes;';
     $s = $db->sql_query($q);
     if(!$s) die('Error getting theme count: '.mysql_error().'<br /><u>SQL:</u><br />'.$q);
     $n = $db->numrows($s);
@@ -1896,10 +1896,42 @@
     $theme_id = $_POST['theme_id'];
     $theme = Array();
     include('./themes/'.$theme_id.'/theme.cfg');
-    $q = 'INSERT INTO '.table_prefix.'themes(theme_id,theme_name,theme_order,enabled) VALUES(\''.$theme['theme_id'].'\', \''.$theme['theme_name'].'\', '.$n.', 1)';
-    $s = $db->sql_query($q);
-    if(!$s) die('Error inserting theme data: '.mysql_error().'<br /><u>SQL:</u><br />'.$q);
-    else echo('<div class="info-box">Theme "'.$theme['theme_name'].'" installed.</div>');
+    if ( !isset($theme['theme_id']) )
+    {
+      echo '<div class="error-box">Could not load theme.cfg (theme metadata file)</div>';
+    }
+    else
+    {
+      $default_style = false;
+      if ( $dh = opendir('./themes/' . $theme_id . '/css') )
+      {
+        while ( $file = readdir($dh) )
+        {
+          if ( $file != '_printable.css' && preg_match('/\.css$/i', $file) )
+          {
+            $default_style = $file;
+            break;
+          }
+        }
+        closedir($dh);
+      }
+      else
+      {
+        die('The /css subdirectory could not be located in the theme\'s directory');
+      }
+      
+      if ( $default_style )
+      {
+        $q = 'INSERT INTO '.table_prefix.'themes(theme_id,theme_name,theme_order,enabled,default_style) VALUES(\''.$db->escape($theme['theme_id']).'\', \''.$db->escape($theme['theme_name']).'\', '.$n.', 1, \'' . $db->escape($default_style) . '\')';
+        $s = $db->sql_query($q);
+        if(!$s) die('Error inserting theme data: '.mysql_error().'<br /><u>SQL:</u><br />'.$q);
+        else echo('<div class="info-box">Theme "'.$theme['theme_name'].'" installed.</div>');
+      }
+      else
+      {
+        echo '<div class="error-box">Could not determine the default style for the theme.</div>';
+      }
+    }
   }
   echo('
   <h3>Currently installed themes</h3>
--- a/plugins/SpecialUpdownload.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/plugins/SpecialUpdownload.php	Wed Aug 01 13:39:27 2007 -0400
@@ -269,29 +269,7 @@
   header('Last-Modified: '.date('r', $row['time_id']));
   echo($data);
   
-  //
-  // Compress buffered output if required and send to browser
-  //
-  if ( $do_gzip )
-  {
-    //
-    // Copied from phpBB, which was in turn borrowed from php.net
-    //
-    $gzip_contents = ob_get_contents();
-    ob_end_clean();
-  
-    $gzip_size = strlen($gzip_contents);
-    $gzip_crc = crc32($gzip_contents);
-  
-    $gzip_contents = gzcompress($gzip_contents, 9);
-    $gzip_contents = substr($gzip_contents, 0, strlen($gzip_contents) - 4);
-  
-    header('Content-encoding: gzip');
-    echo "\x1f\x8b\x08\x00\x00\x00\x00\x00";
-    echo $gzip_contents;
-    echo pack('V', $gzip_crc);
-    echo pack('V', $gzip_size);
-  }
+  gzip_output();
   
   exit;
   
--- a/plugins/admin/PageGroups.php	Mon Jul 30 10:46:17 2007 -0400
+++ b/plugins/admin/PageGroups.php	Wed Aug 01 13:39:27 2007 -0400
@@ -504,7 +504,7 @@
           if ( strval(intval($id)) == $id )
             $good[] = $id;
         }
-        $subquery = 'pg_member_id=' . implode(' OR pg_member_id=', $good);
+        $subquery = ( count($good) > 0 ) ? 'pg_member_id=' . implode(' OR pg_member_id=', $good) : "'foo'='foo'";
         $sql = 'DELETE FROM '.table_prefix."page_group_members WHERE ( $subquery ) AND pg_id=$edit_id;";
         if ( !$db->sql_query($sql) )
         {
@@ -598,7 +598,7 @@
           // More javascript magic!
           ?>
           <script type="text/javascript">
-            var __pg_edit_submitAuthorized = true;;
+            var __pg_edit_submitAuthorized = true;
             var __ol_pg_edit_setup = function()
             {
               var input = document.getElementById('inptext_pg_add_member');
@@ -688,6 +688,8 @@
           $ajax_page_add = true;
           
           break;
+        case PAGE_GRP_TAGGED:
+          break;
       }
       
       if ( $ajax_page_add )
@@ -696,21 +698,27 @@
       }
       else
       {
-        
+        echo '<tr><th colspan="3" class="subhead">
+                <input type="submit" name="action[edit_save]" value="Save and update" />
+                <input type="submit" name="action[noop]" value="Cancel all changes" />
+              </th></tr>';
       }
       
       echo '  </table>
             </div>';
       echo '</form>';
       
-      // This needs to be outside of the form.
-      echo '<div class="tblholder"><table border="0" cellspacing="1" cellpadding="4"><tr>';
-      echo '<th colspan="2">On-the-fly tools</th></tr>';
-      echo '<tr>';
-      // Add pages AJAX form
-      echo '<td class="row2">Add page:<br /><small>You can add multiple pages by entering part of a page title, and it will be auto-completed. Press Enter to quickly add the page. This only works if you a really up-to-date browser.</small></td>';
-      echo '<td class="row1"><input type="text" size="30" name="pg_add_member" id="inptext_pg_add_member" /></td>';
-      echo '</tr></table></div>';
+      if ( $ajax_page_add )
+      {
+        // This needs to be outside of the form.
+        echo '<div class="tblholder"><table border="0" cellspacing="1" cellpadding="4"><tr>';
+        echo '<th colspan="2">On-the-fly tools</th></tr>';
+        echo '<tr>';
+        // Add pages AJAX form
+        echo '<td class="row2">Add page:<br /><small>You can add multiple pages by entering part of a page title, and it will be auto-completed. Press Enter to quickly add the page. This only works if you a really up-to-date browser.</small></td>';
+        echo '<td class="row1"><input type="text" size="30" name="pg_add_member" id="inptext_pg_add_member" /></td>';
+        echo '</tr></table></div>';
+      }
       
       return;
     }
--- a/schema.sql	Mon Jul 30 10:46:17 2007 -0400
+++ b/schema.sql	Wed Aug 01 13:39:27 2007 -0400
@@ -305,7 +305,7 @@
 
 INSERT INTO {{TABLE_PREFIX}}group_members(group_id,user_id,is_mod) VALUES(2, 2, 1);
 
-INSERT INTO {{TABLE_PREFIX}}acl(target_type,target_id,page_id,namespace,rules) VALUES(1,2,NULL,NULL,'read=4;post_comments=4;edit_comments=4;edit_page=4;view_source=4;mod_comments=4;history_view=4;history_rollback=4;history_rollback_extra=4;protect=4;rename=4;clear_logs=4;vote_delete=4;vote_reset=4;delete_page=4;set_wiki_mode=4;password_set=4;password_reset=4;mod_misc=4;edit_cat=4;even_when_protected=4;upload_files=4;upload_new_version=4;create_page=4;php_in_pages={{ADMIN_EMBED_PHP}};edit_acl=4;'),
+INSERT INTO {{TABLE_PREFIX}}acl(target_type,target_id,page_id,namespace,rules) VALUES(1,2,NULL,NULL,'read=4;post_comments=4;edit_comments=4;edit_page=4;view_source=4;mod_comments=4;history_view=4;history_rollback=4;history_rollback_extra=4;protect=4;rename=4;clear_logs=4;vote_delete=4;vote_reset=4;delete_page=4;tag_create=4;tag_delete_own=4;tag_delete_other=4;set_wiki_mode=4;password_set=4;password_reset=4;mod_misc=4;edit_cat=4;even_when_protected=4;upload_files=4;upload_new_version=4;create_page=4;php_in_pages={{ADMIN_EMBED_PHP}};edit_acl=4;'),
   (1,3,NULL,NULL,'read=4;post_comments=4;edit_comments=4;edit_page=4;view_source=4;mod_comments=4;history_view=4;history_rollback=4;history_rollback_extra=4;protect=4;rename=3;clear_logs=2;vote_delete=4;vote_reset=4;delete_page=4;set_wiki_mode=2;password_set=2;password_reset=2;mod_misc=2;edit_cat=4;even_when_protected=4;upload_files=2;upload_new_version=3;create_page=3;php_in_pages=2;edit_acl=2;');
 
 INSERT INTO {{TABLE_PREFIX}}sidebar(item_id, item_order, sidebar_id, block_name, block_type, block_content) VALUES
--- a/themes/admin/footer.tpl	Mon Jul 30 10:46:17 2007 -0400
+++ b/themes/admin/footer.tpl	Wed Aug 01 13:39:27 2007 -0400
@@ -32,7 +32,7 @@
         </tr>
       </table>
       
-      <div id="sidebar-hide" onclick="admin_expand();" class="collapsed"></div>
+      <div id="sidebar-hide" onclick="admin_expand();" class="collapsed" title="Click to expand the sidebar"></div>
     
     </td>
     </tr>
@@ -43,23 +43,6 @@
      <p>If you can see this text, it means that your browser does not support Cascading Style Sheets (CSS). CSS is a fundemental aspect of XHTML, and as a result it is becoming very widely adopted by websites, including this one. You should consider switching to a more modern web browser, such as Mozilla Firefox or Opera 9.</p>
      <p>Because of this, there are a few minor issues that you may experience while browsing this site, not the least of which is some visual elements below that would normally be hidden in most browsers. Please excuse these minor inconveniences.</p>
     </div>
-    <div id="root1" class="jswindow" style="display: none;">
-      <div id="tb1" class="titlebar">Confirm Logout</div>
-      <div class="content" id="cn1">
-        <form action="{CONTENTPATH}Special:Logout" method="get">
-          <div style="text-align: center">
-            <h3>Are you sure you want to log out?</h3>
-            <input type="submit" value="Log out" style="font-weight: bold;" />  <input type="button" onclick="jws.closeWin('root1');" value="Cancel" />
-          </div>
-        </form>
-      </div>  
-    </div>
-    <div id="root2" class="jswindow" style="display: none;">
-      <div id="tb2" class="titlebar">Change style</div>
-      <div class="content" id="cn2">
-        
-      </div>
-    </div>
     <div id="root3" class="jswindow" style="display: none;">
       <div id="tb3" class="titlebar">Wiki formatting help</div>
       <div class="content" id="cn3">
--- a/themes/admin/js/menu.js	Mon Jul 30 10:46:17 2007 -0400
+++ b/themes/admin/js/menu.js	Wed Aug 01 13:39:27 2007 -0400
@@ -1,71 +1,18 @@
-/*
-var menuClicked = false;
-var menuID = false;
-var menuParent = false;
-function adminOpenMenu(menu, parent)
-{
-  menuParent = parent;
-  if ( typeof(menu) == 'string' )
-  {
-    menu = document.getElementById(menu);
-  }
-  if(!menu)
-  {
-    alert('Menu object is invalid');
-    return false;
-  }
-  var off = fetch_offset(parent);
-  var dim = fetch_dimensions(parent);
-  var w = 200;
-  var top = off['top'] + dim['h'];
-  var left = off['left'] + dim['w'] - w;
-  menu.style.top = top + 'px';
-  menu.style.left = left + 'px';
-  menu.style.display = 'block';
-  menuID = menu.id;
-  setTimeout('setMenuoffEvents();', 500);
-  //if(!IE)
-  //  parent.onclick = eval('(function() { this.onclick = function() { adminOpenMenu(\'' + menu.id + '\', this); return false; }; return false; } )');
-}
-
-function adminMenuOff()
-{
-  if ( menuID )
-  {
-    menu = document.getElementById(menuID);
-    menu.style.display = 'none';
-    menu.onmousedown = false;
-    menu.onmouseup = false;
-    menuID = false;
-    document.onclick = false;
-    //menuParent.onclick();
-    //menuParent = false;
-  }
-}
-
-function setMenuoffEvents()
-{
-  menu = document.getElementById(menuID);
-  menu.onmousedown = function() { menuClicked = true; }
-  menu.onmouseup   = function() { setTimeout('menuClicked = false;', 100); }
-  document.onclick = function() { if ( menuClicked ) return false; adminMenuOff(); }
-}
-*/
-
+var TBL_SHOW = ( IE ) ? 'block' : 'table';
 function admin_expand()
 {
   var expander = document.getElementById('sidebar-hide');
   var content  = document.getElementById('sidebar-show');
   var holder  = document.getElementById('td-sidebar');
-  if ( content.style.display == 'table' )
+  if ( content.style.display == TBL_SHOW )
   {
+    admin_collapse_real(expander, content, holder);
     createCookie('theme_admin_sidebar', 'collapsed', 3650);
-    admin_collapse_real(expander, content, holder);
   }
   else
   {
+    admin_expand_real(expander, content, holder);
     createCookie('theme_admin_sidebar', 'expanded', 3650);
-    admin_expand_real(expander, content, holder);
   }
 }
 
@@ -81,7 +28,7 @@
 function admin_expand_real(expander, content, holder)
 {
   expander.className = 'expanded';
-  content.style.display = 'table';
+  content.style.display = TBL_SHOW;
   holder.style.width = '230px';
   holder.style.paddingLeft = '12px';
   holder.style.paddingRight = '0px';
--- a/themes/oxygen/header.tpl	Mon Jul 30 10:46:17 2007 -0400
+++ b/themes/oxygen/header.tpl	Wed Aug 01 13:39:27 2007 -0400
@@ -67,7 +67,7 @@
       
       function ajaxRenameInline()
       {
-        if ( KILL_SWITCH )
+        if ( KILL_SWITCH || IE )
           return false;
         // This trick is _so_ vBulletin...
         elem = document.getElementById('h2PageName');
@@ -101,10 +101,12 @@
       }
       function ajaxRenameInlineCancel(e)
       {
+        if ( typeof(e) != 'object' && IE )
+          e = window.event;
         elem1 = document.getElementById('h2PageName');
         elem2 = document.getElementById('pageheading');
         if(!elem1 || !elem2) return;
-        if ( e.target )
+        if ( typeof(e) == 'object' && e.target )
         {
           if(e.target == elem2)
             return;