Merging branches
authorDan
Sat, 21 Mar 2009 14:55:34 -0400
changeset 878 ecc764c10138
parent 875 0c3dd4c166c0 (diff)
parent 877 e9ee4e246f96 (current diff)
child 879 9788f2b7e08a
Merging branches
install/includes/stages/website.php
language/english/install.json
--- a/ajax.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/ajax.php	Sat Mar 21 14:55:34 2009 -0400
@@ -540,30 +540,78 @@
       $parms = ( isset($_POST['acl_params']) ) ? rawurldecode($_POST['acl_params']) : false;
       echo PageUtils::acl_json($parms);
       break;
+    case 'theme_list':
+      header('Content-type: application/json');
+      
+      $q = $db->sql_query('SELECT theme_name, theme_id FROM ' . table_prefix . "themes WHERE enabled = 1 ORDER BY theme_name ASC;");
+      if ( !$q )
+        $db->die_json();
+      
+      $return = array();
+      while ( $row = $db->fetchrow() )
+        $return[] = $row;
+      
+      foreach ( $return as &$theme )
+      {
+        $theme['have_thumb'] = file_exists(ENANO_ROOT . "/themes/{$theme['theme_id']}/preview.png");
+      }
+      
+      echo enano_json_encode($return);
+      
+      break;
+    case "get_styles":
+      if ( !preg_match('/^[a-z0-9_-]+$/', $_GET['theme_id']) )
+        die(enano_json_encode(array()));
+      
+      $theme_id = $_GET['theme_id'];
+      $return = array();
+      
+      if ( $dr = @opendir(ENANO_ROOT . "/themes/$theme_id/css/") )
+      {
+        while ( $dh = @readdir($dr) )
+        {
+          if ( preg_match('/\.css$/', $dh) && $dh != '_printable.css' )
+          {
+            $return[] = preg_replace('/\.css$/', '', $dh);
+          }
+        }
+      }
+      else
+      {
+        $return = array(
+            'mode' => 'error',
+            'error' => 'Could not open directory.'
+          );
+      }
+      echo enano_json_encode($return);
+      break;
     case "change_theme":
       if ( !isset($_POST['theme_id']) || !isset($_POST['style_id']) )
       {
-        die('Invalid input');
+        die(enano_json_encode(array('mode' => 'error', 'error' => 'Invalid parameter')));
       }
       if ( !preg_match('/^([a-z0-9_-]+)$/i', $_POST['theme_id']) || !preg_match('/^([a-z0-9_-]+)$/i', $_POST['style_id']) )
       {
-        die('Invalid input');
+        die(enano_json_encode(array('mode' => 'error', 'error' => 'Invalid parameter')));
       }
       if ( !file_exists(ENANO_ROOT . '/themes/' . $_POST['theme_id'] . '/css/' . $_POST['style_id'] . '.css') )
       {
-        die('Can\'t find theme file: ' . ENANO_ROOT . '/themes/' . $_POST['theme_id'] . '/css/' . $_POST['style_id'] . '.css');
+        die(enano_json_encode(array('mode' => 'error', 'error' => 'Can\'t find theme file: ' . ENANO_ROOT . '/themes/' . $_POST['theme_id'] . '/css/' . $_POST['style_id'] . '.css')));;
       }
       if ( !$session->user_logged_in )
       {
-        die('You must be logged in to change your theme');
+        die(enano_json_encode(array('mode' => 'error', 'error' => 'You must be logged in to change your theme')));
       }
       // Just in case something slipped through...
       $theme_id = $db->escape($_POST['theme_id']);
       $style_id = $db->escape($_POST['style_id']);
-      $e = $db->sql_query('UPDATE ' . table_prefix . "users SET theme='$theme_id', style='$style_id' WHERE user_id=$session->user_id;");
+      $e = $db->sql_query('UPDATE ' . table_prefix . "users SET theme = '$theme_id', style = '$style_id' WHERE user_id = $session->user_id;");
       if ( !$e )
         die( $db->get_error() );
-      die('GOOD');
+      
+      echo enano_json_encode(array(
+          'success' => true
+        ));
       break;
     case 'get_tags':
       
Binary file images/check-large.png has changed
--- a/includes/clientside/css/enano-shared.css	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/css/enano-shared.css	Sat Mar 21 14:55:34 2009 -0400
@@ -777,6 +777,12 @@
   background-color: #606060;
 }
 
+.abutton.block {
+  display: block;
+  width: 60%;
+  margin: 0 auto 10px auto;
+}
+
 .abutton_green       { color:            #008800 !important; }
 .abutton_green:hover { background-color: #008800 !important; }
 .abutton_blue        { color:            #000088 !important; }
@@ -911,3 +917,88 @@
 span.acl_failed_deps span.title {
   color: #ff0000;
 }
+
+/**
+ * Theme selector
+ */
+
+div#theme-selector-wrapper {
+  position: absolute;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+  top: 0;
+  margin-top: 75px;
+}
+
+div#theme-selector-body {
+  margin: 0 auto;
+  padding: 20px;
+  background-color: #ffffff;
+  text-align: center;
+  /* width: 708px; */
+  width: 130px;
+  height: 130px;
+}
+
+div#theme-selector-inner h3 {
+  font-size: x-large;
+}
+
+div#theme-selector-inner ul {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+div#theme-selector-inner ul li {
+  float: left;
+}
+
+div#theme-selector-inner ul li a {
+  display: block;
+  border: 1px solid #d0d0d0;
+  padding: 4px;
+  width: 216px;
+  line-height: 150px;
+  text-align: center;
+  margin: 2px 5px;
+  text-decoration: none;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-image: url(../../../images/themepreview.png);
+}
+
+div#theme-selector-inner ul li a span {
+  color: #456798;
+  background-color: #fff;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  display: block;
+  width: 216px;
+  line-height: 150px;
+  font-size: x-large;
+}
+
+div#theme-selector-inner ul li a:hover span {
+  opacity: 0.6;
+  filter: alpha(opacity=60);
+}
+
+div#theme-selector-inner .abutton {
+  font-size: x-large;
+}
+
+div#theme-selector-inner ul li a span.loading {
+  background-image: url(../../../images/loading-big.gif);
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+div.theme-selector-spinner {
+  height: 130px;
+  background-image: url(../../../images/loading-big.gif);
+  background-position: center center;
+  background-repeat: no-repeat;
+  margin: 0 auto;
+}
--- a/includes/clientside/jsres.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/jsres.php	Sat Mar 21 14:55:34 2009 -0400
@@ -54,10 +54,7 @@
 //
 
 // We need to see if this is a specially marked Enano development server. You can create an Enano
