Merging branches
authorDan
Sun, 09 Nov 2008 14:22:03 -0500
changeset 734 904fbf10f112
parent 712 331e009416d5 (current diff)
parent 733 e5f638c216f7 (diff)
child 735 f191cb6bd0ca
Merging branches
--- a/ajax.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/ajax.php	Sun Nov 09 14:22:03 2008 -0500
@@ -469,6 +469,7 @@
       break;
     case "preview":
       require_once(ENANO_ROOT.'/includes/pageutils.php');
+      $template->init_vars();
       echo PageUtils::genPreview($_POST['text']);
       break;
     case "pagediff":
--- a/includes/clientside/static/editor.js	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/clientside/static/editor.js	Sun Nov 09 14:22:03 2008 -0500
@@ -162,7 +162,7 @@
   }
   
   // Draft notice
-  if ( response.have_draft )
+  if ( response.have_draft && !readonly )
   {
     var dn = document.createElement('div');
     dn.className = 'warning-box';
--- a/includes/clientside/static/login.js	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/clientside/static/login.js	Sun Nov 09 14:22:03 2008 -0500
@@ -72,6 +72,7 @@
 var AJAX_STATUS_GENERATING_KEY = 2;
 var AJAX_STATUS_LOGGING_IN = 3;
 var AJAX_STATUS_SUCCESS = 4;
+var AJAX_STATUS_ERROR = 5;
 var AJAX_STATUS_DESTROY = 65535;
 
 /**
@@ -297,6 +298,53 @@
       logindata.mb_inner.innerHTML = '';
       logindata.mb_inner.appendChild(div);
       
+      break;
+      
+    case AJAX_STATUS_ERROR:
+      // Create the status div
+      var div = document.createElement('div');
+      div.id = 'ajax_login_status';
+      div.style.marginTop = '10px';
+      div.style.textAlign = 'center';
+      
+      // The circly ball ajaxy image + status message
+      var status_msg = $lang.get('user_login_ajax_err_crypto');
+      
+      // Insert the status message
+      div.appendChild(document.createTextNode(status_msg));
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      var img = document.createElement('img');
+      img.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/checkbad.png';
+      div.appendChild(img);
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      // The circly ball ajaxy image + status message
+      var detail_msg = $lang.get('user_login_ajax_err_crypto_details');
+      var full_link = $lang.get('user_login_ajax_err_crypto_link');
+      var link = document.createElement('a');
+      link.href = makeUrlNS('Special', 'Login/' + title);
+      link.appendChild(document.createTextNode(full_link));
+      var span = document.createElement('span');
+      span.style.fontSize = 'smaller';
+      
+      // Insert the message
+      span.appendChild(document.createTextNode(detail_msg + ' '));
+      span.appendChild(link);
+      div.appendChild(span);
+      
+      // Insert the entire message into the login window
+      logindata.mb_inner.innerHTML = '';
+      logindata.mb_inner.appendChild(div);
+      
+      break;
+      
     case AJAX_STATUS_DESTROY:
     case null:
     case undefined:
@@ -588,6 +636,14 @@
     lbl_dh.innerHTML = $lang.get('user_login_ajax_check_dh_ie');
     form.appendChild(lbl_dh);
   }
+  else if ( !data.allow_diffiehellman )
+  {
+    // create hidden control - server requested that DiffieHellman be disabled (usually means not supported)
+    var check_dh = document.createElement('input');
+    check_dh.type = 'hidden';
+    check_dh.id = 'ajax_login_field_dh';
+    form.appendChild(check_dh);
+  }
   else
   {
     var lbl_dh = document.createElement('label');
@@ -744,6 +800,7 @@
       return false;
     }
   }
+  
   if ( !username )
   {
     var username = document.getElementById('ajax_login_field_username').value;
@@ -757,6 +814,9 @@
     var captcha = document.getElementById('ajax_login_field_captcha').value;
   }
   
+  try
+  {
+  
   if ( do_dh )
   {
     ajaxLoginSetStatus(AJAX_STATUS_GENERATING_KEY);
@@ -836,6 +896,14 @@
       remember: remember_session
     }
   }
+  }
+  catch(e)
+  {
+    ajaxLoginSetStatus(AJAX_STATUS_ERROR);
+    console.error('Exception caught in login process; backtrace follows');
+    console.debug(e);
+    return false;
+  }
   ajaxLoginPerformRequest(json_packet);
 }
 
--- a/includes/clientside/static/userpage.js	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/clientside/static/userpage.js	Sun Nov 09 14:22:03 2008 -0500
@@ -7,6 +7,9 @@
   var wrapper = document.getElementById('userpage_wrap');
   var links = document.getElementById('userpage_links');
   
+  if ( !wrapper )
+    return false;
+  
   wrapper.className = 'userpage_wrap';
   links.className = 'userpage_links';
   
--- a/includes/common.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/common.php	Sun Nov 09 14:22:03 2008 -0500
@@ -170,6 +170,10 @@
 global $timezone;
 $timezone = 0;
 
+// DST settings
+global $dst_params;
+$dst_params = array(0, 0, 0, 0, 60);
+
 // Divert to CLI loader if running from CLI
 if ( isset($argc) && isset($argv) )
 {
@@ -390,7 +394,7 @@
   // One quick security check...
   if ( !is_valid_ip($_SERVER['REMOTE_ADDR']) )
   {
-    die('SECURITY: spoofed IP address');
+    die('SECURITY: spoofed IP address: ' . htmlspecialchars($_SERVER['REMOTE_ADDR']));
   }
 
   // All checks passed! Start the main components up.  
--- a/includes/constants.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/constants.php	Sun Nov 09 14:22:03 2008 -0500
@@ -92,6 +92,12 @@
 define('TOKEN_PARENTHRIGHT', 4);
 define('TOKEN_NOT', 5);
 
+// DST constants
+define('FIRST_SUNDAY', 1);
+define('SECOND_SUNDAY', 2);
+define('THIRD_SUNDAY', 3);
+define('LAST_SUNDAY', 4);
+
 //
 // User types - don't touch these
 //
@@ -617,3 +623,12 @@
   0xFE =>  "COM",   0x01 =>  "TEM",   0x02 =>  "RES",
 );
 
+// DST profiles
+global $dst_profiles;
+$dst_profiles = array(
+    'off' => '0;0;0;0;60',
+    'usa' => '3;' . SECOND_SUNDAY . ';11;' . FIRST_SUNDAY . ';60',
+    'europe' => '3;' . LAST_SUNDAY . ';10;' . LAST_SUNDAY . ';60',
+    'australia' => '10;' . LAST_SUNDAY . ';3;' . LAST_SUNDAY . ';60',
+    'tasmania' => '10;' . FIRST_SUNDAY . ';3;' . LAST_SUNDAY . ';60'
+  );
--- a/includes/functions.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/functions.php	Sun Nov 09 14:22:03 2008 -0500
@@ -271,31 +271,117 @@
   if ( !is_int($timestamp) && !is_double($timestamp) && strval(intval($timestamp)) !== $timestamp )
     $timestamp = time();
   
-  /*
-  // List of valid characters for date()
-  $date_chars = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZFcrU';
-  // Split them into an array
-  $date_chars = enano_str_split($date_chars);
-  // Emulate date() formatting by replacing date characters with their
-  // percentage-signed counterparts, but not escaped characters which
-  // shouldn't be parsed.
-  foreach ( $date_chars as $char )
-  {
-    $string = str_replace($char, "%$char", $string);
-    $string = str_replace("\\%$char", $char, $string);
-  }
-  */
-  
   // perform timestamp offset
   global $timezone;
   // it's gonna be in minutes, so multiply by 60 to offset the unix timestamp
   $timestamp = $timestamp + ( $timezone * 60 );
   
+  // are we in DST?
+  global $dst_params;
+  if ( check_timestamp_dst($timestamp, $dst_params[0], $dst_params[1], $dst_params[2], $dst_params[3]) )
+  {
+    // offset for DST
+    $timestamp += ( $dst_params[4] * 60 );
+  }
+  
   // Let PHP do the work for us =)
   return gmdate($string, $timestamp);
 }
 
 /**
+ * Determine if a timestamp is within DST.
+ * @param int Timestamp
+ * @param int Start month (1-12) of DST
+ * @param int Which Sunday DST starts on (*_SUNDAY constants)
+ * @param int End month of DST
+ * @param int Which Sunday DST ends on
+ * @return bool
+ */
+
+function check_timestamp_dst($time, $start_month, $start_sunday, $end_month, $end_sunday)
+{
+  static $sundays = array(FIRST_SUNDAY, SECOND_SUNDAY, THIRD_SUNDAY, LAST_SUNDAY);
+  
+  // perform timestamp offset
+  global $timezone;
+  // it's gonna be in minutes, so multiply by 60 to offset the unix timestamp
+  $time = $time + ( $timezone * 60 );
+  $year = intval(gmdate('Y', $time));
+  
+  // one-pass validation
+  if ( !in_array($start_sunday, $sundays) || !in_array($end_sunday, $sundays) ||
+       $start_month < 1 || $start_month > 12 || $end_month < 1 || $end_month > 12 )
+    return false;
+    
+  // get timestamp of the selected sunday (start)
+  $dst_start = get_sunday_timestamp($start_month, $start_sunday, $year);
+  $dst_end   = get_sunday_timestamp($end_month, $end_sunday, $year);
+  
+  if ( $dst_start > $dst_end )
+  {
+    // start time is past the end time, this means we're in the southern hemisphere
+    // as a result, if we're within the range, DST is NOT in progress.
+    return !( $time >= $dst_start && $time <= $dst_end );
+  }
+  
+  return $time >= $dst_start && $time <= $dst_end;
+}
+
+/**
+ * Returns a timestamp for the given *_SUNDAY index.
+ * @param int Month
+ * @param int Which Sunday (FIRST, SECOND, THIRD, or LAST)
+ * @param int Year that we're doing our calculations in
+ * @return int
+ */
+
+function get_sunday_timestamp($month, $sunday, $year)
+{
+  $days_in_month = array(
+    1 => 31,
+    2 => $year % 4 == 0 && ( $year % 100 != 0 || ( $year % 100 == 0 && $year % 400 == 0 ) ) ? 29 : 28,
+    3 => 31,
+    4 => 30,
+    5 => 31,
+    6 => 30,
+    7 => 31,
+    8 => 31,
+    9 => 30,
+    10 => 31,
+    11 => 30,
+    12 => 31
+  );
+  
+  $result = mktime(0, 0, 0, $month, 1, $year);
+  
+  // hack. allows a specific day of the month to be set instead of a sunday. not a good place to do this.
+  if ( is_string($sunday) && substr($sunday, -1) === 'd' )
+  {
+    $result += 86400 * ( intval($sunday) - 1);
+    return $result;
+  }
+  
+  $tick = 0;
+  $days_remaining = $days_in_month[$month];
+  while ( true )
+  {
+    if ( date('D', $result) == 'Sun' )
+    {
+      $tick++;
+      if ( ( $tick == 1 && $sunday == FIRST_SUNDAY ) ||
+           ( $tick == 2 && $sunday == SECOND_SUNDAY ) ||
+           ( $tick == 3 && $sunday == THIRD_SUNDAY ) ||
+           ( $sunday == LAST_SUNDAY && $days_remaining < 7 ) )
+        break;
+    }
+    $days_remaining--;
+    $result += 86400;
+  }
+  
+  return $result;
+}
+
+/**
  * Tells you the title for the given page ID string
  * @param string Page ID string (ex: Special:Administration)
  * @param bool Optional. If true, and if the namespace turns out to be something other than Article, the namespace prefix will be prepended to the return value.
@@ -2851,9 +2937,9 @@
 
 function is_valid_ip($ip)
 {
-  // These came from phpBB3.
+  // This next one came from phpBB3.
   $ipv4 = '(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])';
-  $ipv6 = '(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){5}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:))';
+  $ipv6 = '(?:[a-f0-9]{0,4}):(?:[a-f0-9]{0,4}):(?:[a-f0-9]{0,4}:|:)?(?:[a-f0-9]{0,4}:|:)?(?:[a-f0-9]{0,4}:|:)?(?:[a-f0-9]{0,4}:|:)?(?:[a-f0-9]{0,4}:|:)?(?:[a-f0-9]{1,4})';
 
   if ( preg_match("/^{$ipv4}$/", $ip) || preg_match("/^{$ipv6}$/", $ip) )
     return true;
--- a/includes/render.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/render.php	Sun Nov 09 14:22:03 2008 -0500
@@ -145,6 +145,7 @@
         $p = '<b>Notice:</b> RenderMan::getTemplate(): Parameter '.$m.' is not set';
       }
       $text = str_replace('(_'.$m.'_)', $p, $text);
+      $text = str_replace('{{' . ( $m + 1 ) . '}}', $p, $text);
     }
     $text = RenderMan::include_templates($text);
     return $text;
@@ -252,7 +253,7 @@
       $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text);
     }
     
-    preg_match_all('/<lang code="([a-z0-9_]+)">([\w\W]+?)<\/lang>/', $text, $langmatch);
+    preg_match_all('/<lang code="([a-z0-9_-]+)">([\w\W]+?)<\/lang>/', $text, $langmatch);
     foreach ( $langmatch[0] as $i => $match )
     {
       if ( $langmatch[1][$i] == $lang->lang_code )
@@ -271,6 +272,17 @@
       eval($cmd);
     }
     
+    //$template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
+    $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
+    $i = 0;
+    while ( preg_match($template_regex, $text) )
+    {
+      $i++;
+      if ( $i == 5 )
+        break;
+      $text = RenderMan::include_templates($text);
+    }
+    
     if ( !$plaintext )
     {
       // Process images
@@ -287,17 +299,6 @@
       }
     }
     
-    //$template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
-    $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
-    $i = 0;
-    while ( preg_match($template_regex, $text) )
-    {
-      $i++;
-      if ( $i == 5 )
-        break;
-      $text = RenderMan::include_templates($text);
-    }
-    
     // Before shipping it out to the renderer, replace spaces in between headings and paragraphs:
     $text = preg_replace('/<\/(h[0-9]|div|p)>([\s]+)<(h[0-9]|div|p)( .+?)?>/i', '</\\1><\\3\\4>', $text);
     
@@ -495,9 +496,18 @@
     foreach ( $matches[0] as $i => $match )
     {
       list($page_id, $namespace) = RenderMan::strToPageID($matches[1][$i]);
+      if ( ($pos = strrpos($page_id, '#')) !== false )
+      {
+        $hash = substr($page_id, $pos);
+        $page_id = substr($page_id, 0, $pos);
+      }
+      else
+      {
+        $hash = '';
+      }
       $pid_clean = $paths->nslist[$namespace] . sanitize_page_id($page_id);
       
-      $url = makeUrl($pid_clean, false, true);
+      $url = makeUrl($pid_clean, false, true) . $hash;
       $inner_text = $matches[2][$i];
       $quot = '"';
       $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"';
@@ -564,54 +574,43 @@
    * [bar] => dolor sit amet
    */
   
