Login page mostly localized
authorDan
Sun, 28 Oct 2007 14:32:13 -0400
changeset 209 8a00247d1dee
parent 208 c75ad574b56d
child 210 2b283402e4e4
Login page mostly localized
ajax.php
includes/clientside/static/ajax.js
includes/common.php
includes/functions.php
includes/lang.php
includes/sessions.php
language/english/enano.json
plugins/PrivateMessages.php
plugins/SpecialUserFuncs.php
schema.sql
upgrade.sql
--- a/ajax.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/ajax.php	Sun Oct 28 14:32:13 2007 -0400
@@ -33,35 +33,50 @@
     define('ENANO_ROOT', dirname($filename));
     require(ENANO_ROOT.'/includes/functions.php');
     require(ENANO_ROOT.'/includes/dbal.php');
+    require(ENANO_ROOT.'/includes/json.php');
     $db = new mysql();
     $db->connect();
     
-    // should be connected now
+    // result is sent using JSON
+    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+    $return = Array(
+        'mode' => 'success',
+        'users_real' => Array()
+      );
+    
+    // should be connected to the DB now
     $name = (isset($_GET['name'])) ? $db->escape($_GET['name']) : false;
     if ( !$name )
     {
-      die('userlist = new Array(); errorstring=\'Invalid URI\'');
+      $return = array(
+        'mode' => 'error',
+        'error' => 'Invalid URI'
+      );
+      die( $json->encode($return) );
     }
-    $q = $db->sql_query('SELECT username,user_id FROM '.table_prefix.'users WHERE lcase(username) LIKE lcase(\'%'.$name.'%\');');
+    $allowanon = ( isset($_GET['allowanon']) && $_GET['allowanon'] == '1' ) ? '' : ' AND user_id > 1';
+    $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE lcase(username) LIKE lcase(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
     if ( !$q )
     {
-      die('userlist = new Array(); errorstring=\'MySQL error selecting username data: '.addslashes(mysql_error()).'\'');
+      $return = array(
+        'mode' => 'error',
+        'error' => 'MySQL error selecting username data: '.addslashes(mysql_error())
+      );
+      die( $json->encode($return) );
     }
-    if($db->numrows() < 1)
-    {
-      die('userlist = new Array(); errorstring=\'No usernames found\';');
-    }
-    echo 'var errorstring = false; userlist = new Array();';
     $i = 0;
     while($r = $db->fetchrow())
     {
-      echo "userlist[$i] = '".addslashes($r['username'])."'; ";
+      $return['users_real'][] = $r['username'];
       $i++;
     }
     $db->free_result();
     
     // all done! :-)
     $db->close();
+    
+    echo $json->encode( $return );
+    
     exit;
   }
  
--- a/includes/clientside/static/ajax.js	Sat Oct 27 13:54:44 2007 -0400
+++ b/includes/clientside/static/ajax.js	Sun Oct 28 14:32:13 2007 -0400
@@ -1192,6 +1192,7 @@
   mydiv.style.position = 'absolute';
   mydiv.style.top = '0px';
   mydiv.id = 'autoCaptcha';
+  mydiv.style.zIndex = String( getHighestZ() + 1 );
   var img = document.createElement('img');
   img.onload = function()
   {
--- a/includes/common.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/includes/common.php	Sun Oct 28 14:32:13 2007 -0400
@@ -104,6 +104,7 @@
                       // In addition, $enano_config is used to fetch config information if die_semicritical() is called.
                       
 global $email;
+global $lang;
 
 if(!isset($_SERVER['HTTP_HOST'])) grinding_halt('Cannot get hostname', '<p>Your web browser did not provide the HTTP Host: field. This site requires a modern browser that supports the HTTP 1.1 standard.</p>');
                      
@@ -188,6 +189,27 @@
   }
 }
 