-// development server by cloning the Mercurial repository into a directory named repo, and then
-// using symlinks to reference the original files so as to segregate unique files from non-unique
-// and distribution-standard ones. Enano will pivot its root directory accordingly if the file
-// .enanodev is found in the Enano root (not /repo/).
+// development server using the script found on hg.enanocms.org.
 if ( strpos(__FILE__, '/repo/') && ( file_exists('../../.enanodev') || file_exists('../../../.enanodev') ) )
 {
   // We have a development directory. Remove /repo/ from the picture.
--- a/includes/clientside/static/ajax.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/ajax.js	Sat Mar 21 14:55:34 2009 -0400
@@ -155,7 +155,6 @@
     return false;
   
   var input = obj.getElementsByTagName('input')[0];
-  console.debug(obj, input);
   if ( !input )
     return false;
   var newname = input.value;
@@ -738,142 +737,170 @@
   // IE <6 pseudo-compatibility
   if ( KILL_SWITCH )
     return true;
-  load_component(['l10n', 'messagebox', 'flyin', 'fadefilter']);
+  load_component(['l10n', 'fadefilter', 'jquery', 'jquery-ui']);
+  
+  // force string fetch
+  $lang.get('etc_cancel');
+  
+  // preload some images
+  var i1 = new Image();
+  i1.src = cdnPath + '/images/loading-big.gif';
+  var i2 = new Image();
+  i2.src = cdnPath + '/images/check-large.png';
+  
+  darken(true, 70, 'theme-selector-shade');
+  
+  $('body').append('<div id="theme-selector-wrapper"><div id="theme-selector-body"><div id="theme-selector-inner"><div class="theme-selector-spinner"></div></div></div></div>');
+  $('#theme-selector-wrapper')
+    .css('top', String(getScrollOffset()) + 'px')
+    .css('z-index', String( getHighestZ() + 20 ));
   
-  var inner_html = '';
-  inner_html += '<p><label>' + $lang.get('ajax_changestyle_lbl_theme') + ' ';
-  inner_html += '  <select id="chtheme_sel_theme" onchange="ajaxGetStyles(this.value);">';
-  inner_html += '    <option value="_blank" selected="selected">' + $lang.get('ajax_changestyle_select') + '</option>';
-  inner_html +=      ENANO_THEME_LIST;
-  inner_html += '  </select>';
-  inner_html += '</label></p>';
-  var chtheme_mb = new MessageBox(MB_OKCANCEL|MB_ICONQUESTION, $lang.get('ajax_changestyle_title'), inner_html);
-  chtheme_mb.onbeforeclick['OK'] = ajaxChangeStyleComplete;
+  $.get(stdAjaxPrefix + '&_mode=theme_list', {}, function(data, status)
+    {
+      $('#theme-selector-inner .theme-selector-spinner').fadeOut(650);
+      $('#theme-selector-body').animate({ width: 708 }, 600, function()
+        {
+          // avoiding jQuery's fade functions because they insist on toggling display as well
+          changeOpac(0, 'theme-selector-inner');
+          $('#theme-selector-inner').html('<h3></h3>');
+          $('#theme-selector-inner > h3').text($lang.get('ajax_thmsel_lbl_choosetheme'));
+          $('#theme-selector-inner').append('<ul></ul>');
+          for ( var i = 0; i < data.length; i++ )
+          {
+            var bgi = data[i].have_thumb ? cdnPath + '/themes/' + data[i].theme_id + '/preview.png' : cdnPath + '/images/themepreview.png';
+            $('#theme-selector-inner > ul').append('<li id="theme_' + i + '"><a href="#"><span></span></a></li>');
+            $('#theme-selector-inner li#theme_' + i + ' > a')
+              .css('background-image', 'url(' + bgi + ')')
+              .attr('enano:theme_id', data[i].theme_id);
+            $('#theme-selector-inner li#theme_' + i + ' > a > span')
+              .text(data[i].theme_name);
+          }
+          $('#theme-selector-inner').append('<span class="menuclear"></span>');
+          $('#theme-selector-inner').append('<div style="padding-top: 40px;"><a class="abutton abutton_green" style="font-size: larger;" href="#" onclick="ajaxChangeStyleClose(); return false;">' + $lang.get('etc_cancel') + '</a></div>');
+          
+          $('#theme-selector-body').animate({ height: $('#theme-selector-inner').height() + 30 }, 600, function()
+            {
+              opacity('theme-selector-inner', 0, 100, 750);
+            });
+          
+          $('#theme-selector-inner li a').click(function()
+            {
+              var theme_id = $(this).attr('enano:theme_id');
+              $('span', this).html('&nbsp;').addClass('loading').fadeTo('fast', 0.6)
+              $.get(stdAjaxPrefix + '&_mode=get_styles', { theme_id: theme_id }, function(data, status)
+                {
+                  if ( data.length > 1 )
+                  {
+                    $('#theme-selector-inner').css('height', $('#theme-selector-inner').height()).fadeOut(600, function()
+                    {
+                      var div = document.createElement('div');
+                      domObjChangeOpac(0, div);
+                      
+                      $(div).attr('id', 'theme-selector-style-list').append('<h3></h3>');
+                      $('h3', div).text($lang.get('ajax_thmsel_lbl_choosestyle'));
+                      
+                      for ( var i = 0; i < data.length; i++ )
+                      {
+                        $(div).append('<a class="abutton block stylebtn" id="stylebtn_' + i + '" enano:style_id="' + data[i] + '">' + themeid_to_title(data[i]) + '</a>');
+                      }
+                      
+                      $(div).append('<div style="padding-top: 40px;"><a class="abutton abutton_green" style="font-size: larger;" href="#" onclick="ajaxChangeStyleClose(); return false;">' + $lang.get('etc_cancel') + '</a></div>');
+                      
+                      changeOpac(0, 'theme-selector-style-list');
+                      $(this).html(div).show();
+                      
+                      $('#theme-selector-body').animate({width: 300, height: $('#theme-selector-style-list').height() + 30}, 300, function()
+                        {
+                          opacity('theme-selector-style-list', 0, 100, 500);
+                        });
+                      
+                      $('.stylebtn').click(function()
+                        {
+                          ajaxChangeThemeSetLoading();
+                          $.post(stdAjaxPrefix + '&_mode=change_theme', { theme_id: theme_id, style_id: $(this).attr('enano:style_id') }, function(data, status)
+                            {
+                              if ( data.error )
+                              {
+                                alert(data.error);
+                                ajaxChangeStyleClose();
+                                return false;
+                              }
+                              ajaxChangeThemeShowSuccess();
+                            }, 2000);
+                          
+                          return false;
+                        });
+                    });
+                  }
+                  else
+                  {
+                    if ( !data[0] )
+                    {
+                      alert('Didn\'t find any CSS files. :-/');
+                      ajaxChangeStyleClose();
+                    }
+                    
+                    $.post(stdAjaxPrefix + '&_mode=change_theme', { theme_id: theme_id, style_id: data[0] }, function(data, status)
+                      {
+                        if ( data.error )
+                        {
+                          alert(data.error);
+                          ajaxChangeStyleClose();
+                          return false;
+                        }
+                        ajaxChangeThemeShowSuccess();
+                      }, 'json');
+                  }
+                }, 'json');
+              return false;
+            }); // click function
+        }); // animate
+    }, 'json'); // get
 }
 