-  public static function parse_template_vars($input)
+  public static function parse_template_vars($input, $newlinemode = true)
   {
-    if ( !preg_match('/^(\|[ ]*([A-z0-9_]+)([ ]*)=([ ]*)(.+?))*$/is', trim($input)) )
+    $parms = array();
+    $input = trim($input);
+    if ( $newlinemode )
     {
-      $using_pipes = false;
-      $input = explode("\n", trim( $input ));
+      $result = preg_match_all('/
+                                  (?:^|[\s]*)\|?    # start of parameter - string start or series of spaces
+                                  [ ]*              
+                                  (?:               
+                                    ([A-z0-9_]+)    # variable name
+                                    [ ]* = [ ]*     # assignment
+                                  )?                # this is optional - if the parameter name is not given, a numerical index is assigned
+                                  (.+)              # value
+                                /x', trim($input), $matches);
     }
     else
     {
-      $using_pipes = true;
-      $input = substr($input, 1);
-      $input = explode("|", trim( $input ));
-    }
-    $parms = Array();
-    $current_line = '';
-    $current_parm = '';
-    foreach ( $input as $num => $line )
+      $result = preg_match_all('/
+                                  (?:^|[ ]*)\|         # start of parameter - string start or series of spaces
+                                  [ ]*
+                                  (?:
+                                    ([A-z0-9_]+)       # variable name
+                                    [ ]* = [ ]*        # assignment
+                                  )?                   # name section is optional - if the parameter name is not given, a numerical index is assigned
+                                  ([^\|]+|.+?\n[ ]*\|) # value
+                                /x', trim($input), $matches);
+    }                   
+    if ( $result )
     {
-      if ( preg_match('/^[ ]*([A-z0-9_]+)([ ]*)=([ ]*)(.+?)$/is', $line, $matches) )
+      $pi = 0;
+      for ( $i = 0; $i < count($matches[0]); $i++ )
       {
-        $parm =& $matches[1];
-        $text =& $matches[4];
-        if ( $parm == $current_parm )
-        {
-          $current_line .= $text;
-        }
-        else
-        {
-          // New parameter
-          if ( $current_parm != '' )
-            $parms[$current_parm] = $current_line;
-          $current_line = $text;
-          $current_parm = $parm;
-        }
+        $matches[1][$i] = trim($matches[1][$i]);
+        $parmname = !empty($matches[1][$i]) ? $matches[1][$i] : strval(++$pi);
+        $parms[ $parmname ] = $matches[2][$i];
       }
-      else if ( $num == 0 )
-      {
-        // Syntax error
-        return false;
-      }
-      else
-      {
-        $current_line .= "\n$line";
-      }
-    }
-    if ( !empty($current_parm) && !empty($current_line) )
-    {
-      $parms[$current_parm] = $current_line;
     }
     return $parms;
   }
@@ -624,8 +623,8 @@
    * @example
    * <code>
    $text = '{{Template
-     parm1 = Foo
-     parm2 = Bar
+       | parm1 = Foo
+       | parm2 = Bar
      }}';
    $text = RenderMan::include_templates($text);
    * </code>