+// Is there no default language?
+if ( getConfig('lang_default') === false )
+{
+  $q = $db->sql_query('SELECT lang_id FROM '.table_prefix.'language LIMIT 1;');
+  if ( !$q )
+    $db->_die('common.php - setting default language');
+  if ( $db->numrows() < 1 && !defined('ENANO_ALLOW_LOAD_NOLANG') )
+  {
+    grinding_halt('No languages', '<p>There are no languages installed on this site.</p>
+        <p>If you are the website administrator, you may install a language by writing and executing a simple PHP script to install it:</p>
+        <pre>
+&lt;?php
+define("ENANO_ALLOW_LOAD_NOLANG", 1);
+$_GET["title"] = "langinstall";
+require("includes/common.php");
+install_language("eng", "English", "English", ENANO_ROOT . "/language/english/enano.json");</pre>');
+  }
+  $row = $db->fetchrow();
+  setConfig('default_language', $row['lang_id']);
+}
+
 // Our list of tables included in Enano
 $system_table_list = Array(
     table_prefix.'categories',
--- a/includes/functions.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/includes/functions.php	Sun Oct 28 14:32:13 2007 -0400
@@ -3166,6 +3166,53 @@
   return $score;
 }
 
+/**
+ * Installs a language.
+ * @param string The ISO-639-3 identifier for the language. Maximum of 6 characters, usually 3.
+ * @param string The name of the language in English (Spanish)
+ * @param string The name of the language natively (EspaƱol)
+ * @param string The path to the file containing the language's strings. Optional.
+ */
+
+function install_language($lang_code, $lang_name_neutral, $lang_name_local, $lang_file = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  $q = $db->sql_query('SELECT 1 FROM '.table_prefix.'language WHERE lang_code = "' . $db->escape($lang_code) . '";');
+  if ( !$q )
+    $db->_die('functions.php - checking for language existence');
+  
+  if ( $db->numrows() > 0 )
+    // Language already exists
+    return false;
+  
+  $q = $db->sql_query('INSERT INTO ' . table_prefix . 'language(lang_code, lang_name_default, lang_name_native) 
+                         VALUES(
+                           "' . $db->escape($lang_code) . '",
+                           "' . $db->escape($lang_name_neutral) . '",
+                           "' . $db->escape($lang_name_native) . '"
+                         );');
+  if ( !$q )
+    $db->_die('functions.php - installing language');
+  
+  $lang_id = $db->insert_id();
+  if ( empty($lang_id) )
+    return false;
+  
+  // Do we also need to install a language file?
+  if ( is_string($lang_file) && file_exists($lang_file) )
+  {
+    $lang = new Language($lang_id);
+    $lang->import($lang_file);
+  }
+  else if ( is_string($lang_file) && !file_exists($lang_file) )
+  {
+    echo '<b>Notice:</b> Can\'t load language file, so the specified language wasn\'t fully installed.<br />';
+    return false;
+  }
+  return true;
+}
+
 //die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
 
 ?>
--- a/includes/lang.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/includes/lang.php	Sun Oct 28 14:32:13 2007 -0400
@@ -81,7 +81,7 @@
       $db->_die('lang.php - attempting to pass invalid value to constructor');
     }
     
-    $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : 1;
+    $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : 'def';
     $q = $db->sql_query("SELECT lang_id, lang_code, ( lang_id = $lang_default ) AS is_default FROM " . table_prefix . "language WHERE $sql_col OR lang_id = $lang_default ORDER BY is_default DESC LIMIT 1;");
     
     if ( !$q )
@@ -372,6 +372,8 @@
     }
     // Found it!
     // Perform substitutions.
+    // if ( is_array($substitutions) )
+    //   die('<pre>' . print_r($substitutions, true) . '</pre>');
     if ( !is_array($substitutions) )
       $substitutions = array();
     return $this->substitute($string, $substitutions);
@@ -395,11 +397,21 @@
         $string = str_replace($matches[0][$i], $result, $string);
       }
     }
+    preg_match_all('/%config\.([a-z0-9_]+)%/', $string, $matches);
+    if ( count($matches[0]) > 0 )
+    {
+      foreach ( $matches[1] as $i => $string_id )
+      {
+        $result = getConfig($string_id);
+        $string = str_replace($matches[0][$i], $result, $string);
+      }
+    }
     foreach ( $subs as $key => $value )
     {
-      $string = str_replace("%$key%", $value, $string);
+      $subs[$key] = strval($value);
+      $string = str_replace("%{$key}%", "{$subs[$key]}", $string);
     }
-    return $string;
+    return "[L] $string";
   }
   
 } // class Language
--- a/includes/sessions.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/includes/sessions.php	Sun Oct 28 14:32:13 2007 -0400
@@ -362,6 +362,7 @@
   function start()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
     if($this->started) return;
     $this->started = true;
     $user = false;