-window.ajaxGetStyles = function(id)
+window.ajaxChangeThemeSetLoading = function()
 {
-  // IE <6 pseudo-compatibility
-  if ( KILL_SWITCH )
-    return true;
-  var thediv = document.getElementById('chtheme_sel_style_parent');
-  if ( thediv )
-  {
-    thediv.parentNode.removeChild(thediv);
-  }
-  if ( id == '_blank' )
-  {
-    return null;
-  }
-  ajaxGet(stdAjaxPrefix + '&_mode=getstyles&id=' + id, function(ajax) {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
+  $('#theme-selector-body').animate({width: 130, height: 130});
+  $('#theme-selector-inner').empty().html('<div class="theme-selector-spinner"></div>');
+}
+
+window.ajaxChangeThemeShowSuccess = function()
+{
+  $('#theme-selector-body').animate({width: 400, height: 300 }, 600, function()
       {
-        // IE doesn't like substr() on ajax.responseText
-        var response = String(ajax.responseText + '');
-        if ( response.substr(0,1) != '[' )
-        {
-          alert('Invalid or unexpected JSON response from server:\n' + response);
-          return null;
-        }
-        
-        // Build a selector and matching label
-        var data = parseJSON(response);
-        var options = new Array();
-        for( var i in data )
-        {
-          var item = data[i];
-          var title = themeid_to_title(item);
-          var option = document.createElement('option');
-          option.value = item;
-          option.appendChild(document.createTextNode(title));
-          options.push(option);
-        }
-        var p_parent = document.createElement('p');
-        var label  = document.createElement('label');
-        p_parent.id = 'chtheme_sel_style_parent';
-        label.appendChild(document.createTextNode($lang.get('ajax_changestyle_lbl_style') + ' '));
-        var select = document.createElement('select');
-        select.id = 'chtheme_sel_style';
-        for ( var i in options )
-        {
-          select.appendChild(options[i]);
-        }
-        label.appendChild(select);
-        p_parent.appendChild(label);
-        
-        // Stick it onto the messagebox
-        var div = document.getElementById('messageBox');
-        var kid = div.firstChild.nextSibling;
-        
-        kid.appendChild(p_parent);
-        
-      }
-    }, true);
+        $('#theme-selector-inner').append('<img src="' + cdnPath + '/images/check-large.png" alt=" " style="display: block; margin: 15px auto;" />');
+        $('#theme-selector-inner').append('<h3>' + $lang.get('ajax_thmsel_msg_success') + '</h3>');
+        $('#theme-selector-inner').append('<div style="padding-top: 20px;"><a class="abutton abutton_green" style="font-size: larger;" href="#" onclick="window.location.reload(); return false;">' + $lang.get('ajax_thmsel_btn_reload') + '</a></div>');
+        $('#theme-selector-inner').append('<div style="padding-top: 25px;"><a href="#" style="font-size: smaller;" onclick="ajaxChangeStyleClose(); return false;">' + $lang.get('ajax_thmsel_btn_close') + '</a><br /><small>' + $lang.get('ajax_thmsel_btn_close_hint') + '</small></div>');
+        $('#theme-selector-inner').fadeIn();
+      });
+  setTimeout(function()
+    {
+      $('#theme-selector-inner').empty();
+    }, 10);
 }
 
-window.ajaxChangeStyleComplete = function()
+window.ajaxChangeStyleClose = function()
 {
-  // IE <6 pseudo-compatibility
-  if ( KILL_SWITCH )
-    return true;
-  var theme = $dynano('chtheme_sel_theme');
-  var style = $dynano('chtheme_sel_style');
-  if ( !theme.object || !style.object )
-  {
-    alert($lang.get('ajax_changestyle_pleaseselect_theme'));
-    return true;
-  }
-  var theme_id = theme.object.value;
-  var style_id = style.object.value;
-  
-  if ( typeof(theme_id) != 'string' || typeof(style_id) != 'string' )
-  {
-    alert('Couldn\'t get theme or style ID');
-    return true;
-  }
-  
-  if ( theme_id.length < 1 || style_id.length < 1 )
-  {
-    alert('Theme or style ID is zero length');
-    return true;
-  }
-  
-  ajaxPost(stdAjaxPrefix + '&_mode=change_theme', 'theme_id=' + ajaxEscape(theme_id) + '&style_id=' + ajaxEscape(style_id), function(ajax)
+  setTimeout(function()
     {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        if ( ajax.responseText == 'GOOD' )
+      enlighten(false, 'theme-selector-shade');
+      $('#theme-selector-wrapper').fadeOut(500, function()
         {
-          var c = confirm($lang.get('ajax_changestyle_success'));
-          if ( c )
-            window.location.reload();
-        }
-        else
-        {
-          alert('Error occurred during attempt to change theme:\n' + ajax.responseText);
-        }
-      }
-    }, true);
-  
-  return false;
-  
+          $(this).remove();
+        });
+    }, 300);
+  opacity('theme-selector-inner', 100, 0, 250);
 }
 
-window.ajaxSwapCSS = function()
+function themeid_to_title(id)
 {
-  // IE <6 pseudo-compatibility
-  if ( KILL_SWITCH )
-    return true;
-  setAjaxLoading();
-  if(_css) {
-    document.getElementById('mdgCss').href = main_css;
-    _css = false;
-  } else {
-    document.getElementById('mdgCss').href = print_css;
-    _css = true;
-  }
-  unsetAjaxLoading();
-  menuOff();
+  if ( typeof(id) != 'string' )
+    return false;
+  id = id.substr(0, 1).toUpperCase() + id.substr(1);
+  id = id.replace(/_/g, ' ');
+  id = id.replace(/-/g, ' ');
+  return id;
 }
 
 window.ajaxSetPassword = function()
@@ -1379,7 +1406,7 @@
     });
 }
 
-window.ajaxPluginAction = function(action, plugin_filename, btnobj)
+window.ajaxPluginAction = function(action, plugin_filename, btnobj, send_confirm)
 {
   // if installing, uninstalling, or re-importing, confirm
   if ( action == 'install' || action == 'uninstall' || action == 'reimport' )
@@ -1420,12 +1447,12 @@
           ajaxPluginAction(this._action + '_confirm', this._filename, this._button);
           miniPromptDestroy(this);
           return false;
-        }
+        };
         btn_cancel.onclick = function()
         {
           miniPromptDestroy(this);
           return false;
-        }
+        };
       });
     return true;
   }
@@ -1436,10 +1463,15 @@
     var td = btnobj.parentNode.parentNode.parentNode.parentNode;
     var blackbox = whiteOutElement(td);
   }
-  var request = toJSONString({
+  var request = {
       mode: action,
       plugin: plugin_filename
-    });
+    };
+  if ( send_confirm )
+  {
+    request.install_confirmed = true;
+  }
+  request = toJSONString(request);
   ajaxPost(makeUrlNS('Admin', 'PluginManager/action.json'), 'r=' + ajaxEscape(request), function(ajax)
     {
       if ( ajax.readyState == 4 && ajax.status == 200 )
@@ -1451,15 +1483,43 @@
           return false;
         }
         response = parseJSON(response);
+        if ( blackbox )
+        {
+          blackbox.parentNode.removeChild(blackbox);
+        }
         if ( response.success )
         {
-          if ( blackbox )
-          {
-            blackbox.parentNode.removeChild(blackbox);
-          }
           ajaxPage( namespace_list['Admin'] + 'PluginManager' );
           return true;
-        } 
+        }
+        if ( response.need_confirm )
+        {
+          miniPromptMessage({
+              title: $lang.get(response.confirm_title),
+              message: $lang.get(response.confirm_body),
+              buttons: [
+                {
+                  text: $lang.get('acppl_btn_install'),
+                  color: 'red',
+                  style: {
+                    fontWeight: 'bold',
+                  },
+                  onclick: function() {
+                    ajaxPluginAction(action + '_confirm', plugin_filename, btnobj, true);
+                    miniPromptDestroy(this);
+                  }
+                },
+                {
+                  text: $lang.get('etc_cancel'),
+                  color: 'blue',
+                  onclick: function() {
+                    miniPromptDestroy(this);
+                  }
+                }
+              ]
+            });
+          return true;
+        }
         // wait for fade effect to finish its run
         setTimeout(function()
           {
@@ -1532,12 +1592,3 @@
     });
 }
 
-function themeid_to_title(id)
-{
-  if ( typeof(id) != 'string' )
-    return false;
-  id = id.substr(0, 1).toUpperCase() + id.substr(1);
-  id = id.replace(/_/g, ' ');
-  id = id.replace(/-/g, ' ');
-  return id;
-}
--- a/includes/clientside/static/dropdown.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/dropdown.js	Sat Mar 21 14:55:34 2009 -0400
@@ -456,6 +456,8 @@
   if(!type)
     type = '*';
   ret = new Array();