@@ -635,17 +634,26 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     // $template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is";
-    $template_regex = "/\{\{(.+)(((\n|[ ]*\|)[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU";
+    // matches:
+    //  1 - template name
+    //  2 - parameter section
+    $template_regex = "/
+                         \{\{                     # opening
+                           ([^\n\t\a\r]+)         # template name
+                           ((?:(?:[\s]+\|?)[ ]*(?:[A-z0-9_]+)[ ]*=[ ]*?(?:.+))*) # parameters
+                         \}\}                     # closing
+                       /isxU";
     if ( $count = preg_match_all($template_regex, $text, $matches) )
     {
       //die('<pre>' . print_r($matches, true) . '</pre>');
       for ( $i = 0; $i < $count; $i++ )
       {
         $matches[1][$i] = sanitize_page_id($matches[1][$i]);
+        $newlinemode = ( substr($matches[2][$i], 0, 1) == "\n" );
         $parmsection = trim($matches[2][$i]);
         if ( !empty($parmsection) )
         {
-          $parms = RenderMan::parse_template_vars($parmsection);
+          $parms = RenderMan::parse_template_vars($parmsection, $newlinemode);
           if ( !is_array($parms) )
             // Syntax error
             $parms = array();
--- a/includes/sessions.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/sessions.php	Sun Nov 09 14:22:03 2008 -0500
@@ -473,7 +473,15 @@
           }
         }
         $user = true;
+        
+        // set timezone params
         $GLOBALS['timezone'] = $userdata['user_timezone'];
+        $GLOBALS['dst_params'] = explode(';', $userdata['user_dst']);
+        foreach ( $GLOBALS['dst_params'] as &$parm )
+        {
+          if ( substr($parm, -1) != 'd' )
+            $parm = intval($parm);
+        }
         
         // Set language
         if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
@@ -1038,7 +1046,7 @@
     // using a normal call to $db->sql_query to avoid failing on errors here
     $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,' . "\n"
                              . '    u.reg_time,u.account_active,u.activation_key,u.user_lang,u.user_title,k.source_ip,k.time,k.auth_level,k.key_type,COUNT(p.message_id) AS num_pms,' . "\n"
-                             . '    u.user_timezone, x.* FROM '.table_prefix.'session_keys AS k' . "\n"
+                             . '    u.user_timezone, u.user_dst, x.* FROM '.table_prefix.'session_keys AS k' . "\n"
                              . '  LEFT JOIN '.table_prefix.'users AS u' . "\n"
                              . '    ON ( u.user_id=k.user_id )' . "\n"
                              . '  LEFT JOIN '.table_prefix.'users_extra AS x' . "\n"
@@ -1051,7 +1059,7 @@
     
     if ( !$query && ( defined('IN_ENANO_INSTALL') or defined('IN_ENANO_UPGRADE') ) )
     {
-      $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms, 1440 AS user_timezone, ' . SK_SHORT . ' AS key_type FROM '.table_prefix.'session_keys AS k
+      $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms, 1440 AS user_timezone, \'0;0;0;0;60\' AS user_dst, ' . SK_SHORT . ' AS key_type FROM '.table_prefix.'session_keys AS k
                              LEFT JOIN '.table_prefix.'users AS u
                                ON ( u.user_id=k.user_id )
                              LEFT JOIN '.table_prefix.'privmsgs AS p
@@ -2707,7 +2715,8 @@
       if ( !$q )
         $db->_die();
       
-      $groups = array();
+      // The l10n engine takes care of this later.
+      $groups = array(1 => 'Everyone');
       
       if ( $row = $db->fetchrow() )
       {
@@ -2983,7 +2992,7 @@
     {
       if ( isset($perm2[$i]) )
       {
-        if ( $is_everyone && !$defaults_used[$i] )
+        if ( $is_everyone && isset($defaults_used[$i]) && $defaults_used[$i] === false )
           continue;
         // Decide precedence
         if ( isset($defaults_used[$i]) )
@@ -3396,17 +3405,20 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     
     // Setup EnanoMath and Diffie-Hellman
-    require_once(ENANO_ROOT.'/includes/math.php');
-    
     global $dh_supported;
-    $dh_supported = true;
-    try
+    if ( !function_exists('dh_gen_private') )
     {
-      require_once(ENANO_ROOT . '/includes/diffiehellman.php');
-    }
-    catch ( Exception $e )
-    {
-      $dh_supported = false;
+      require_once(ENANO_ROOT.'/includes/math.php');
+      
+      $dh_supported = true;
+      try
+      {
+        require_once(ENANO_ROOT . '/includes/diffiehellman.php');
+      }
+      catch ( Exception $e )
+      {
+        $dh_supported = false;
+      }
     }
     global $_math;
     
--- a/includes/template.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/template.php	Sun Nov 09 14:22:03 2008 -0500
@@ -2051,84 +2051,59 @@
   
   /**
    * Fetches the contents of both sidebars.
-   * @return array - key 0 is left, key 1 is right
+   * @return array - key 0 is left, key 1 is right, key 2 is the HTML that makes up an empty sidebar
    * @example list($left, $right) = $template->fetch_sidebar();
    */
   
   function fetch_sidebar()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    global $cache;
     
+    // first, check the cache
+    if ( $result = $this->fetch_cached_sidebar() )
+    {
+      return $result;
+    }
+    
+    profiler_log('Started sidebar parsing');
+    
+    // init our block contents
     $left = '';
     $right = '';
+    $min = '';
     
-    // check the cache
-    if ( !$session->user_logged_in && $data = $cache->fetch('anon_sidebar') )
-    {
-      if ( @$data['_theme_'] === $this->theme )
-      {
-        unset($data['_theme_']);
-        foreach ( $data as &$md )
-        {
-          $md = str_replace('$USERNAME$', $session->username, $md);
-          $md = str_replace('$PAGEID$', $paths->page, $md);
-          $md = str_replace('$MAIN_PAGE$', getConfig('main_page'), $md);
-        }
-        return $data;
-      }
-    }
-    
+    // also might want the links block
     if ( !$this->fetch_block('Links') )
       $this->initLinksWidget();
     
-    $q = $db->sql_query('SELECT item_id,sidebar_id,block_name,block_type,block_content FROM '.table_prefix.'sidebar' . "\n"
-                           . '  WHERE item_enabled=1 ORDER BY sidebar_id ASC, item_order ASC;');
-    if(!$q) $db->_die('The sidebar text data could not be selected.');
-    
+    // templates to work with
     $vars = $this->extract_vars('elements.tpl');
     
-    if(isset($vars['sidebar_top'])) 
+    // sidebar_top and sidebar_bottom are HTML that is prepended/appended to sidebar content. Themes can
+    // choose whether or not to display a sidebar depending on whether the sidebar is empty ( $left == $min )
+    // or not using the sidebar_left and sidebar_right template booleans (the oxygen theme does this).
+    if ( isset($vars['sidebar_top']) ) 
     {
       $top = $this->parse($vars['sidebar_top']);
       $left  .= $top;
       $right .= $top;
+      $min .= $top;
     }
     
-    while($row = $db->fetchrow())
+    // grab the blocks from the DB
+    $q = $db->sql_query('SELECT item_id,sidebar_id,block_name,block_type,block_content FROM '.table_prefix.'sidebar' . "\n"
+                           . '  WHERE item_enabled=1 ORDER BY sidebar_id ASC, item_order ASC;');
+    if ( !$q )
+      $db->_die('The sidebar text data could not be selected.');
+    
+    // explicitly specify $q in case a plugin or PHP block makes a query
+    while ( $row = $db->fetchrow($q) )
     {
-      switch($row['block_type'])
-      {
-        case BLOCK_WIKIFORMAT:
-        default:
-          $parser = $this->makeParserText($vars['sidebar_section']);
-          $c = RenderMan::render($row['block_content']);
-          break;
-        case BLOCK_TEMPLATEFORMAT:
-          $parser = $this->makeParserText($vars['sidebar_section']);
-          $c = $this->tplWikiFormat($row['block_content']);
-          break;
-        case BLOCK_HTML:
-          $parser = $this->makeParserText($vars['sidebar_section_raw']);
-          $c = $row['block_content'];
-          break;
-        case BLOCK_PHP:
-          $parser = $this->makeParserText($vars['sidebar_section_raw']);
-          ob_start();
-          @eval($row['block_content']);
-          $c = ob_get_contents();
-          ob_end_clean();
-          break;
-        case BLOCK_PLUGIN:
-          $parser = $this->makeParserText('{CONTENT}');
-          $c = '<!-- PLUGIN -->' . (gettype($this->fetch_block($row['block_content'])) == 'string') ?
-                  $this->fetch_block($row['block_content']) :
-                  // This used to say "can't find plugin block" but I think it's more friendly to just silently hide it.
-                  '';
-          break;
-      }
+      // format the block
+      $block_content = $this->format_sidebar_block($row, $vars, $parser);
+      
       // is there a {restrict} or {hideif} block?
-      if ( preg_match('/\{(restrict|hideif) ([a-z0-9_\(\)\|&! ]+)\}/', $c, $match) )
+      if ( preg_match('/\{(restrict|hideif) ([a-z0-9_\(\)\|&! ]+)\}/', $block_content, $match) )
       {
         // we have one, check the condition
         $type =& $match[1];
@@ -2140,53 +2115,253 @@
           continue;
         }
         // didn't get a match, so hide the conditional logic
-        $c = str_replace_once($match[0], '', $c);
+        // FIXME: this needs to be verbose about syntax errors
+        $block_content = str_replace_once($match[0], '', $block_content);
       }
       
-      $parser->assign_vars(Array( 'TITLE'=>$this->tplWikiFormat($row['block_name']), 'CONTENT'=>$c ));
-      $run = $parser->run();
+      // if we made it here, this block definitely needs to be displayed. send it to the
+      // parser (set by format_sidebar_block) and decide where to append it (but not in that order ;))
+      $appender = false;
+      
+      if ( $row['sidebar_id'] == SIDEBAR_LEFT )
+      {
+        $appender =& $left;
+      }
+      else if ( $row['sidebar_id'] == SIDEBAR_RIGHT )
+      {
+        $appender =& $right;
+      }
+      else
+      {
+        // uhoh, block_id isn't valid. maybe a plugin wants to put this block somewhere else?
+        $code = $plugins->setHook('sidebar_block_id');
+        foreach ( $code as $cmd )
+        {
+          eval($cmd);
+        }
+        // if $appender wasn't set by a plugin, don't parse this block to save some CPU cycles
+        if ( !$appender )
+        {
+          continue;
+        }
+      }
+      
+      // assign variables to parser
+      $block_name = $this->tplWikiFormat($row['block_name']);
+      $parser->assign_vars(array(
+          // be nice and format the title (FIXME, does this use a lot of CPU time? still needs l10n in some cases though)
+          'TITLE' => $block_name,
+          'CONTENT' => $block_content
+        ));
+      $parsed = $parser->run();
+      
+      // plugins are parsed earlier due to the way disabled/missing plugins that add sidebar blocks are
+      // handled, so the {TITLE} var won't be replaced until now. this allows completely eliminating a
+      // block if it's not available
       if ( $row['block_type'] == BLOCK_PLUGIN )
       {
-        $run = str_replace('{TITLE}', $this->tplWikiFormat($row['block_name']), $run);
+        $parsed = str_replace('{TITLE}', $block_name, $parsed);
       }
-      if    ($row['sidebar_id'] == SIDEBAR_LEFT ) $left  .= $run;
-      elseif($row['sidebar_id'] == SIDEBAR_RIGHT) $right .= $run;
-      unset($parser);
+      
+      // done parsing - append and continue
+      $appender .= $parsed;
+      
+      // we're done with this - unset it because it's a reference and we don't want it overwritten.
+      // also free the parser to get some RAM back
+      unset($appender, $parser);
     }
-    $db->free_result();
+    
+    // lastly, append any footer HTML
     if(isset($vars['sidebar_bottom'])) 
     {
       $bottom = $this->parse($vars['sidebar_bottom']);
       $left  .= $bottom;
       $right .= $bottom;
+      $min   .= $bottom;
     }
-    $min = '';
-    if(isset($vars['sidebar_top'])) 
+    
+    $return = array($left, $right, $min);
+    
+    // allow any plugins to append what they want to the return
+    $code = $plugins->setHook('sidebar_fetch_return');
+    foreach ( $code as $cmd )
     {
-      $min .= $top;
+      eval($cmd);
     }
-    if(isset($vars['sidebar_bottom']))
-    {
-      $min .= $bottom;
-    }
-    $return = Array($left, $right, $min);
-    if ( getConfig('cache_thumbs') == '1' && !$session->user_logged_in )
+    
+    // cache the result if appropriate
+    $this->cache_compiled_sidebar($return);
+    
+    profiler_log('Finished sidebar parsing');
+    
+    return $return;
+  }
+  
+  /**
+   * Runs the appropriate formatting routine on a sidebar row.
+   * @param array Row in sidebar table
+   * @param array Template variable set from elements.tpl
+   * @param null Pass by reference, will be filled with the parser according to the block's type (sidebar_section or sidebar_section_raw)
+   * @return string HTML + directives like {restrict} or {hideif}
+   */
+  
+  function format_sidebar_block($row, $vars, &$parser)
+  {
+    // import globals in case a PHP block wants to call the Enano API
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    $parser = null;
+    
+    switch($row['block_type'])
     {
-      $cachestore = enano_json_encode($return);
-      $cachestore = str_replace($session->username, '$USERNAME$', $cachestore);
-      $cachestore = str_replace($paths->page, '$PAGEID$', $cachestore);
-      $cachestore = str_replace('__STATICLINK__', $paths->page, $cachestore);
-      $cachestore = str_replace('__MAINPAGELINK__', '$MAIN_PAGE$', $cachestore);
-      $cachestore = enano_json_decode($cachestore);
-      $cachestore['_theme_'] = $this->theme;
-      $cache->store('anon_sidebar', $cachestore, 10);
-      
-      foreach ( $return as &$el )
+      case BLOCK_WIKIFORMAT:
+        $parser = $this->makeParserText($vars['sidebar_section']);
+        $c = RenderMan::render($row['block_content']);
+        break;
+        
+      case BLOCK_TEMPLATEFORMAT:
+        $parser = $this->makeParserText($vars['sidebar_section']);
+        $c = $this->tplWikiFormat($row['block_content']);
+        break;
+        
+      case BLOCK_HTML:
+        $parser = $this->makeParserText($vars['sidebar_section_raw']);
+        $c = $row['block_content'];
+        break;
+        
+      case BLOCK_PHP:
+        // PHP blocks need to be sent directly to eval()
+        $parser = $this->makeParserText($vars['sidebar_section_raw']);
+        ob_start();
+        @eval($row['block_content']);
+        $c = ob_get_contents();
+        ob_end_clean();
+        break;
+        
+      case BLOCK_PLUGIN:
+        $parser = $this->makeParserText('{CONTENT}');
+        $c = '<!-- PLUGIN -->' . (gettype($this->fetch_block($row['block_content'])) == 'string') ?
+                $this->fetch_block($row['block_content']) :
+                // This used to say "can't find plugin block" but I think it's more friendly to just silently hide it.
+                '';
+        break;
+      default:
+        // unknown block type - can a plugin handle it?
+        $code = $plugins->setHook('format_sidebar_block');
+        foreach ( $code as $cmd )
+        {
+          eval($cmd);
+        }
+        if ( !isset($c) )
+        {
+          // default to wiki formatting (this was going to be straight HTML but this is done for backwards compatibility reasons)
+          $c = RenderMan::render($row['block_content']);
+        }
+        if ( !$parser )
+        {
+          // no parser defined, use the "raw" section by default (plugins are more likely to want raw content
+          // rather than a list of links, and they can set the parser to use sidebar_section if they want)
+          $parser = $this->makeParserText($vars['sidebar_section_raw']);
+        }
+        
+        break;
+    }
+    
+    return $c;
+  }
+  
+  /**
+   * Returns the list of things that should not be cached (sorry, I was listening to "The Thing That Should Not Be" by Metallica when I
+   * wrote this routine. You should get a copy of Master of Puppets, it's a damn good album.)
+   * @return array
+   */
+  
+  function get_cache_replacements()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    return array(
+          '$LOGIN_LINK$' => $this->tpl_strings['LOGIN_LINK'],
+          '$MAIN_PAGE$' => $this->tpl_strings['MAIN_PAGE'],
+          '$USERNAME$' => $session->username
+        );
+  }
+  
+  /**
+   * Attempts to load a cached compiled sidebar.
+   * @return array Same format as fetch_sidebar()
+   */
+  
+  function fetch_cached_sidebar()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $cache;
+    
+    $cached = false;
+    if ( ($result = $cache->fetch('anon_sidebar')) && !$session->user_logged_in )
+    {
+      if ( isset($result[$this->theme]) )
       {
-        $el = str_replace('__STATICLINK__', $paths->page, $el);
+        $cached = $result[$this->theme];
       }
     }
-    return $return;
+    
+    // if we haven't been able to fetch yet, see if a plugin wants to give us something
+    if ( !$cached )
+    {
+      $code = $plugins->setHook('fetch_cached_sidebar');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+    } 
+    
+    if ( is_array($cached) )
+    {
+      // fetch certain variables that can't be stored in the cache and quickly substitute
+      $replace = $this->get_cache_replacements();
+      foreach ( $cached as &$val )
+      {
+        $val = strtr($val, $replace);
+      }
+      
+      // done processing, send it back
+      return $cached;
+    }
+    
+    return false;
+  }
+  
+  /**
+   * Caches a completely compiled sidebar, if appropriate
+   * @param array Effectively, return from fetch_sidebar()
+   */
+  
+  function cache_compiled_sidebar($sidebar)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    global $cache;
+    
+    // check if conditions are right
+    if ( !$session->user_logged_in && getConfig('cache_thumbs') === '1' )
+    {
+      // load any existing cache to make sure other themes' cached sidebars aren't discarded
+      $cached = ( $_ = $cache->fetch('anon_sidebar') ) ? $_ : array();
+      
+      // replace variables
+      $replace = array_flip($this->get_cache_replacements());
+      
+      foreach ( $sidebar as &$section )
+      {
+        $section = strtr($section, $replace);
+      }
+      
+      // compile
+      $cached[$this->theme] = $sidebar;
+      
+      // store
+      $cache->store('anon_sidebar', $cached, 15);
+    }
   }
   
   function initLinksWidget()
