--- convey.module	2008-08-13 14:44:24.000000000 +0100
+++ convey.module	2009-03-19 12:47:16.563000000 +0000
@@ -1,2182 +1,2181 @@
-<?php 
-// $Id: convey.module,v 1.7.2.16 2008/08/13 13:44:24 illuminateict Exp $
-/*
- * Copyright (C) 2008 Illuminate ICT (www.illuminateict.org.uk).
- * 
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- * 
- * The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
- * system and in the file COPYING in the Linux kernel source.
- * 
- * Author: Nick Blundell
- */
-
-
-
-define("NBDEBUG", True);
-
-
-define("SEND_PAUSE_MS",10);
-define("PROCESS_PAUSE_MS",10);
-define("MIN_SUB_CHECK_INTERVAL_MINUTES",60);
-define("MAX_MAILLISTS",10);
-define("MAX_EMAIL_SIZE", 3000000);
-define("CUT_OFF_DAYS", 10);
-define("MAX_RECENT_FEED_ITEMS", 50);
-define("DEFAULT_SUB_TEMPLATE", "Dear %user,<br/><br/>%content<br/><br/>Thank you");
-
-/*
- * ======================================
- * DEBUG
- * ======================================
- */
-
-//define("TEST_EMAIL", "nick"."@"."illuminateict.org.uk");
-//define("TEST_EMAIL", False);
-//define("COPY_EMAIL_TO_TEST", "nick"."@"."illuminateict.org.uk");
-//define("SIMULATE",True);
-//define("KEEP_EMAIL_ON_SERVER",True);
-
-/* Print debug message. */
-function nbdebug($message, $printr_msg=Null) {
-  global $user;
-  if ($user->name == "illuminate") {
-    if (defined("NBDEBUG")) {
-      $output = $message;
-      if ($printr_msg) {
-        $output .= ": <pre>".print_r($printr_msg,True)."</pre>";
-      }
-      drupal_set_message("DEBUG: ".$output);
-    }
-  }
-}
-
-function debugLog($message) {
-  if (defined("NBDEBUG")) {
-    watchdog('convey', $message);
-  }
-}
-
-function logError($message) {
-  watchdog('convey', $message, WATCHDOG_ERROR);
-  nbdebug("Error: $message");
-}
-
-function logMessage($message) {
-  watchdog('convey', $message);
-}
-
-/*
- * ======================================
- * MAIN
- * ======================================
- */
-
-function process() {
-  
-  // If we have not done so too recently, process data subscriptions.
-  $lastProcessed = variable_get("convey.lastsubprocessed", 0);
-  $secsSinceProcess = time() - $lastProcessed;
-  if ($secsSinceProcess > MIN_SUB_CHECK_INTERVAL_MINUTES * 60) {
-    //nbdebug("Processing subscriptions (secsSinceProcess $secsSinceProcess)");
-    processSubscriptions();
-    variable_set("convey.lastsubprocessed", time());
-  } else {
-    //nbdebug("Not processing subscriptions");
-  }
- 
-  // We likely want to do this more frequently than processing subscriptions.
-  processMailingLists();
-}
-
-
-
-/*
- * ======================================
- * SUBSCRIPTION FUNCTIONS
- * ======================================
- */
-
-/* Checks if a user is ready to receive some more subscribed content. */
-function userReadyForUpdate($user) {
- 
-  // How often does the user want updating?
-  $emailMode = $user->convey["digestmethod"];
-  
-  // Calculate the number of seconds since last update.
-  $time = time();
-  $date = getdate($time);
-  $secsSinceUpdate = $time - user_variable_get($user, "lastupdate", 0);
-  //nbdebug("$user->name $emailMode $secsSinceUpdate");
-
-  $userReady = False;
-
-  if ($emailMode == "perpost") {
-    // This type of user is always ready for more info.
-    $userReady = True;
-  } else if ($emailMode == "daydigest") {
-    // The user is ready if 22 hours (not 24 to avoid drift) since last update and >= 10am.
-    if ($secsSinceUpdate > (22 * 60 * 60) and $date["hours"] >= 10 ) {
-      $userReady = True;
-    }
-  } else if ($emailMode == "weekdigest") {
-    // if days > 5 and mon >= 10am
-    $dayOfWeek = getdate($time);
-    if ($secsSinceUpdate > (5 * 24 * 60 * 60) and $dayOfWeek["wday"] == 1 and $date["hours"] >= 10 ) {
-      $userReady = True;
-    }
-  }
-
-  if ($userReady) {
-    //nbdebug("$user->name ready for content.");
-  } else {
-    //nbdebug("$user->name not ready for content.");
-  }
-
-  return $userReady;
-}
-
-
-// Collect recent data all from RSS feeds.
-function collectRSSData(&$data) {
-  
-  // Get news feedsa.
-  $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
-  
-  // Process each news feed.
-  while ($feed = db_fetch_object($result)) {
-
-    // Get the most recent feed items from the feed.
-    $items = db_query_range('SELECT * FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, MAX_RECENT_FEED_ITEMS);
-    
-    // Process each feed item.
-    while ($item = db_fetch_object($items)) {
-     
-      // Remove \n from some feed titles.
-      $item->title = str_replace("\\n", "", $item->title);
-
-      // Normalise the attribute names to match those of a node
-      $item->created = $item->timestamp;
-      $item->body = $item->description;
-      $item->teaser = strip_tags(node_teaser($item->description));
-
-      // Store the source of this data.
-      $item->source = "feed-$item->fid";
-      $item->sourceName = $feed->title;
-
-      // Add the feed item to overall data.
-      $key = $item->created;
-      while (array_key_exists($key, $data)) { $key += 1; }
-      $data[$key] = $item;
-
-      // TODO: Add categories to feeds
-      $item->taxonomy = array();
-    
-      // Have a little sleep now so we don't swamp the CPU.
-      usleep(PROCESS_PAUSE_MS * 1000);
-    }
-  }
-}
-
-// Returns a list of consistent recent data items from various sources, keyed with publish date.
-function getRecentData() {
-  global $base_url;
-  
-  $data = array();
-
-  // Fields we want in each item: created, title, teaser, body, link, taxonomy, source, sourceName.
-
-  $siteName = variable_get("site_name", "");
-
-  // Get recently created published nodes limited to 10 days - so we don't consider really old stuff.
-  $cutOffTime = time() - (CUT_OFF_DAYS * 24 * 60 * 60);
-  $result = db_query('SELECT * FROM {node} WHERE status = 1 AND created > %d ORDER BY created DESC', $cutOffTime);
-  while ($nodeRecord = db_fetch_object($result)) {
-    
-    // Load the node.
-    $rawNode = node_load(array('nid' => $nodeRecord->nid));
-
-    // Render the node's teaser and body, allowing modules to do their bits.
-    $node = node_build_content($rawNode);
-    $teaserNode = node_build_content($rawNode, true);
-    $node->teaser = $teaserNode->teaser;
-
-    // Add an absolute link to the article:
-    $node->link = "$base_url/node/$node->nid";
-
-    // Strip tags from teaser, so we don't have to worry about severed tags.
-    $node->teaser = strip_tags($node->teaser);
-
-    // Store the source of this data, so we later know where it came from.
-    $node->source = "local";
-    $node->sourceName = $siteName;
-
-    // Add the node to the data list by a unique time key.
-    $key = $node->created;
-    while (array_key_exists($key, $data)) { $key += 1; }
-    $data[$key] = $node;
-
-    // Have a little sleep now so we don't swamp the CPU.
-    usleep(PROCESS_PAUSE_MS * 1000);
-  }
-
-  // Collect RSS data
-  collectRSSData($data);
-
-  // Sort the data so most recent item is first.
-  krsort($data);
-
-  // Process each item to add additional data.
-  foreach ($data as $key => $item) {
-    
-    // Mangle the name and time into something hopefully unique.
-    $data[$key]->mangleID = $item->title . $item->created;
-
-    // Add smart tags to categorise uncategorised content.
-    $data[$key]->smartTaxonomy = inferTaxonomies($data[$key]);
-    
-    usleep(PROCESS_PAUSE_MS * 1000);
-  }
-
-  return $data;
-}
-
-/* Delivers data to the user. */
-function deliverUserData(&$user, $userData) {
-  
-  // Don't do anything if there is no data to deliver.
-  if (!$userData) {
-    //nbdebug("There is no data to deliver to $user->name.");
-    return;
-  }
-
-  $siteName = variable_get("site_name", "");
-  $subject = "Update: $siteName";
-  
-  // Render the email.
-  $content = theme("convey_email", $user, $userData, variable_get("convey.emailtemplate",""));
-
-  if (defined("SIMULATE")) {
-    nbdebug("delivering data to $user->mail");
-    nbdebug($content);
-  }
-
-  // Wrap content in HTML.
-  //$encoding = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
-  //$content = "$encoding <head></head><body>$content</body></html>";
-  $content = wrapInHTML($content);
-
-  // Get the sender address.
-  $sender = variable_get("convey.subreplyto", "");
-
-  // Send the email.
-  sendEmail($user->mail, $subject, $content, $sender);
-  
-  // Update last updated time for this user.
-  user_variable_set($user, "lastupdate", time());
-}
-
-
-// Main method for processing subscription delivery.
-function processSubscriptions() {
-
-  // Get a list of recent data from various sources.
-  $recentData = getRecentData();
-
-  //nbdebug("No recent articles: " .count($recentData));
-
-  // Process each non-blocked user. 
-  $result = db_query("SELECT uid FROM {users} WHERE status != 0");
-  while ($userRecord = db_fetch_object($result)) {
-    
-    // Load the user.
-    $user = user_load(array("uid"=>$userRecord->uid));
-
-    // The user must have an email address.
-    if (!$user->mail) { continue; }
-    
-    // Is this user ready for an update (i.e. do they get updates immediately, daily or weekly?)
-    if (!userReadyForUpdate($user)) {continue;}
-
-    // Get data filtered for this user.
-    $userData = getUserFilteredData($user, $recentData);
-
-    // Deliver the filtered data to the user.
-    deliverUserData($user, $userData);
-
-    // Wait a short time so we don't hog the CPU.
-    usleep(SEND_PAUSE_MS * 1000);
-  }
-}
-
-
-
-/*
- * ======================================
- * MAILING LIST SYSTEM
- * ======================================
- */
-
-/* given a user id and new mailling list settings, notify them if they have joined or left mailing lists. */
-function notifyMailinglistUpdate($uid, $newMailingLists) {
-  
-  global $base_url;
-
-  $user = user_load(array("uid"=>$uid));
-  if (!$user) {return;}
-
-  $site_name = variable_get("site_name","Drupal");
-  $site_email = variable_get("site_mail", "SITE_MAIL");
-
-  $currentMailingLists = checkboxSelectionsToList($user->convey["usermailinglists"]);
-
-  /* Has the user just joined any lists? */
-  if ($newMailingLists) {
-    foreach ($newMailingLists as $newMailingList) {
-      if (!in_array($newMailingList, $currentMailingLists)) {
-        $message = theme("welcome",$user, $newMailingList, $site_name, $base_url, $site_email);
-        $subject = "SUBSCRIPTION: $newMailingList";
-        $message = wrapInHTML($message);
-        sendEmail($user->mail, $subject, $message);
-        //nbdebug("welcome: $message");
-      }
-    }
-  }
- 
-  /* Has the user left any lists? */
-  if ($currentMailingLists) {
-    foreach ($currentMailingLists as $currentMailingList) {
-      if (!in_array($currentMailingList, $newMailingLists)) {
-        $message = theme("goodbye",$user, $currentMailingList, $site_name, $base_url, $site_email);
-        $subject = "UNSUBSCRIPTION: $currentMailingList";
-        $message = wrapInHTML($message);
-        sendEmail($user->mail, $subject, $message);
-        //nbdebug("goodbye: $message");
-      }
-    }
-  }
-}
-
-
-/*  Handles mass add/remove mailing list user operations. called by convey_user_operations */
-function convey_user_ops($users, $mode, $mailingList) {
-  
-  // For each user id
-  foreach ($users as $uid) {
-    $user = user_load(array("uid"=>$uid));
-    $conveySettings = $user->convey;
-    
-    if ($mode == "remove") {
-      unset($conveySettings["usermailinglists"][$mailingList]);
-    } else {
-      $conveySettings["usermailinglists"][$mailingList] = $mailingList;
-    }
-    
-    /* Notify the user if their subscription has changed. */
-    $newMailingLists = checkboxSelectionsToList($conveySettings["usermailinglists"]);
-    notifyMailinglistUpdate($user->uid, $newMailingLists);
-
-    // Save the user with updates.
-    user_save($user, array("convey" => $conveySettings));
-  }
-}
-
-/* Gets a structure of member-populated mailing lists and a similar structure of valid members for those list (which may have email aliases). */
-function getMailingLists() {
-
-  $mailingLists = variable_get("convey.maillists", array());
-  $mailingListMembers = array();
-  $validSenders = array();
- 
-  if (!$mailingLists) {
-    return Null;
-  }
-
-
-  foreach ($mailingLists as $mailingList => $listDetails) {
-    $mailingListMembers[$mailingList] = array();
-  }
-
-  // Get uids of all non-blocked users.
-  $result = db_query("SELECT uid FROM {users} WHERE status != 0");
-
-  // For each user...
-  while ($userRecord = db_fetch_object($result)) {
-    
-    // Load the user.
-    $user = user_load(array("uid"=>$userRecord->uid));
-    
-    // The user must have an email address.
-    if (!$user->mail) { continue; }
- 
-    // Get the user's selected mailing lists.
-    $userMailingLists = checkboxSelectionsToList($user->convey["usermailinglists"]);
-    //nbdebug("$user->mail : ",  $userMailingLists);
-
-    // Go no further if this user does not subscribe to any lists.
-    if (!$userMailingLists) { continue; }
-
-    //nbdebug("usermailinglists", $userMailingLists);
-
-    // Get user email aliases.
-    $aliases = getCleanList($user->convey["emailaliases"]);
-
-    // For each mailing list...
-    foreach ($mailingLists as $mailingList => $listDetails) {
-      
-      // If the user is subscribed to this list, add their email address to the subscription list and their associated email addresses as valid senders.
-      if (in_array($mailingList, $userMailingLists)) {
-        $mailingListMembers[$mailingList][] = strtolower($user->mail);
-        
-        // Update valid senders.
-        $validSenders[$mailingList][] = strtolower($user->mail);
-        foreach ($aliases as $alias) {
-          $validSenders[$mailingList][] = strtolower($alias);
-        }
-      }
-    }
-    
-    // Wait a short while so we don't hog CPU.
-    usleep(PROCESS_PAUSE_MS * 1000);
-  }
-
-  // Also add addresses from the non-user members lists.
-  foreach ($mailingLists as $mailingList => $listDetails) {
-
-    // Get the list of addresses of imported email addresses.
-    $members = getCleanList($listDetails[3]);
-    if (!$members) {
-      continue;
-    }
-
-    // Process each email address.
-    foreach ($members as $member) {
-
-      $member = strtolower($member);
-
-      // A little sanity check to see if this is an email address.
-      if (!strstr($member, "@")) {
-        continue;
-      }
-
-      // Check we don't add duplicate email addresses.
-      if (!in_array($member, $mailingListMembers[$mailingList])) {
-        $mailingListMembers[$mailingList][] = $member;
-        // Update valid senders.
-        $validSenders[$mailingList][] = $member;
-      }
-    }
-  }
-
-  return array($mailingListMembers, $validSenders);
-}
-
-
-
-// Processes mailing lists, checking for new mail and forwarding it to members.
-function processMailingLists() {
-  nbdebug("Processing mailing lists");
-
-  // Get the maillist vars.
-  $mailbox = variable_get("convey.mailbox", Null);
-  $username = variable_get("convey.username",Null);
-  $password = variable_get("convey.password",Null);
-  $maillists = variable_get("convey.maillists",Null);
-  
-  // Check if a mailbox has been configured.
-  if (!($mailbox and $username and $password)) {
-    nbdebug("No mailbox has been configured for incoming mail.");
-    return;
-  }
-  
-  // Check if any mailing lists have been defined.
-  if (!$maillists) {
-    nbdebug("There are no mailing lists to process.");
-    return;
-  }
-
-  // Get mailing lists and valid senders to those lists.
-  list($mailingLists, $validSenders) = getMailingLists();
-
-  // Get a list of user email accounts for early, simple authorisation.
-  // This means we will not even download spam emails from the mailbox.
-  $ackAllSenders = variable_get("convey.ackallsenders", True);
-  $allValidSenders = array();
-  if (!$ackAllSenders) {
-    foreach ($validSenders as $mailingList => $members) {
-      $allValidSenders = array_merge($allValidSenders, $members);
-    }
-  }
-
-  //nbdebug("allValidSenders", $allValidSenders);
-
-  // Get list of new valid email from the mailbox.
-  $newMail = getNewMail($mailbox, $username, $password, $allValidSenders);
-
-  // Get the mailing list configs.
-  $mailingListSettings = variable_get("convey.maillists", array());
-
-  // Process each new email.
-  foreach ($newMail as $email) {
-    
-    // Examine each 'to' address in the email.
-    foreach ($email->to as $to) {
-      //nbdebug($to);
-
-      // On process this if there are members of this mailing list.
-      $members = $mailingLists[$to];
-      if (!$members) {continue;}
-      
-      // Check the sender is a member, and email them if they are not.
-      if (!in_array($email->from, $validSenders[$to])) {
-        emailError($email, "You cannot post to the mailing list '$to', because you are not a member of it.");
-        continue;
-      }
-
-      // Set the email subject prefix.
-      $prefix = $mailingListSettings[$to][2];
-      # If people reply, we may get many prefixes in the subject, so remove them.
-      $subject = str_replace($prefix, "", $email->subject);
-      $subject = "$prefix $subject";
-     
-      // Set the reply-to email address if the listis configured like this.
-      if ($mailingListSettings[$to][4]) {
-        $replyTo = $to;
-        $footer = Null;
-      } else {
-        $replyTo = Null;
-        // Add the list email address to the email footer, so users can reply.
-        $footer = "<br/><br/>To reply to this list, send an email to: $to<br/><br/>";
-      }
-
-      // Log the message
-      logMessage("Email '$email->from' -> '$to': $subject");
-
-      // Forward the email to each member of the list.
-      foreach ($members as $member) {
-        
-        //nbdebug("would send email $subject to $member.");
-        sendEmail($member, $subject, $email->body, $email->from, $replyTo, $footer, $email->attachments);
-        
-        // Wait a short time so we don't hog the CPU.
-        usleep(SEND_PAUSE_MS * 1000);
-      }
-    }
-  }
-
-}
-
-
-/*
- * ======================================
- * GENERATED PAGES 
- * ======================================
- */
-
-/* Displays members of mailing lists. */
-function convey_mailing_lists() {
-  // Note, this is not optimised as is just a quick fix to the problem of seeing who is on which lists.
-  $output = "<p>Here are lists of members on particular mailing lists.</p>";
-  $rows = array();
-  
-  $lists = array();
-  
-  $mailingLists = variable_get("convey.maillists", array());
-  
-  if (!$mailingLists) {
-    return "No lists.";
-  }
-
-  // Get the mailing list members.
-  list($mailingListMembers, $validSenders) = getMailingLists();
-
-
-
-  // For each mailling list.
-  foreach ($mailingLists as $mailingList => $listDetails) {
-
-    $memberEmails = $mailingListMembers[$mailingList];
-
-    foreach ($memberEmails as $memberEmail) {
-
-      // Try to get a user for this email address.
-      $user = user_load(array("mail"=>$memberEmail));
-
-      if ($user) {
-        $lists[$mailingList][$memberEmail] = array("name"=>$user->name, "email"=>$memberEmail, "uid"=>$user->uid);
-      } else {
-        $lists[$mailingList][$memberEmail] = array("name"=>"", "email"=>$memberEmail, "uid"=>"");
-      }
-    }
-
-  }
-
-  // Render list members in a table.
-  $headings = array("Email","Name");
-  foreach ($mailingLists as $mailingList => $listDetails) {
-    
-    $output .= "<h1>List: $mailingList</h1>";
-    ksort($lists[$mailingList]);
-    $users = $lists[$mailingList];
-    if (!$users) {
-      $output .= "<p>No members</p>";
-      continue;
-    }
-    $rows = array();
-    foreach ($users as $email => $user) {
-      if ($user["uid"]) {
-        $rows[] = array($user["email"], l($user["name"], "user/".$user["uid"]."/edit"));
-      } else {
-        $rows[] = array($user["email"], "[Not site user]");
-      }
-    }
-    $output .= theme_table($headings, $rows);
-  }
-  
-  return $output;
-}
-
-/*
- * ======================================
- * DRUPAL HOOKS 
- * ======================================
- */
-
-// Define permissions.
-function convey_perm() {
-  return array('administer convey');
-}
-
-
-// Define menu items and pages.
-function convey_menu($may_cache) {
-  $items = array();
-  $admin_access = user_access('administer convey');
-
-  if ($may_cache) {
-    $items[] = array('path' => 'admin/settings/convey', 'title' => t('Convey'),
-      'callback' => 'convey_admin',
-      'description' => t('Configure convey.'),
-      'access' => $admin_access);
-    $items[] = array('path' => 'convey/rssfeed', 'title' => t('Convey RSS Feed'),
-      'callback' => 'convey_rss_feed',
-      'access' => True,
-      'type' => MENU_CALLBACK,
-      );
-    $items[] = array('path' => 'admin/settings/convey/maillists', 'title' => t('Convey: Mailing Lists'),
-      'callback' => 'convey_mailing_lists',
-      'description' => t('Mailing lists.'),
-      'type' => MENU_CALLBACK,
-      'access' => $admin_access);
-  }
-  else {
-    $path = drupal_get_path('module', 'convey');
-    drupal_add_css($path .'/convey.css', 'module', 'all', TRUE);
-    
-    $items[] = array('path' => 'convey/cron_process', 'title' => t('Cron process'),
-      'callback' => 'convey_cron_run',
-      'access' => True,
-      'type' => MENU_CALLBACK,
-      );
-  }
-
-  return $items;
-}
-
-
-// Called when Drupal initialises.
-function convey_init() {
-  //resetUser(1);
-  //convey_cron();
-  //resetUser(1);
-  //processSubscriptions();
-  if (defined("SIMULATE")) {
-    cache_clear_all('*', 'cache_menu', TRUE);
-    //db_query("truncate cache_menu;");
-    //process();
-
-    //$maillists = variable_get("convey.maillists", Null);
-    //filterListImport($maillists);
-    
-    resetUser(2);
-    processSubscriptions();
-    //processMailingLists();
-  }
-}
-
-
-// Called by CRON.
-function convey_cron() {
-  if (!defined("SIMULATE")) {
-    process();
-  }
-}
-
-
-// Adds mailing list mass-user options to Drupal user page.
-function convey_user_operations() {
- 
-  $mailingLists = variable_get("convey.maillists", array());
-  if (!$mailingLists) {
-    return Null;
-  }
- 
-  $operations = array();
-
-  foreach ($mailingLists as $mailingList => $listDetails) {
-    $operations['maillist-add-'.$mailingList] = array(
-      'label' => t('[Convey] ADD users to mail list '. $mailingList),
-      'callback' => 'convey_user_ops',
-      'callback arguments' => array("add", $mailingList),
-    );
-    $operations['maillist-remove-'.$mailingList] = array(
-      'label' => t('[Convey] REMOVE users from mail list '. $mailingList),
-      'callback' => 'convey_user_ops',
-      'callback arguments' => array("remove", $mailingList),
-    );
-  }
-  
-  return $operations;
-}
-
-
-function convey_user($op, &$edit, &$account, $category = NULL) {
-  // Note that the user module stores addtional field data in serialised user field.
-  
-  // Only add our form on the account tab of the user's profile.
-  if ($category != "account") {
-    return;
-  }
-  
-  switch ($op) {
-    case "register" :
-    case "form" :
-      return convey_user_form($account);
-      break;
-    case "submit" :
-      return _convey_user_form_submit($account, $edit);
-      break;
-  }
-}
-
-
-
-function convey_help($section) {
- 
-  global $base_url;
-
-  if (!($section == "admin/help#convey" or $section == "admin/help/convey")) { return; }
-
-  $guidePath = drupal_get_path("module","convey") . "/convey-user-and-admin-guides.pdf" ;
-  $guideLink = l("Convey Guides",$guidePath);
-  $output = "<p>For more information on the Convey module, see the $guideLink.</p>";
-
-  return $output;
-}
-
-/*
- * ======================================
- * EMAIL FUNCTIONS
- * ======================================
- */
-
-/* Sends an email. */
-function sendEmail($to, $subject, $message, $from=Null, $replyTo=Null, $footer=Null, $attachments=Null) {
-
-  //nbdebug("Sending email to: $to.");
-
-  // Set the from address.
-  if (!$from) { $from = variable_get('site_mail', ini_get('sendmail_from')); }
-
-  // Create a mime part boundary token.
-  $rand = md5(time());
-  $boundary = "==Multipart_Boundary_x{$rand}x";
-
-  // Set-up the global email headers.
-  $headers = array();
-  $headers["MIME-Version"] = '1.0';
-  $headers['Precedence'] = 'bulk'; // This should stop auto responders.
-  $headers["Content-Type"] = "multipart/mixed; boundary=\"$boundary\"";
-  if ($replyTo) { $headers["Reply-To"] = $replyTo; }
-
-  //
-  // MAIN BODY PART
-  //
-  
-  // Get the email signature and add the footer to it if there is one.
-  $emailSig = getEmailFooter();
-  if ($footer) {
-    $emailSig = $footer . $emailSig;
-  }
-
-  // Get the charset of the email.
-  $charset = variable_get("convey.emailcharset", "utf-8");
-
-  // Encode the email body from utf-8 to the specified charset.
-  // Outlook doesn't support UTF-8
-  if ($charset != "utf-8") {
-    if (!($message = iconv("utf-8",$charset."//TRANSLIT", $message))) {
-      //$message = utf8_decode($message);
-      nbdebug("Message cannot be encoded to $charset");
-    }
-  }
-
-  // Determine if this is html or not.
-  // TODO: Sort this out.  The message should not be html-wrapped before this stage.
-  if (stristr($message, "html>")) {
-    $encoding = "text/html";
-    $message = str_replace("</body>","<div class='sig'><hr/>$emailSig</div></body>", $message);
-    $message = str_replace("</BODY>","<div class='sig'><hr/>$emailSig</div></BODY>", $message);
-  } else {
-    $encoding = "text/plain";
-    $message .= "\n-----------\n$emailSig";
-  }
-  
-  $mainPart = new stdClass();
-  $mainPart->content = $message;
-  $mainPart->headers = array(
-    "Content-Type" => "$encoding; charset=$charset;",
-    "Content-Transfer-Encoding" => "8bit",
-  );
-
-  // write the parts as mime
-  $emailBody = "This is a multi-part message in MIME format.\n\n";
-  $emailBody .= "--$boundary\n";
-
-  // Add the main body part.
-  $mimeheaders = array();
-  foreach ($mainPart->headers as $name => $value) {
-    $mimeheaders[] = $name .': '. mime_header_encode($value);
-  }
-  $emailBody .= join("\n", $mimeheaders) . "\n\n";
-  $emailBody .= $mainPart->content . "\n\n";
-
-  //
-  // Attachments.
-  //
-
-  // Now add the file attachments.
-  if ($attachments) {
-    foreach ($attachments as $filename => $data) {
-      $type = "application/octet-stream";
-      $emailBody .= "--{$boundary}\n" .
-      "Content-Type: {$type};\n" .
-      " name=\"{$filename}\"\n" .
-      "Content-Disposition: attachment;\n" .
-      " filename=\"{$filename}\"\n" .
-      "Content-Transfer-Encoding: base64\n\n" .
-      $data . "\n\n";
-    }
-    $emailBody .= "--{$boundary}--\n";
-  }
- 
-  // If simulating, do not send the email: just print details.
-  if (defined("SIMULATE")) {
-    nbdebug("sending email to: '$to' subject '$subject' (from: $from, replyTo: $replyTo) attached ".count($attachments).".");
-    return;
-  }
-
-  // Redirect to test address.
-  if (defined("TEST_EMAIL")) {
-    nbdebug("Would have sent to: $to");
-    $to = TEST_EMAIL;
-    nbdebug("But sending to: $to");
-  }
-
-  // Send the email via drupal API.
-  if (drupal_mail('iweb_asc', $to, $subject, $emailBody, $from, $headers)) {
-    watchdog('action', t('Sent email to %recipient', array('%recipient' => $to)));
-  } else {
-    watchdog('error', t('Unable to send email to %recipient', array('%recipient' => $to)));
-  }
-
-  // Check if to copy this email to a test address.
-  if (defined("COPY_EMAIL_TO_TEST")) {
-    drupal_mail('iweb_asc', COPY_EMAIL_TO_TEST, $subject, $message."<br/><br/> This was sent to '$to'.", $from, $headers);
-    nbdebug("Copying email to ".COPY_EMAIL_TO_TEST);
-  }
-}
-
-
-
-// Gets new mail from a mailbox as an array of email objects.
-function getNewMail($mailbox, $username, $password, $validSenders=Null) {
-  
-  $mail = array();
-
-  // Open the mailbox connection.
-  $mbox = imap_open($mailbox, $username, $password);
-  if (!$mbox) {
-    nbdebug("$mailbox $username $password");
-    logError("Could not open mailbox - perhaps it is busy.");
-    return array();
-  }
-
-  // Process each new email in the mailbox.
-  $noEmail = imap_num_msg($mbox);
-  for ($i = 1; $i <= $noEmail; $i++) {
-    
-    // Get the email in an object form and add it to our list of new mail.
-    $email = getEmailObject($mbox, $i, $validSenders);
-    if ($email) {
-      $mail[] = $email;
-    }
-
-    // Mark the message for deletion on the mailbox now that it has been read.
-    if (!defined("KEEP_EMAIL_ON_SERVER")) {
-      imap_delete($mbox, $i);
-    }
-  }
-
-  // Close the mailbox, deleting all read email.
-  imap_close($mbox, CL_EXPUNGE);
-  
-  return $mail;
-}
-
-
-
-/* gets email from the mailbox in a consistent format. */
-function getEmailObject($mbox, $mid, $validSenders) {
-  
-  // Get the email header.
-  $header = imap_header($mbox, $mid);
- 
-  //nbdebug("header",$header);
-
-  // We require that the email has a sender, recevier, and a subject.
-  if (!($header->to and $header->from and $header->subject)) {
-    nbdebug("Bad email");
-    return Null;
-  }
- 
-  // Check if the message is an autoreply.
-  if (stristr($header->subject, "autoreply") 
-      or stristr($header->subject, "out of office")
-      or stristr($header->subject, "vacation")
-      ) {
-    nbdebug("Rejecting autoreply message");
-    return Null;
-  }
-
-  // Create an object to store the email.
-  $email = new stdClass();
- 
-  // Store from and to.
-  $email->from = strtolower($header->from[0]->mailbox."@".$header->from[0]->host);
-  $email->to = array();
-  foreach($header->to as $to) {
-    $email->to[] = "$to->mailbox@$to->host";
-  }
-  
-  // Store other attributes in the object.
-  $email->subject = $header->subject;
-  $email->size = $header->Size;
-  $email->created = $header->udate;
-
-  // If the sender is not a member of ANY lists on the site, ignore this and send no repsonse.
-  if ($validSenders and !in_array($email->from, $validSenders)) {
-    logMessage("Sender $email->from is not a valid list sender.");
-    return Null;
-  }
-
-  // Do header checks so we don't waste time reading bad emails.
-  nbdebug("email size $email->size");
-  if ($email->size > MAX_EMAIL_SIZE) {
-    emailError($email, "Your email was too big to distribute on the mailing list.  This list cannot handle large file attachments, so please encode your files into a smaller size or simply link to the files from a website.");
-    return Null;
-  }
-
-  //$structure = imap_fetchstructure($mbox, $mid);
-  
-  // Get attached files an an array.
-  $structure = getEmailStructure($mbox, $mid);
-  $email->body = $structure["body"];
-  $email->attachments = $structure["attachments"];
-
-  // Why didn't this skeleton go to the party...
-  if (!$email->body) {
-    emailError($email, "Your email text could not be decoded for sending on the mailing list.");
-    return Null;
-  }
-
-  return $email;
-}
-
-function getEmailStructure($mbox, $mid) {
- 
-  $structure = array();
-  
-  // Get the body as utf8 text
-  // We use mailhandler module to decode the message, trying HTML then plain.
-  $structure["body"] = mailhandler_get_part($mbox, $mid, 'TEXT/HTML');
-  if (!$structure["body"]) {
-    $structure["body"] = mailhandler_get_part($mbox, $mid, 'TEXT/PLAIN');
-  }
-
-  // Get attachments as an array.
-  $structure["attachments"] = array();
-  $struct = imap_fetchstructure($mbox, $mid);
-  if (!$struct->parts) {
-    return $structure;
-  }
-
-  for ($p=0; $p<count($struct->parts); $p++) {
-    $part = $struct->parts[$p];
-    if ( strtoupper($part->disposition == "INLINE") or strtoupper($part->disposition == "ATTACHMENT")) {
-      $filename = $part->parameters[0]->value;
-      $partString = "" + ($p+1);
-      if ($part->encoding == 3) {
-        $data = imap_fetchbody($mbox, $mid, $partString);
-        //nbdebug("Part: $filename $partString");
-        //$f = fopen("/tmp/$filename", "w");
-        //fwrite($f, $data);
-        //fclose($f);
-        $structure["attachments"][$filename] = $data;
-      }
-    }
-  }
-
-  //nbdebug("struct", $struct);
-  
-  return $structure;
-}
-
-
-
-/* Sends a response to a bad email. */
-function emailError($original, $message) {
-  logMessage("Email error: $message ($original->from -> ".print_r($original->to, True).")");
-  sendEmail($original->from, "MAILING LIST POSTING DENIED", $message);
-}
-
-
-/*
- * ======================================
- * USER INTERFACE + FORM HOOKS
- * ======================================
- */
-
-
-function convey_admin_form() {
-  
-  /*
-   * Choose categories, and their order, for user subscription.
-   */
-
-  // Build up list of vocabs.
-  $categories = array(""=>"");
-  $vocabularies = taxonomy_get_vocabularies();
-  foreach ($vocabularies as $vocabulary) {
-    $categories[$vocabulary->vid] = $vocabulary->name;
-  }
-
-  $form['categories'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Subscription settings'),
-      //'#tree' => TRUE,
-      '#description' => t("These settings allow you to select categories that users may use to filter content by in their subscriptions."),
-      );
-
-  for ($i=1; $i<6; $i++) {
-    $form['categories']['category'.$i] = array(
-        '#type' => 'select',
-        '#title' => t('Category '.$i),
-        '#options' => $categories,
-        '#default_value' => variable_get("convey.category".$i, ""),
-        '#required' => False,
-        );
-  }
-  $form['categories']['allowand'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Allow AND-ed subscriptions.'),
-      '#default_value' => variable_get("convey.allowand", True),
-      '#description' => t("This allows users to say: 'I want content only if it has this category AND that category'."),
-      );
-  
-  $form['categories']['subreplyto'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Sender address of subscription emails'),
-      '#size' => 60,
-      '#description' => t("The email address in the 'from' field of email sent to subscribers.  Usually you will set this to 'no-reply@something.com', etc."),
-      '#default_value' => variable_get("convey.subreplyto", ""),
-      );
-  
-  $form['categories']['emailtemplate'] = array(
-      '#type' => 'textarea',
-      '#title' => t('Subscription email template'),
-      '#cols' => 60,
-      '#rows' => 15,
-      '#size' => 30,
-      '#default_value' => variable_get("convey.emailtemplate", DEFAULT_SUB_TEMPLATE),
-      '#description' => t("Enter the format of the subscrption email, which may include variables %user and %content."),
-      );
-
-
-  /*
-   * Mailbox settings
-   */
-  $form['email'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Email settings'),
-      //'#tree' => TRUE,
-      '#description' => t("These settings allow convey to pick up email from a mailbox to use for mailing lists and also set character encoding of email.  You must ensure that mail for all of the mailing lists is forwarded to this inbox."),
-      );
-  $form['email']['mailbox'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Mailbox'),
-      '#size' => 40,
-      '#description' => t("Enter the mailbox location (e.g. for a gmail account, enter '{imap.gmail.com:993/imap/ssl}INBOX' )"),
-      '#default_value' => variable_get("convey.mailbox", ""),
-      );
-
-  $form['email']['username'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Username'),
-      '#size' => 30,
-      '#description' => t("Enter the mailbox username."),
-      '#default_value' => variable_get("convey.username", ""),
-      );
-  $form['email']['password'] = array(
-      '#type' => 'password',
-      '#title' => t('Password'),
-      '#size' => 30,
-      '#description' => t("Enter the mailbox password."),
-      '#default_value' => variable_get("convey.password", ""),
-      );
-
-  // Email encoding.
-  $options = array(
-    "utf-8"=>"UTF-8",
-    "ISO-8859-1"=>"Western (ISO-8859-1)",
-  );
-  $form['email']["emailcharset"] = array(
-        '#type' => 'select',
-        '#title' => t('Email encoding'),
-        '#options' => $options,
-        '#default_value' => variable_get("convey.emailcharset", "utf-8"),
-        '#description' => t("It is best to use UTF-8 unless you have character encoding issues in, say, Outlook."),
-        );
- 
-
-
-  $form['maillists'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Mailing list settings'),
-      '#tree' => TRUE,
-      '#description' => t("Here you can setup mailing lists for users to subscribe to.  To remove a list, simply set the email address blank.  To <b>view current members of the lists</b> go to the <a href='/admin/settings/convey/maillists'>mailing lists</a> page."),
-      //'#collapsible' => TRUE,
-      //'#collapsed' => FALSE,
-      );
-
-  
-  $options = array(
-    "user"=>"User can subscribe",
-    "admin"=>"Admin must subscribe user",
-  );
-  
-  // Loop through each of the mailing lists
-  $maillists = variable_get("convey.maillists", array());
-  $maillists["NEW"] = array();
-  foreach($maillists as $listAddress => $listDetails) {
-    
-    // Set up the list variables.
-    $listemail = $listAddress;
-    $listsub = $listDetails[0];
-    $listdesc = $listDetails[1];
-    $listprefix = $listDetails[2];
-    $listnonusers = $listDetails[3];
-    $replytolist = $listDetails[4];
-
-    // Set a default prefix.
-    if (!$listprefix) {
-      $listprefix = "[mail list]";
-    }
-
-    // Blank the address for a new list.
-    if ($listAddress == "NEW") {
-      $listAddress = "";
-    }
-
-
-    $form['maillists'][$listemail] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Mailing list: '.$listemail),
-      //'#tree' => TRUE,
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      );
-
-    $form['maillists'][$listemail]['listemail'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Email address'),
-        '#size' => 30,
-        '#default_value' => $listAddress,
-        );
-    $form['maillists'][$listemail]['replytolist'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('Reply to list'),
-        '#default_value' => $replytolist,
-        '#description' => t("Tick this is you want members to reply to the whole list, rather than only the sender, by default."),
-        );
-    $form['maillists'][$listemail]['listprefix'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Email subject prefix'),
-        '#size' => 30,
-        '#default_value' => $listprefix,
-        );
-    $form['maillists'][$listemail]['listsub'] = array(
-        '#type' => 'select',
-        '#title' => t('Subscription method'),
-        '#options' => $options,
-        '#default_value' => $listsub,
-        );
-    $form['maillists'][$listemail]['listdesc'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Short mailing list description'),
-        '#size' => 50,
-        '#default_value' => $listdesc,
-        );
-    $form['maillists'][$listemail]['listnonusers'] = array(
-        '#type' => 'textarea',
-        '#title' => t('Non-user member addresses'),
-        '#cols' => 80,
-        '#rows' => 8,
-        '#default_value' => $listnonusers,
-        '#description' => t("Enter email addresses, one per line, for non-user members of this mailing list.  Note, if you later add these members as users of the site, their addresses will automatically be removed from this list, so that they can control their own list subscriptions from their profile page."),
-        );
-  }
-
-  $cronLink = l("this link", "convey/cron_process");
-  $form['cron'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Cron Performance'),
-      '#description' => t("In order to provide responsive mailing lists, Convey needs to check for new mail very frequently (e.g. every 2-5 minutes).  If, for performance reasons, you want Convey to check for new mail more frequently than you want to run all other cron jobs on the site, then call $cronLink in your cron scripts."),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      );
-
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
-  return $form;
-}
-
-
-function convey_admin_form_validate($form_id, $form_values) {
-  // Check: mailbox is valid.
-  $mailbox = $form_values['mailbox'];
-  $username = $form_values['username'];
-  $password = $form_values['password'];
-  
-  // If a new password was not posted, then use the last set password.
-  if (!$password) {
-    $password = variable_get("convey.password", "");
-  }
- 
-  // Check that the mailbox settings are valid by testing a connection.
-  if ($mailbox) {
-    $mbox = imap_open($mailbox, $username, $password);
-    if (!$mbox) {
-      form_set_error('', t('The mailbox could not be opened, check the mailbox, username and password are correct.'));
-    } else {
-      imap_close($mbox);
-    }
-  }
-}
-
-function convey_admin_form_submit($form_id, $form_values) {
-
-  $maillists = array();
-  $categories = array();
-  
-  // Build an array of categories.
-  for ($i=0; $i<20; $i++) {
-    if ($category = $form_values["category".$i]) {
-      $categories[] = $category;
-    }
-  }
-
-  /* Store the mailing list settings. */
-  $keys = array_keys($form_values["maillists"]);
-  foreach ($keys as $maillist) {
-    $listValues = $form_values["maillists"][$maillist];
-
-    if ($mailingList = $listValues["listemail"]) {
-      if ($mailingList) {
-        $maillists[$mailingList] = array($listValues["listsub"], $listValues["listdesc"], $listValues["listprefix"], $listValues["listnonusers"], $listValues["replytolist"]);
-      }
-    }
-  }
-
-  // Filter the import lists of maillists, so we don't store site user addresses.
-  filterListImport($maillists);
-
-  variable_set("convey.categories", $categories);
-  variable_set("convey.maillists", $maillists);
-
-  
-  // TODO: Don't need to store all vars individually.
-  foreach ($form_values as $key => $value) {
-    if (in_array($key, array("op","submit","form_token","form_id","maillists"))) {
-      continue;
-    }
-    // Don't store an empty password.
-    if ($key == "password" and !$value) {
-      continue;
-    }
-    variable_set("convey.".$key, $value);
-  }
-  drupal_set_message(t("Settings have been saved"));
-}
-
-function convey_admin() {
-  return drupal_get_form('convey_admin_form');
-}
-
-function convey_user_form($account) {
-  /*
-   * Mailbox settings
-   */
-  $form['convey'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Information subscription [convey]'),
-      '#tree' => True,
-      '#description' => t("<p>Here you can tune the type of information you would like to receive from this site.  For example, if you tick a category, you will receive all information within that category.</p>  You can also further tune your filtering by choosing to receive certain categories only when other categories are marked on content, for example: I would like to receive 'training' and 'family' information only in the geographic areas of 'Coventry' or 'Birmingham'.  <p>If you cannot get the information you need with categories alone, you can also enter <b>key phrases</b> that you are interested in, then any data seen by this website with those key phrases will be included in your news updates.</p>"),
-      '#weight' => 1,
-      );
-
-  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2>Information Filters</h2>');
-
-  $categories = variable_get("convey.categories", array());
-  $allowAnd = variable_get("convey.allowand", False);
-
-    
-  foreach ($categories as $index => $vid) {
-    $vocab = taxonomy_get_vocabulary($vid);
-    $terms = taxonomy_get_tree($vid,0,-1,1);
-    $options = array();
-    foreach ($terms as $term) {
-      $options[$term->tid] = $term->name;
-    }
-    $form['convey']["category".$vid] = array(
-        '#type' => 'checkboxes',
-        '#title' => t($vocab->name),
-        '#options' => $options,
-        '#prefix' => '<div class="convey-category">',
-        '#default_value' => $account->convey["category".$vid],
-        );
-    
-    $joinOptions = array();
-    if ($allowAnd) {
-      for ($i=0; $i<count($categories); $i++) {
-        $vocab = taxonomy_get_vocabulary($categories[$i]);
-        if ($vocab->vid == $vid) {
-          continue;
-        }
-        $joinOptions[$vocab->vid] = "Only with my <b>".$vocab->name."</b> selections";
-      }
-    }
-    
-    $form['convey']["categoryjoin".$vid] = array(
-        '#type' => 'checkboxes',
-        '#options' => $joinOptions,
-        '#prefix' => '<hr/>',
-        '#suffix' => '</div>',
-        '#default_value' => $account->convey["categoryjoin".$vid],
-        );
-  }
-
-  
-  $form['convey']['keywords'] = array(
-      '#type' => 'textarea',
-      '#title' => t('Key phrases'),
-      '#cols' => 60,
-      '#rows' => 8,
-      '#size' => 30,
-      '#default_value' => $account->convey["keywords"],
-      '#prefix' => '<div style="clear:both;">',
-      '#suffix' => '</div>',
-      '#description' => t("Enter key phrases or words you are interested in receiving information about, one word or phrase per line."),
-      );
-
-  if (!isset($account->convey["intelligentfilter"])) {
-    $iFilter = True;
-  } else {
-    $iFilter = $account->convey["intelligentfilter"];
-  }
-  $form['convey']["intelligentfilter"] = array(
-      '#type' => 'checkbox',
-      '#title' => t("Intelligent filtering"),
-      '#default_value' => $iFilter,
-      '#description' => t("Tick this if you want <b>uncategorised content</b> to be filtered intelligently based upon your category selections above."),
-      );
-  
-  $form['convey']["iwanteverything"] = array(
-      '#type' => 'checkbox',
-      '#title' => t("I want everything!"),
-      '#default_value' => $account->convey["iwanteverything"],
-      '#description' => t("Tick this if you want to be informed about everything, regardless of your filter settings.  CAUTION: you may get swamped with information if you tick this."),
-      );
-  
-  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2 style="clear:left;">Information Delivery</h2>');
-
-
-  $options = array(
-    "noemail"=>"No email (I will use RSS)",
-    "perpost"=>"Immediate email",
-    "daydigest"=>"Daily digest email",
-    "weekdigest"=>"weekly digest email",
-  );
-  $default = $account->convey["digestmethod"];
-  if (!$default) {
-    $default = "daydigest";
-  }
-  $form['convey']["digestmethod"] = array(
-        '#type' => 'select',
-        '#title' => t('Email options'),
-        '#options' => $options,
-        '#default_value' => $default,
-        '#description' => t("If you choose 'No email', you can collect news from this personal ".l("RSS Feed", getUserFeedUrl($account))." using your preferred news reader."),
-        );
- 
-  
-  $emailFormat = $account->convey["emailformat"];
-  if (!$emailFormat) {
-    $emailFormat = "teasers";
-  }
-  $options = array(
-    "links"=>"Bullet-style links to articles",
-    "teasers"=>"Article summaries with links",
-    "full"=>"Full article text",
-  );
-  $form['convey']["emailformat"] = array(
-        '#type' => 'select',
-        '#title' => t('Level of detail in email'),
-        '#options' => $options,
-        //'#default_value' => variable_get("convey.listsub".$i, ""),
-        '#default_value' => $emailFormat,
-        );
-
-  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2>Mailing lists</h2>');
-  $form['convey'][] = array('#type' => 'markup', '#value' => '<div class="description">Tick the mailing lists you would like to send and receive email on.</div>');
-  
-  // List the mailing lists the user can subscribe to or unsubscribe from.
-  $userMailingLists = checkboxSelectionsToList($account->convey["usermailinglists"]);
-  $options = array();
-  $maillists = variable_get("convey.maillists", array());
-  foreach($maillists as $listAddress => $listDetails) {
-    if ($listDetails[0] == "user" or user_access("administer convey") or in_array($listAddress, $userMailingLists)) {
-      $options[$listAddress] = $listAddress . " [".$listDetails[1]."]";
-    }
-  }
-
-  $form['convey']["usermailinglists"] = array(
-      '#type' => 'checkboxes',
-      '#options' => $options,
-      '#default_value' => $account->convey["usermailinglists"],
-      );
-
-  $form['convey']["emailaliases"] = array(
-      '#type' => 'textarea',
-      '#title' => t('Your alternate email addresses'),
-      '#cols' => 60,
-      '#rows' => 10,
-      '#size' => 30,
-      '#default_value' => $account->convey["emailaliases"],
-      '#description' => t("If you wish to mail the mailing lists using alternate email addresses, declare them here on separate lines; otherwise, your emails will be regarded to have come from strangers to the list and will be rejected."),
-      );
-  
-  return $form;
-}
-
-
-function _convey_user_form_submit($account, $form_values) {
-  // get current account, pass subscriptions current and new to function
-  $newMailingLists = checkboxSelectionsToList($form_values["convey"]["usermailinglists"]);
-  notifyMailinglistUpdate($account->uid, $newMailingLists);
-}
-
-
-/*
- * ======================================
- * THEMES
- * ======================================
- */
-
-
-function theme_welcome($user, $maillist, $site_name, $site_addr, $site_email) {
-  $output = <<<END_STRING
-<p>Dear $user->name,</p>
-
-<p>Welcome, you are now subscribed to $maillist. Either you have activated your own subscription or a list administrator has added you.</p>
-
-<p>
-To email the list:
-  <ul>
-  <li>send a message to $maillist</li>
-  </ul>
-</p>
-
-<p>
-To unsubscribe from the list:
-  <ul>
-  <li>If you are a registered user on $site_addr, login and update your user profile accordingly.</li>
-
-  <li>If you are not a registered user or are unable to login, then contact $site_email</li>
-  </ul>
-</p>
-
-<p>
-Please retain this email for future reference.
-</p>
-
-<p>Thank you,</p>
-
-<p>$site_name</p>
-END_STRING;
-
-  return $output;
-}
-
-
-function theme_goodbye($user, $maillist, $site_name, $site_addr, $site_email) {
-  $output = <<<END_STRING
-<p>Dear $user->name,</p>
-
-<p>
-You are now unsubscribed from $maillist. Either you have de-activated your own subscription or a list administrator has removed you.
-</p>
-
-<p>
-For more information please contact $site_email
-</p>
-
-<p>Thank you,</p>
-
-<p>$site_name</p>
-END_STRING;
-
-  return $output;
-}
-
-
-
-function theme_convey_email($user, $userData, $template) {
-  $output = "";
-  $output .= "<style>body{ font-family:arial,helvetica,clean,sans-serif;} /
-  .datestamp {font-size:60%;padding:0; margin:0;} a:link, a:visited {text-decoration:none;} /
-  .article-source {font-size:70%; color:gray;} .sig {font-size:70%; color:gray;} h3 {padding-bottom:1px;margin-bottom:1px}</style>";
-
-  $emailFormat = $user->convey["emailformat"];
- 
-  // Build the content.
-  $content = "";
-  foreach ($userData as $key => $item) {
-    $content .= "<h3><a href='$item->link'>$item->title</a></h3>";
-    $content .= "<div>";
-    if ($emailFormat == "full") {
-      $content .= "<div class='datestamp'>[Posted: ".format_date($item->created,"small")." at '$item->sourceName']</div>";
-      if ($item->body) {
-        $content .= $item->body;
-      } else {
-        $content .= $item->teaser;
-      }
-    } else if ($emailFormat == "teasers") {
-      $content .= "<div class='datestamp'>[Posted: ".format_date($item->created,"small")." at '$item->sourceName']</div>";
-      $content .= $item->teaser;
-    }
-    $content .= "</div>";
-  }
-
-  $formattedContent = $template;
-  $formattedContent = str_replace("%user", $user->name, $formattedContent);
-  $formattedContent = str_replace("%content", $content, $formattedContent);
-
-  $output .= $formattedContent;
-
-  return $output;
-}
-
-/*
- * ======================================
- * RSS OUTPUT FUNCTIONS 
- * ======================================
- */
-
-// Gets a link to a user's RSS feed.
-function getUserFeedUrl($user) {
-  global $base_url;
-  return $base_url . "/convey/rssfeed/$user->uid/".getUserHash($user);
-}
-
-// This is use to give a bit of security to user RSS feeds
-function getUserHash($user) {
-  $hash = 23;
-  $input = "$user->mail---$user->uid---$user->name";
-  for ($i=0; $i<strlen($input); $i++) {
-    $hash += ord($input{$i});
-  }
-  return "$hash";
-}
-
-
-// Outputs a user's RSS feed.
-function convey_rss_feed($uid, $secretHash=Null) {
-
-  //resetUser(1);
-
-  // Check user is not receving news via email.
-  $feedUser = user_load(array("uid"=>$uid));
-  if (!$feedUser) { return; }
-
-  // Check the user hash argument to add a level of simple security.
-  if (!(isset($secretHash) and $secretHash == getUserHash($feedUser))) {
-    print "Access denied.";
-    return;
-  }
-
-  // Check if the user has opted to get news through RSS.
-  if ($feedUser->convey["digestmethod"] != "noemail") {
-    print "User does not use RSS.";
-    return;
-  }
-
-  // Get all recent data.
-  $recentData = getRecentData();
-  
-  // Get data filtered for this user without synchronising (RSS client will do this).
-  $userData = getUserFilteredData($feedUser, $recentData, False);
-
-  // Render and print the rss feed.
-  createRSSFeedFromItems($userData);
-
-  // Cache the page, to stop DoS attacks.
-  page_set_cache();
-}
-
-// Renders an RSS feed from an array of articles.
-function createRSSFeedFromItems($items) {
-  
-  global $base_url, $locale;
-  
-  $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"');
-  $rssItems = "";
-  foreach ($items as $item) {
-    //$output .= "$item->title ";
-    $extra = array(array('key' => 'pubDate', 'value' => format_date($item->created, 'custom', 'r')), array('key' => 'guid', 'value' => $item->mangleID, 'attributes' => array('isPermaLink' => 'false')));
-    $rssItems .= format_rss_item($item->title, $item->link, $item->teaser, $extra);
-  }
- 
-  $channel = array(
-      'version'     => '2.0',
-      'title'       => variable_get('site_name', 'Drupal'),
-      'link'        => $base_url,
-      'description' => variable_get('site_mission', ''),
-      'language'    => $locale,
-      );
-
-   $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-   $output .= "<rss version=\"". $channel["version"] ."\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n";
-   $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $rssItems, $channel['language']);
-   $output .= "</rss>\n";
-
-   drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
-   print $output;
-}
-
-/*
- * ======================================
- * DATA FILTERING FUNCTIONS 
- * ======================================
- */
-
-
-// Gets a list of categories the user is interested in, including joined categories.
-function getUserJoinedCategories($user) {
-
-  // We will store categories of interest in here.
-  // This is basically an array of term join lists.
-  $joinedCategories = array();
-  
-  // Are ANDed joins configured?
-  $allowAnd = variable_get("convey.allowand", False);
-  
-  // Get a list of categories users may subscribe to.
-  $categories = variable_get("convey.categories", array());
- 
-  // Process each category.
-  foreach ($categories as $index => $vid) {
-
-    // Get the user's requested terms for this vocab.
-    $userTerms = checkboxSelectionsToList($user->convey["category".$vid]);
-    if (!$userTerms) {continue;}
-    
-    // If and-ed joins, get the joins for this vocab (i.e. does a user want this only if two categories are present).
-    if ($allowAnd) {
-      $joinsRaw = checkboxSelectionsToList($user->convey["categoryjoin".$vid]);
-    } else {
-      $joinsRaw = array();
-    }
-
-    // Ensure only vocabs specified by config are in joins.
-    $joins = array();
-    foreach ($joinsRaw as $joinVid) {
-      if (in_array($joinVid, $categories)) {
-        $joins[] = $joinVid;
-      }
-    }
-
-    // Loop through the user terms selected in this vocab.
-    foreach ($userTerms as $userTerm) {
-      
-      // If user has ticked join vocabs, add joined terms. 
-      if ($joins) {
-        // Have to add the terms with join terms
-        foreach($joins as $joinVid) {
-          
-          // Add each combination of the term and joined terms.
-          $joinTerms = checkboxSelectionsToList($user->convey["category".$joinVid]);
-          foreach ($joinTerms as $joinTerm) {
-            $joinedCategories[] = array($userTerm, $joinTerm);
-          }
-
-        }
-      } else {
-        // No joins, so just add the single term
-        $joinedCategories[] = array($userTerm);
-      }
-
-    }
-
-  }
-
-  //XXX: Check joins are working.
-  //nbdebug("joined cats for $user->name: ", $joinedCategories);
-
-  return $joinedCategories;
-
-}
-
-
-/* Looks for taxonomy phrases in text of an item to infer categories of the host website. */ 
-function inferTaxonomies($item) {
-  
-  //nbdebug("Adding smart tags to item $item->title");
-  
-  // Start out with the items taxonomies.
-  $smartTags = $item->taxonomy;
-
-  // Get the bits of text we want to search in.
-  $textParts = array($item->title, $item->teaser, $item->body);
-  
-  // Process each vocabulary
-  $vocabularies = taxonomy_get_vocabularies();
-  foreach ($vocabularies as $vocabulary) {
-    
-    // Process each term of the taxonomy.
-    $terms = taxonomy_get_tree($vocabulary->vid,0,-1,1);
-    foreach ($terms as $term) {
-      
-      // Search within each text part of the item.
-      foreach ($textParts as $text) {
-        // Look for whole-word matches.
-        if (preg_match("/\b($term->name)\b/i", $text)) {
-          // Store the matching term.
-          $smartTags[] = $term;
-        }
-      }
-    }
-  }
-
-  //if ($smartTags) {
-  //  nbdebug("$item->title smartTags: ", $smartTags);
-  //}
-
-  return $smartTags;
-}
-
-/* Check if keyphrases are in an article. */
-function hasKeyphrases(&$item, &$keyPhrases) {
-
-  if (!$keyPhrases) {
-    return False;
-  }
-
-  // Get keyphrases as a list.
-  $keyPhrasesList = $keyPhrases;
-
-  if (!$keyPhrasesList) {
-    return False;
-  }
-
-  foreach ($keyPhrasesList as $keyPhrase) {
-    $textParts = array(strtolower($item->title), strtolower($item->teaser), strtolower($item->body));
-    //nbdebug("textParts: ", $textParts);
-    $keyPhrase = strtolower($keyPhrase);
-    foreach ($textParts as $textPart) {
-      $pos = strpos($textPart, $keyPhrase);
-      if ($pos === False) {
-      } else {
-        return True;
-      }
-    }
-  }
-
-  return False;
-}
-
-
-/* Check if a user wants to see a content item. */
-function userWantsItem($user, $item, $joinedCategories, $userKeyphrases) {
-
-  // Check if the user has seen this item.
-  //$lastSourceRead = $user->convey["sourcetimestamps"][$item->source];
-  $sourceTimestamps = user_variable_get($user, "sourcetimestamps", array());
-  $lastSourceRead = $sourceTimestamps[$item->source];
-  if ($item->created <= $lastSourceRead) {
-    //nbdebug("$user->name has seen $item->title: create $item->created lastSourceRead $lastSourceRead");
-    return False;
-  }
-
-  // Check if the user wants everything.
-  if ($user->convey["iwanteverything"]) {
-    return True;
-  }
-
-  // Get the taxonomy terms of the item.
-  if ($user->convey["intelligentfilter"]) {
-    $taxonomy = $item->smartTaxonomy;
-  } else {
-    $taxonomy = $item->taxonomy;
-  }
-  
-  // Create a simple array of item ids
-  // TODO: Do not included non-covey vocabs.
-  $itemTerms = array();
-  if ($taxonomy) {
-    foreach ($taxonomy as $term) {
-      $itemTerms[] = $term->tid;
-    }
-  }
-
-  // Check the item has categories, using joins if specified.
-  foreach ($joinedCategories as $joinedCategory) {
-    if (count($joinedCategory) == 1) {
-      if (in_array($joinedCategory[0], $itemTerms)) {
-        return True;
-      }
-    } else if (count($joinedCategory) == 2) {
-      if (in_array($joinedCategory[0], $itemTerms) and in_array($joinedCategory[1], $itemTerms)) {
-        return True;
-      }
-    }
-  }
- 
-  if (hasKeyphrases($item, $userKeyphrases)) {
-    return True;
-  }
-  //XXX: Old way Now check for keywords
-  if (False and $userKeyphrases and $item->keywords) {
-    if (array_intersect($userKeyphrases, $item->keywords)) {
-      return True;
-    }
-  }
-
-  return False;
-}
-
-
-
-/* Gets recent data filtered specially for the user. */
-function getUserFilteredData(&$user, &$recentData, $timeSync=True) {
-
-  $filteredData = array();
-
-  // Build a list of categories this user is interested in, with joined categories where specified.
-  $joinedCategories = getUserJoinedCategories($user);
-
-  // Get a list of keyphrases the user is interested in.
-  $userKeyphrases = getCleanList($user->convey["keywords"]);
-
-  // Get a list of times when the user last read content from various sources.
-  $sourceTimestamps = user_variable_get($user, "sourcetimestamps", array());
-
-  //nbdebug("$user->name sourceTimestamps: ", $sourceTimestamps);
-  
-  // Process each item of recent data.
-  foreach ($recentData as $time => $item) {
-    
-    // Check user has permission to see the item.
-    if (!user_item_access("view", $item, $user)) {
-      //nbdebug("$user->name not allowed to see $item->title");
-      continue;
-    }
-
-    // Check if the user wants this item or not.
-    if (userWantsItem($user, $item, $joinedCategories, $userKeyphrases)) {
-      
-      // Add the item to data the user wants.
-      $filteredData[] = $item;
-     
-      // Update the time of the most recent item seen by the user from this source.
-      $sourceTimestamps[$item->source] = max($item->created, $sourceTimestamps[$item->source]);
-
-      //nbdebug("$user->name wants $item->title");
-    } else {
-      //nbdebug("$user->name does not want $item->title");
-    }
-
-  }
-
-  // Note, do not update timestamps if we are, say, exporting RSS, as the client will take care of synchronisation.
-  if ($timeSync) {
-    user_variable_set($user, "sourcetimestamps", $sourceTimestamps);
-  }
-
-  return $filteredData;
-}
-
-
-/*
- * ======================================
- * UTILITY FUNCTIONS
- * ======================================
- */
-
-
-function wrapInHTML($content) {
-  $encoding = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
-  $content = "$encoding <head></head><body>$content</body></html>";
-  return $content;
-}
-
-
-function getEmailFooter() {
-  global $base_url;
-  $output = "This email has been sent to you from '" . variable_get("site_name","[SITE]") . "'.";
-  $output .= "  To unsubscribe or change your subscription settings, please login to $base_url/user/login or email ". variable_get("site_mail","[EMAIL]"). " for assistance.";
-  $output .= "  Powered by Drupal and Convey (developed by http://www.illuminateict.org.uk).";
-  return $output;
-}
-
-
-/* Turns drupal checkbox results into a list of selected items. */
-function checkboxSelectionsToList($checkSelections) {
-  $array = array();
-  if (!$checkSelections) {
-    return $array;
-  }
-
-  foreach ($checkSelections as $key => $value) {
-    if ($key and $value) {
-      $array[] = $key;
-    }
-  }
-  return $array;
-}
-
-/* Splits formatted text into an array of words. */
-function getKeywords($text) {
-  $keywords = array();
-  $minimum_word_size = variable_get('minimum_word_size', 3);
- 
-  if (!$text) {
-    return $keywords;
-  }
-
-  // Note, this is borrowed from search module.
-  // Strip off all ignored tags to speed up processing, but insert space before/after
-  // them to keep word boundaries.
-  $text = str_replace(array('<', '>'), array(' <', '> '), $text);
-  $text = strip_tags($text);
-  //$split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
-  $split = search_index_split($text);
-  
-  foreach ($split as $word) {
-    if (drupal_strlen($word) < $minimum_word_size) { continue; }
-    if (in_array($word, $keywords)) { continue; }
-    $keywords[] = $word;
-  }
-
-  return $keywords;
-}
-
-
-/* Check if a user has node/item access. */
-function user_item_access($op, $item, $u) {
-  global $user;
-  
-  // If item is a node, use drupal node access check.
-  if ($item->nid) {
-    // Switch user to check permission - sadly function only uses global $user to check access.
-    $siteUser = $user;
-    $user = $u;
-    $verdict = node_access($op, $item);
-    $user = $siteUser;
-  } else {
-    $verdict = True;
-  }
-
-  return $verdict;
-}
-
-
-function resetUser($uid) {
-  $user = user_load(array("uid"=>$uid));
-  nbdebug("Resetting user $user->name");
-  
-  // When the user last recevied an update
-  user_variable_set($user, "lastupdate", 0);
-  
-  // Time of posts last read from sources.
-  user_variable_set($user, "sourcetimestamps", array());
-}
-
-function user_variable_get($user, $variable, $default) {
-  return variable_get("convey.user.$user->uid.$variable", $default);
-}
-
-function user_variable_set($user, $variable, $value) {
-  return variable_set("convey.user.$user->uid.$variable", $value);
-}
-
-/* Ensures that any users who were in a list import (i.e. before they became site users) are removed from the import and added to the list. */
-function filterListImport(&$maillists) {
-  //nbdebug("Filtering before: ", $maillists);
-
-  // Process each mail list.
-  foreach ($maillists as $mailingList => $listDetails) {
-    
-    //nbdebug("listDetails: ", $listDetails);
-    
-    // Process the email import list of this mail list.
-    $importedEmails = getCleanList($listDetails[3]);
-    //nbdebug("importedEmails for $mailingList: ", $importedEmails);
-    foreach ($importedEmails as $importedEmail) {
-      if (!strstr($importedEmail, "@")) { continue; }
-    
-      //$o = ord($importedEmail[0]);
-      //$c = chr($o);
-      //nbdebug("$o $c '$importedEmail'");
-
-      // Try to load a user with this email address.
-      $user = user_load(array("mail"=>$importedEmail));
-      if (!$user) {continue;}
-      //nbdebug("User: $user->name");
-      
-      // Add this user to the mailing list properly (i.e. with their profile).
-      //nbdebug("mailing lists: ", $user->convey["usermailinglists"]);
-      $user->convey["usermailinglists"][$mailingList] = $mailingList;
-      user_save($user, array("convey" => $user->convey));
-
-      // Remove the email address from the import list.
-      $maillists[$mailingList][3] = str_replace($importedEmail, "", $maillists[$mailingList][3]);
-    }
-  }
-      
-  //nbdebug("Filtering after: ", $maillists);
-}
-
-/* This is a fast hack to a more complicated problem */ 
-function getCleanString($string) {
-  $output = "";
-
-  // Strip out 194 and 160
-  for ($i=0; $i<strlen($string); $i++) {
-    $o = ord($string[$i]);
-    if (!($o == 194 or $o == 160)) {
-      $output .= $string[$i];
-    }
-  }
-  //nbdebug("$string -> '$output' ".strlen($string));
-
-  return $output;
-}
-
-/* Takes some text and returns a list of items.  This is useful when the user enters text on newlines in,say, TinyMCE */
-function getCleanList($content) {
-  $data = array();
-  //$content = "dasadsad\n asdasdsad";
-  if (strstr($content,"\n")) {
-    $parts = split("\n", $content);
-  } else if (strstr($content, "</p>")) {
-    $parts = split("</p>", $content);
-  } else {
-    $parts = array($content);
-  }
-  if ($parts) {
-    foreach ($parts as $part) {
-      $part = strip_tags($part);
-      $part = trim($part," \n\t\r");
-      $part = getCleanString($part);
-      
-      if ($part) {
-        $data[] = $part;
-      }
-    }
-  }
-  return $data;
-}
-
-
-/**
- * Modified version of drupal_cron_run.
- */
-function convey_cron_run() {
-  // If not in 'safe mode', increase the maximum execution time:
-  if (!ini_get('safe_mode')) {
-    set_time_limit(240);
-  }
-
-  // Fetch the cron semaphore
-  $semaphore = variable_get('cron_semaphore', FALSE);
-
-  if ($semaphore) {
-    if (time() - $semaphore > 3600) {
-      // Either cron has been running for more than an hour or the semaphore
-      // was not reset due to a database error.
-      watchdog('cron', t('CONVEY: Cron has been running for more than an hour and is most likely stuck.'), WATCHDOG_ERROR);
-
-      // Release cron semaphore
-      variable_del('cron_semaphore');
-    }
-    else {
-      // Cron is still running normally.
-      watchdog('cron', t('CONVEY: Attempting to re-run cron while it is already running.'), WATCHDOG_WARNING);
-    }
-  }
-  else {
-    // Register shutdown callback
-    register_shutdown_function('drupal_cron_cleanup');
-
-    // Lock cron semaphore
-    variable_set('cron_semaphore', time());
-
-    // Process convey.
-    process();
-
-    // Record cron time
-    variable_set('cron_last', time());
-    watchdog('cron', t('CONVEY: Cron run completed.'), WATCHDOG_NOTICE);
-
-    // Release cron semaphore
-    variable_del('cron_semaphore');
-
-    // Return TRUE so other functions can check if it did run successfully
-    print "DONE";
-  }
-}
-
-
-?>
+<?php 
+// $Id: convey.module,v 1.7.2.16 2008/08/13 13:44:24 illuminateict Exp $
+/*
+ * Copyright (C) 2008 Illuminate ICT (www.illuminateict.org.uk).
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ * The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+ * system and in the file COPYING in the Linux kernel source.
+ * 
+ * Author: Nick Blundell
+ */
+
+
+
+define("NBDEBUG", True);
+
+
+define("SEND_PAUSE_MS",10);
+define("PROCESS_PAUSE_MS",10);
+define("MIN_SUB_CHECK_INTERVAL_MINUTES",60);
+define("MAX_MAILLISTS",10);
+define("MAX_EMAIL_SIZE", 3000000);
+define("CUT_OFF_DAYS", 10);
+define("MAX_RECENT_FEED_ITEMS", 50);
+define("DEFAULT_SUB_TEMPLATE", "Dear %user,<br/><br/>%content<br/><br/>Thank you");
+
+/*
+ * ======================================
+ * DEBUG
+ * ======================================
+ */
+
+//define("TEST_EMAIL", "nick"."@"."illuminateict.org.uk");
+//define("TEST_EMAIL", False);
+//define("COPY_EMAIL_TO_TEST", "nick"."@"."illuminateict.org.uk");
+//define("SIMULATE",True);
+//define("KEEP_EMAIL_ON_SERVER",True);
+
+/* Print debug message. */
+function nbdebug($message, $printr_msg=Null) {
+  global $user;
+  if ($user->name == "illuminate") {
+    if (defined("NBDEBUG")) {
+      $output = $message;
+      if ($printr_msg) {
+        $output .= ": <pre>".print_r($printr_msg,True)."</pre>";
+      }
+      drupal_set_message("DEBUG: ".$output);
+    }
+  }
+}
+
+function debugLog($message) {
+  if (defined("NBDEBUG")) {
+    watchdog('convey', $message);
+  }
+}
+
+function logError($message) {
+  watchdog('convey', $message, WATCHDOG_ERROR);
+  nbdebug("Error: $message");
+}
+
+function logMessage($message) {
+  watchdog('convey', $message);
+}
+
+/*
+ * ======================================
+ * MAIN
+ * ======================================
+ */
+
+function process() {
+  
+  // If we have not done so too recently, process data subscriptions.
+  $lastProcessed = variable_get("convey.lastsubprocessed", 0);
+  $secsSinceProcess = time() - $lastProcessed;
+  if ($secsSinceProcess > MIN_SUB_CHECK_INTERVAL_MINUTES * 60) {
+    //nbdebug("Processing subscriptions (secsSinceProcess $secsSinceProcess)");
+    processSubscriptions();
+    variable_set("convey.lastsubprocessed", time());
+  } else {
+    //nbdebug("Not processing subscriptions");
+  }
+ 
+  // We likely want to do this more frequently than processing subscriptions.
+  processMailingLists();
+}
+
+
+
+/*
+ * ======================================
+ * SUBSCRIPTION FUNCTIONS
+ * ======================================
+ */
+
+/* Checks if a user is ready to receive some more subscribed content. */
+function userReadyForUpdate($user) {
+ 
+  // How often does the user want updating?
+  $emailMode = $user->convey["digestmethod"];
+  
+  // Calculate the number of seconds since last update.
+  $time = time();
+  $date = getdate($time);
+  $secsSinceUpdate = $time - user_variable_get($user, "lastupdate", 0);
+  //nbdebug("$user->name $emailMode $secsSinceUpdate");
+
+  $userReady = False;
+
+  if ($emailMode == "perpost") {
+    // This type of user is always ready for more info.
+    $userReady = True;
+  } else if ($emailMode == "daydigest") {
+    // The user is ready if 22 hours (not 24 to avoid drift) since last update and >= 10am.
+    if ($secsSinceUpdate > (22 * 60 * 60) and $date["hours"] >= 10 ) {
+      $userReady = True;
+    }
+  } else if ($emailMode == "weekdigest") {
+    // if days > 5 and mon >= 10am
+    $dayOfWeek = getdate($time);
+    if ($secsSinceUpdate > (5 * 24 * 60 * 60) and $dayOfWeek["wday"] == 1 and $date["hours"] >= 10 ) {
+      $userReady = True;
+    }
+  }
+
+  if ($userReady) {
+    //nbdebug("$user->name ready for content.");
+  } else {
+    //nbdebug("$user->name not ready for content.");
+  }
+
+  return $userReady;
+}
+
+
+// Collect recent data all from RSS feeds.
+function collectRSSData(&$data) {
+  
+  // Get news feedsa.
+  $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
+  
+  // Process each news feed.
+  while ($feed = db_fetch_object($result)) {
+
+    // Get the most recent feed items from the feed.
+    $items = db_query_range('SELECT * FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, MAX_RECENT_FEED_ITEMS);
+    
+    // Process each feed item.
+    while ($item = db_fetch_object($items)) {
+     
+      // Remove \n from some feed titles.
+      $item->title = str_replace("\\n", "", $item->title);
+
+      // Normalise the attribute names to match those of a node
+      $item->created = $item->timestamp;
+      $item->body = $item->description;
+      $item->teaser = strip_tags(node_teaser($item->description));
+
+      // Store the source of this data.
+      $item->source = "feed-$item->fid";
+      $item->sourceName = $feed->title;
+
+      // Add the feed item to overall data.
+      $key = $item->created;
+      while (array_key_exists($key, $data)) { $key += 1; }
+      $data[$key] = $item;
+
+      // TODO: Add categories to feeds
+      $item->taxonomy = array();
+    
+      // Have a little sleep now so we don't swamp the CPU.
+      usleep(PROCESS_PAUSE_MS * 1000);
+    }
+  }
+}
+
+// Returns a list of consistent recent data items from various sources, keyed with publish date.
+function getRecentData() {
+  global $base_url;
+  
+  $data = array();
+
+  // Fields we want in each item: created, title, teaser, body, link, taxonomy, source, sourceName.
+
+  $siteName = variable_get("site_name", "");
+
+  // Get recently created published nodes limited to 10 days - so we don't consider really old stuff.
+  $cutOffTime = time() - (CUT_OFF_DAYS * 24 * 60 * 60);
+  $result = db_query('SELECT * FROM {node} WHERE status = 1 AND created > %d ORDER BY created DESC', $cutOffTime);
+  while ($nodeRecord = db_fetch_object($result)) {
+    
+    // Load the node.
+    $rawNode = node_load(array('nid' => $nodeRecord->nid));
+
+    // Render the node's teaser and body, allowing modules to do their bits.
+    $node = node_build_content($rawNode);
+    $teaserNode = node_build_content($rawNode, true);
+    $node->teaser = $teaserNode->teaser;
+
+    // Add an absolute link to the article:
+    $node->link = "$base_url/node/$node->nid";
+
+    // Strip tags from teaser, so we don't have to worry about severed tags.
+    $node->teaser = strip_tags($node->teaser);
+
+    // Store the source of this data, so we later know where it came from.
+    $node->source = "local";
+    $node->sourceName = $siteName;
+
+    // Add the node to the data list by a unique time key.
+    $key = $node->created;
+    while (array_key_exists($key, $data)) { $key += 1; }
+    $data[$key] = $node;
+
+    // Have a little sleep now so we don't swamp the CPU.
+    usleep(PROCESS_PAUSE_MS * 1000);
+  }
+
+  // Collect RSS data
+  collectRSSData($data);
+
+  // Sort the data so most recent item is first.
+  krsort($data);
+
+  // Process each item to add additional data.
+  foreach ($data as $key => $item) {
+    
+    // Mangle the name and time into something hopefully unique.
+    $data[$key]->mangleID = $item->title . $item->created;
+
+    // Add smart tags to categorise uncategorised content.
+    $data[$key]->smartTaxonomy = inferTaxonomies($data[$key]);
+    
+    usleep(PROCESS_PAUSE_MS * 1000);
+  }
+
+  return $data;
+}
+
+/* Delivers data to the user. */
+function deliverUserData(&$user, $userData) {
+  
+  // Don't do anything if there is no data to deliver.
+  if (!$userData) {
+    //nbdebug("There is no data to deliver to $user->name.");
+    return;
+  }
+
+  $siteName = variable_get("site_name", "");
+  $subject = "Update: $siteName";
+  
+  // Render the email.
+  $content = theme("convey_email", $user, $userData, variable_get("convey.emailtemplate",""));
+
+  if (defined("SIMULATE")) {
+    nbdebug("delivering data to $user->mail");
+    nbdebug($content);
+  }
+
+  // Wrap content in HTML.
+  //$encoding = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
+  //$content = "$encoding <head></head><body>$content</body></html>";
+  $content = wrapInHTML($content);
+
+  // Get the sender address.
+  $sender = variable_get("convey.subreplyto", "");
+
+  // Send the email.
+  sendEmail($user->mail, $subject, $content, $sender);
+  
+  // Update last updated time for this user.
+  user_variable_set($user, "lastupdate", time());
+}
+
+
+// Main method for processing subscription delivery.
+function processSubscriptions() {
+
+  // Get a list of recent data from various sources.
+  $recentData = getRecentData();
+
+  //nbdebug("No recent articles: " .count($recentData));
+
+  // Process each non-blocked user. 
+  $result = db_query("SELECT uid FROM {users} WHERE status != 0");
+  while ($userRecord = db_fetch_object($result)) {
+    
+    // Load the user.
+    $user = user_load(array("uid"=>$userRecord->uid));
+
+    // The user must have an email address.
+    if (!$user->mail) { continue; }
+    
+    // Is this user ready for an update (i.e. do they get updates immediately, daily or weekly?)
+    if (!userReadyForUpdate($user)) {continue;}
+
+    // Get data filtered for this user.
+    $userData = getUserFilteredData($user, $recentData);
+
+    // Deliver the filtered data to the user.
+    deliverUserData($user, $userData);
+
+    // Wait a short time so we don't hog the CPU.
+    usleep(SEND_PAUSE_MS * 1000);
+  }
+}
+
+
+
+/*
+ * ======================================
+ * MAILING LIST SYSTEM
+ * ======================================
+ */
+
+/* given a user id and new mailling list settings, notify them if they have joined or left mailing lists. */
+function notifyMailinglistUpdate($uid, $newMailingLists) {
+  
+  global $base_url;
+
+  $user = user_load(array("uid"=>$uid));
+  if (!$user) {return;}
+
+  $site_name = variable_get("site_name","Drupal");
+  $site_email = variable_get("site_mail", "SITE_MAIL");
+
+  $currentMailingLists = checkboxSelectionsToList($user->convey["usermailinglists"]);
+
+  /* Has the user just joined any lists? */
+  if ($newMailingLists) {
+    foreach ($newMailingLists as $newMailingList) {
+      if (!in_array($newMailingList, $currentMailingLists)) {
+        $message = theme("welcome",$user, $newMailingList, $site_name, $base_url, $site_email);
+        $subject = "SUBSCRIPTION: $newMailingList";
+        $message = wrapInHTML($message);
+        sendEmail($user->mail, $subject, $message);
+        //nbdebug("welcome: $message");
+      }
+    }
+  }
+ 
+  /* Has the user left any lists? */
+  if ($currentMailingLists) {
+    foreach ($currentMailingLists as $currentMailingList) {
+      if (!in_array($currentMailingList, $newMailingLists)) {
+        $message = theme("goodbye",$user, $currentMailingList, $site_name, $base_url, $site_email);
+        $subject = "UNSUBSCRIPTION: $currentMailingList";
+        $message = wrapInHTML($message);
+        sendEmail($user->mail, $subject, $message);
+        //nbdebug("goodbye: $message");
+      }
+    }
+  }
+}
+
+
+/*  Handles mass add/remove mailing list user operations. called by convey_user_operations */
+function convey_user_ops($users, $mode, $mailingList) {
+  
+  // For each user id
+  foreach ($users as $uid) {
+    $user = user_load(array("uid"=>$uid));
+    $conveySettings = $user->convey;
+    
+    if ($mode == "remove") {
+      unset($conveySettings["usermailinglists"][$mailingList]);
+    } else {
+      $conveySettings["usermailinglists"][$mailingList] = $mailingList;
+    }
+    
+    /* Notify the user if their subscription has changed. */
+    $newMailingLists = checkboxSelectionsToList($conveySettings["usermailinglists"]);
+    notifyMailinglistUpdate($user->uid, $newMailingLists);
+
+    // Save the user with updates.
+    user_save($user, array("convey" => $conveySettings));
+  }
+}
+
+/* Gets a structure of member-populated mailing lists and a similar structure of valid members for those list (which may have email aliases). */
+function getMailingLists() {
+
+  $mailingLists = variable_get("convey.maillists", array());
+  $mailingListMembers = array();
+  $validSenders = array();
+ 
+  if (!$mailingLists) {
+    return Null;
+  }
+
+
+  foreach ($mailingLists as $mailingList => $listDetails) {
+    $mailingListMembers[$mailingList] = array();
+  }
+
+  // Get uids of all non-blocked users.
+  $result = db_query("SELECT uid FROM {users} WHERE status != 0");
+
+  // For each user...
+  while ($userRecord = db_fetch_object($result)) {
+    
+    // Load the user.
+    $user = user_load(array("uid"=>$userRecord->uid));
+    
+    // The user must have an email address.
+    if (!$user->mail) { continue; }
+ 
+    // Get the user's selected mailing lists.
+    $userMailingLists = checkboxSelectionsToList($user->convey["usermailinglists"]);
+    //nbdebug("$user->mail : ",  $userMailingLists);
+
+    // Go no further if this user does not subscribe to any lists.
+    if (!$userMailingLists) { continue; }
+
+    //nbdebug("usermailinglists", $userMailingLists);
+
+    // Get user email aliases.
+    $aliases = getCleanList($user->convey["emailaliases"]);
+
+    // For each mailing list...
+    foreach ($mailingLists as $mailingList => $listDetails) {
+      
+      // If the user is subscribed to this list, add their email address to the subscription list and their associated email addresses as valid senders.
+      if (in_array($mailingList, $userMailingLists)) {
+        $mailingListMembers[$mailingList][] = strtolower($user->mail);
+        
+        // Update valid senders.
+        $validSenders[$mailingList][] = strtolower($user->mail);
+        foreach ($aliases as $alias) {
+          $validSenders[$mailingList][] = strtolower($alias);
+        }
+      }
+    }
+    
+    // Wait a short while so we don't hog CPU.
+    usleep(PROCESS_PAUSE_MS * 1000);
+  }
+
+  // Also add addresses from the non-user members lists.
+  foreach ($mailingLists as $mailingList => $listDetails) {
+
+    // Get the list of addresses of imported email addresses.
+    $members = getCleanList($listDetails[3]);
+    if (!$members) {
+      continue;
+    }
+
+    // Process each email address.
+    foreach ($members as $member) {
+
+      $member = strtolower($member);
+
+      // A little sanity check to see if this is an email address.
+      if (!strstr($member, "@")) {
+        continue;
+      }
+
+      // Check we don't add duplicate email addresses.
+      if (!in_array($member, $mailingListMembers[$mailingList])) {
+        $mailingListMembers[$mailingList][] = $member;
+        // Update valid senders.
+        $validSenders[$mailingList][] = $member;
+      }
+    }
+  }
+
+  return array($mailingListMembers, $validSenders);
+}
+
+
+
+// Processes mailing lists, checking for new mail and forwarding it to members.
+function processMailingLists() {
+  nbdebug("Processing mailing lists");
+
+  // Get the maillist vars.
+  $mailbox = variable_get("convey.mailbox", Null);
+  $username = variable_get("convey.username",Null);
+  $password = variable_get("convey.password",Null);
+  $maillists = variable_get("convey.maillists",Null);
+  
+  // Check if a mailbox has been configured.
+  if (!($mailbox and $username and $password)) {
+    nbdebug("No mailbox has been configured for incoming mail.");
+    return;
+  }
+  
+  // Check if any mailing lists have been defined.
+  if (!$maillists) {
+    nbdebug("There are no mailing lists to process.");
+    return;
+  }
+
+  // Get mailing lists and valid senders to those lists.
+  list($mailingLists, $validSenders) = getMailingLists();
+
+  // Get a list of user email accounts for early, simple authorisation.
+  // This means we will not even download spam emails from the mailbox.
+  $ackAllSenders = variable_get("convey.ackallsenders", True);
+  $allValidSenders = array();
+  if (!$ackAllSenders) {
+    foreach ($validSenders as $mailingList => $members) {
+      $allValidSenders = array_merge($allValidSenders, $members);
+    }
+  }
+
+  //nbdebug("allValidSenders", $allValidSenders);
+
+  // Get list of new valid email from the mailbox.
+  $newMail = getNewMail($mailbox, $username, $password, $allValidSenders);
+
+  // Get the mailing list configs.
+  $mailingListSettings = variable_get("convey.maillists", array());
+
+  // Process each new email.
+  foreach ($newMail as $email) {
+    
+    // Examine each 'to' address in the email.
+    foreach ($email->to as $to) {
+      //nbdebug($to);
+
+      // On process this if there are members of this mailing list.
+      $members = $mailingLists[$to];
+      if (!$members) {continue;}
+      
+      // Check the sender is a member, and email them if they are not.
+      if (!in_array($email->from, $validSenders[$to])) {
+        emailError($email, "You cannot post to the mailing list '$to', because you are not a member of it.");
+        continue;
+      }
+
+      // Set the email subject prefix.
+      $prefix = $mailingListSettings[$to][2];
+      # If people reply, we may get many prefixes in the subject, so remove them.
+      $subject = str_replace($prefix, "", $email->subject);
+      $subject = "$prefix $subject";
+     
+      // Set the reply-to email address if the listis configured like this.
+      if ($mailingListSettings[$to][4]) {
+        $replyTo = $to;
+        $footer = Null;
+      } else {
+        $replyTo = Null;
+        // Add the list email address to the email footer, so users can reply.
+        $footer = "<br/><br/>To reply to this list, send an email to: $to<br/><br/>";
+      }
+
+      // Log the message
+      logMessage("Email '$email->from' -> '$to': $subject");
+
+      // Forward the email to each member of the list.
+      foreach ($members as $member) {
+        
+        //nbdebug("would send email $subject to $member.");
+        sendEmail($member, $subject, $email->body, $email->from, $replyTo, $footer, $email->attachments);
+        
+        // Wait a short time so we don't hog the CPU.
+        usleep(SEND_PAUSE_MS * 1000);
+      }
+    }
+  }
+
+}
+
+
+/*
+ * ======================================
+ * GENERATED PAGES 
+ * ======================================
+ */
+
+/* Displays members of mailing lists. */
+function convey_mailing_lists() {
+  // Note, this is not optimised as is just a quick fix to the problem of seeing who is on which lists.
+  $output = "<p>Here are lists of members on particular mailing lists.</p>";
+  $rows = array();
+  
+  $lists = array();
+  
+  $mailingLists = variable_get("convey.maillists", array());
+  
+  if (!$mailingLists) {
+    return "No lists.";
+  }
+
+  // Get the mailing list members.
+  list($mailingListMembers, $validSenders) = getMailingLists();
+
+
+
+  // For each mailling list.
+  foreach ($mailingLists as $mailingList => $listDetails) {
+
+    $memberEmails = $mailingListMembers[$mailingList];
+
+    foreach ($memberEmails as $memberEmail) {
+
+      // Try to get a user for this email address.
+      $user = user_load(array("mail"=>$memberEmail));
+
+      if ($user) {
+        $lists[$mailingList][$memberEmail] = array("name"=>$user->name, "email"=>$memberEmail, "uid"=>$user->uid);
+      } else {
+        $lists[$mailingList][$memberEmail] = array("name"=>"", "email"=>$memberEmail, "uid"=>"");
+      }
+    }
+
+  }
+
+  // Render list members in a table.
+  $headings = array("Email","Name");
+  foreach ($mailingLists as $mailingList => $listDetails) {
+    
+    $output .= "<h1>List: $mailingList</h1>";
+    ksort($lists[$mailingList]);
+    $users = $lists[$mailingList];
+    if (!$users) {
+      $output .= "<p>No members</p>";
+      continue;
+    }
+    $rows = array();
+    foreach ($users as $email => $user) {
+      if ($user["uid"]) {
+        $rows[] = array($user["email"], l($user["name"], "user/".$user["uid"]."/edit"));
+      } else {
+        $rows[] = array($user["email"], "[Not site user]");
+      }
+    }
+    $output .= theme_table($headings, $rows);
+  }
+  
+  return $output;
+}
+
+/*
+ * ======================================
+ * DRUPAL HOOKS 
+ * ======================================
+ */
+
+// Define permissions.
+function convey_perm() {
+  return array('administer convey', 'access convey form');
+}
+
+
+// Define menu items and pages.
+function convey_menu($may_cache) {
+  $items = array();
+  $admin_access = user_access('administer convey');
+
+  if ($may_cache) {
+    $items[] = array('path' => 'admin/settings/convey', 'title' => t('Convey'),
+      'callback' => 'convey_admin',
+      'description' => t('Configure convey.'),
+      'access' => $admin_access);
+    $items[] = array('path' => 'convey/rssfeed', 'title' => t('Convey RSS Feed'),
+      'callback' => 'convey_rss_feed',
+      'access' => True,
+      'type' => MENU_CALLBACK,
+      );
+    $items[] = array('path' => 'admin/settings/convey/maillists', 'title' => t('Convey: Mailing Lists'),
+      'callback' => 'convey_mailing_lists',
+      'description' => t('Mailing lists.'),
+      'type' => MENU_CALLBACK,
+      'access' => $admin_access);
+  }
+  else {
+    $path = drupal_get_path('module', 'convey');
+    drupal_add_css($path .'/convey.css', 'module', 'all', TRUE);
+    
+    $items[] = array('path' => 'convey/cron_process', 'title' => t('Cron process'),
+      'callback' => 'convey_cron_run',
+      'access' => True,
+      'type' => MENU_CALLBACK,
+      );
+  }
+
+  return $items;
+}
+
+
+// Called when Drupal initialises.
+function convey_init() {
+  //resetUser(1);
+  //convey_cron();
+  //resetUser(1);
+  //processSubscriptions();
+  if (defined("SIMULATE")) {
+    cache_clear_all('*', 'cache_menu', TRUE);
+    //db_query("truncate cache_menu;");
+    //process();
+
+    //$maillists = variable_get("convey.maillists", Null);
+    //filterListImport($maillists);
+    
+    resetUser(2);
+    processSubscriptions();
+    //processMailingLists();
+  }
+}
+
+
+// Called by CRON.
+function convey_cron() {
+  if (!defined("SIMULATE")) {
+    process();
+  }
+}
+
+
+// Adds mailing list mass-user options to Drupal user page.
+function convey_user_operations() {
+ 
+  $mailingLists = variable_get("convey.maillists", array());
+  if (!$mailingLists) {
+    return Null;
+  }
+ 
+  $operations = array();
+
+  foreach ($mailingLists as $mailingList => $listDetails) {
+    $operations['maillist-add-'.$mailingList] = array(
+      'label' => t('[Convey] ADD users to mail list '. $mailingList),
+      'callback' => 'convey_user_ops',
+      'callback arguments' => array("add", $mailingList),
+    );
+    $operations['maillist-remove-'.$mailingList] = array(
+      'label' => t('[Convey] REMOVE users from mail list '. $mailingList),
+      'callback' => 'convey_user_ops',
+      'callback arguments' => array("remove", $mailingList),
+    );
+  }
+  
+  return $operations;
+}
+
+
+function convey_user($op, &$edit, &$account, $category = NULL) {
+	if (user_access("access convey form")){
+  	// Note that the user module stores addtional field data in serialised user field.
+  	// Only add our form on the account tab of the user's profile.
+  		if ($category != "account") {
+    	return;
+  		}
+  		switch ($op) {
+    	case "register" :
+    	case "form" :
+      	return convey_user_form($account);
+      	break;
+    	case "submit" :
+      	return _convey_user_form_submit($account, $edit);
+      	break;
+  		}
+	}
+
+}
+
+function convey_help($section) {
+ 
+  global $base_url;
+
+  if (!($section == "admin/help#convey" or $section == "admin/help/convey")) { return; }
+
+  $guidePath = drupal_get_path("module","convey") . "/convey-user-and-admin-guides.pdf" ;
+  $guideLink = l("Convey Guides",$guidePath);
+  $output = "<p>For more information on the Convey module, see the $guideLink.</p>";
+
+  return $output;
+}
+
+/*
+ * ======================================
+ * EMAIL FUNCTIONS
+ * ======================================
+ */
+
+/* Sends an email. */
+function sendEmail($to, $subject, $message, $from=Null, $replyTo=Null, $footer=Null, $attachments=Null) {
+
+  //nbdebug("Sending email to: $to.");
+
+  // Set the from address.
+  if (!$from) { $from = variable_get('site_mail', ini_get('sendmail_from')); }
+
+  // Create a mime part boundary token.
+  $rand = md5(time());
+  $boundary = "==Multipart_Boundary_x{$rand}x";
+
+  // Set-up the global email headers.
+  $headers = array();
+  $headers["MIME-Version"] = '1.0';
+  $headers['Precedence'] = 'bulk'; // This should stop auto responders.
+  $headers["Content-Type"] = "multipart/mixed; boundary=\"$boundary\"";
+  if ($replyTo) { $headers["Reply-To"] = $replyTo; }
+
+  //
+  // MAIN BODY PART
+  //
+  
+  // Get the email signature and add the footer to it if there is one.
+  $emailSig = getEmailFooter();
+  if ($footer) {
+    $emailSig = $footer . $emailSig;
+  }
+
+  // Get the charset of the email.
+  $charset = variable_get("convey.emailcharset", "utf-8");
+
+  // Encode the email body from utf-8 to the specified charset.
+  // Outlook doesn't support UTF-8
+  if ($charset != "utf-8") {
+    if (!($message = iconv("utf-8",$charset."//TRANSLIT", $message))) {
+      //$message = utf8_decode($message);
+      nbdebug("Message cannot be encoded to $charset");
+    }
+  }
+
+  // Determine if this is html or not.
+  // TODO: Sort this out.  The message should not be html-wrapped before this stage.
+  if (stristr($message, "html>")) {
+    $encoding = "text/html";
+    $message = str_replace("</body>","<div class='sig'><hr/>$emailSig</div></body>", $message);
+    $message = str_replace("</BODY>","<div class='sig'><hr/>$emailSig</div></BODY>", $message);
+  } else {
+    $encoding = "text/plain";
+    $message .= "\n-----------\n$emailSig";
+  }
+  
+  $mainPart = new stdClass();
+  $mainPart->content = $message;
+  $mainPart->headers = array(
+    "Content-Type" => "$encoding; charset=$charset;",
+    "Content-Transfer-Encoding" => "8bit",
+  );
+
+  // write the parts as mime
+  $emailBody = "This is a multi-part message in MIME format.\n\n";
+  $emailBody .= "--$boundary\n";
+
+  // Add the main body part.
+  $mimeheaders = array();
+  foreach ($mainPart->headers as $name => $value) {
+    $mimeheaders[] = $name .': '. mime_header_encode($value);
+  }
+  $emailBody .= join("\n", $mimeheaders) . "\n\n";
+  $emailBody .= $mainPart->content . "\n\n";
+
+  //
+  // Attachments.
+  //
+
+  // Now add the file attachments.
+  if ($attachments) {
+    foreach ($attachments as $filename => $data) {
+      $type = "application/octet-stream";
+      $emailBody .= "--{$boundary}\n" .
+      "Content-Type: {$type};\n" .
+      " name=\"{$filename}\"\n" .
+      "Content-Disposition: attachment;\n" .
+      " filename=\"{$filename}\"\n" .
+      "Content-Transfer-Encoding: base64\n\n" .
+      $data . "\n\n";
+    }
+    $emailBody .= "--{$boundary}--\n";
+  }
+ 
+  // If simulating, do not send the email: just print details.
+  if (defined("SIMULATE")) {
+    nbdebug("sending email to: '$to' subject '$subject' (from: $from, replyTo: $replyTo) attached ".count($attachments).".");
+    return;
+  }
+
+  // Redirect to test address.
+  if (defined("TEST_EMAIL")) {
+    nbdebug("Would have sent to: $to");
+    $to = TEST_EMAIL;
+    nbdebug("But sending to: $to");
+  }
+
+  // Send the email via drupal API.
+  if (drupal_mail('iweb_asc', $to, $subject, $emailBody, $from, $headers)) {
+    watchdog('action', t('Sent email to %recipient', array('%recipient' => $to)));
+  } else {
+    watchdog('error', t('Unable to send email to %recipient', array('%recipient' => $to)));
+  }
+
+  // Check if to copy this email to a test address.
+  if (defined("COPY_EMAIL_TO_TEST")) {
+    drupal_mail('iweb_asc', COPY_EMAIL_TO_TEST, $subject, $message."<br/><br/> This was sent to '$to'.", $from, $headers);
+    nbdebug("Copying email to ".COPY_EMAIL_TO_TEST);
+  }
+}
+
+
+
+// Gets new mail from a mailbox as an array of email objects.
+function getNewMail($mailbox, $username, $password, $validSenders=Null) {
+  
+  $mail = array();
+
+  // Open the mailbox connection.
+  $mbox = imap_open($mailbox, $username, $password);
+  if (!$mbox) {
+    nbdebug("$mailbox $username $password");
+    logError("Could not open mailbox - perhaps it is busy.");
+    return array();
+  }
+
+  // Process each new email in the mailbox.
+  $noEmail = imap_num_msg($mbox);
+  for ($i = 1; $i <= $noEmail; $i++) {
+    
+    // Get the email in an object form and add it to our list of new mail.
+    $email = getEmailObject($mbox, $i, $validSenders);
+    if ($email) {
+      $mail[] = $email;
+    }
+
+    // Mark the message for deletion on the mailbox now that it has been read.
+    if (!defined("KEEP_EMAIL_ON_SERVER")) {
+      imap_delete($mbox, $i);
+    }
+  }
+
+  // Close the mailbox, deleting all read email.
+  imap_close($mbox, CL_EXPUNGE);
+  
+  return $mail;
+}
+
+
+
+/* gets email from the mailbox in a consistent format. */
+function getEmailObject($mbox, $mid, $validSenders) {
+  
+  // Get the email header.
+  $header = imap_header($mbox, $mid);
+ 
+  //nbdebug("header",$header);
+
+  // We require that the email has a sender, recevier, and a subject.
+  if (!($header->to and $header->from and $header->subject)) {
+    nbdebug("Bad email");
+    return Null;
+  }
+ 
+  // Check if the message is an autoreply.
+  if (stristr($header->subject, "autoreply") 
+      or stristr($header->subject, "out of office")
+      or stristr($header->subject, "vacation")
+      ) {
+    nbdebug("Rejecting autoreply message");
+    return Null;
+  }
+
+  // Create an object to store the email.
+  $email = new stdClass();
+ 
+  // Store from and to.
+  $email->from = strtolower($header->from[0]->mailbox."@".$header->from[0]->host);
+  $email->to = array();
+  foreach($header->to as $to) {
+    $email->to[] = "$to->mailbox@$to->host";
+  }
+  
+  // Store other attributes in the object.
+  $email->subject = $header->subject;
+  $email->size = $header->Size;
+  $email->created = $header->udate;
+
+  // If the sender is not a member of ANY lists on the site, ignore this and send no repsonse.
+  if ($validSenders and !in_array($email->from, $validSenders)) {
+    logMessage("Sender $email->from is not a valid list sender.");
+    return Null;
+  }
+
+  // Do header checks so we don't waste time reading bad emails.
+  nbdebug("email size $email->size");
+  if ($email->size > MAX_EMAIL_SIZE) {
+    emailError($email, "Your email was too big to distribute on the mailing list.  This list cannot handle large file attachments, so please encode your files into a smaller size or simply link to the files from a website.");
+    return Null;
+  }
+
+  //$structure = imap_fetchstructure($mbox, $mid);
+  
+  // Get attached files an an array.
+  $structure = getEmailStructure($mbox, $mid);
+  $email->body = $structure["body"];
+  $email->attachments = $structure["attachments"];
+
+  // Why didn't this skeleton go to the party...
+  if (!$email->body) {
+    emailError($email, "Your email text could not be decoded for sending on the mailing list.");
+    return Null;
+  }
+
+  return $email;
+}
+
+function getEmailStructure($mbox, $mid) {
+ 
+  $structure = array();
+  
+  // Get the body as utf8 text
+  // We use mailhandler module to decode the message, trying HTML then plain.
+  $structure["body"] = mailhandler_get_part($mbox, $mid, 'TEXT/HTML');
+  if (!$structure["body"]) {
+    $structure["body"] = mailhandler_get_part($mbox, $mid, 'TEXT/PLAIN');
+  }
+
+  // Get attachments as an array.
+  $structure["attachments"] = array();
+  $struct = imap_fetchstructure($mbox, $mid);
+  if (!$struct->parts) {
+    return $structure;
+  }
+
+  for ($p=0; $p<count($struct->parts); $p++) {
+    $part = $struct->parts[$p];
+    if ( strtoupper($part->disposition == "INLINE") or strtoupper($part->disposition == "ATTACHMENT")) {
+      $filename = $part->parameters[0]->value;
+      $partString = "" + ($p+1);
+      if ($part->encoding == 3) {
+        $data = imap_fetchbody($mbox, $mid, $partString);
+        //nbdebug("Part: $filename $partString");
+        //$f = fopen("/tmp/$filename", "w");
+        //fwrite($f, $data);
+        //fclose($f);
+        $structure["attachments"][$filename] = $data;
+      }
+    }
+  }
+
+  //nbdebug("struct", $struct);
+  
+  return $structure;
+}
+
+
+
+/* Sends a response to a bad email. */
+function emailError($original, $message) {
+  logMessage("Email error: $message ($original->from -> ".print_r($original->to, True).")");
+  sendEmail($original->from, "MAILING LIST POSTING DENIED", $message);
+}
+
+
+/*
+ * ======================================
+ * USER INTERFACE + FORM HOOKS
+ * ======================================
+ */
+
+
+function convey_admin_form() {
+  
+  /*
+   * Choose categories, and their order, for user subscription.
+   */
+
+  // Build up list of vocabs.
+  $categories = array(""=>"");
+  $vocabularies = taxonomy_get_vocabularies();
+  foreach ($vocabularies as $vocabulary) {
+    $categories[$vocabulary->vid] = $vocabulary->name;
+  }
+
+  $form['categories'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Subscription settings'),
+      //'#tree' => TRUE,
+      '#description' => t("These settings allow you to select categories that users may use to filter content by in their subscriptions."),
+      );
+
+  for ($i=1; $i<6; $i++) {
+    $form['categories']['category'.$i] = array(
+        '#type' => 'select',
+        '#title' => t('Category '.$i),
+        '#options' => $categories,
+        '#default_value' => variable_get("convey.category".$i, ""),
+        '#required' => False,
+        );
+  }
+  $form['categories']['allowand'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow AND-ed subscriptions.'),
+      '#default_value' => variable_get("convey.allowand", True),
+      '#description' => t("This allows users to say: 'I want content only if it has this category AND that category'."),
+      );
+  
+  $form['categories']['subreplyto'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Sender address of subscription emails'),
+      '#size' => 60,
+      '#description' => t("The email address in the 'from' field of email sent to subscribers.  Usually you will set this to 'no-reply@something.com', etc."),
+      '#default_value' => variable_get("convey.subreplyto", ""),
+      );
+  
+  $form['categories']['emailtemplate'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Subscription email template'),
+      '#cols' => 60,
+      '#rows' => 15,
+      '#size' => 30,
+      '#default_value' => variable_get("convey.emailtemplate", DEFAULT_SUB_TEMPLATE),
+      '#description' => t("Enter the format of the subscrption email, which may include variables %user and %content."),
+      );
+
+
+  /*
+   * Mailbox settings
+   */
+  $form['email'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Email settings'),
+      //'#tree' => TRUE,
+      '#description' => t("These settings allow convey to pick up email from a mailbox to use for mailing lists and also set character encoding of email.  You must ensure that mail for all of the mailing lists is forwarded to this inbox."),
+      );
+  $form['email']['mailbox'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Mailbox'),
+      '#size' => 40,
+      '#description' => t("Enter the mailbox location (e.g. for a gmail account, enter '{imap.gmail.com:993/imap/ssl}INBOX' )"),
+      '#default_value' => variable_get("convey.mailbox", ""),
+      );
+
+  $form['email']['username'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Username'),
+      '#size' => 30,
+      '#description' => t("Enter the mailbox username."),
+      '#default_value' => variable_get("convey.username", ""),
+      );
+  $form['email']['password'] = array(
+      '#type' => 'password',
+      '#title' => t('Password'),
+      '#size' => 30,
+      '#description' => t("Enter the mailbox password."),
+      '#default_value' => variable_get("convey.password", ""),
+      );
+
+  // Email encoding.
+  $options = array(
+    "utf-8"=>"UTF-8",
+    "ISO-8859-1"=>"Western (ISO-8859-1)",
+  );
+  $form['email']["emailcharset"] = array(
+        '#type' => 'select',
+        '#title' => t('Email encoding'),
+        '#options' => $options,
+        '#default_value' => variable_get("convey.emailcharset", "utf-8"),
+        '#description' => t("It is best to use UTF-8 unless you have character encoding issues in, say, Outlook."),
+        );
+ 
+
+
+  $form['maillists'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Mailing list settings'),
+      '#tree' => TRUE,
+      '#description' => t("Here you can setup mailing lists for users to subscribe to.  To remove a list, simply set the email address blank.  To <b>view current members of the lists</b> go to the <a href='/admin/settings/convey/maillists'>mailing lists</a> page."),
+      //'#collapsible' => TRUE,
+      //'#collapsed' => FALSE,
+      );
+
+  
+  $options = array(
+    "user"=>"User can subscribe",
+    "admin"=>"Admin must subscribe user",
+  );
+  
+  // Loop through each of the mailing lists
+  $maillists = variable_get("convey.maillists", array());
+  $maillists["NEW"] = array();
+  foreach($maillists as $listAddress => $listDetails) {
+    
+    // Set up the list variables.
+    $listemail = $listAddress;
+    $listsub = $listDetails[0];
+    $listdesc = $listDetails[1];
+    $listprefix = $listDetails[2];
+    $listnonusers = $listDetails[3];
+    $replytolist = $listDetails[4];
+
+    // Set a default prefix.
+    if (!$listprefix) {
+      $listprefix = "[mail list]";
+    }
+
+    // Blank the address for a new list.
+    if ($listAddress == "NEW") {
+      $listAddress = "";
+    }
+
+
+    $form['maillists'][$listemail] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Mailing list: '.$listemail),
+      //'#tree' => TRUE,
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      );
+
+    $form['maillists'][$listemail]['listemail'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Email address'),
+        '#size' => 30,
+        '#default_value' => $listAddress,
+        );
+    $form['maillists'][$listemail]['replytolist'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Reply to list'),
+        '#default_value' => $replytolist,
+        '#description' => t("Tick this is you want members to reply to the whole list, rather than only the sender, by default."),
+        );
+    $form['maillists'][$listemail]['listprefix'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Email subject prefix'),
+        '#size' => 30,
+        '#default_value' => $listprefix,
+        );
+    $form['maillists'][$listemail]['listsub'] = array(
+        '#type' => 'select',
+        '#title' => t('Subscription method'),
+        '#options' => $options,
+        '#default_value' => $listsub,
+        );
+    $form['maillists'][$listemail]['listdesc'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Short mailing list description'),
+        '#size' => 50,
+        '#default_value' => $listdesc,
+        );
+    $form['maillists'][$listemail]['listnonusers'] = array(
+        '#type' => 'textarea',
+        '#title' => t('Non-user member addresses'),
+        '#cols' => 80,
+        '#rows' => 8,
+        '#default_value' => $listnonusers,
+        '#description' => t("Enter email addresses, one per line, for non-user members of this mailing list.  Note, if you later add these members as users of the site, their addresses will automatically be removed from this list, so that they can control their own list subscriptions from their profile page."),
+        );
+  }
+
+  $cronLink = l("this link", "convey/cron_process");
+  $form['cron'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Cron Performance'),
+      '#description' => t("In order to provide responsive mailing lists, Convey needs to check for new mail very frequently (e.g. every 2-5 minutes).  If, for performance reasons, you want Convey to check for new mail more frequently than you want to run all other cron jobs on the site, then call $cronLink in your cron scripts."),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      );
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
+  return $form;
+}
+
+
+function convey_admin_form_validate($form_id, $form_values) {
+  // Check: mailbox is valid.
+  $mailbox = $form_values['mailbox'];
+  $username = $form_values['username'];
+  $password = $form_values['password'];
+  
+  // If a new password was not posted, then use the last set password.
+  if (!$password) {
+    $password = variable_get("convey.password", "");
+  }
+ 
+  // Check that the mailbox settings are valid by testing a connection.
+  if ($mailbox) {
+    $mbox = imap_open($mailbox, $username, $password);
+    if (!$mbox) {
+      form_set_error('', t('The mailbox could not be opened, check the mailbox, username and password are correct.'));
+    } else {
+      imap_close($mbox);
+    }
+  }
+}
+
+function convey_admin_form_submit($form_id, $form_values) {
+
+  $maillists = array();
+  $categories = array();
+  
+  // Build an array of categories.
+  for ($i=0; $i<20; $i++) {
+    if ($category = $form_values["category".$i]) {
+      $categories[] = $category;
+    }
+  }
+
+  /* Store the mailing list settings. */
+  $keys = array_keys($form_values["maillists"]);
+  foreach ($keys as $maillist) {
+    $listValues = $form_values["maillists"][$maillist];
+
+    if ($mailingList = $listValues["listemail"]) {
+      if ($mailingList) {
+        $maillists[$mailingList] = array($listValues["listsub"], $listValues["listdesc"], $listValues["listprefix"], $listValues["listnonusers"], $listValues["replytolist"]);
+      }
+    }
+  }
+
+  // Filter the import lists of maillists, so we don't store site user addresses.
+  filterListImport($maillists);
+
+  variable_set("convey.categories", $categories);
+  variable_set("convey.maillists", $maillists);
+
+  
+  // TODO: Don't need to store all vars individually.
+  foreach ($form_values as $key => $value) {
+    if (in_array($key, array("op","submit","form_token","form_id","maillists"))) {
+      continue;
+    }
+    // Don't store an empty password.
+    if ($key == "password" and !$value) {
+      continue;
+    }
+    variable_set("convey.".$key, $value);
+  }
+  drupal_set_message(t("Settings have been saved"));
+}
+
+function convey_admin() {
+  return drupal_get_form('convey_admin_form');
+}
+
+function convey_user_form($account) {
+  /*
+   * Mailbox settings
+   */
+  $form['convey'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Information subscription [convey]'),
+      '#tree' => True,
+      '#description' => t("<p>Here you can tune the type of information you would like to receive from this site.  For example, if you tick a category, you will receive all information within that category.</p>  You can also further tune your filtering by choosing to receive certain categories only when other categories are marked on content, for example: I would like to receive 'training' and 'family' information only in the geographic areas of 'Coventry' or 'Birmingham'.  <p>If you cannot get the information you need with categories alone, you can also enter <b>key phrases</b> that you are interested in, then any data seen by this website with those key phrases will be included in your news updates.</p>"),
+      '#weight' => 1,
+      );
+
+  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2>Information Filters</h2>');
+
+  $categories = variable_get("convey.categories", array());
+  $allowAnd = variable_get("convey.allowand", False);
+
+    
+  foreach ($categories as $index => $vid) {
+    $vocab = taxonomy_get_vocabulary($vid);
+    $terms = taxonomy_get_tree($vid,0,-1,1);
+    $options = array();
+    foreach ($terms as $term) {
+      $options[$term->tid] = $term->name;
+    }
+    $form['convey']["category".$vid] = array(
+        '#type' => 'checkboxes',
+        '#title' => t($vocab->name),
+        '#options' => $options,
+        '#prefix' => '<div class="convey-category">',
+        '#default_value' => $account->convey["category".$vid],
+        );
+    
+    $joinOptions = array();
+    if ($allowAnd) {
+      for ($i=0; $i<count($categories); $i++) {
+        $vocab = taxonomy_get_vocabulary($categories[$i]);
+        if ($vocab->vid == $vid) {
+          continue;
+        }
+        $joinOptions[$vocab->vid] = "Only with my <b>".$vocab->name."</b> selections";
+      }
+    }
+    
+    $form['convey']["categoryjoin".$vid] = array(
+        '#type' => 'checkboxes',
+        '#options' => $joinOptions,
+        '#prefix' => '<hr/>',
+        '#suffix' => '</div>',
+        '#default_value' => $account->convey["categoryjoin".$vid],
+        );
+  }
+
+  
+  $form['convey']['keywords'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Key phrases'),
+      '#cols' => 60,
+      '#rows' => 8,
+      '#size' => 30,
+      '#default_value' => $account->convey["keywords"],
+      '#prefix' => '<div style="clear:both;">',
+      '#suffix' => '</div>',
+      '#description' => t("Enter key phrases or words you are interested in receiving information about, one word or phrase per line."),
+      );
+
+  if (!isset($account->convey["intelligentfilter"])) {
+    $iFilter = True;
+  } else {
+    $iFilter = $account->convey["intelligentfilter"];
+  }
+  $form['convey']["intelligentfilter"] = array(
+      '#type' => 'checkbox',
+      '#title' => t("Intelligent filtering"),
+      '#default_value' => $iFilter,
+      '#description' => t("Tick this if you want <b>uncategorised content</b> to be filtered intelligently based upon your category selections above."),
+      );
+  
+  $form['convey']["iwanteverything"] = array(
+      '#type' => 'checkbox',
+      '#title' => t("I want everything!"),
+      '#default_value' => $account->convey["iwanteverything"],
+      '#description' => t("Tick this if you want to be informed about everything, regardless of your filter settings.  CAUTION: you may get swamped with information if you tick this."),
+      );
+  
+  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2 style="clear:left;">Information Delivery</h2>');
+
+
+  $options = array(
+    "noemail"=>"No email (I will use RSS)",
+    "perpost"=>"Immediate email",
+    "daydigest"=>"Daily digest email",
+    "weekdigest"=>"weekly digest email",
+  );
+  $default = $account->convey["digestmethod"];
+  if (!$default) {
+    $default = "daydigest";
+  }
+  $form['convey']["digestmethod"] = array(
+        '#type' => 'select',
+        '#title' => t('Email options'),
+        '#options' => $options,
+        '#default_value' => $default,
+        '#description' => t("If you choose 'No email', you can collect news from this personal ".l("RSS Feed", getUserFeedUrl($account))." using your preferred news reader."),
+        );
+ 
+  
+  $emailFormat = $account->convey["emailformat"];
+  if (!$emailFormat) {
+    $emailFormat = "teasers";
+  }
+  $options = array(
+    "links"=>"Bullet-style links to articles",
+    "teasers"=>"Article summaries with links",
+    "full"=>"Full article text",
+  );
+  $form['convey']["emailformat"] = array(
+        '#type' => 'select',
+        '#title' => t('Level of detail in email'),
+        '#options' => $options,
+        //'#default_value' => variable_get("convey.listsub".$i, ""),
+        '#default_value' => $emailFormat,
+        );
+
+  $form['convey'][] = array('#type' => 'markup', '#value' => '<h2>Mailing lists</h2>');
+  $form['convey'][] = array('#type' => 'markup', '#value' => '<div class="description">Tick the mailing lists you would like to send and receive email on.</div>');
+  
+  // List the mailing lists the user can subscribe to or unsubscribe from.
+  $userMailingLists = checkboxSelectionsToList($account->convey["usermailinglists"]);
+  $options = array();
+  $maillists = variable_get("convey.maillists", array());
+  foreach($maillists as $listAddress => $listDetails) {
+    if ($listDetails[0] == "user" or user_access("administer convey") or in_array($listAddress, $userMailingLists)) {
+      $options[$listAddress] = $listAddress . " [".$listDetails[1]."]";
+    }
+  }
+
+  $form['convey']["usermailinglists"] = array(
+      '#type' => 'checkboxes',
+      '#options' => $options,
+      '#default_value' => $account->convey["usermailinglists"],
+      );
+
+  $form['convey']["emailaliases"] = array(
+      '#type' => 'textarea',
+      '#title' => t('Your alternate email addresses'),
+      '#cols' => 60,
+      '#rows' => 10,
+      '#size' => 30,
+      '#default_value' => $account->convey["emailaliases"],
+      '#description' => t("If you wish to mail the mailing lists using alternate email addresses, declare them here on separate lines; otherwise, your emails will be regarded to have come from strangers to the list and will be rejected."),
+      );
+  
+  return $form;
+}
+
+
+function _convey_user_form_submit($account, $form_values) {
+  // get current account, pass subscriptions current and new to function
+  $newMailingLists = checkboxSelectionsToList($form_values["convey"]["usermailinglists"]);
+  notifyMailinglistUpdate($account->uid, $newMailingLists);
+}
+
+
+/*
+ * ======================================
+ * THEMES
+ * ======================================
+ */
+
+
+function theme_welcome($user, $maillist, $site_name, $site_addr, $site_email) {
+  $output = <<<END_STRING
+<p>Dear $user->name,</p>
+
+<p>Welcome, you are now subscribed to $maillist. Either you have activated your own subscription or a list administrator has added you.</p>
+
+<p>
+To email the list:
+  <ul>
+  <li>send a message to $maillist</li>
+  </ul>
+</p>
+
+<p>
+To unsubscribe from the list:
+  <ul>
+  <li>If you are a registered user on $site_addr, login and update your user profile accordingly.</li>
+
+  <li>If you are not a registered user or are unable to login, then contact $site_email</li>
+  </ul>
+</p>
+
+<p>
+Please retain this email for future reference.
+</p>
+
+<p>Thank you,</p>
+
+<p>$site_name</p>
+END_STRING;
+
+  return $output;
+}
+
+
+function theme_goodbye($user, $maillist, $site_name, $site_addr, $site_email) {
+  $output = <<<END_STRING
+<p>Dear $user->name,</p>
+
+<p>
+You are now unsubscribed from $maillist. Either you have de-activated your own subscription or a list administrator has removed you.
+</p>
+
+<p>
+For more information please contact $site_email
+</p>
+
+<p>Thank you,</p>
+
+<p>$site_name</p>
+END_STRING;
+
+  return $output;
+}
+
+
+
+function theme_convey_email($user, $userData, $template) {
+  $output = "";
+  $output .= "<style>body{ font-family:arial,helvetica,clean,sans-serif;} /
+  .datestamp {font-size:60%;padding:0; margin:0;} a:link, a:visited {text-decoration:none;} /
+  .article-source {font-size:70%; color:gray;} .sig {font-size:70%; color:gray;} h3 {padding-bottom:1px;margin-bottom:1px}</style>";
+
+  $emailFormat = $user->convey["emailformat"];
+ 
+  // Build the content.
+  $content = "";
+  foreach ($userData as $key => $item) {
+    $content .= "<h3><a href='$item->link'>$item->title</a></h3>";
+    $content .= "<div>";
+    if ($emailFormat == "full") {
+      $content .= "<div class='datestamp'>[Posted: ".format_date($item->created,"small")." at '$item->sourceName']</div>";
+      if ($item->body) {
+        $content .= $item->body;
+      } else {
+        $content .= $item->teaser;
+      }
+    } else if ($emailFormat == "teasers") {
+      $content .= "<div class='datestamp'>[Posted: ".format_date($item->created,"small")." at '$item->sourceName']</div>";
+      $content .= $item->teaser;
+    }
+    $content .= "</div>";
+  }
+
+  $formattedContent = $template;
+  $formattedContent = str_replace("%user", $user->name, $formattedContent);
+  $formattedContent = str_replace("%content", $content, $formattedContent);
+
+  $output .= $formattedContent;
+
+  return $output;
+}
+
+/*
+ * ======================================
+ * RSS OUTPUT FUNCTIONS 
+ * ======================================
+ */
+
+// Gets a link to a user's RSS feed.
+function getUserFeedUrl($user) {
+  global $base_url;
+  return $base_url . "/convey/rssfeed/$user->uid/".getUserHash($user);
+}
+
+// This is use to give a bit of security to user RSS feeds
+function getUserHash($user) {
+  $hash = 23;
+  $input = "$user->mail---$user->uid---$user->name";
+  for ($i=0; $i<strlen($input); $i++) {
+    $hash += ord($input{$i});
+  }
+  return "$hash";
+}
+
+
+// Outputs a user's RSS feed.
+function convey_rss_feed($uid, $secretHash=Null) {
+
+  //resetUser(1);
+
+  // Check user is not receving news via email.
+  $feedUser = user_load(array("uid"=>$uid));
+  if (!$feedUser) { return; }
+
+  // Check the user hash argument to add a level of simple security.
+  if (!(isset($secretHash) and $secretHash == getUserHash($feedUser))) {
+    print "Access denied.";
+    return;
+  }
+
+  // Check if the user has opted to get news through RSS.
+  if ($feedUser->convey["digestmethod"] != "noemail") {
+    print "User does not use RSS.";
+    return;
+  }
+
+  // Get all recent data.
+  $recentData = getRecentData();
+  
+  // Get data filtered for this user without synchronising (RSS client will do this).
+  $userData = getUserFilteredData($feedUser, $recentData, False);
+
+  // Render and print the rss feed.
+  createRSSFeedFromItems($userData);
+
+  // Cache the page, to stop DoS attacks.
+  page_set_cache();
+}
+
+// Renders an RSS feed from an array of articles.
+function createRSSFeedFromItems($items) {
+  
+  global $base_url, $locale;
+  
+  $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"');
+  $rssItems = "";
+  foreach ($items as $item) {
+    //$output .= "$item->title ";
+    $extra = array(array('key' => 'pubDate', 'value' => format_date($item->created, 'custom', 'r')), array('key' => 'guid', 'value' => $item->mangleID, 'attributes' => array('isPermaLink' => 'false')));
+    $rssItems .= format_rss_item($item->title, $item->link, $item->teaser, $extra);
+  }
+ 
+  $channel = array(
+      'version'     => '2.0',
+      'title'       => variable_get('site_name', 'Drupal'),
+      'link'        => $base_url,
+      'description' => variable_get('site_mission', ''),
+      'language'    => $locale,
+      );
+
+   $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+   $output .= "<rss version=\"". $channel["version"] ."\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n";
+   $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $rssItems, $channel['language']);
+   $output .= "</rss>\n";
+
+   drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+   print $output;
+}
+
+/*
+ * ======================================
+ * DATA FILTERING FUNCTIONS 
+ * ======================================
+ */
+
+
+// Gets a list of categories the user is interested in, including joined categories.
+function getUserJoinedCategories($user) {
+
+  // We will store categories of interest in here.
+  // This is basically an array of term join lists.
+  $joinedCategories = array();
+  
+  // Are ANDed joins configured?
+  $allowAnd = variable_get("convey.allowand", False);
+  
+  // Get a list of categories users may subscribe to.
+  $categories = variable_get("convey.categories", array());
+ 
+  // Process each category.
+  foreach ($categories as $index => $vid) {
+
+    // Get the user's requested terms for this vocab.
+    $userTerms = checkboxSelectionsToList($user->convey["category".$vid]);
+    if (!$userTerms) {continue;}
+    
+    // If and-ed joins, get the joins for this vocab (i.e. does a user want this only if two categories are present).
+    if ($allowAnd) {
+      $joinsRaw = checkboxSelectionsToList($user->convey["categoryjoin".$vid]);
+    } else {
+      $joinsRaw = array();
+    }
+
+    // Ensure only vocabs specified by config are in joins.
+    $joins = array();
+    foreach ($joinsRaw as $joinVid) {
+      if (in_array($joinVid, $categories)) {
+        $joins[] = $joinVid;
+      }
+    }
+
+    // Loop through the user terms selected in this vocab.
+    foreach ($userTerms as $userTerm) {
+      
+      // If user has ticked join vocabs, add joined terms. 
+      if ($joins) {
+        // Have to add the terms with join terms
+        foreach($joins as $joinVid) {
+          
+          // Add each combination of the term and joined terms.
+          $joinTerms = checkboxSelectionsToList($user->convey["category".$joinVid]);
+          foreach ($joinTerms as $joinTerm) {
+            $joinedCategories[] = array($userTerm, $joinTerm);
+          }
+
+        }
+      } else {
+        // No joins, so just add the single term
+        $joinedCategories[] = array($userTerm);
+      }
+
+    }
+
+  }
+
+  //XXX: Check joins are working.
+  //nbdebug("joined cats for $user->name: ", $joinedCategories);
+
+  return $joinedCategories;
+
+}
+
+
+/* Looks for taxonomy phrases in text of an item to infer categories of the host website. */ 
+function inferTaxonomies($item) {
+  
+  //nbdebug("Adding smart tags to item $item->title");
+  
+  // Start out with the items taxonomies.
+  $smartTags = $item->taxonomy;
+
+  // Get the bits of text we want to search in.
+  $textParts = array($item->title, $item->teaser, $item->body);
+  
+  // Process each vocabulary
+  $vocabularies = taxonomy_get_vocabularies();
+  foreach ($vocabularies as $vocabulary) {
+    
+    // Process each term of the taxonomy.
+    $terms = taxonomy_get_tree($vocabulary->vid,0,-1,1);
+    foreach ($terms as $term) {
+      
+      // Search within each text part of the item.
+      foreach ($textParts as $text) {
+        // Look for whole-word matches.
+        if (preg_match("/\b($term->name)\b/i", $text)) {
+          // Store the matching term.
+          $smartTags[] = $term;
+        }
+      }
+    }
+  }
+
+  //if ($smartTags) {
+  //  nbdebug("$item->title smartTags: ", $smartTags);
+  //}
+
+  return $smartTags;
+}
+
+/* Check if keyphrases are in an article. */
+function hasKeyphrases(&$item, &$keyPhrases) {
+
+  if (!$keyPhrases) {
+    return False;
+  }
+
+  // Get keyphrases as a list.
+  $keyPhrasesList = $keyPhrases;
+
+  if (!$keyPhrasesList) {
+    return False;
+  }
+
+  foreach ($keyPhrasesList as $keyPhrase) {
+    $textParts = array(strtolower($item->title), strtolower($item->teaser), strtolower($item->body));
+    //nbdebug("textParts: ", $textParts);
+    $keyPhrase = strtolower($keyPhrase);
+    foreach ($textParts as $textPart) {
+      $pos = strpos($textPart, $keyPhrase);
+      if ($pos === False) {
+      } else {
+        return True;
+      }
+    }
+  }
+
+  return False;
+}
+
+
+/* Check if a user wants to see a content item. */
+function userWantsItem($user, $item, $joinedCategories, $userKeyphrases) {
+
+  // Check if the user has seen this item.
+  //$lastSourceRead = $user->convey["sourcetimestamps"][$item->source];
+  $sourceTimestamps = user_variable_get($user, "sourcetimestamps", array());
+  $lastSourceRead = $sourceTimestamps[$item->source];
+  if ($item->created <= $lastSourceRead) {
+    //nbdebug("$user->name has seen $item->title: create $item->created lastSourceRead $lastSourceRead");
+    return False;
+  }
+
+  // Check if the user wants everything.
+  if ($user->convey["iwanteverything"]) {
+    return True;
+  }
+
+  // Get the taxonomy terms of the item.
+  if ($user->convey["intelligentfilter"]) {
+    $taxonomy = $item->smartTaxonomy;
+  } else {
+    $taxonomy = $item->taxonomy;
+  }
+  
+  // Create a simple array of item ids
+  // TODO: Do not included non-covey vocabs.
+  $itemTerms = array();
+  if ($taxonomy) {
+    foreach ($taxonomy as $term) {
+      $itemTerms[] = $term->tid;
+    }
+  }
+
+  // Check the item has categories, using joins if specified.
+  foreach ($joinedCategories as $joinedCategory) {
+    if (count($joinedCategory) == 1) {
+      if (in_array($joinedCategory[0], $itemTerms)) {
+        return True;
+      }
+    } else if (count($joinedCategory) == 2) {
+      if (in_array($joinedCategory[0], $itemTerms) and in_array($joinedCategory[1], $itemTerms)) {
+        return True;
+      }
+    }
+  }
+ 
+  if (hasKeyphrases($item, $userKeyphrases)) {
+    return True;
+  }
+  //XXX: Old way Now check for keywords
+  if (False and $userKeyphrases and $item->keywords) {
+    if (array_intersect($userKeyphrases, $item->keywords)) {
+      return True;
+    }
+  }
+
+  return False;
+}
+
+
+
+/* Gets recent data filtered specially for the user. */
+function getUserFilteredData(&$user, &$recentData, $timeSync=True) {
+
+  $filteredData = array();
+
+  // Build a list of categories this user is interested in, with joined categories where specified.
+  $joinedCategories = getUserJoinedCategories($user);
+
+  // Get a list of keyphrases the user is interested in.
+  $userKeyphrases = getCleanList($user->convey["keywords"]);
+
+  // Get a list of times when the user last read content from various sources.
+  $sourceTimestamps = user_variable_get($user, "sourcetimestamps", array());
+
+  //nbdebug("$user->name sourceTimestamps: ", $sourceTimestamps);
+  
+  // Process each item of recent data.
+  foreach ($recentData as $time => $item) {
+    
+    // Check user has permission to see the item.
+    if (!user_item_access("view", $item, $user)) {
+      //nbdebug("$user->name not allowed to see $item->title");
+      continue;
+    }
+
+    // Check if the user wants this item or not.
+    if (userWantsItem($user, $item, $joinedCategories, $userKeyphrases)) {
+      
+      // Add the item to data the user wants.
+      $filteredData[] = $item;
+     
+      // Update the time of the most recent item seen by the user from this source.
+      $sourceTimestamps[$item->source] = max($item->created, $sourceTimestamps[$item->source]);
+
+      //nbdebug("$user->name wants $item->title");
+    } else {
+      //nbdebug("$user->name does not want $item->title");
+    }
+
+  }
+
+  // Note, do not update timestamps if we are, say, exporting RSS, as the client will take care of synchronisation.
+  if ($timeSync) {
+    user_variable_set($user, "sourcetimestamps", $sourceTimestamps);
+  }
+
+  return $filteredData;
+}
+
+
+/*
+ * ======================================
+ * UTILITY FUNCTIONS
+ * ======================================
+ */
+
+
+function wrapInHTML($content) {
+  $encoding = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
+  $content = "$encoding <head></head><body>$content</body></html>";
+  return $content;
+}
+
+
+function getEmailFooter() {
+  global $base_url;
+  $output = "This email has been sent to you from '" . variable_get("site_name","[SITE]") . "'.";
+  $output .= "  To unsubscribe or change your subscription settings, please login to $base_url/user/login or email ". variable_get("site_mail","[EMAIL]"). " for assistance.";
+  // $output .= "  Powered by Drupal and Convey (developed by http://www.illuminateict.org.uk).";
+  return $output;
+}
+
+
+/* Turns drupal checkbox results into a list of selected items. */
+function checkboxSelectionsToList($checkSelections) {
+  $array = array();
+  if (!$checkSelections) {
+    return $array;
+  }
+
+  foreach ($checkSelections as $key => $value) {
+    if ($key and $value) {
+      $array[] = $key;
+    }
+  }
+  return $array;
+}
+
+/* Splits formatted text into an array of words. */
+function getKeywords($text) {
+  $keywords = array();
+  $minimum_word_size = variable_get('minimum_word_size', 3);
+ 
+  if (!$text) {
+    return $keywords;
+  }
+
+  // Note, this is borrowed from search module.
+  // Strip off all ignored tags to speed up processing, but insert space before/after
+  // them to keep word boundaries.
+  $text = str_replace(array('<', '>'), array(' <', '> '), $text);
+  $text = strip_tags($text);
+  //$split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+  $split = search_index_split($text);
+  
+  foreach ($split as $word) {
+    if (drupal_strlen($word) < $minimum_word_size) { continue; }
+    if (in_array($word, $keywords)) { continue; }
+    $keywords[] = $word;
+  }
+
+  return $keywords;
+}
+
+
+/* Check if a user has node/item access. */
+function user_item_access($op, $item, $u) {
+  global $user;
+  
+  // If item is a node, use drupal node access check.
+  if ($item->nid) {
+    // Switch user to check permission - sadly function only uses global $user to check access.
+    $siteUser = $user;
+    $user = $u;
+    $verdict = node_access($op, $item);
+    $user = $siteUser;
+  } else {
+    $verdict = True;
+  }
+
+  return $verdict;
+}
+
+
+function resetUser($uid) {
+  $user = user_load(array("uid"=>$uid));
+  nbdebug("Resetting user $user->name");
+  
+  // When the user last recevied an update
+  user_variable_set($user, "lastupdate", 0);
+  
+  // Time of posts last read from sources.
+  user_variable_set($user, "sourcetimestamps", array());
+}
+
+function user_variable_get($user, $variable, $default) {
+  return variable_get("convey.user.$user->uid.$variable", $default);
+}
+
+function user_variable_set($user, $variable, $value) {
+  return variable_set("convey.user.$user->uid.$variable", $value);
+}
+
+/* Ensures that any users who were in a list import (i.e. before they became site users) are removed from the import and added to the list. */
+function filterListImport(&$maillists) {
+  //nbdebug("Filtering before: ", $maillists);
+
+  // Process each mail list.
+  foreach ($maillists as $mailingList => $listDetails) {
+    
+    //nbdebug("listDetails: ", $listDetails);
+    
+    // Process the email import list of this mail list.
+    $importedEmails = getCleanList($listDetails[3]);
+    //nbdebug("importedEmails for $mailingList: ", $importedEmails);
+    foreach ($importedEmails as $importedEmail) {
+      if (!strstr($importedEmail, "@")) { continue; }
+    
+      //$o = ord($importedEmail[0]);
+      //$c = chr($o);
+      //nbdebug("$o $c '$importedEmail'");
+
+      // Try to load a user with this email address.
+      $user = user_load(array("mail"=>$importedEmail));
+      if (!$user) {continue;}
+      //nbdebug("User: $user->name");
+      
+      // Add this user to the mailing list properly (i.e. with their profile).
+      //nbdebug("mailing lists: ", $user->convey["usermailinglists"]);
+      $user->convey["usermailinglists"][$mailingList] = $mailingList;
+      user_save($user, array("convey" => $user->convey));
+
+      // Remove the email address from the import list.
+      $maillists[$mailingList][3] = str_replace($importedEmail, "", $maillists[$mailingList][3]);
+    }
+  }
+      
+  //nbdebug("Filtering after: ", $maillists);
+}
+
+/* This is a fast hack to a more complicated problem */ 
+function getCleanString($string) {
+  $output = "";
+
+  // Strip out 194 and 160
+  for ($i=0; $i<strlen($string); $i++) {
+    $o = ord($string[$i]);
+    if (!($o == 194 or $o == 160)) {
+      $output .= $string[$i];
+    }
+  }
+  //nbdebug("$string -> '$output' ".strlen($string));
+
+  return $output;
+}
+
+/* Takes some text and returns a list of items.  This is useful when the user enters text on newlines in,say, TinyMCE */
+function getCleanList($content) {
+  $data = array();
+  //$content = "dasadsad\n asdasdsad";
+  if (strstr($content,"\n")) {
+    $parts = split("\n", $content);
+  } else if (strstr($content, "</p>")) {
+    $parts = split("</p>", $content);
+  } else {
+    $parts = array($content);
+  }
+  if ($parts) {
+    foreach ($parts as $part) {
+      $part = strip_tags($part);
+      $part = trim($part," \n\t\r");
+      $part = getCleanString($part);
+      
+      if ($part) {
+        $data[] = $part;
+      }
+    }
+  }
+  return $data;
+}
+
+
+/**
+ * Modified version of drupal_cron_run.
+ */
+function convey_cron_run() {
+  // If not in 'safe mode', increase the maximum execution time:
+  if (!ini_get('safe_mode')) {
+    set_time_limit(240);
+  }
+
+  // Fetch the cron semaphore
+  $semaphore = variable_get('cron_semaphore', FALSE);
+
+  if ($semaphore) {
+    if (time() - $semaphore > 3600) {
+      // Either cron has been running for more than an hour or the semaphore
+      // was not reset due to a database error.
+      watchdog('cron', t('CONVEY: Cron has been running for more than an hour and is most likely stuck.'), WATCHDOG_ERROR);
+
+      // Release cron semaphore
+      variable_del('cron_semaphore');
+    }
+    else {
+      // Cron is still running normally.
+      watchdog('cron', t('CONVEY: Attempting to re-run cron while it is already running.'), WATCHDOG_WARNING);
+    }
+  }
+  else {
+    // Register shutdown callback
+    register_shutdown_function('drupal_cron_cleanup');
+
+    // Lock cron semaphore
+    variable_set('cron_semaphore', time());
+
+    // Process convey.
+    process();
+
+    // Record cron time
+    variable_set('cron_last', time());
+    watchdog('cron', t('CONVEY: Cron run completed.'), WATCHDOG_NOTICE);
+
+    // Release cron semaphore
+    variable_del('cron_semaphore');
+
+    // Return TRUE so other functions can check if it did run successfully
+    print "DONE";
+  }
+}
+
+
+?>