+  if ( !parent )
+    return ret;
   el = parent.getElementsByTagName(type);
   for ( var i = 0; i < el.length; i++ )
   {
--- a/includes/clientside/static/fadefilter.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/fadefilter.js	Sat Mar 21 14:55:34 2009 -0400
@@ -14,7 +14,7 @@
   if ( !opacVal )
     opacVal = 70;
   darkener_index[layerid] = ( typeof(darkener_index[layerid]) == 'number' ) ? darkener_index[layerid] + 1 : 1;
-  if(document.getElementById(layerid))
+  if(document.getElementById(layerid) && !document.getElementById(layerid).destroying)
   {
     document.getElementById(layerid).style.zIndex = getHighestZ() + 1;
     if(nofade)
@@ -38,7 +38,18 @@
         opacity(layerid, 0, opacVal, 1000);
       }
     }
-  } else {
+  }
+  else if(document.getElementById(layerid) && document.getElementById(layerid).destroying)
+  {
+    // fade in progress - abort
+    console.warn('Aborting fade');
+    abortFades();
+    changeOpac(opacVal, layerid);
+    document.getElementById(layerid).destroying = false;
+    return document.getElementById(layerid);
+  }
+  else
+  {
     w = getWidth();
     h = getHeight();
     var thediv = document.createElement('div');
@@ -103,9 +114,10 @@
     }
     else
     {
+      document.getElementById(layerid).destroying = true;
       var from = document.getElementById(layerid).myOpacVal;
       opacity(layerid, from, 0, 1000);
-      setTimeout("document.getElementById('" + layerid + "').style.display = 'none';", 1000);
+      setTimeout("if ( document.getElementById('" + layerid + "').destroying ) { document.getElementById('" + layerid + "').destroying = false; document.getElementById('" + layerid + "').style.display = 'none'; }", 1000);
     }
   }
   return document.getElementById(layerid);
--- a/includes/clientside/static/functions.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/functions.js	Sat Mar 21 14:55:34 2009 -0400
@@ -598,7 +598,7 @@
     domOpacity(object, opacStart, opacEnd, millisec);
 }
 
-var opacityDOMCache = new Object();
+var opacityDOMCache = {};
 function domOpacity(obj, opacStart, opacEnd, millisec) {
     //speed for each frame
     var speed = Math.round(millisec / 100);
@@ -611,19 +611,24 @@
     //determine the direction for the blending, if start and end are the same nothing happens
     if(opacStart > opacEnd) {
         for(i = opacStart; i >= opacEnd; i--) {
-            setTimeout("var obj = opacityDOMCache["+uniqid+"]; domObjChangeOpac(" + i + ",obj)",(timer * speed));
+            setTimeout("if ( opacityDOMCache["+uniqid+"] ) { var obj = opacityDOMCache["+uniqid+"]; domObjChangeOpac(" + i + ",obj) }",(timer * speed));
             timer++;
         }
     } else if(opacStart < opacEnd) {
         for(i = opacStart; i <= opacEnd; i++)
             {
-            setTimeout("var obj = opacityDOMCache["+uniqid+"]; domObjChangeOpac(" + i + ",obj)",(timer * speed));
+            setTimeout("if ( opacityDOMCache["+uniqid+"] ) { var obj = opacityDOMCache["+uniqid+"]; domObjChangeOpac(" + i + ",obj); }",(timer * speed));
             timer++;
         }
     }
     setTimeout("delete(opacityDOMCache["+uniqid+"]);",(timer * speed));
 }
 