--- a/includes/wikiengine/Render/Xhtml/Wikilink.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/includes/wikiengine/Render/Xhtml/Wikilink.php	Sun Nov 09 14:22:03 2008 -0500
@@ -56,6 +56,11 @@
 
     function token($options)
     {
+      ##
+      ## THIS IS NOT WHAT YOU ARE LOOKING FOR!!
+      ## All of this code is deprecated. Patch RenderMan::parse_internal_links() instead!
+      ##
+      
         global $session;
         if ( $session->sid_super )
         {
@@ -120,16 +125,8 @@
             // the HREF.  we support both the old form where
             // the page always comes at the end, and the new
             // form that uses %s for sprintf()
-            $href = $this->getConf('view_url');
-
-            if (strpos($href, '%s') === false) {
-                // use the old form (page-at-end)
-                $href = $href . $page . $anchor;
-            } else {
-                // use the new form (sprintf format string)
-                $href = sprintf($href, $page . $anchor);
-            }
-
+            $href = makeUrl($page, false, true);
+            
             // get the CSS class and generate output
             $css = $this->formatConf(' class="%s"', 'css');
 
@@ -143,22 +140,13 @@
             // the HREF.  we support both the old form where
             // the page always comes at the end, and the new
             // form that uses %s for sprintf()
-            $href = $this->getConf('view_url');
-
-            if (strpos($href, '%s') === false) {
-                // use the old form (page-at-end)
-                $href = $href . $page . $anchor;
-            } else {
-                // use the new form (sprintf format string)
-                $href = sprintf($href, $page . $anchor);
-            }
+            $href = makeUrl($page, false, true);
 
             // get the CSS class and generate output
             $css = $this->formatConf(' class="%s"', 'css');
 
-            $start = '<a'.$css.' href="'.$href.$as.'"'.$nobg.' class="wikilink-nonexistent">';
+            $start = '<a'.$css.' href="'.$href.'"'.$nobg.' class="wikilink-nonexistent">';
             $end = '</a>';
-            
         }
         if (!strlen($text)) {
             $start .= $this->textEncode($options['page']);
--- a/install/schemas/mysql_stage2.sql	Mon Sep 29 08:24:26 2008 -0400
+++ b/install/schemas/mysql_stage2.sql	Sun Nov 09 14:22:03 2008 -0500
@@ -115,6 +115,7 @@
   user_timezone int(12) UNSIGNED NOT NULL DEFAULT 0,
   user_title varchar(64) DEFAULT NULL,
   user_group mediumint(5) NOT NULL DEFAULT 1,
+  user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60',
   PRIMARY KEY  (user_id)
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
--- a/install/schemas/postgresql_stage2.sql	Mon Sep 29 08:24:26 2008 -0400
+++ b/install/schemas/postgresql_stage2.sql	Sun Nov 09 14:22:03 2008 -0500
@@ -115,6 +115,7 @@
   user_timezone int NOT NULL DEFAULT 0,
   user_title varchar(64) DEFAULT NULL,
   user_group int NOT NULL DEFAULT 1,
+  user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60',
   CHECK (avatar_type IN ('jpg', 'png', 'gif', 'grv')),
   PRIMARY KEY  (user_id)
 );
--- a/install/schemas/upgrade/1.1.4-1.1.5-mysql.sql	Mon Sep 29 08:24:26 2008 -0400
+++ b/install/schemas/upgrade/1.1.4-1.1.5-mysql.sql	Sun Nov 09 14:22:03 2008 -0500
@@ -1,2 +1,3 @@
 ALTER TABLE {{TABLE_PREFIX}}session_keys ADD COLUMN key_type tinyint(1) NOT NULL DEFAULT 0;
 UPDATE {{TABLE_PREFIX}}session_keys SET key_type = 2 WHERE auth_level > 2;
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60';
--- a/install/schemas/upgrade/1.1.4-1.1.5-postgresql.sql	Mon Sep 29 08:24:26 2008 -0400
+++ b/install/schemas/upgrade/1.1.4-1.1.5-postgresql.sql	Sun Nov 09 14:22:03 2008 -0500
@@ -1,3 +1,4 @@
 ALTER TABLE {{TABLE_PREFIX}}session_keys ADD COLUMN key_type smallint NOT NULL DEFAULT 0;
 UPDATE {{TABLE_PREFIX}}session_keys SET key_type = 2 WHERE auth_level > 2;
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60';
 
--- a/language/english/core.json	Mon Sep 29 08:24:26 2008 -0400
+++ b/language/english/core.json	Sun Nov 09 14:22:03 2008 -0500
@@ -692,7 +692,14 @@
       title_13: '[UTC + 13] Tonga Time, Phoenix Islands Time',
       title_14: '[UTC + 14] Line Island Time',
       // This is a JSON string that lists all the timezones that are defined here.
-      list: '{"n12":-12,"n11":-11,"n10":-10,"n9p5":-9.5,"n9":-9,"n8":-8,"n7":-7,"n6":-6,"n5":-5,"n4":-4,"n3p5":-3.5,"n3":-3,"n2":-2,"n1":-1,"0":0,"1":1,"2":2,"3":3,"3p5":3.5,"4":4,"4p5":4.5,"5":5,"5p5":5.5,"5p75":5.75,"6":6,"6p5":6.5,"7":7,"8":8,"8p75":8.75,"9":9,"9p5":9.5,"10":10,"10p5":10.5,"11":11,"11p5":11.5,"12":12,"12p75":12.75,"13":13,"14":14}'
+      list: '{"n12":-12,"n11":-11,"n10":-10,"n9p5":-9.5,"n9":-9,"n8":-8,"n7":-7,"n6":-6,"n5":-5,"n4":-4,"n3p5":-3.5,"n3":-3,"n2":-2,"n1":-1,"0":0,"1":1,"2":2,"3":3,"3p5":3.5,"4":4,"4p5":4.5,"5":5,"5p5":5.5,"5p75":5.75,"6":6,"6p5":6.5,"7":7,"8":8,"8p75":8.75,"9":9,"9p5":9.5,"10":10,"10p5":10.5,"11":11,"11p5":11.5,"12":12,"12p75":12.75,"13":13,"14":14}',
+      
+      // DST profiles
+      dst_off: 'My region doesn\'t use DST',
+      dst_usa: 'United States: second Sunday of March to first Sunday of November',
+      dst_europe: 'Europe: last Sunday of March to last Sunday of October',
+      dst_australia: 'Australia (except Tasmania): last Sunday of October to last Sunday of March',
+      dst_tasmania: 'Tasmania: first Sunday of October to last Sunday of March'
     },
     etc: {
       redirect_title: 'Redirecting...',
--- a/language/english/user.json	Mon Sep 29 08:24:26 2008 -0400
+++ b/language/english/user.json	Sun Nov 09 14:22:03 2008 -0500
@@ -75,6 +75,9 @@
       login_ajax_check_dh_ie: 'Use a standards-compliant browser to help protect your password. <a href="http://docs.enanocms.org/Help:Appendix_B#dh" onclick="window.open(this.href); return false;">Learn more</a>',
       login_ajax_check_remember: 'Keep me logged in on this computer for %session_length% %length_units% unless I log out',
       login_ajax_check_remember_infinite: 'Keep me logged in on this computer until I log out',
+      login_ajax_err_crypto: 'Encryption failed.',
+      login_ajax_err_crypto_details: 'Details available on console.',
+      login_ajax_err_crypto_link: 'Use full login form',
       
       err_login_generic_title: 'There was an error in the login process',
       err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.',
@@ -308,6 +311,7 @@
       publicinfo_field_changetheme: 'Change my theme...',
       publicinfo_field_timezone: 'Time zone:',
       publicinfo_field_timezone_hint: 'Select the time zone you live in and when Daylight Savings Time occurs, if at all.',
+      publicinfo_field_dst: 'Daylight saving time:',
       publicinfo_field_usertitle_title: 'User title:',
       publicinfo_field_usertitle_hint: 'This can be some text that will be displayed underneath your username.',
       publicinfo_th_im: 'Instant messenger contact information',
--- a/plugins/SpecialAdmin.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/plugins/SpecialAdmin.php	Sun Nov 09 14:22:03 2008 -0500
@@ -44,8 +44,26 @@
     ));
 }
 
