--- listhandler.module.org	2011-01-17 16:17:23.000000000 +0100
+++ listhandler.module	2011-01-24 01:16:04.000000000 +0100
@@ -2,21 +2,25 @@
 // $Id: listhandler.module,v 1.85 2008/10/27 20:09:29 philipnet Exp $
 
 /**
- * @file
- * Listhandler main module.
+ * @file Listhandler main module.
  */
 
 /**
- * To do:
+ *  @todo
  *   Does attachment handling screw up druapl_html_to_text or vise-versa?
- *   listhandler_update_5000 needs testing with the new db_drop_index code
- *   5.x to 6.x migration needs testing
  *   Descriptions for database schema
  *   Check attachment handling after admin screen UI improvement.
  */
 
 /**
  * Implementation of hook_help().
+ *
+ * @param $path
+ *   The path
+ * @param $arg
+ *
+ * @return $output
+ *  The help texts
  */
 function listhandler_help($path, $arg) {
   $output = '';
@@ -46,6 +50,9 @@ url('admin/content/mailhandler'), '!admi
 /**
  * Implementation of hook_perm().
  * Permissions for this module
+ *
+ * @return array
+ *   administer listhandler
  */
 function listhandler_perm() {
   return array('administer listhandler');
@@ -53,6 +60,8 @@ function listhandler_perm() {
 
 /**
  * Implementation of hook_menu().
+ *
+ * @return $items
  */
 function listhandler_menu() {
   $items = array();
@@ -70,6 +79,9 @@ function listhandler_menu() {
 
 /**
  * Implementation of hook_theme().
+ *
+ * @return array
+ *  admin settings
  */
 function listhandler_theme() {
   return array(
@@ -81,6 +93,9 @@ function listhandler_theme() {
 
 /**
  * Configuration options for this module
+ *
+ * @return
+ *  admin_settings form data
  */
 function listhandler_admin_settings() {
   // This is the first chance we get to identify any new Mailhandler mailboxes.
@@ -88,8 +103,6 @@ function listhandler_admin_settings() {
   //   * new mailboxes
   //   * removed mailboxes
 
-
-
   $form = array();
   $form['listhandler_from'] = array(
     '#type' => 'textfield',
@@ -157,6 +170,11 @@ function listhandler_admin_settings() {
 
 /**
  * Theme call to render the tabular form of mailing lists and prefixes
+ *
+ * @param $form
+ *   The form data
+ * @return
+ *   The full form data
  */
 function theme_listhandler_admin_settings($form) {
   $output = '';
@@ -187,6 +205,11 @@ function theme_listhandler_admin_setting
 /**
  * Writes configuration settings as Drupal variables and entries in
  * the listhandler and mailhandler tables
+ *
+ * @param $form
+ *   form data
+ * @param $form_state
+ *   The form state
  */
 function listhandler_admin_settings_submit($form, &$form_state) {
   // Drupal can save the standard options, it's just the table it can't:
@@ -205,6 +228,13 @@ function listhandler_admin_settings_subm
 
 /**
  * Implementation of hook_nodeapi().
+ *
+ * @param $node
+ *   The $node with needed data
+ * @param $op
+ *   The API operation
+ * @param $arg
+ *   Additional arguments as defined in NodeAPI
  */
 function listhandler_nodeapi(&$node, $op, $arg = 0) {
   switch ($op) {
@@ -277,6 +307,11 @@ function listhandler_comment($edit = arr
 /**
  * Send email to mailing list.
  * Includes a References/In_reply-To header if possible.
+ *
+ * @global $user
+ * @global $base_url
+ * @global $language
+ * @param  $edit
  */
 function listhandler_send_mail($edit) {
   global $user, $base_url, $language;
@@ -292,7 +327,7 @@ function listhandler_send_mail($edit) {
   // Find the list to send to.
   $mboxen = array();
 
-  // taxo terms of the node.
+  // taxonomy terms of the node.
   if (count($edit['taxonomy'])) {
     foreach ($edit['taxonomy'] as $taxoterm) {
       // Now for the mailbox.
@@ -415,6 +450,12 @@ function listhandler_send_mail($edit) {
 
 /**
  * Implements hook_mail.
+ *
+ * @param $key
+ *
+ * @param $message
+ *
+ * @param $params
  */
 function listhandler_mail($key, &$message, $params) {
   $message['subject'] = $params['subject'];
@@ -425,13 +466,26 @@ function listhandler_mail($key, &$messag
 /**
  * Called by mailhandler after a new mail has been received.
  *
- * Only the first and last argument are used.
- *
  * $node: The new node or comment
- * $stream: The number of the opened imap stream.
+ * $result: The number of the opened imap stream.
  * $msg_number: The number of the current message in the mailbox.
+ * $header: Some, but not all headers (From, To, message_id, ...) of the current message
  * $mailbox: Array containing info about the mailbox the message is from.
- * $header: Some, but not all headers (From, To, message_id, ...) of the current message.
+ *
+ * @global $base_url
+ *   URL to use to calculate the path
+ * @param $node
+ *   The node data to use
+ * @param $result
+ *   IMAP result of open
+ * @param $msg_number
+ *   the email msg id
+ * @param $header
+ *   the email header information data
+ * @param $mailbox
+ *
+ * @return $node
+ *  The new node data
  */
 function listhandler_mailhandler($node, $result, $msg_number, $header, $mailbox) {
   global $base_url;
@@ -442,6 +496,9 @@ function listhandler_mailhandler($node, 
     $node->mid = $mailbox['mid'];
     // We need to ensure that no messages that have been sent by listhandler on this site (!)
     // get processed. We would get an infinite loop on a mailing list.
+    /** @todo  make sure we do not use fixed protocals embedded in code prevent e.g. https: use
+     * better use PHP  parse_url ( $url ) function
+     */
     $dir = str_replace("/", ".", substr(strchr(str_replace("http://", "", $base_url), "/"), 1));
     $regex = "<listhandler=[[:print:]]+@". $dir . strtolower($_SERVER["SERVER_NAME"]) .">";
     if (ereg($regex, $header->message_id)) {
@@ -494,9 +551,16 @@ function listhandler_mailhandler($node, 
  * a real account or a blocked one (see conf).
  * if no account can be created (email or name already taken) the name and address
  * are prepended to the node body and posted as anonymous.
+ *
+ * @param $node
+ * @param $header
+ * @return $node
+ *   Node object
  */
 function listhandler_create_author($node, $header) {
+
   $from = $header->from;
+
   $from_address = strtolower(sprintf("%s@%s", $from[0]->mailbox, $from[0]->host));
   // decode encoded author name
   $from_name = '';
@@ -509,38 +573,125 @@ function listhandler_create_author($node
       $from_name .=  $from_arr[$i]->text;
     }
   }
-  $from_name = trim($from_name, '"');
+
+  // some "from" real names are set as email addresses by users in their email
+  // client, therefore take he part before first "at" sign if this is the case
+  if (strpos($from_name, '@') > -1) {
+    $from_name = substr($from_name,0,strpos($from_name, '@'));
+  }
+  $from_name = listhandler_transliterate_username_from_authorname($from_name);
+  //If there is no name a result we generate one from the email address
   if ($from_name == '') {
-    $from_name = $from_address;
+    $from_name = substr($from_address,0,strpos($from_address, '@'));
+    $from_name = listhandler_transliterate_username_from_authorname($from_name);
+    //Unlikely, but if still no name, then make the name into a name and random number
+    if ($from_name == '') {
+      $from_name = "user".rand(100000, 999999);
+    }
   }
-  // check if name is available
-  if (db_result(db_query("SELECT COUNT(name) FROM {users} WHERE LOWER(name) = LOWER('%s')", $from_name)) > 0) {
-    $node->body = t('Message from !n !a', array('!n' => $from_name, '!a' => $from_address)) ."\n\n". $node->body;
-    watchdog('listhandler', "Cannot create account: The name '!s' is already taken.", array("!s" => $from_name), WATCHDOG_WARNING);
+
+  //Clean up and get rid of character we do not want in an username
+  $from_name=listhandler_remove_unwanted_username_characters($from_name);
+  //If still empty, very very unlikely but anyhow, make one up again
+  //It happens if e.g a Thai name transliterates to an empty one
+  //and this then get stripped by
+  if ($from_name == '') {
+    $from_name = "user".rand(100000, 999999);
   }
-  // check if address is valid
-  elseif (!valid_email_address($from_address)) {
+
+  // check if name is available and if so just add a number to the name in the
+  // case it's already taken and test again.
+  // same names are bound to happen with lots of emails this way users are still
+  // automatically created
+  while (db_result(db_query("SELECT COUNT(name) FROM {users} WHERE LOWER(name) = LOWER('%s')", $from_name)) > 0) {
+    //If same name found but different user, then add a number to the from_name
+    $from_name = $from_name.rand(100000, 999999);
+    //$node->body = t('Message from !n !a', array('!n' => $from_name, '!a' => $from_address)) ."\n\n". $node->body;
+  }
+  // Again remove unwanted username character, in case the the function
+  // remove_unwanted_username_characters changed and prohibits numbers e.g. 0-9
+  $from_name=listhandler_remove_unwanted_username_characters($from_name);
+
+  // check if address is valid and only create a user if the email address is valid
+  if (!valid_email_address($from_address)) {
     $node->body = t('Message from !n !a', array('!n' => $from_name, '!a' => $from_address)) ."\n\n". $node->body;
     watchdog('listhandler', "Cannot create account: The email address '!s' is not valid.", array("!s" => $from_address), WATCHDOG_WARNING);
   }
-  // create account
+  // Create a new user account and populate initial fields
   else {
+
+    // Get the email date to use. Tested if this date is not in future of
+    // servertime and optionally also older than one day.
+    // This makes sure import of old mailinglist data with preserving
+    // maillist users original subscription date and timestamp
+    // This is needed to make sure the "Member since" information is correct
+    //
+    // udate always and available value since mail arrived art server otherwise
+    // the email could not have been retrieved via listhandler
+    // If maildate is < x hours it is assumed not old import and then the
+    // default behaviour of not forced setting the user creation timestamp by
+    // code is in effect again.
+
+    // Set $minumum_email_age_in_hours to -1 for disableing taking the value of
+    // the email datetimestamp as user creation datetimestamp
+    // Set $minumum_email_age_in_hours to 0 for no age and thus always use the
+    // email datetimestamp if a valid one is available
+
+    /** @todo make minimum_email_age settable via listhandler admin GUI */
+    $minimum_email_age_in_hours = 0;
+    $email_age_difference = $header->udate + ($minimum_email_age_in_hours * 60);
+
+    // The hours added and if then still the datetime < current datetime,
+    // ...the email is considered an old email from an maillist archive
+    // If var not is set then at user creation from users.module we can see that
+    // time(); is used to set the value
+    if (($minimum_email_age_in_hours >= 0) && ($email_age_difference < time())) {
+      $new_datetime = $header->udate;
+    }
+
+    // The "date" or if not set "Date" from the email will be used to determine
+    // the timezone of the senders email and set within user
+    if (variable_get('configurable_timezones', 1)) {
+      $date_of_email = $header->date;
+      if (!isset($date_of_email)){
+        $date_of_email = $header->Date;
+      }
+
+      if (isset($date_of_email)){
+        $timezone_of_email = listhandler_get_timezone_offset_from_email_date($date_of_email)*3600;
+        $timezonename_of_email = listhandler_timezone_offset_to_timezone_name(listhandler_get_timezone_offset_from_email_date($date_of_email));
+      }
+    }
+
     $empty_account = new stdClass();
-    $from_user = user_save($empty_account, array('name' => $from_name, 'pass' => user_password(), 'init' => $from_address,  'mail' => $from_address, 'roles' => array(DRUPAL_AUTHENTICATED_RID), 'status' => variable_get('listhandler_accountstatus', 0)), 'account');
+    $from_user = user_save($empty_account, array('name' => $from_name, 'pass' => user_password(), 'init' => $from_address,  'mail' => $from_address, 'created' => $new_datetime , 'timezone' => $timezone_of_email, 'timezone_name' => $timezonename_of_email ,'roles' => array(DRUPAL_AUTHENTICATED_RID), 'status' => variable_get('listhandler_accountstatus', 0)), 'account');
     watchdog('listhandler', 'Created account for !fa, with roles !rid', array('!fa' => $from_address, '!rid' => implode(', ', $from_user->roles)), WATCHDOG_NOTICE);
     mailhandler_switch_user($from_user->uid);
   }
+
   $node->uid = $from_user->uid;
   $node->name = $from_user->name;
+  $node->created = $from_user->created;
+
+  if (variable_get('configurable_timezones', 1)) {
+    $node->timezone = $from_user->timezone;
+    if (isset($from_user->timezone_name)) {
+      $node->timezone_name = $from_user->timezone_name;
+    }
+  }
 
   return $node;
 }
 
 /**
- * Find the parent of a new mailsubmitted comment. First use some heuristics
+ *  Find the parent of a new mailsubmitted comment. First use some heuristics
  * and assumptions about Message-ID, References, and In-Reply-To headers.
  * Then try to find a parent based on Subject line.
  * If all else fails, start a new forum topic.
+ *
+ * @param $node
+ * @param $header
+ * @return BOOL
  */
 function listhandler_find_parent($node, $header) {
 
@@ -578,6 +729,10 @@ function listhandler_find_parent($node, 
 /**
  * Convert upload files into links.
  * Optional argument $fragment is used in supporting comments
+ * @param $lfiles
+ * @param $nid
+ * @param $fragment
+ * @return $body
  */
 function _listhandler_mail_links($lfiles, $nid, $fragment=NULL) {
   $body = '';
@@ -609,6 +764,12 @@ function _listhandler_mail_links($lfiles
 
 /**
  * Convert upload files into mail attachments.
+ * @param $body
+ * @param $files
+ * @param $mid
+ * @param $fragment
+ * @return $message
+ *   The altered message data
  */
 function _listhandler_mail_attachments($body, $files, &$mid, $fragment=NULL) {
   $trenner  = md5(uniqid(time()));
@@ -637,6 +798,11 @@ function _listhandler_mail_attachments($
 
 /**
  * Sets $node-> attributes to that of the parent thread.
+ *
+ * @param  $node
+ * @param $parent
+ * @return
+ *   resulted node data
  */
 function listhandler_parent_thread($node, $parent) {
   // Debug
@@ -651,12 +817,17 @@ function listhandler_parent_thread($node
   // because mailhandler, when it detects that this is a comment, will
   // properly reverse it
   $node->status = user_access('post comments without approval') ? 1 : 0;
- 
+
   return $node;
 }
 
 /**
  * Sets $node-> attributes to create a new thread.
+ *
+ * @param $node
+ *   THe node set data object
+ * @return
+ *   The node data altered to defeaults
  */
 function listhandler_new_thread($node) {
   $node->type = 'forum';
@@ -669,6 +840,133 @@ function listhandler_new_thread($node) {
 }
 
 /**
+ * Cleanup generated UserName from AuthorName
+ *
+ * @param $from_name
+ *   The base data to create the username from
+ * @return $transliterated
+ *   The newly generated username
+ */
+function listhandler_transliterate_username_from_authorname($from_name, $unknown='?') {
+  // If http://drupal.org/project/transliteration  module is installed
+  // a Better username will be generated
+  //
+  // If not, for lots of character like names in Russian, Chinese, Japanese,
+  // Korean etc. the username returned will be quite basic or even empty
+  // e.g. $from_name = 'Самоле_ANAÏS_Žluťoučký kůň-中國百家姓氏來源';
+
+  // return something if transliteration fails, the unwanted character will be
+  // removed afterwards anyhow via other function
+  $transliterated = $from_name;
+
+  if (function_exists('transliteration_get')) {
+    $transliterated = transliteration_get($from_name, $unknown, language_default('language'));
+  } elseif (function_exists('iconv')) {
+    //Fall back to iconv if exists
+    //Convert to something as reasonable as possible for ASCII charset
+    setlocale(LC_ALL, en_US.utf8);
+    $transliterated = iconv('UTF-8', 'ASCII//TRANSLIT', $from_name);
+  }
+
+  $transliterated = trim($transliterated);
+  return $transliterated;
+}
+
+/**
+ * Remove unwanted characters from username
+ *
+ * @param $raw_username
+ *   The raw username to cleanup
+ * @return $clean_username
+ *   The cleaned username
+ */
+function listhandler_remove_unwanted_username_characters($raw_username) {
+  $clean_username = trim($clean_username, '"');
+  // Default implementation of name generation
+  $clean_username = preg_replace('/@.*$/', '', $raw_username);
+  // Remove unwanted characters
+  $clean_username = preg_replace('/[^a-zA-Z0-9.-]/', '', $clean_username);
+  $clean_username=drupal_strtolower($clean_username);
+  return $clean_username;
+}
+
+
+ /**
+  * Timezone offset number of hours to timezone name
+  * If more extensive timezone handling is needed improvent should be such that
+  * the "Date" module should become a requirement for this
+  *
+  * @param $tzOffset
+  *   The Timezone offset
+  * @param $isDst
+  *   BOOL if value is DST
+  * @return <type>
+ */
+function listhandler_timezone_offset_to_timezone_name($tzOffset, $isDst = null) {
+  if ($isDst === null) {
+    $isDst = date('I');
+  }
+
+  $tzOffset    *= 3600;
+
+  // To avoid wrong values entering the system -12 to +14
+  if ($tzOffset < -43200 || $tzOffset > 50400) {
+    $tzOffset = 0;
+  }
+
+  $timezonename = timezone_name_from_abbr('', $tzOffset, $isDst);
+
+  if ($timezonename === false) {
+    foreach (timezone_abbreviations_list() as $abbr) {
+      foreach ($abbr as $city) {
+        if ((bool)$city['dst'] === (bool)$isDst &&
+        strlen($city['timezone_id']) > 0 &&
+        $city['offset'] == $tzOffset) {
+          $timezonename = $city['timezone_id'];
+          break;
+        }
+      }
+
+      if ($timezonename !== false) {
+        break;
+      }
+    }
+  }
+  return $timezonename;
+}
+
+
+/**
+ * Get timezone offset from an email date as best as we can guess
+ *
+ * @param $datetime
+ *   A datetimevalue retreived from an email
+ * @return
+ *   The extracted timezone offset
+ */
+function listhandler_get_timezone_offset_from_email_date($datetime) {
+  $pos = strrpos($datetime, '-');
+  if($pos===false) {
+    $pos = strrpos($datetime, '+');
+    if($pos===false) {
+      return false;
+    } else {
+      $tzOffset = substr($datetime, $pos,3);
+      if ($tzOffset > 14) {
+        $tzOffset = 0;
+      }
+      return $tzOffset;
+    }
+  } else {
+    $tzOffset = substr($datetime, $pos,3);
+    if ($tzOffset < -12 ) {
+      $tzOffset = 0;
+    }
+    return $tzOffset;
+  }
+}
+
+/**
  * Implementation of hook_simpletest().
  */
 function listhandler_simpletest() {