@@ -381,6 +382,9 @@
         
         if(!$this->compat && $userdata['account_active'] != 1 && $data[1] != 'Special' && $data[1] != 'Admin')
         {
+          $language = intval(getConfig('default_language'));
+          $lang = new Language($language);
+          
           $this->logout();
           $a = getConfig('account_activation');
           switch($a)
@@ -480,6 +484,13 @@
         }
         $user = true;
         
+        // Set language
+        if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
+        {
+          $lang_id = intval($userdata['user_lang']);
+          $lang = new Language($lang_id);
+        }
+        
         if(isset($_REQUEST['auth']) && !$this->sid_super)
         {
           // Now he thinks he's a moderator. Or maybe even an administrator. Let's find out if he's telling the truth.
@@ -1111,6 +1122,7 @@
   function register_guest_session()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
+    global $lang;
     $this->username = $_SERVER['REMOTE_ADDR'];
     $this->user_level = USER_LEVEL_GUEST;
     if($this->compat || defined('IN_ENANO_INSTALL'))
@@ -1124,6 +1136,12 @@
       $this->style = ( isset($_GET['style']) && file_exists(ENANO_ROOT.'/themes/'.$this->theme . '/css/'.$_GET['style'].'.css' )) ? $_GET['style'] : substr($template->named_theme_list[$this->theme]['default_style'], 0, strlen($template->named_theme_list[$this->theme]['default_style'])-4);
     }
     $this->user_id = 1;