+$plugins->attachHook('session_started', 'SpecialAdmin_theme_init();');
 $plugins->attachHook('common_post', 'SpecialAdmin_include();');
 
+function SpecialAdmin_theme_init()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  // Admin pages that were too enormous to be in this file were split off into the plugins/admin/ directory in 1.0.1.
+  // Only load these files if we're looking to load the admin panel
+  list($pid, $ns) = RenderMan::strToPageID($paths->get_pageid_from_url());
+  if ( $ns == 'Admin' || ( $pid == 'Administration' && $ns == 'Special' ) )
+  {
+    // Set the theme
+    $session->theme = 'admin';
+    $session->style = 'default';
+    
+    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/admin-menu.js"></script>');
+  }
+}
+
 function SpecialAdmin_include()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -66,12 +84,6 @@
     require(ENANO_ROOT . '/plugins/admin/ThemeManager.php');
     require(ENANO_ROOT . '/plugins/admin/PluginManager.php');
     require(ENANO_ROOT . '/plugins/admin/CacheManager.php');
-    
-    // Set the theme
-    $session->theme = 'admin';
-    $session->style = 'default';
-    
-    $template->add_header('<script type="text/javascript" src="' . cdnPath . '/includes/clientside/static/admin-menu.js"></script>');
   }
 }
 