+function abortFades()
+{
+  opacityDOMCache = {};
+}
+
 // change the opacity for different browsers
 function changeOpac(opacity, id)
 {
--- a/includes/clientside/static/json.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/json.js	Sat Mar 21 14:55:34 2009 -0400
@@ -11,6 +11,10 @@
 
 function toJSONString(input)
 {
+  if ( window.JSON )
+  {
+    return window.JSON.stringify(input);
+  }
   var m = {
           '\b': '\\b',
           '\t': '\\t',
@@ -142,6 +146,11 @@
 
 function parseJSON(string, filter)
 {
+  if ( window.JSON )
+  {
+    return window.JSON.parse(string);
+  }
+  
   try {
     if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
             test(string))
--- a/includes/clientside/static/paginate.js	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/clientside/static/paginate.js	Sat Mar 21 14:55:34 2009 -0400
@@ -322,6 +322,7 @@
   var offset = ( userinput - 1 ) * perpage;
   if ( userinput > max || isNaN(userinput) || userinput < 1 )
   {
+    load_component(['messagebox', 'fadefilter', 'flyin']);
     new MessageBox(MB_OK|MB_ICONSTOP, $lang.get('paginate_err_bad_page_title'), $lang.get('paginate_err_bad_page_body', { max: max }));
     return false;
   }
--- a/includes/functions.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/functions.php	Sat Mar 21 14:55:34 2009 -0400
@@ -2195,6 +2195,127 @@
 }
 
 /**
+ * Generates the HTML of a pagination control.
+ * @param int Current page
+ * @param int Number of pages
+ * @param string sprintf()-style formatting URL for pages
+ * @param int Multiplier for start offset, defaults to 1
+ * @param int Add to each $i for addition to result urls, usually either 0 or 1 (depends on whether you want your ?page= to start with 0 ro 1)
+ * @return string HTML
+ */
+
+function generate_paginator($current_page, $num_pages, $result_url, $start_mult = 1, $start_add = 1)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  global $lang;
+  
+  $out = '';
+  $i = 0;
+
+  // Build paginator
+  $pg_css = ( strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') ) ?
+            // IE-specific hack
+            'display: block; width: 1px;':
+            // Other browsers
+            'display: table; margin: 10px 0 0 auto;';
+  
+  $begin = '<div class="tblholder" style="'. $pg_css . '">
+    <table border="0" cellspacing="1" cellpadding="4">
+      <tr><th>' . $lang->get('paginate_lbl_page') . '</th>';
+  $block = '<td class="row1" style="text-align: center;">{LINK}</td>';
+  $end = '</tr></table></div>';
+  $blk = $template->makeParserText($block);
+  $inner = '';
+  $cls = 'row2';
+  if ( $num_pages < 5 )
+  {
+    for ( $i = 0; $i < $num_pages; $i++ )
+    {
+      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
+      $offset = strval(($i * $start_mult) + $start_add);
+      $url = htmlspecialchars(sprintf($result_url, $offset));
+      $j = $i + 1;
+      $link = ( $i == $current_page ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
+      $blk->assign_vars(array(
+        'CLASS'=>$cls,
+        'LINK'=>$link
+        ));
+      $inner .= $blk->run();
+    }
+  }
+  else
+  {
+    if ( $current_page + 5 > $num_pages )
+    {
+      $list = Array();
+      $tp = $current_page;
+      if ( $current_page + 0 == $num_pages ) $tp = $tp - 3;
+      if ( $current_page + 1 == $num_pages ) $tp = $tp - 2;
+      if ( $current_page + 2 == $num_pages ) $tp = $tp - 1;
+      for ( $i = $tp - 1; $i <= $tp + 1; $i++ )
+      {
+        $list[] = $i;
+      }
+    }
+    else
+    {
+      $list = Array();
+      $current = $current_page;
+      $lower = ( $current < 3 ) ? 1 : $current - 1;
+      for ( $i = 0; $i < 3; $i++ )
+      {
+        $list[] = $lower + $i;
+      }
+    }
+    $url = sprintf($result_url, '0');
+    $link = ( 0 == $current_page ) ? "<b>" . $lang->get('paginate_btn_first') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>&laquo; " . $lang->get('paginate_btn_first') . "</a>";
+    $blk->assign_vars(array(
+      'CLASS'=>$cls,
+      'LINK'=>$link
+      ));
+    $inner .= $blk->run();
+
+    foreach ( $list as $i )
+    {
+      if ( $i == $num_pages )
+        break;
+      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
+      $offset = strval(($i * $start_mult) + $start_add);
+      $url = sprintf($result_url, $offset);
+      $j = $i + 1;
+      $link = ( $i == $current_page ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
+      $blk->assign_vars(array(
+        'CLASS'=>$cls,
+        'LINK'=>$link
+        ));
+      $inner .= $blk->run();
+    }
+
+    // "Last" button
+    $total = (($num_pages - 1) * $start_mult) + $start_add;
+
+    if ( $current_page < $num_pages )
+    {
+      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
+      $offset = strval($total);
+      $url = sprintf($result_url, $offset);
+      $link = ( $num_pages - 1 == $current_page ) ? "<b>" . $lang->get('paginate_btn_last') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>" . $lang->get('paginate_btn_last') . " &raquo;</a>";
+      $blk->assign_vars(array(
+        'CLASS'=>$cls,
+        'LINK'=>$link
+        ));
+      $inner .= $blk->run();
+    }
+
+  }
+
+  $inner .= '<td class="row2" style="cursor: pointer;" onclick="paginator_goto(this, '.$current_page.', '.$num_pages.', '.$start_mult.', unescape(\'' . rawurlencode($result_url) . '\'));">&darr;</td>';
+
+  $paginator = "\n$begin$inner$end\n";
+  return $paginator;
+}
+
+/**
  * Paginates (breaks into multiple pages) a MySQL result resource, which is treated as unbuffered.
  * @param resource The MySQL result resource. This should preferably be an unbuffered query.
  * @param string A template, with variables being named after the column name
@@ -2211,124 +2332,15 @@
 function paginate($q, $tpl_text, $num_results, $result_url, $start = 0, $perpage = 10, $callers = Array(), $header = '', $footer = '')
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  global $lang;
   
   $parser = $template->makeParserText($tpl_text);
+  
   $num_pages = ceil ( $num_results / $perpage );
   $out = '';
+  $this_page = ceil ( $start / $perpage );
   $i = 0;
-  $this_page = ceil ( $start / $perpage );
-
-  // Build paginator
-  $pg_css = ( strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') ) ?
-            // IE-specific hack
-            'display: block; width: 1px;':
-            // Other browsers
-            'display: table; margin: 10px 0 0 auto;';
-  $begin = '<div class="tblholder" style="'. $pg_css . '">
-    <table border="0" cellspacing="1" cellpadding="4">
-      <tr><th>' . $lang->get('paginate_lbl_page') . '</th>';
-  $block = '<td class="row1" style="text-align: center;">{LINK}</td>';
-  $end = '</tr></table></div>';
-  $blk = $template->makeParserText($block);
-  $inner = '';
-  $cls = 'row2';
-  if ( $num_pages < 5 )
-  {
-    for ( $i = 0; $i < $num_pages; $i++ )
-    {
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($i * $perpage);
-      $url = htmlspecialchars(sprintf($result_url, $offset));
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-  }
-  else
-  {
-    if ( $this_page + 5 > $num_pages )
-    {
-      $list = Array();
-      $tp = $this_page;
-      if ( $this_page + 0 == $num_pages ) $tp = $tp - 3;
-      if ( $this_page + 1 == $num_pages ) $tp = $tp - 2;
-      if ( $this_page + 2 == $num_pages ) $tp = $tp - 1;
-      for ( $i = $tp - 1; $i <= $tp + 1; $i++ )
-      {
-        $list[] = $i;
-      }
-    }
-    else
-    {
-      $list = Array();
-      $current = $this_page;
-      $lower = ( $current < 3 ) ? 1 : $current - 1;
-      for ( $i = 0; $i < 3; $i++ )
-      {
-        $list[] = $lower + $i;
-      }
-    }
-    $url = sprintf($result_url, '0');
-    $link = ( 0 == $start ) ? "<b>" . $lang->get('paginate_btn_first') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>&laquo; " . $lang->get('paginate_btn_first') . "</a>";
-    $blk->assign_vars(array(
-      'CLASS'=>$cls,
-      'LINK'=>$link
-      ));
-    $inner .= $blk->run();
-
-    // if ( !in_array(1, $list) )
-    // {
-    //   $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-    //   $blk->assign_vars(array('CLASS'=>$cls,'LINK'=>'...'));
-    //   $inner .= $blk->run();
-    // }
-
-    foreach ( $list as $i )
-    {
-      if ( $i == $num_pages )
-        break;
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($i * $perpage);
-      $url = sprintf($result_url, $offset);
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-
-    $total = $num_pages * $perpage - $perpage;
-
-    if ( $this_page < $num_pages )
-    {
-      // $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      // $blk->assign_vars(array('CLASS'=>$cls,'LINK'=>'...'));
-      // $inner .= $blk->run();
-
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($total);
-      $url = sprintf($result_url, $offset);
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>" . $lang->get('paginate_btn_last') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>" . $lang->get('paginate_btn_last') . " &raquo;</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-
-  }
-
-  $inner .= '<td class="row2" style="cursor: pointer;" onclick="paginator_goto(this, '.$this_page.', '.$num_pages.', '.$perpage.', unescape(\'' . rawurlencode($result_url) . '\'));">&darr;</td>';
-
-  $paginator = "\n$begin$inner$end\n";
+  
+  $paginator = generate_paginator($this_page, $num_pages, $result_url, $perpage, 0);
   $out .= $paginator;
 
   $cls = 'row2';
@@ -2394,138 +2406,9 @@
   $i = 0;
   $this_page = ceil ( $start / $perpage );
 
-  // Build paginator
-  $begin = '<div class="tblholder" style="display: table; margin: 10px 0 0 auto;">
-    <table border="0" cellspacing="1" cellpadding="4">
-      <tr><th>' . $lang->get('paginate_lbl_page') . '</th>';
-  $block = '<td class="row1" style="text-align: center;">{LINK}</td>';
-  $end = '</tr></table></div>';
-  $blk = $template->makeParserText($block);
-  $inner = '';
-  $cls = 'row2';
-  $total = $num_pages * $perpage - $perpage;
-  /*
-  if ( $start > 0 )
-  {
-    $url = sprintf($result_url, abs($start - $perpage));
-    $link = "<a href=".'"'."$url".'"'." style='text-decoration: none;'>&laquo; " . $lang->get('paginate_btn_prev') . "</a>";
-    $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-    $blk->assign_vars(array(
-      'CLASS'=>$cls,
-      'LINK'=>$link
-      ));
-    $inner .= $blk->run();
-  }
-  */
-  if ( $num_pages < 5 )
-  {
-    for ( $i = 0; $i < $num_pages; $i++ )
-    {
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($i * $perpage);
-      $url = htmlspecialchars(sprintf($result_url, $offset));
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-  }
-  else
-  {
-    if ( $this_page + 5 > $num_pages )
-    {
-      $list = Array();
-      $tp = $this_page;
-      if ( $this_page + 0 == $num_pages ) $tp = $tp - 3;
-      if ( $this_page + 1 == $num_pages ) $tp = $tp - 2;
-      if ( $this_page + 2 == $num_pages ) $tp = $tp - 1;
-      for ( $i = $tp - 1; $i <= $tp + 1; $i++ )
-      {
-        $list[] = $i;
-      }
-    }
-    else
-    {
-      $list = Array();
-      $current = $this_page;
-      $lower = ( $current < 3 ) ? 1 : $current - 1;
-      for ( $i = 0; $i < 3; $i++ )
-      {
-        $list[] = $lower + $i;
-      }
-    }
-    $url = sprintf($result_url, '0');
-    $link = ( 0 == $start ) ? "<b>" . $lang->get('paginate_btn_first') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>&laquo; " . $lang->get('paginate_btn_first') . "</a>";
-    $blk->assign_vars(array(
-      'CLASS'=>$cls,
-      'LINK'=>$link
-      ));
-    $inner .= $blk->run();
-
-    // if ( !in_array(1, $list) )
-    // {
-    //   $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-    //   $blk->assign_vars(array('CLASS'=>$cls,'LINK'=>'...'));
-    //   $inner .= $blk->run();
-    // }
-
-    foreach ( $list as $i )
-    {
-      if ( $i == $num_pages )
-        break;
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($i * $perpage);
-      $url = sprintf($result_url, $offset);
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>$j</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>$j</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-
-    if ( $this_page < $num_pages )
-    {
-      // $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      // $blk->assign_vars(array('CLASS'=>$cls,'LINK'=>'...'));
-      // $inner .= $blk->run();
-
-      $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-      $offset = strval($total);
-      $url = sprintf($result_url, $offset);
-      $j = $i + 1;
-      $link = ( $offset == strval($start) ) ? "<b>" . $lang->get('paginate_btn_last') . "</b>" : "<a href=".'"'."$url".'"'." style='text-decoration: none;'>" . $lang->get('paginate_btn_last') . " &raquo;</a>";
-      $blk->assign_vars(array(
-        'CLASS'=>$cls,
-        'LINK'=>$link
-        ));
-      $inner .= $blk->run();
-    }
-
-  }
-
-  /*
-  if ( $start < $total )
-  {
-    $link_offset = abs($start + $perpage);
-    $url = htmlspecialchars(sprintf($result_url, strval($link_offset)));
-    $link = "<a href=".'"'."$url".'"'." style='text-decoration: none;'>" . $lang->get('paginate_btn_next') . " &raquo;</a>";
-    $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-    $blk->assign_vars(array(
-      'CLASS'=>$cls,
-      'LINK'=>$link
-      ));
-    $inner .= $blk->run();
-  }
-  */
-
-  $inner .= '<td class="row2" style="cursor: pointer;" onclick="paginator_goto(this, '.$this_page.', '.$num_pages.', '.$perpage.', unescape(\'' . rawurlencode($result_url) . '\'));">&darr;</td>';
-
-  $paginator = "\n$begin$inner$end\n";
+  $paginator = generate_paginator($this_page, $num_pages, $result_url, $perpage, 0);
+  $out .= $paginator;
+  
   if ( $total > 1 )
   {
     $out .= $paginator;
--- a/includes/namespaces/default.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/namespaces/default.php	Sat Mar 21 14:55:34 2009 -0400
@@ -362,7 +362,7 @@
     }
     else
     {
-      $pathskey = $paths->nslist[ $this->namespace ] . $paths->page_id;
+      $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
       $page_format = $paths->pages[$pathskey]['page_format'];
     }
     
--- a/includes/output.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/output.php	Sat Mar 21 14:55:34 2009 -0400
@@ -163,6 +163,24 @@
     echo $template->getFooter();
     echo $this->after_footer;
     
+    global $aggressive_optimize_html;
+    if ( $aggressive_optimize_html )
+    {
+      $content = ob_get_contents();
+      ob_end_clean();
+      
+      ob_start();
+      echo aggressive_optimize_html($content);
+    }
+    else
+    {
+      $content = ob_get_contents();
+      ob_end_clean();
+      
+      ob_start();
+      echo preg_replace('~</?enano:no-opt>~', '', $content);
+    }
+    
   }
   
   public function set_title($title)
@@ -175,6 +193,51 @@
 }
 
 /**
+ * Same as HTML, except uses simple-header and simple-footer.
+ */
+
+class Output_HTML_Simple extends Output_HTML
+{
+  public function footer()
+  {
+    global $template;
+    if ( !$this->headers_sent )
+      return;
+    
+    $this->headers_sent = false;
+    $content = ob_get_contents();
+    ob_end_clean();
+    
+    ob_start();
+    echo $this->before_header;
+    echo $template->getHeader(true);
+    echo $this->after_header;
+    echo $content;
+    echo $this->before_footer;
+    echo $template->getFooter(true);
+    echo $this->after_footer;
+    
+    global $aggressive_optimize_html;
+    if ( $aggressive_optimize_html )
+    {
+      $content = ob_get_contents();
+      ob_end_clean();
+      
+      ob_start();
+      echo aggressive_optimize_html($content);
+    }
+    else
+    {
+      $content = ob_get_contents();
+      ob_end_clean();
+      
+      ob_start();
+      echo preg_replace('~</?enano:no-opt>~', '', $content);
+    }
+  }
+}
+
+/**
  * Outputter that bypasses $template->header() and $template->footer(), but still shows HTML added via {before,after}_{header,footer}.
  */
 
--- a/includes/plugins.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/plugins.php	Sat Mar 21 14:55:34 2009 -0400
@@ -513,6 +513,29 @@
   }
   
   /**
+   * Determines if a file is an authentication extension by looking at the file contents.
+   * @param string Plugin filename
+   * @return bool
+   */
+  
+  function is_file_auth_plugin($filename)
+  {
+    $filename = ENANO_ROOT . '/plugins/' . $filename;
+    if ( !file_exists($filename) )
+      return false;
+    
+    $info = $this->get_plugin_info($filename);
+    if ( isset($info['auth plugin']) )
+      return true;
+    
+    $contents = @file_get_contents($filename);
+    if ( strstr($contents, 'login_process_userdata_json') )
+      return true;
+    
+    return false;
+  }
+  
+  /**
    * Installs a plugin.
    * @param string Filename of plugin.
    * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time.
--- a/includes/template.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/includes/template.php	Sat Mar 21 14:55:34 2009 -0400
@@ -1014,7 +1014,7 @@
       $js_foot = <<<JSEOF
     <!-- jsres.php is a wrapper script that compresses and caches single JS files to minimize requests -->
     <script type="text/javascript" src="$cdnpath/includes/clientside/jsres.php"></script>
-    <script type="text/javascript">
+    <script type="text/javascript">//<![CDATA[
       // This initializes the Javascript runtime when the DOM is ready - not when the page is
       // done loading, because enano-lib-basic still has to load some 15 other script files
       // check for the init function - this is a KHTML fix
@@ -1025,7 +1025,7 @@
         enano_init();
         window.onload = function(e) {  };
       }
-    </script>
+    //]]></script>
 JSEOF;
     }
     
@@ -1195,6 +1195,7 @@
       'ADMIN_SID_AMP_HTML' => $ash,
       'ADMIN_SID_AUTO' => $as2,
       'ADMIN_SID_RAW' =>  ( is_string($session->sid_super) ? $session->sid_super : '' ),
+      'CSRF_TOKEN' => $session->csrf_token,
       'COPYRIGHT' => RenderMan::parse_internal_links(getConfig('copyright_notice')),
       'TOOLBAR_EXTRAS' => $this->toolbar_menu,
       'REQUEST_URI' => ( defined('ENANO_CLI') ? '' : $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] ),
@@ -1271,9 +1272,20 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    ob_start();
+    echo $this->getHeader($simple);
+  }
+  
+  function footer($simple = false)
+  {
+    echo $this->getFooter($simple);
+  }
+  
+  function getHeader($simple = false)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
     
-    if(!$this->theme_loaded)
+    if ( !$this->theme_loaded )
     {
       $this->load_theme($session->theme, $session->style);
     }
@@ -1296,36 +1308,23 @@
     }
     if ( !$simple && $session->user_logged_in && $session->unread_pms > 0 )
     {
-      echo $this->notify_unread_pms();
+      $header .= $this->notify_unread_pms();
     }
     if ( !$simple && $session->sw_timed_out )
     {
       $login_link = makeUrlNS('Special', 'Login/' . $paths->fullpage, 'level=' . $session->user_level, true);
-      echo '<div class="usermessage">';
-      echo $lang->get('user_msg_elev_timed_out', array( 'login_link' => $login_link ));
-      echo '</div>';
+      $header .= '<div class="usermessage">';
+      $header .= $lang->get('user_msg_elev_timed_out', array( 'login_link' => $login_link ));
+      $header .= '</div>';
     }
     if ( $this->site_disabled && $session->user_level >= USER_LEVEL_ADMIN && ( $paths->page != $paths->nslist['Special'] . 'Administration' ) )
     {
       $admin_link = makeUrlNS('Special', 'Administration', 'module=' . $paths->nslist['Admin'] . 'GeneralConfig', true);
-      echo '<div class="usermessage"><b>' . $lang->get('page_sitedisabled_admin_msg_title') . '</b><br />
+      $header .= '<div class="usermessage"><b>' . $lang->get('page_sitedisabled_admin_msg_title') . '</b><br />
             ' . $lang->get('page_sitedisabled_admin_msg_body', array('admin_link' => $admin_link)) . '
             </div>';
     }
   }
-  
-  function footer($simple = false)
-  {
-    echo $this->getFooter($simple);
-  }
-  
-  function getHeader()
-  {
-    $headers_sent = true;
-    if(!defined('ENANO_HEADERS_SENT'))
-      define('ENANO_HEADERS_SENT', '');
-    if(!$this->no_headers) return $this->process_template('header.tpl');
-  }
   function getFooter($simple = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
@@ -1644,21 +1643,30 @@
   /**
    * Post-processor for template code. Basically what this does is it localizes {lang:foo} blocks.
    * @param string Mostly-processed TPL code
+   * @param bool Post-eval switch. If true, does not escape code.
    * @return string
    */
   
-  function compile_template_text_post($text)
+  function compile_template_text_post($text, $post_eval = false)
   {
+    global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
+    
+    // Language strings
     preg_match_all('/\{lang:([a-z0-9]+_[a-z0-9_]+)\}/', $text, $matches);
     foreach ( $matches[1] as $i => $string_id )
     {
       $string = $lang->get($string_id);
-      $string = str_replace('\\', '\\\\', $string);
-      $string = str_replace('\'', '\\\'', $string);
+      if ( !$post_eval )
+      {
+        $string = str_replace('\\', '\\\\', $string);
+        $string = str_replace('\'', '\\\'', $string);
+      }
       $text = str_replace_once($matches[0][$i], $string, $text);
     }
-    preg_match_all('/\{url:([A-z0-9]+):([\w\.\/:;\(\)@\[\]_=-]+)(?::([^\s\}]+))?(?:\|(escape))?\}/', $text, $matches);
+    
+    // URLs
+    preg_match_all('/\{url:([A-z0-9]+):([^\s\}]+?)(?:;([^\s\}]+?))?(?:\|(escape))?\}/i', $text, $matches);
     foreach ( $matches[1] as $i => $string_id )
     {
       $namespace =& $matches[1][$i];
@@ -1672,11 +1680,47 @@
       
       $result = makeUrlNS($namespace, $page_id, $params, $escape);
       
+      if ( !$post_eval )
+      {
+        $result = str_replace('\\', '\\\\', $result);
+        $result = str_replace('\'', '\\\'', $result);
+      }
+      
       $text = str_replace_once($matches[0][$i], $result, $text);
     }
+    
+    $code = $plugins->setHook('compie_template_text_post');
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    
     return $text;
   }
   
+  /**
+   * Returns the output of a theme hook
+   * @param string Hook name
+   * @return string
+   */
+  
+  function get_theme_hook($hook)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
+    
+    ob_start();
+    $code = $plugins->setHook($hook);
+    foreach ( $code as $cmd )
+    {
+      eval($cmd);
+    }
+    $out = ob_get_contents();
+    ob_end_clean();
+    
+    return $out;
+  }
+  
   // n00bish comments removed from here. 2008-03-13 @ 12:02AM when I had nothing else to do.
   
   /**
@@ -2601,6 +2645,9 @@
   // System messages
   $text = preg_replace('/<!-- SYSMSG ([A-z0-9\._-]+?) -->/is', '\' . $template->tplWikiFormat($paths->sysMsg(\'\\1\')) . \'', $text);
   
+  // Hooks
+  $text = preg_replace('/<!-- HOOK ([A-z0-9_]+) -->/', '\' . $this->get_theme_hook(\'\\1\') . \'', $text);
+  
   // only do this if the plugins API is loaded
   if ( is_object(@$plugins) )
   {
--- a/index.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/index.php	Sat Mar 21 14:55:34 2009 -0400
@@ -575,40 +575,24 @@
       break;
   }
   
-  //
-  // Optimize HTML by replacing newlines with spaces (excludes <pre>, <script>, and <style> blocks)
-  //
-  if ($aggressive_optimize_html)
+  // Generate an ETag
+  /*
+  // format: first 10 digits of SHA1 of page name, user id in hex, user and auth levels, page timestamp in hex
+  $etag = substr(sha1($paths->namespace . ':' . $paths->page_id), 0, 10) . '-' .
+          "u{$session->user_id}l{$session->user_level}a{$session->auth_level}-" .
+          dechex($page_timestamp);
+          
+  if ( isset($_SERVER['HTTP_IF_NONE_MATCH']) )
   {
-    // Load up the HTML
-    $html = ob_get_contents();
-    @ob_end_clean();
-    
-    $html = aggressive_optimize_html($html);
-    
-    // Re-enable output buffering to allow the Gzip function (below) to work
-    ob_start();
-    
-    // Generate an ETag
-    // format: first 10 digits of SHA1 of page name, user id in hex, user and auth levels, page timestamp in hex
-    $etag = substr(sha1($paths->namespace . ':' . $paths->page_id), 0, 10) . '-' .
-            "u{$session->user_id}l{$session->user_level}a{$session->auth_level}-" .
-            dechex($page_timestamp);
+    if ( "\"$etag\"" == $_SERVER['HTTP_IF_NONE_MATCH'] )
+    {
+      header('HTTP/1.1 304 Not Modified');
+      exit();
+    }
+  }
             
-    if ( isset($_SERVER['HTTP_IF_NONE_MATCH']) )
-    {
-      if ( "\"$etag\"" == $_SERVER['HTTP_IF_NONE_MATCH'] )
-      {
-        header('HTTP/1.1 304 Not Modified');
-        exit();
-      }
-    }
-            
-    // header("ETag: \"$etag\"");
-    
-    // Done, send it to the user
-    echo( $html );
-  }
+  header("ETag: \"$etag\"");
+  */
   
   $db->close();  
   gzip_output();
--- a/language/english/admin.json	Sat Mar 21 14:54:53 2009 -0400
+++ b/language/english/admin.json	Sat Mar 21 14:55:34 2009 -0400
@@ -1,6 +1,6 @@
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.1.6 (Caoineag beta 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
@@ -493,6 +493,9 @@
       msg_confirm_install: 'Plugins are not supported by the Enano project and could harm your site if malicious. You should only install plugins from sources that you trust.',
       msg_confirm_reimport: 'Re-importing strings will reload all language data from the plugin file. This should fix missing messages, but any customizations you have made to the plugin\'s language strings will be lost.',
       
+      msg_confirm_authext_title: 'This plugin is an authentication extension.',
+      msg_confirm_authext_body: 'This plugin hooks into Enano\'s login system. It might be used to allow non-password-based authentication. If there is a security vulnerability in this plugin, it might open your site up to attack. Only continue if you trust the author of this plugin.\n\nBe sure to be vigilant in checking for updates to this plugin.',
+      
       err_upgrade_not_supported: 'This plugin doesn\'t support automatic upgrades. The version number has been updated so the plugin will be re-enabled, but you should check the plugin file to see if the author provided instructions for finishing the upgrade.',
       err_upgrade_bad_version: 'This plugin cannot be upgraded because you are running a version of the plugin that is not listed in the plugin\'s version list.',
       err_upgrade_bad_target_version: 'This plugin cannot be upgraded because it does not support its own version. Please contact the author and ask them to fix this.',
--- a/language/english/core.json	Sat Mar 21 14:54:53 2009 -0400
+++ b/language/english/core.json	Sat Mar 21 14:55:34 2009 -0400
@@ -1,6 +1,6 @@
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.1.6 (Caoineag beta 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
@@ -40,7 +40,7 @@
       tz: 'Time zones',
       plural: 's',
       enano_about_th: 'About the Enano Content Management System',
-      enano_about_poweredby: '<p>This website is powered by <a href="http://enanocms.org/">Enano</a>, the lightweight and open source CMS that everyone can use. Enano is copyright &copy; 2006-2007 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please see <a href="http://enanocms.org/Legal_information">Legal Information</a>.</p><p>The developers and maintainers of Enano strongly believe that software should not only be free to use, but free to be modified, distributed, and used to create derivative works. For more information about Free Software, check out the <a href="http://en.wikipedia.org/wiki/Free_Software" onclick="window.open(this.href); return false;">Wikipedia page</a> or the <a href="http://www.fsf.org/" onclick="window.open(this.href); return false;">Free Software Foundation\'s</a> homepage.</p>',
+      enano_about_poweredby: '<p>This website is powered by <a href="http://enanocms.org/">Enano</a>, the lightweight and open source CMS that everyone can use. Enano is copyright &copy; 2006-2009 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please see <a href="http://enanocms.org/Legal_information">Legal Information</a>.</p><p>The developers and maintainers of Enano strongly believe that software should not only be free to use, but free to be modified, distributed, and used to create derivative works. For more information about Free Software, check out the <a href="http://en.wikipedia.org/wiki/Free_Software" onclick="window.open(this.href); return false;">Wikipedia page</a> or the <a href="http://www.fsf.org/" onclick="window.open(this.href); return false;">Free Software Foundation\'s</a> homepage.</p>',
       enano_about_gpl: '<p>This program is Free Software; you can redistribute it 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.</p><p>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.</p><p>You should have received <a href="%gpl_link%">a copy of the GNU General Public License</a> along with this program; if not, write to:</p><p style="margin-left 2em;">Free Software Foundation, Inc.,<br />51 Franklin Street, Fifth Floor<br />Boston, MA 02110-1301, USA</p><p>Alternatively, you can <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">read it online</a>.</p>',
       enano_about_lbl_enanoversion: '<a href="http://enanocms.org">Enano</a> version:',
       enano_about_lbl_webserver: 'Web server:',
@@ -258,6 +258,13 @@
       lbl_page_category: 'category',
       lbl_page_external: 'external page',
       
+      lbl_sitetools: 'site tools',
+      lbl_changes: 'changes',
+      
+      btn_changes_mine: 'mine',
+      btn_changes_recent: 'recent',
+      btn_changes_history: 'this page',
+      
       btn_discussion: 'discussion (%num_comments%)',
       btn_discussion_unapp: '<span title="Approved: %num_app% | Unapproved: %num_unapp% | Spam: %num_spam%">discussion (%num_comments%) [!]</span>',
       btn_edit: 'edit this page',
@@ -474,17 +481,18 @@
       clearlogs_confirm_title: 'Clear this page\'s logs?',
       clearlogs_confirm_body: 'This will erase this page\'s entire history and is an irreversible operation.',
       clearlogs_btn_submit: 'Clear Logs',
-      changestyle_select: '[Select]',
-      changestyle_title: 'Change your theme',
-      changestyle_pleaseselect_theme: 'Please select a theme from the list.',
-      changestyle_lbl_theme: 'Theme:',
-      changestyle_lbl_style: 'Style:',
-      changestyle_success: 'Your theme preference has been changed.\nWould you like to reload the page now to see the changes?',
       killphp_confirm: 'Are you really sure you want to do this? Some pages might not function if this emergency-only feature is activated.',
       killphp_success: 'Embedded PHP in pages has been disabled.',
       lbl_moreoptions_nojs: 'More options for this page',
       msg_loading_component: 'Loading %component%...',
       
+      thmsel_lbl_choosetheme: 'Choose a theme',
+      thmsel_lbl_choosestyle: 'Choose a style variation',
+      thmsel_msg_success: 'Theme changed',
+      thmsel_btn_reload: 'Reload page',
+      thmsel_btn_close: 'Close selector and reload later',
+      thmsel_btn_close_hint: 'You will see your new theme choice go into effect the next time you refresh this page.',
+      
       badjson_title: 'The site encountered an error while processing your request.',
       badjson_body: 'We unexpectedly received the following response from the server. The response should have been in the JSON serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers for this problem:',
       badjson_tip1: 'The server sent back a bad HTTP response code and thus sent an error page instead of running Enano. This indicates a possible problem with your server, and is not likely to be a bug with Enano.',
@@ -529,6 +537,7 @@
       btn_userpage: 'User page',
       btn_mycontribs: 'My contributions',
       btn_preferences: 'Preferences',
+      btn_preferences_short: 'User CP',
       btn_privatemessages: 'Private messages',
       btn_groupcp: 'Group control panel',
       btn_register: 'Create an account',
--- a/language/english/tools.json	Sat Mar 21 14:54:53 2009 -0400
+++ b/language/english/tools.json	Sat Mar 21 14:55:34 2009 -0400
@@ -1,6 +1,6 @@
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.1.6 (Caoineag beta 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/language/english/user.json	Sat Mar 21 14:54:53 2009 -0400
+++ b/language/english/user.json	Sat Mar 21 14:55:34 2009 -0400
@@ -1,6 +1,6 @@
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
+ * Version 1.1.6 (Caoineag beta 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/SpecialPageFuncs.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/plugins/SpecialPageFuncs.php	Sat Mar 21 14:55:34 2009 -0400
@@ -424,9 +424,9 @@
     $platform = 'Linux';
   else if(file_exists('/hurd/pfinet')) // I have a little experience with GNU/Hurd :-) http://hurdvm.enanocms.org/
     $platform = 'GNU/Hurd';
-  else if(file_exists('C:\Windows\system32\ntoskrnl.exe'))
+  else if(strtolower(PHP_OS) == 'winnt')
     $platform = 'Windows NT';
-  else if(file_exists('C:\Windows\system\krnl386.exe'))
+  else if(strtolower(PHP_OS) == 'win32')
     $platform = 'Windows 9x/DOS';
   else if(file_exists('/System/Library/CoreServices/SystemVersion.plist'))
     $platform = 'Mac OS X';
@@ -454,7 +454,7 @@
         <h3>(English)</h3>
         <p>
           This website is powered by <a href="http://enanocms.org/">Enano</a>, the lightweight and open source CMS that everyone can use.
-          Enano is copyright &copy; 2006-2007 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please
+          Enano is copyright &copy; 2006-2009 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please
           see <a href="http://enanocms.org/Legal_information">Legal Information</a>.
         </p>
         <p>
--- a/plugins/admin/PluginManager.php	Sat Mar 21 14:54:53 2009 -0400
+++ b/plugins/admin/PluginManager.php	Sat Mar 21 14:55:34 2009 -0400
@@ -151,6 +151,19 @@
                 );
                 break;
               }
+              if ( !isset($request['install_confirmed']) )
+              {
+                if ( $plugins->is_file_auth_plugin($request['plugin']) )
+                {
+                  $return = array(
+                    'confirm_title' => 'acppl_msg_confirm_authext_title',
+                    'confirm_body' => 'acppl_msg_confirm_authext_body',
+                    'need_confirm' => true,
+                    'success' => false
+                  );
+                  break;
+                }
+              }
               
               $return = $plugins->install_plugin($request['plugin'], $plugin_list);
               break;