+    if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
+    {
+      // This is a VERY special case we are allowing. It lets the installer create languages using the Enano API.
+      $language = intval(getConfig('default_language'));
+      $lang = new Language($language);
+    }
   }
   
   /**
@@ -1151,7 +1169,7 @@
     }
     $keyhash = md5($key);
     $salt = $db->escape($keydata[3]);
-    $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,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,x.* FROM '.table_prefix.'session_keys AS k
+    $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,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,u.user_lang,x.* 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.'users_extra AS x
--- a/language/english/enano.json	Sat Oct 27 13:54:44 2007 -0400
+++ b/language/english/enano.json	Sun Oct 28 14:32:13 2007 -0400
@@ -35,15 +35,27 @@
       err_key_wrong_length: 'The encryption key was the wrong length.',
       err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.',
       err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.',
-      err_invalid_credentials_lockout: ' You have used up %lockout_fails% out of %lockout_threshold% login attempts. After you have used up all %lockout_threshold% login attempts, you will be locked out from logging in for %lockout_duration% minutes.',
-      err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %lockout_threshold% login attempts. After you have used up all %lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %lockout_duration% minutes.',
+      err_invalid_credentials_lockout: ' You have used up %fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will be locked out from logging in for %config.lockout_duration% minutes.',
+      err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %config.lockout_duration% minutes.',
       err_backend_fail: 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.',
-      err_locked_out: 'You have used up all %lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%captcha_blurb%.',
-      err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box'
+      err_locked_out: 'You have used up all %config.lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%config.captcha_blurb%.',
+      err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box',
+      login_body: 'Logging in enables you to use your preferences and access member information. If you don\'t have a username and password here, you can <a href="%reg_link%">create an account</a>.',
+      login_body_elev: 'You are requesting that a sensitive operation be performed. To continue, please re-enter your password to confirm your identity.',
+      login_field_username: 'Username',
+      login_field_password: 'Password',
+      login_forgotpass_blurb: 'Forgot your password? <a href="%forgotpass_link%">No problem.</a>',
+      login_createaccount_blurb: 'Maybe you need to <a href="%reg_link%">create an account</a>.',
+      login_field_captcha: 'Code in image',
+      login_nocrypt_title: 'Important note regarding cryptography',
+      login_nocrypt_body: 'Some countries do not allow the import or use of cryptographic technology. If you live in one of the countries listed below, you should <a href="%nocrypt_link%">log in without using encryption</a>.',
+      login_nocrypt_countrylist: 'This restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.',
     },
     page: {
     },
-    adm: {
+    comment: {
+    },
+    admhome: {
     },
   }
 };
--- a/plugins/PrivateMessages.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/plugins/PrivateMessages.php	Sun Oct 28 14:32:13 2007 -0400
@@ -35,12 +35,18 @@
 function page_Special_PrivateMessages()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  if(!$session->user_logged_in) die_friendly('Access denied', '<p>You need to <a href="'.makeUrlNS('Special', 'Login/'.$paths->page).'">log in</a> to view your private messages.</p>');
+  if ( !$session->user_logged_in )
+  {
+    die_friendly('Access denied', '<p>You need to <a href="'.makeUrlNS('Special', 'Login/'.$paths->page).'">log in</a> to view your private messages.</p>');
+  }
   $argv = Array();
   $argv[] = $paths->getParam(0);
   $argv[] = $paths->getParam(1);
   $argv[] = $paths->getParam(2);
-  if(!$argv[0]) $argv[0] = 'InVaLiD';
+  if ( !$argv[0] )
+  {
+    $argv[0] = 'InVaLiD';
+  }
   switch($argv[0])
   {
     default:
@@ -48,17 +54,29 @@
       break;
     case 'View':
       $id = $argv[1];
-      if(!preg_match('#^([0-9]+)$#', $id)) die_friendly('Message error', '<p>Invalid message ID</p>');
+      if ( !preg_match('#^([0-9]+)$#', $id) )
+      {
+        die_friendly('Message error', '<p>Invalid message ID</p>');
+      }
       $q = $db->sql_query('SELECT p.message_from, p.message_to, p.subject, p.message_text, p.date, p.folder_name, u.signature FROM '.table_prefix.'privmsgs AS p LEFT JOIN '.table_prefix.'users AS u ON (p.message_from=u.username) WHERE message_id='.$id.'');
-      if(!$q) $db->_die('The message data could not be selected.');
+      if ( !$q )
+      {
+        $db->_die('The message data could not be selected.');
+      }
       $r = $db->fetchrow();
       $db->free_result();
-      if( ($r['message_to'] != $session->username && $r['message_from'] != $session->username ) || $r['folder_name']=='drafts' ) die_friendly('Access denied', '<p>You are not authorized to view this message.</p>');
-      if($r['message_to'] == $session->username)
+      if ( ($r['message_to'] != $session->username && $r['message_from'] != $session->username ) || $r['folder_name']=='drafts' )
+      {
+        die_friendly('Access denied', '<p>You are not authorized to view this message.</p>');
+      }
+      if ( $r['message_to'] == $session->username )
       {
         $q = $db->sql_query('UPDATE '.table_prefix.'privmsgs SET message_read=1 WHERE message_id='.$id.'');
         $db->free_result();
-        if(!$q) $db->_die('Could not mark message as read');
+        if ( !$q )
+        {
+          $db->_die('Could not mark message as read');
+        }
       }
       $template->header();
       userprefs_show_menu();
@@ -69,7 +87,7 @@
           <tr><td class="row1">Subject:</td><td class="row1"><?php echo $r['subject']; ?></td></tr>
           <tr><td class="row2">Date:</td><td class="row2"><?php echo date('M j, Y G:i', $r['date']); ?></td></tr>
           <tr><td class="row1">Message:</td><td class="row1"><?php echo RenderMan::render($r['message_text']);
-          if($r['signature'] != '')
+          if ( $r['signature'] != '' )
           {
             echo '<hr style="margin-left: 1em; width: 200px;" />';
             echo RenderMan::render($r['signature']);
@@ -82,33 +100,60 @@
       break;
     case 'Move':
       $id = $argv[1];
-      if(!preg_match('#^([0-9]+)$#', $id)) die_friendly('Message error', '<p>Invalid message ID</p>');
+      if ( !preg_match('#^([0-9]+)$#', $id) )
+      {
+        die_friendly('Message error', '<p>Invalid message ID</p>');
+      }
       $q = $db->sql_query('SELECT message_to FROM '.table_prefix.'privmsgs WHERE message_id='.$id.'');
-      if(!$q) $db->_die('The message data could not be selected.');
+      if ( !$q )
+      {
+        $db->_die('The message data could not be selected.');
+      }
       $r = $db->fetchrow();
       $db->free_result();
-      if($r['message_to'] != $session->username) die_friendly('Access denied', '<p>You are not authorized to alter this message.</p>');
+      if ( $r['message_to'] != $session->username )
+      {
+        die_friendly('Access denied', '<p>You are not authorized to alter this message.</p>');
+      }
       $fname = $argv[2];
-      if(!$fname || ( $fname != 'Inbox' && $fname != 'Outbox' && $fname != 'Sent' && $fname != 'Drafts' && $fname != 'Archive' ) ) die_friendly('Invalid request', '<p>The folder name "'.$fname.'" is invalid.</p>');
+      if ( !$fname || ( $fname != 'Inbox' && $fname != 'Outbox' && $fname != 'Sent' && $fname != 'Drafts' && $fname != 'Archive' ) )
+      {
+        die_friendly('Invalid request', '<p>The folder name "'.$fname.'" is invalid.</p>');
+      }
       $q = $db->sql_query('UPDATE '.table_prefix.'privmsgs SET folder_name=\''.strtolower($fname).'\' WHERE message_id='.$id.';');
       $db->free_result();
-      if(!$q) $db->_die('The message was not successfully moved.');
+      if ( !$q )
+      {
+        $db->_die('The message was not successfully moved.');
+      }
       die_friendly('Message status', '<p>Your message has been moved to the folder "'.$fname.'".</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">Return to inbox</a></p>');
       break;
     case 'Delete':
       $id = $argv[1];
-      if(!preg_match('#^([0-9]+)$#', $id)) die_friendly('Message error', '<p>Invalid message ID</p>');
+      if ( !preg_match('#^([0-9]+)$#', $id) )
+      {
+        die_friendly('Message error', '<p>Invalid message ID</p>');
+      }
       $q = $db->sql_query('SELECT message_to FROM '.table_prefix.'privmsgs WHERE message_id='.$id.'');
-      if(!$q) $db->_die('The message data could not be selected.');
+      if ( !$q )
+      {
+        $db->_die('The message data could not be selected.');
+      }
       $r = $db->fetchrow();
-      if($r['message_to'] != $session->username) die_friendly('Access denied', '<p>You are not authorized to delete this message.</p>');
+      if ( $r['message_to'] != $session->username )
+      {
+        die_friendly('Access denied', '<p>You are not authorized to delete this message.</p>');
+      }
       $q = $db->sql_query('DELETE FROM '.table_prefix.'privmsgs WHERE message_id='.$id.';');
-      if(!$q) $db->_die('The message was not successfully deleted.');
+      if ( !$q )
+      {
+        $db->_die('The message was not successfully deleted.');
+      }
       $db->free_result();
       die_friendly('Message status', '<p>The message has been deleted.</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">Return to inbox</a></p>');
       break;
     case 'Compose':
-      if($argv[1]=='Send' && isset($_POST['_send']))
+      if ( $argv[1]=='Send' && isset($_POST['_send']) )
       {
         // Check each POST DATA parameter...
         if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '')) die_friendly('Sending of message failed', '<p>Please enter the username to which you want to send your message.</p>');
@@ -191,10 +236,26 @@
         ?>
         <br />
         <div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">
-          <tr><th colspan="2">Compose new private message</th></tr>
-          <tr><td class="row1">To:<br /><small>Separate multiple names with a single comma; you<br />can send this message to up to <b><?php echo (string)MAX_PMS_PER_BATCH; ?></b> users.</small></td><td class="row1"><?php echo $template->username_field('to', (isset($_POST['_savedraft'])) ? $_POST['to'] : $to ); ?></td></tr>
-          <tr><td class="row2">Subject:</td><td class="row2"><input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo $_POST['subject']; else echo $subj; ?>" /></td></tr>
-          <tr><td class="row1">Message:</td><td class="row1" style="min-width: 80%;"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo $_POST['message']; else echo $text; ?></textarea></td></tr>
+          <tr>
+            <th colspan="2">Compose new private message</th>
+          </tr>
+          <tr>
+            <td class="row1">
+              To:<br />
+              <small>Separate multiple names with a single comma; you<br />
+                     may send this message to up to <b><?php echo (string)MAX_PMS_PER_BATCH; ?></b> users.</small>
+            </td>
+            <td class="row1">
+              <?php echo $template->username_field('to', (isset($_POST['_savedraft'])) ? $_POST['to'] : $to ); ?>
+            </td>
+          </tr>
+          <tr>
+            <td class="row2">
+              Subject:
+            </td>
+            <td class="row2">
+              <input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $subj; ?>" /></td></tr>
+          <tr><td class="row1">Message:</td><td class="row1" style="min-width: 80%;"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['message']); else echo $text; ?></textarea></td></tr>
           <tr><th colspan="2"><input type="submit" name="_send" value="Send message" />  <input type="submit" name="_savedraft" value="Save as draft" /> <input type="submit" name="_inbox" value="Back to Inbox" /></th></tr>
         </table></div>
         <?php
@@ -254,9 +315,9 @@
         <br />
         <div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">
           <tr><th colspan="2">Edit draft</th></tr>
-          <tr><td class="row1">To:<br /><small>Separate multiple names with a single comma</small></td><td class="row1"><input name="to" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo $_POST['to']; else echo $r['message_to']; ?>" /></td></tr>
-          <tr><td class="row2">Subject:</td><td class="row2"><input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo $_POST['subject']; else echo $r['subject']; ?>" /></td></tr>
-          <tr><td class="row1">Message:</td><td class="row1"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo $_POST['message']; else echo $r['message_text']; ?></textarea></td></tr>
+          <tr><td class="row1">To:<br /><small>Separate multiple names with a single comma</small></td><td class="row1"><input name="to" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['to']); else echo $r['message_to']; ?>" /></td></tr>
+          <tr><td class="row2">Subject:</td><td class="row2"><input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $r['subject']; ?>" /></td></tr>
+          <tr><td class="row1">Message:</td><td class="row1"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['message']); else echo $r['message_text']; ?></textarea></td></tr>
           <tr><th colspan="2"><input type="submit" name="_send" value="Send message" />  <input type="submit" name="_savedraft" value="Save as draft" /></th></tr>
         </table></div>
         <?php
--- a/plugins/SpecialUserFuncs.php	Sat Oct 27 13:54:44 2007 -0400
+++ b/plugins/SpecialUserFuncs.php	Sun Oct 28 14:32:13 2007 -0400
@@ -100,6 +100,7 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $__login_status;
+  global $lang;
   
   $pubkey = $session->rijndael_genkey();
   $challenge = $session->dss_rand();
@@ -181,34 +182,34 @@
     $paths->main_page();
   $template->header();
   echo '<form action="'.makeUrl($paths->nslist['Special'].'Login').'" method="post" name="loginform" onsubmit="runEncryption();">';
-  $header = ( $level > USER_LEVEL_MEMBER ) ? 'Please re-enter your login details' : 'Please enter your username and password to log in.';
+  $header = ( $level > USER_LEVEL_MEMBER ) ? $lang->get('user_login_message_short_elev') : $lang->get('user_login_message_short');
   if ( isset($_POST['login']) )
   {
     $errstring = $__login_status['error'];
     switch($__login_status['error'])
     {
       case 'key_not_found':
-        $errstring = '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.';
+        $errstring = $lang->get('user_err_key_not_found');
         break;
       case 'key_wrong_length':
-        $errstring = 'The encryption key was the wrong length.';
+        $errstring = $lang->get('user_err_key_wrong_length');
         break;
       case 'too_big_for_britches':
-        $errstring = 'You are trying to authenticate at a level that your user account does not permit.';
+        $errstring = $lang->get('user_err_too_big_for_britches');
         break;
       case 'invalid_credentials':
-        $errstring = 'You have entered an invalid username or password. Please enter your login details again.';
+        $errstring = $lang->get('user_err_invalid_credentials');
         if ( $__login_status['lockout_policy'] == 'lockout' )
         {
-          $errstring .= ' You have used up '.$__login_status['lockout_fails'].' out of '.$__login_status['lockout_threshold'].' login attempts. After you have used up all '.$data['lockout_threshold'].' login attempts, you will be locked out from logging in for '.$__login_status['lockout_duration'].' minutes.';
+          $errstring .= $lang->get('err_invalid_credentials_lockout', array('lockout_fails' => $__login_status['lockout_fails']));
         }
         else if ( $__login_status['lockout_policy'] == 'captcha' )
         {
-          $errstring .= ' You have used up '.$__login_status['lockout_fails'].' out of '.$__login_status['lockout_threshold'].' login attempts. After you have used up all '.$data['lockout_threshold'].' login attempts, you will have to enter a visual confirmation code before logging in, effective for '.$__login_status['lockout_duration'].' minutes.';
+          $errstring .= $lang->get('user_err_invalid_credentials_lockout_captcha', array('lockout_fails' => $__login_status['lockout_fails']));
         }
         break;
       case 'backend_fail':
-        $errstring = 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.';
+        $errstring = $lang->get('user_err_backend_fail');
         break;
       case 'locked_out':
         $attempts = intval($__login_status['lockout_fails']);
@@ -216,13 +217,15 @@
           $attempts = $__login_status['lockout_threshold'];
         
         $server_time = time();
-        $time_rem = $__login_status['lockout_duration'] - round( ( $server_time - $__login_status['lockout_last_time'] ) / 60 );
+        $time_rem = ( $__login_status['lockout_last_time'] == time() ) ? $__login_status['lockout_duration'] : $__login_status['lockout_duration'] - round( ( $server_time - $__login_status['lockout_last_time'] ) / 60 );
+        if ( $time_rem < 1 )
+          $time_rem = $__login_status['lockout_duration'];
         
-        $s = ( $time_rem == 1 ) ? '' : 's';
-        $errstring = "You have used up all {$__login_status['lockout_threshold']} allowed login attempts. Please wait {$time_rem} minute$s before attempting to log in again";
-        if ( $__login_status['lockout_policy'] == 'captcha' )
-        $errstring .= ', or enter the visual confirmation code shown above in the appropriate box';
-        $errstring .= '.';
+        $s = ( $time_rem == 1 ) ? '' : $lang->get('meta_plural');
+        
+        $captcha_string = ( $__login_status['lockout_policy'] == 'captcha' ) ? $lang->get('err_locked_out_captcha_blurb') : '';
+        $errstring = $lang->get('user_err_locked_out', array('plural' => $s, 'captcha_blurb' => $captcha_string, 'time_rem' => $time_rem));
+        
         break;
     }
     echo '<div class="error-box-mini">'.$errstring.'</div>';
@@ -246,18 +249,18 @@
             <?php
             if ( $level <= USER_LEVEL_MEMBER )
             {
-              echo '<p>Logging in enables you to use your preferences and access member information. If you don\'t have a username and password here, you can <a href="'.makeUrl($paths->nslist['Special'].'Register').'">create an account</a>.</p>';
+              echo '<p>' . $lang->get('user_login_body', array('reg_link' => makeUrlNS('Special', 'Register'))) . '</p>';
             }
             else
             {
-              echo '<p>You are requesting that a sensitive operation be performed. To continue, please re-enter your password to confirm your identity.</p>';
+              echo '<p>' . $lang->get('user_login_body_elev') . '</p>';
             }
             ?>
           </td>
         </tr>
         <tr>
           <td class="row2">
-            Username:
+            <?php echo $lang->get('user_login_field_username'); ?>:
           </td>
           <td class="row1">
             <input name="username" size="25" type="text" <?php
@@ -277,20 +280,22 @@
           </td>
           <?php if ( $level <= USER_LEVEL_MEMBER ) { ?>
           <td rowspan="<?php echo ( ( $locked_out && $lockdata['lockout_policy'] == 'captcha' ) ) ? '4' : '2'; ?>" class="row3">
-            <small>Forgot your password? <a href="<?php echo makeUrlNS('Special', 'PasswordReset'); ?>">No problem.</a><br />
-            Maybe you need to <a href="<?php echo makeUrlNS('Special', 'Register'); ?>">create an account</a>.</small>
+            <small><?php echo $lang->get('user_login_forgotpass_blurb', array('forgotpass_link' => makeUrlNS('Special', 'PasswordReset'))); ?><br />
+            <?php echo $lang->get('user_login_createaccount_blurb', array('reg_link' => makeUrlNS('Special', 'Register'))); ?></small>
           </td>
           <?php } ?>
         </tr>
         <tr>
-          <td class="row2">Password:<br /></td><td class="row1"><input name="pass" size="25" type="password" tabindex="<?php echo ( $level <= USER_LEVEL_MEMBER ) ? '2' : '1'; ?>" /></td>
+          <td class="row2">
+            <?php echo $lang->get('user_login_field_password'); ?>:
+          </td><td class="row1"><input name="pass" size="25" type="password" tabindex="<?php echo ( $level <= USER_LEVEL_MEMBER ) ? '2' : '1'; ?>" /></td>
          </tr>
          <?php
          if ( $locked_out && $lockdata['lockout_policy'] == 'captcha' )
          {
            ?>
            <tr>
-             <td class="row2" rowspan="2">Code in image:<br /></td><td class="row1"><input type="hidden" name="captcha_hash" value="<?php echo $lockdata['captcha']; ?>" /><input name="captcha_code" size="25" type="text" tabindex="<?php echo ( $level <= USER_LEVEL_MEMBER ) ? '3' : '4'; ?>" /></td>
+             <td class="row2" rowspan="2"><?php echo $lang->get('user_login_field_captcha'); ?>:<br /></td><td class="row1"><input type="hidden" name="captcha_hash" value="<?php echo $lockdata['captcha']; ?>" /><input name="captcha_code" size="25" type="text" tabindex="<?php echo ( $level <= USER_LEVEL_MEMBER ) ? '3' : '4'; ?>" /></td>
            </tr>
            <tr>
              <td class="row3">
@@ -303,8 +308,12 @@
          <?php if ( $level <= USER_LEVEL_MEMBER ) { ?>
          <tr>
            <td class="row3" colspan="3">
-             <p><b>Important note regarding cryptography:</b> Some countries do not allow the import or use of cryptographic technology. If you live in one of the countries listed below, you should <a href="<?php if($p=$paths->getParam(0))$u='/'.$p;else $u='';echo makeUrl($paths->page.$u, 'level='.$level.'&use_crypt=0', true); ?>">log in without using encryption</a>.</p>
-             <p>This restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.</p>
+             <?php
+             $returnpage_link = ( $return = $paths->getAllParams() ) ? '/' . $return : '';
+             $nocrypt_link = makeUrlNS('Special', "Login$returnpage_link", "level=$level&use_crypt=0", true);
+             echo '<p><b>' . $lang->get('user_login_nocrypt_title') . ':</b> ' . $lang->get('user_login_nocrypt_body', array('nocrypt_link' => $nocrypt_link)) . '</p>';
+             echo '<p>' . $lang->get('user_login_nocrypt_countrylist') . '</p>';
+             ?>
            </td>
          </tr>
          <?php } ?>
--- a/schema.sql	Sat Oct 27 13:54:44 2007 -0400
+++ b/schema.sql	Sun Oct 28 14:32:13 2007 -0400
@@ -104,6 +104,7 @@
   temp_password text,
   temp_password_time int(12) NOT NULL DEFAULT 0,
   user_coppa tinyint(1) NOT NULL DEFAULT 0,
+  user_lang smallint(5) NOT NULL,
   PRIMARY KEY  (user_id)
 ) CHARACTER SET `utf8`;
 
--- a/upgrade.sql	Sat Oct 27 13:54:44 2007 -0400
+++ b/upgrade.sql	Sun Oct 28 14:32:13 2007 -0400
@@ -9,6 +9,9 @@
 -- UPDATE {{TABLE_PREFIX}}group_members SET group_id=9998 WHERE group_id=4;
 -- INSERT INTO {{TABLE_PREFIX}}groups(group_id,group_name,group_type,system_group) VALUES(4, 'Regular members', 3, 1);
 CREATE TABLE {{TABLE_PREFIX}}lockout( id int(12) NOT NULL auto_increment, ipaddr varchar(40) NOT NULL, action ENUM('credential', 'level') NOT NULL DEFAULT 'credential', timestamp int(12) NOT NULL DEFAULT 0, PRIMARY KEY ( id ) ) CHARACTER SET `utf8`;
+CREATE TABLE {{TABLE_PREFIX}}language( lang_id smallint(5) NOT NULL auto_increment, lang_code varchar(16) NOT NULL, lang_name_default varchar(64) NOT NULL, lang_name_native varchar(64) NOT NULL, PRIMARY KEY ( lang_id ) ) CHARACTER SET `utf8`;
+CREATE TABLE {{TABLE_PREFIX}}language_strings( string_id bigint(15) NOT NULL auto_increment, lang_id smallint(5) NOT NULL, string_category varchar(32) NOT NULL, string_name varchar(64) NOT NULL, string_content longtext NOT NULL, PRIMARY KEY ( string_id ) );
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_lang smallint(5) NOT NULL;
 ---END Stable1.0ToUnstable1.1---
 ---BEGIN 1.0.2---
 ---END 1.0.2---