@@ -2093,7 +2105,7 @@
                 onclick: function()
                 {
                   var tigraentry = document.getElementById('i_div0_0').parentNode;
-                  var tigraobj = $(tigraentry);
+                  var tigraobj = $dynano(tigraentry);
                   var div = document.createElement('div');
                   div.style.backgroundColor = '#FFFFFF';
                   domObjChangeOpac(70, div);
--- a/plugins/SpecialUserPrefs.php	Mon Sep 29 08:24:26 2008 -0400
+++ b/plugins/SpecialUserPrefs.php	Sun Nov 09 14:22:03 2008 -0500
@@ -652,6 +652,22 @@
             <td class="row2"><?php echo $lang->get('usercp_publicinfo_field_timezone'); ?><br /><small><?php echo $lang->get('usercp_publicinfo_field_timezone_hint'); ?></small></td>
             <td class="row1"><?php echo $tz_select; ?></td>
           </tr>
+          <tr>
+            <td class="row2"><?php echo $lang->get('usercp_publicinfo_field_dst'); ?></td>
+            <td class="row1">
+              <select name="dst">
+                <?php
+                global $dst_profiles, $dst_params;
+                $user_dst = implode(';', $dst_params);
+                foreach ( $dst_profiles as $region => $data )
+                {
+                  $selected = ( $data === $user_dst ) ? ' selected="selected"' : '';
+                  echo '<option value="' . $data . '"' . $selected . '>' . $lang->get("tz_dst_$region") . '</option>';
+                }
+                ?>
+              </select>
+            </td>
+          </tr>
           <?php
           if ( $session->get_permissions('custom_user_title') ):
           ?>