You are here

support.module in Support Ticketing System 6

Same filename and directory in other branches
  1. 8 support.module
  2. 7 support.module

File

support.module
View source
<?php

/**
 * @file support.module
 */
define('SUPPORT_STATE_CLOSED', -3);
define('SUPPORT_SORT_NONE', 0);
define('SUPPORT_SORT_UPDATE', 1);
define('SUPPORT_SORT_NID', 2);
define('SUPPORT_SORT_STATE', 3);
define('SUPPORT_SORT_PRIORITY', 4);
define('SUPPORT_SORT_ASC', 0);
define('SUPPORT_SORT_DESC', 1);
define('SUPPORT_ALL_CLIENTS', '-1');

/**
 * Implementation of hook_node_info().
 */
function support_node_info() {
  return array(
    'support_ticket' => array(
      'name' => t('Support ticket'),
      'module' => 'support',
      'description' => t('A <em>support ticket</em>.'),
    ),
  );
}

/**
 * Implementation of hook_access().
 */
function support_access($op, $node, $account) {
  switch ($op) {
    case 'create':
      return user_access('create tickets', $account);
    case 'update':
      return user_access('edit any ticket', $account) || user_access('edit own tickets', $account) && $node->uid == $account->uid || user_access('administer support', $account);
    case 'delete':
      return user_access('delete any ticket', $account) || user_access('delete own tickets', $account) && $node->uid == $account->uid || user_access('administer support', $account);
    case 'view':
      if (isset($node->client)) {
        $client = support_client_load($node->client);

        // User can access at least some of this client's tickets.
        if (support_access_clients($client, $account)) {

          // User can access this ticket.
          if (user_access('view other users tickets') || user_access('administer support') || user_access('edit any ticket') || user_access('delete any ticket')) {
            $access = NULL;
          }
          else {

            // User created this ticket, allow access.
            if ($account->uid == $node->uid && $account->uid != 0) {
              $access = NULL;
            }
            else {
              if (db_result(db_query('SELECT 1 FROM {support_assigned} WHERE nid = %d AND uid = %d', $node->nid, $account->uid))) {
                $access = NULL;
              }
              else {
                $access = FALSE;
              }
            }
          }
        }
        else {
          $access = FALSE;
        }

        // We return FALSE to explicitly block access to a ticket.  Otherwise
        // we return NULL to allow other access modules to weigh in.
        return $access;
      }
  }
}

/**
 * Implementation of hook_menu().
 */
function support_menu() {
  $items['support'] = array(
    'title' => 'Support tickets',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_page_form',
    ),
    'access callback' => 'support_access_clients',
    'access arguments' => array(),
  );
  $items['support/fetch'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'support_fetch_client_mail',
    'access arguments' => array(
      'download mail via support/fetch',
    ),
  );

  // Autocomplete paths
  $items['support/autocomplete/assigned'] = array(
    'title' => 'Autocomplete support assigned user',
    'page callback' => 'support_autocomplete_assigned',
    'access callback' => 'support_access_clients',
    'access arguments' => array(),
    'type' => MENU_CALLBACK,
  );
  $items['support/autocomplete/autosubscribe'] = array(
    'title' => 'Autocomplete support autosubscribed user',
    'page callback' => 'support_autocomplete_autosubscribe',
    'access callback' => '_support_autosubscribe_access',
    'type' => MENU_CALLBACK,
  );
  $states = array(
    'all' => 'all',
    'all open' => 'all open',
    'my open' => 'my open',
  ) + _support_states();
  $result = db_query('SELECT clid, path, name FROM {support_client} WHERE status = 1 AND parent = 0');
  $clients = array();
  while ($client = db_fetch_object($result)) {
    $clients[] = $client;
  }
  if (count($clients) > 1) {
    $combined = new stdClass();
    $combined->clid = SUPPORT_ALL_CLIENTS;

    // @todo: use reserved path
    $combined->path = '_all_';
    $combined->name = '** All Tickets **';
    $clients[] = $combined;
  }
  foreach ($clients as $client) {
    $items['support/' . $client->path] = array(
      'title' => check_plain($client->name),
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'support_page_form',
        $client->clid,
      ),
      'access callback' => 'support_access_clients',
      'access arguments' => array(
        $client,
      ),
    );
    foreach ($states as $sid => $state) {
      $items["support/{$client->path}/{$state}"] = array(
        'title' => "{$state}",
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
          'support_page_form',
          $client->clid,
          $state,
        ),
        'access callback' => 'support_access_clients',
        'access arguments' => array(
          $client,
        ),
        'weight' => $sid,
        'type' => $sid == 'all open' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
      );
    }
    $result2 = db_query('SELECT clid, path, name FROM {support_client} WHERE status = 1 AND parent = %d', $client->clid);
    while ($subclient = db_fetch_object($result2)) {
      $items["support/{$client->path}/{$subclient->path}"] = array(
        'title' => check_plain($subclient->name),
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
          'support_page_form',
          $subclient->clid,
        ),
        'access callback' => 'support_access_clients',
        'access arguments' => array(
          $subclient,
        ),
      );
      foreach ($states as $sid => $state) {
        $items["support/{$client->path}/{$subclient->path}/{$state}"] = array(
          'title' => "{$state}",
          'page callback' => 'drupal_get_form',
          'page arguments' => array(
            'support_page_form',
            $subclient->clid,
            $state,
          ),
          'access callback' => 'support_access_clients',
          'access arguments' => array(
            $subclient,
          ),
          'weight' => $sid,
          'type' => $sid == 'all open' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
        );
      }
    }
  }
  $items['support/user/%user'] = array(
    'page callback' => 'support_page_user',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'support_access_user_tickets',
    'access arguments' => array(
      2,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'support.user.inc',
  );
  $items['support/%user_uid_optional/assigned'] = array(
    'title' => 'My tickets',
    'page callback' => 'support_page_user',
    'page arguments' => array(
      1,
      TRUE,
    ),
    'access callback' => 'support_page_user_access',
    'access arguments' => array(
      1,
    ),
    'file' => 'support.user.inc',
  );
  unset($states['my open']);
  foreach ($states as $sid => $state) {
    $items["support/%user_uid_optional/assigned/{$state}"] = array(
      'title' => "{$state}",
      'page callback' => 'support_page_user',
      'page arguments' => array(
        1,
        TRUE,
        $state,
      ),
      'access callback' => 'support_access_clients',
      'access arguments' => array(
        $client,
      ),
      'file' => 'support.user.inc',
      'weight' => $sid,
      'type' => $sid == 'all open' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
    );
  }
  $items['support/%node/unsubscribe/%user/%'] = array(
    'page callback' => 'support_unsubscribe_user',
    'page arguments' => array(
      1,
      3,
      4,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['support/all/unsubscribe/%user/%'] = array(
    'page callback' => 'support_unsubscribe_user',
    'page arguments' => array(
      'all',
      3,
      4,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['admin/support'] = array(
    'title' => 'Support ticketing system',
    'description' => 'Manage the support ticket system.',
    'position' => 'right',
    'weight' => 5,
    'page callback' => 'support_admin_menu_block_page',
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/clients'] = array(
    'title' => 'Clients',
    'description' => 'Manage clients.',
    'page callback' => 'support_admin_client_overview',
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/clients/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/support/clients/%support_client/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_admin_client',
      3,
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/clients/%support_client/delete'] = array(
    'title' => 'Delete',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_admin_client_delete',
      3,
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/clients/%support_client/fetch'] = array(
    'title' => 'Fetch mail',
    'type' => MENU_CALLBACK,
    'page callback' => 'support_client_fetch',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/clients/add'] = array(
    'title' => 'Add client',
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_admin_client',
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_admin_settings',
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  $items['admin/support/settings/general'] = array(
    'title' => 'General settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/support/settings/mail'] = array(
    'title' => 'Mail text settings',
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_admin_mail_settings',
    ),
    'access arguments' => array(
      'administer support',
    ),
    'file' => 'support.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_cron().
 */
function support_cron() {
  if (variable_get('support_cron_download_mail', TRUE)) {
    support_fetch_client_mail();
  }
}

/**
 * Implementation of hook_theme().
 */
function support_theme() {
  return array(
    'support_page_form' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'support_page_user' => array(
      'arguments' => array(
        'header' => NULL,
        'rows' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_theme_registry_alter().
 */
function support_theme_registry_alter(&$theme_registry) {
  if (!empty($theme_registry['comment_wrapper'])) {
    $theme_registry['comment_wrapper']['function'] = 'support_comment_wrapper';
  }
}

/**
 * Implementation of theme_content_wrapper().
 *
 * Remove 'post new comment' heading from support tickets.
 */
function support_comment_wrapper($content, $node) {
  if ($node->type == 'support_ticket' && variable_get('support_disable_post_comment', FALSE)) {
    $content = str_replace(t('Post new comment'), '', $content);
  }
  return $content;
}

/**
 * Implementation of hook_link_alter().
 */
function support_link_alter(&$links, $node, $comment = NULL) {
  if ($node->type == 'support_ticket' && variable_get('support_disable_comment_reply', FALSE)) {
    unset($links['comment_reply']);
  }
  if ($node->type == 'support_ticket' && isset($node->taxonomy)) {
    foreach ($node->taxonomy as $tid => $term) {
      if (isset($links["taxonomy_term_{$tid}"])) {
        $client = support_client_load($node->client);
        $links["taxonomy_term_{$tid}"]['href'] = "support/{$client->path}";
        $links["taxonomy_term_{$tid}"]['query'] = "tid={$tid}";
      }
    }
  }
}

/**
 * Implementation of hook_init().
 */
function support_init() {
  global $conf;
  if (module_exists('i18n')) {

    // Make all mail text variables translatable.
    foreach (_support_mail_text_default(NULL) as $key => $text) {
      $conf['i18n_variables'][] = 'support_mail_' . $key;
    }
  }
}

/**
 * Implemtation of hook_block().
 */
function support_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('Support client access');

      // Too dynamic to cache.
      $blocks[0]['cache'] = BLOCK_CACHE_GLOBAL;
      return $blocks;
    case 'view':
      $block = array();

      // Only display block on support pages.
      if (arg(0) == 'support') {
        $clid = _support_current_client();
        $access_users = array();
        $access_roles = array();
        if ($clid) {
          $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $clid));

          // retrieve all roles giving permission to access current tickets
          $result = db_query("SELECT p.rid, r.name FROM {permission} p JOIN {role} r ON r.rid = p.rid WHERE perm LIKE '%%%s%%' OR perm LIKE '%%%s%%'", "access {$client} tickets", 'administer support');
          $roles = array();
          while ($role = db_fetch_object($result)) {
            $roles[$role->rid] = $role->rid;
            $access_roles[] = l($role->name, "admin/user/permissions/{$role->rid}");
          }
          if (!empty($roles)) {
            $role_sql = "SELECT u.name, u.uid FROM {users} u LEFT JOIN {users_roles} r ON u.uid = r.uid WHERE r.rid IN (%s) AND u.status = 1";
            if (variable_get('support_filter_uid1', FALSE)) {
              $role_sql .= ' AND u.uid <> 1';
            }
            $result = db_query($role_sql, implode(', ', $roles));
            while ($account = db_fetch_object($result)) {
              $access_users[$account->uid] = theme('username', $account);
            }
          }
        }
        $block['subject'] = t('Client access');
        $block['content'] = theme_item_list($access_roles, t('Roles')) . theme_item_list($access_users, t('Users'));
      }
      return $block;
  }
}

/**
 * Autocomplete usernames to assign to ticket.
 */
function support_autocomplete_assigned($clid = 0, $string = '') {
  $matches = array();
  if ($string) {
    if ($clid) {
      $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $clid));

      // retrieve all roles giving permission to access current tickets
      $result = db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%%s%%' OR perm LIKE '%%%s%%'", "access {$client} tickets", 'administer support');
    }
    $roles = array();
    while ($role = db_fetch_object($result)) {
      $roles[$role->rid] = $role->rid;
    }

    // also get people with administer support permissions
    $result = db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%%s%%'", 'administer support');
    while ($role = db_fetch_object($result)) {
      $roles[$role->rid] = $role->rid;
    }
    if (!empty($roles)) {
      $role_sql = "SELECT u.name FROM {users} u LEFT JOIN {users_roles} r ON u.uid = r.uid WHERE r.rid IN (%s) AND u.status = 1 AND LOWER(name) LIKE LOWER('%s%%')";
      if (variable_get('support_filter_uid1', FALSE)) {
        $role_sql .= ' AND u.uid <> 1';
      }
      $result = db_query_range($role_sql, implode(', ', $roles), $string, 0, 10);
      while ($user = db_fetch_object($result)) {
        $matches[$user->name] = check_plain($user->name);
      }
    }
  }
  drupal_json($matches);
}

/**
 * Autocomplete usernames to assign to ticket.
 */
function support_autocomplete_autosubscribe($clid, $string = '') {

  // The user enters a comma-separated list of users.  We only autocomplete the
  // last user.
  $array = drupal_explode_tags($string);

  // Fetch last user.
  $last_string = trim(array_pop($array));
  $matches = array();
  if ($last_string) {
    $roles = array();
    $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $clid));

    // retrieve all roles giving permission to access current tickets
    $result = db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%%s%%' OR perm LIKE '%%%s%%'", "access {$client} tickets", 'administer support');
    while ($role = db_fetch_object($result)) {
      $roles[$role->rid] = $role->rid;
    }
    if (!$clid) {
      $result = db_query_range("SELECT name FROM {users} WHERE status = 1 AND LOWER(name) LIKE LOWER('%s%%')", $last_string, 0, 10);
    }
    else {
      if (!empty($roles)) {
        $result = db_query_range("SELECT u.name FROM {users} u LEFT JOIN {users_roles} r ON u.uid = r.uid WHERE r.rid IN (%s) AND u.status = 1 AND LOWER(u.name) LIKE LOWER('%s%%')", implode(', ', $roles), $last_string, 0, 10);
      }
    }
    $prefix = count($array) ? implode(', ', $array) . ', ' : '';
    while ($account = db_fetch_object($result)) {
      $a = $account->name;
      $matches[$prefix . $a] = check_plain($account->name);
    }
  }
  drupal_json($matches);
}

/**
 * Be sure a valid user is being assigned to a ticket.
 */
function _support_validate_assigned_user($uid, $client) {
  $account = user_load(array(
    'uid' => $uid,
  ));
  return user_access("access {$client} tickets", $account) || user_access('administer support', $account);
}

/**
 * Automatically download messages for all active clients.
 */
function support_fetch_client_mail() {
  $clients = support_active_clients();
  if (is_array($clients)) {
    foreach ($clients as $clid => $client) {
      if ($client->integrate_email) {
        support_client_fetch($client, FALSE);
      }
    }
  }
}

/**
 * Provide some inline documentation.
 */
function support_help($path, $arg) {
  switch ($path) {
    case 'admin/support/clients':
      $output = '<p>' . t('Each support ticket can only be assigned to one client. !create one or more clients, then !assign allowing users to access these tickets.  Users can only create tickets for clients they have permission to access.  If working with multiple clients you will need to !define for each to prevent one client from viewing the tickets of another client.', array(
        '!create' => l(t('Create'), 'admin/support/clients/add'),
        '!assign' => l(t('assign permissions'), 'admin/user/permissions', array(
          'fragment' => 'module-support',
        )),
        '!define' => l(t('define roles'), 'admin/user/roles'),
      )) . '</p>';
      break;
    case 'admin/support/clients/add':
    case 'admin/support/clients/%/edit':
      $output = '<p>' . t("Each support ticket must be assigned to one client.  Support can be configured so one client can't view another client's tickets.") . '</p>';
      $output .= '<p>' . t('If you would like users to be able to create and update tickets via email, you will require a dedicated email address for each client.  This email address is used to send and receive update notifications.  A cronjob automatically downloads emails sent to this address and converts them into tickets and ticket updates.  The message_id of each email is tracked, allowing support to properly associate replies with existing tickets.') . '</p>';
      break;
    case 'admin/support/settings':
      $output = '<p>' . t('Global settings for the support module.') . '</p>';
      break;
    default:
      $output = '';
      break;
  }
  return $output;
}

/**
 * Load all active clients.
 */
function support_active_clients() {
  static $clients = NULL;
  if (is_null($clients)) {
    $result = db_query('SELECT * FROM {support_client} WHERE status = 1');
    while ($client = db_fetch_object($result)) {
      $clients[$client->clid] = $client;
    }
  }
  return $clients;
}

/**
 * Unsubscribe user from tickets.
 */
function support_unsubscribe_user($node, $account, $key) {

  // unsubscribe from a single node
  if (is_object($node) && is_object($account)) {
    $lock = md5($account->uid . $node->nid);
    if ($key == $lock) {
      db_query('DELETE FROM {support_assigned} WHERE uid = %d AND nid = %d', $account->uid, $node->nid);
      drupal_set_message(t('%email has been unsubscribed from ticket %ticket.', array(
        '%email' => check_plain($account->mail),
        '%ticket' => check_plain($node->title),
      )));
    }
    else {
      drupal_set_message(t('Invalid key, failed to unsubscribe %email.', array(
        '%email' => check_plain($account->mail),
      )), 'error');
    }
    drupal_goto("node/{$node->nid}");
  }
  else {
    if (is_object($account)) {
      $lock = md5($account->uid);
      if ($key == $lock) {
        db_query('DELETE FROM {support_assigned} WHERE uid = %d', $account->uid);
        drupal_set_message(t('%email has been unsubscribed from all tickets.', array(
          '%email' => check_plain($account->mail),
        )));
      }
      else {
        drupal_set_message(t('Invalid key, failed to unsubscribe %email.', array(
          '%email' => check_plain($account->mail),
        )), 'error');
      }
    }
  }
  drupal_goto('');
}

/**
 * Custom permissions function.
 */
function support_access_clients($client = NULL, $account = NULL) {
  if (is_object($client)) {
    if (is_object($account)) {
      return user_access('administer support', $account) || user_access("access {$client->name} tickets", $account);
    }
    else {
      if ($client->clid == SUPPORT_ALL_CLIENTS) {
        if (_support_access_tickets() > 1) {
          return TRUE;
        }
        else {
          return FALSE;
        }
      }
      return user_access('administer support') || user_access("access {$client->name} tickets");
    }
  }
  else {
    return _support_access_tickets();
  }
}

/**
 * Menu callback, load a client.
 */
function support_client_load($id, $integer = TRUE) {
  static $clients = array();
  if (!isset($clients[$id])) {
    if ($integer) {
      $client = db_fetch_object(db_query('SELECT clid, name, path, status, parent, integrate_email, server_name, server_username, server_password, mailfrom, mailbox, protocol, extra, port, notes, autoassign, autosubscribe, thread_subject, domains, user_creation FROM {support_client} WHERE clid = %d', $id));
    }
    else {
      $client = db_fetch_object(db_query("SELECT clid, name, path, status, integrate_email, server_name, server_username, server_password, mailfrom, mailbox, protocol, extra, port, notes, autoassign, autosubscribe, thread_subject, domains, user_creation FROM {support_client} WHERE path = '%s'", $id));
    }
    drupal_alter('support_client_load', $client);
    $clients[$id] = $client;
  }
  return $clients[$id];
}

/**
 * Menu callback, load a ticket.
 */
function support_ticket_load($nid) {
  static $tickets = array();
  if (!isset($tickets[$nid])) {
    $ticket = db_fetch_object(db_query('SELECT * FROM {support_ticket} WHERE nid = %d', $nid));
    drupal_alter('support_ticket_load', $ticket);
    $tickets[$nid] = $ticket;
  }
  return $tickets[$nid];
}

/**
 * Menu callback.
 */
function support_access_user_tickets($account = array()) {
  global $user;
  if (user_access('administer support') || user_access('edit any ticket') || user_access('create tickets') && $account->uid == $user->uid) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Extract domains.
 */
function _support_domains($client, $global) {
  $domains = array();
  $string = "{$client}, {$global}";
  $raw = explode(',', $string);
  foreach ($raw as $domain) {
    if ($domain) {
      $domains[] = check_plain(trim($domain));
    }
  }
  return $domains;
}

/**
 * Match up a user account with an incoming email.  Create account if email
 * doesn't match any.
 * TODO: Make it possible to assign multiple email addresses to one account.
 */
function support_account_load($from, $ticket, $subject, $client) {
  $uid = db_result(db_query("SELECT uid FROM {users} WHERE mail = '%s'", $from));
  if ($uid) {
    return user_load($uid);
  }
  else {
    if ($client->user_creation == 2 || $client->user_creation == 0 && variable_get('support_autocreate_users', TRUE) == FALSE) {

      // User does not exist and the setting to allow automatic creation is
      // disabled.  Send notification to user.
      watchdog('support', 'An autocreation of a user from the e-mail address: !from was denied. The client recieving the request was: !client', array(
        '!from' => utf8_encode($from),
        '!client' => $client->name,
      ));
      _support_mail_deny($from);
      return FALSE;
    }
    else {

      // extract the domain out of the from email address
      $matches = array();
      preg_match('~[\\w-]+\\.\\w+(?=/|$)~', $from, $matches);
      $domain = $matches[0];
      $ticket = support_ticket_load($ticket);
      $domains = _support_domains($client->domains, variable_get('support_global_domains', ''));
      $valid = TRUE;
      if (!empty($domains)) {
        $valid = FALSE;
        foreach ($domains as $match) {
          if ($domain == $match || $match == '*') {
            $valid = TRUE;
            break;
          }
        }
      }

      // TODO: this isn't a role, this is a permission

      //$role = "access $client->name tickets";
      if ($valid) {
        watchdog('support', 'User !username automatically created.', array(
          '!username' => $from,
        ), WATCHDOG_NOTICE);

        //return user_save(NULL, array('mail' => $from, 'init' => $from, 'name' => $from, 'status' => 1, 'roles' => array($role)));
        return user_save(NULL, array(
          'mail' => $from,
          'init' => $from,
          'name' => $from,
          'status' => 1,
        ));
      }
      else {
        $node = node_load($ticket->nid);
        watchdog('support', 'Email update from !from denied for ticket "!title", subject "!subject."', array(
          '!from' => $from,
          '!title' => check_plain($node->title),
          '!subject' => $subject,
        ), WATCHDOG_NOTICE);
        return FALSE;
      }
    }
  }
}

/**
 * Implementation of hook_perm().
 */
function support_perm() {
  $perm = array(
    'administer support',
    'create tickets',
    'delete any ticket',
    'delete own tickets',
    'edit any ticket',
    'edit own tickets',
    'edit multiple tickets',
    'can administer state',
    'can suppress notification',
    'can subscribe other users to notifications',
    'download mail via support/fetch',
    'view other users tickets',
    'can select state',
    'can select priority',
    'can select client',
    'can assign tickets to self',
    'can assign tickets to any user',
    'move ticket',
  );
  $result = db_query('SELECT name FROM {support_client} WHERE status = 1');
  while ($client = db_fetch_object($result)) {
    $perm[] = 'access ' . check_plain($client->name) . ' tickets';
  }
  return $perm;
}

/**
 * Implementation of hook_user().
 */
function support_user($type, &$edit, &$account) {
  global $user;
  if (variable_get('support_display_user_links', TRUE)) {
    if ($type == 'view' && (user_access('create tickets', $account) && $user->uid == $account->uid || user_access('administer support'))) {
      $items = array();
      $items[] = l(t('View recent tickets'), "support/user/{$account->uid}", array(
        'attributes' => array(
          'title' => t("Read @username's latest tickets.", array(
            '@username' => check_plain($account->name),
          )),
        ),
      ));
      $items[] = l(t('Create new ticket'), 'node/add/support-ticket');
      $account->content['summary']['support'] = array(
        '#type' => 'user_profile_item',
        '#title' => t('Tickets'),
        '#value' => theme('item_list', $items),
        '#attributes' => array(
          'class' => 'support',
        ),
      );
    }
  }
}

/**
 * Implementation of hook_form().
 */
function support_form(&$node) {
  $type = node_get_types('type', $node);
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => check_plain($type->title_label),
    '#required' => TRUE,
    '#default_value' => !empty($node->title) ? $node->title : NULL,
    '#weight' => -5,
  );
  $reference = array();
  $form = array_merge($form, support_status_form($reference, $node, ''));
  $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
  if (isset($node->nid) && $node->nid) {
    $form['ticket'] = array(
      '#type' => 'fieldset',
      '#title' => t('Support ticket'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#access' => user_access('administer support'),
    );
    $form['ticket']['move'] = array(
      '#type' => 'textfield',
      '#title' => t('Move ticket'),
      '#maxlength' => 12,
      '#size' => 8,
      '#description' => t('Optionally specify another ticket id to move this ticket and all of its updates.  When moved, this ticket and all of its updates will become updates to the specified ticket and this ticket will be removed.  This action can not be undone.'),
      '#access' => user_access('administer support') || user_access('move ticket'),
    );
  }
  $form = array_merge($form, support_subscribe_form($reference, $node, ''));
  return $form;
}

/**
 * Implementation of hook_nodeapi().
 */
function support_nodeapi(&$node, $op, $teaser, $page) {
  global $user;
  if ($node->type == 'support_ticket') {
    $autocomplete = 'subscribed-users';
    switch ($op) {
      case 'view':

        // viewing a ticket
        drupal_add_css(drupal_get_path('module', 'support') . '/support-tickets.css');
        $breadcrumb = array();
        $breadcrumb[] = l(t('Home'), NULL);
        $breadcrumb[] = l(t('Support tickets'), 'support');
        if (is_numeric($node->client)) {
          $_SESSION['support_client'] = $node->client;
          $client = support_client_load($node->client);
          if (!empty($client->parent)) {
            $parent = support_client_load($client->parent);
            $breadcrumb[] = l(check_plain($parent->name), "support/{$parent->path}");
            $breadcrumb[] = l(check_plain($client->name), "support/{$parent->path}/{$client->path}");
          }
          else {
            $breadcrumb[] = l(check_plain($client->name), "support/{$client->path}");
          }
        }
        drupal_set_breadcrumb($breadcrumb);
        break;
      case 'load':
        return db_fetch_array(db_query('SELECT message_id, state, priority, client, assigned FROM {support_ticket} WHERE nid = %d', $node->nid));
      case 'validate':
        $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $node->client));
        if (!isset($node->client) || $node->client == 0) {
          form_set_error('client', t('You must select a client'));
        }
        if (isset($node->assigned) && !is_numeric($node->assigned)) {
          $assigned = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $node->assigned));
          if ($node->assigned && !$assigned) {
            form_set_error('assigned', t('You must specify a valid user.'));
          }
          else {
            if ($assigned) {
              $valid = _support_validate_assigned_user($assigned, $client);
              if (!$valid) {
                form_set_error('assigned', t('You must specify a user that has permission to view this ticket.'));
              }
            }
          }
        }
        if (isset($node->move) && is_numeric($node->move) && $node->move) {
          $destination = node_load(array(
            'nid' => $node->move,
          ));
          if (!is_object($destination) || !$destination->nid) {
            form_set_error('move', t('Destination node does not exist.'));
          }
        }

        // check for users subscribed during ticket creation (checkboxes)
        if (isset($node->notifications) && !empty($node->notifications)) {
          $notifications = explode(',', $node->notifications);
          foreach ($notifications as $notify) {
            $valid = _support_validate_assigned_user($notify, $client);
            if (!$valid) {
              $account = user_load(array(
                'uid' => $notify,
              ));
              form_set_error("notify-{$notify}", t('Unable to subscribe user, %user does not have permission to view this ticket.', array(
                '%user' => $account->name,
              )));
            }
          }
        }
        else {
          if (!empty($node->{$autocomplete})) {
            $notifications = explode(',', $node->{$autocomplete});
            foreach ($notifications as $notify) {
              $account = user_load(array(
                'name' => trim($notify),
              ));
              if (empty($account) || !user_access("access {$client} tickets", $account) && !user_access('administer support', $account)) {
                form_set_error('subscribed-users', t('Unable to subscribe user, %user does not have permission to view this ticket.', array(
                  '%user' => $notify,
                )));
              }
            }
          }
        }
        break;
      case 'insert':
      case 'update':
        if (isset($node->move) && is_numeric($node->move) && $node->move) {
          $destination = node_load($node->move);
          _support_node_move($node, $destination);
        }
        if (isset($node->assigned) && !is_numeric($node->assigned)) {
          $assigned = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $node->assigned));
          if ($assigned) {
            $node->assigned = $assigned;
          }
          else {
            $node->assigned = 0;
          }
        }
        db_query("UPDATE {support_ticket} SET message_id = '%s', state = %d, priority = %d, client = %d, assigned = %d WHERE nid = %d", isset($node->message_id) ? $node->message_id : '', $node->state, $node->priority, $node->client, $node->assigned, $node->nid);
        if (!db_affected_rows()) {
          @db_query("INSERT INTO {support_ticket} (nid, message_id, state, priority, client, assigned) VALUES(%d, '%s', %d, %d, %d, %d)", $node->nid, isset($node->message_id) ? $node->message_id : '', $node->state, $node->priority, $node->client, $node->assigned);
        }
        if (isset($node->notifications) && !empty($node->notifications)) {
          $notifications = explode(',', $node->notifications);
          foreach ($notifications as $notify) {
            $enabled = "notify-{$notify}";
            support_subscribe_user($node->nid, $notify, $node->{$enabled});
          }
        }
        else {
          if (isset($node->{$autocomplete})) {
            $notifications = explode(',', $node->{$autocomplete});
            foreach ($notifications as $notify) {
              $account = user_load(array(
                'name' => trim($notify),
              ));
              if (!empty($account)) {
                support_subscribe_user($node->nid, $account->uid);
              }
            }
          }
        }
        if ($op == 'insert') {

          // auto-subscribe ticket creator
          if (variable_get('support_autosubscribe_creator', FALSE) || isset($node->created_by_email)) {
            support_subscribe_user($node->nid, $node->uid);
          }
          else {
            support_subscribe_user($node->nid, $node->uid, $node->notification);
          }

          // auto-subscribe assigned user
          if ($node->assigned || isset($node->created_by_email) || !user_access('can subscribe other users to notifications')) {
            support_subscribe_user($node->nid, $node->assigned);
          }

          // auto-subscribe configured users
          if (variable_get('support_autosubscribe_force', FALSE) || isset($node->created_by_email) || !user_access('can subscribe other users to notifications')) {
            _support_autosubscribe($node->nid, $node->client);
          }

          // generate notification emails
          support_notification(array(), $node->nid, 'ticket_new', isset($node->suppress) ? $node->suppress : FALSE);
        }
        cache_clear_all();
        break;
      case 'delete':
        db_query('DELETE FROM {support_ticket} WHERE nid = %d', $node->nid);
        break;
    }
  }
}

/**
 * Implementation of hook_comment().
 */
function support_comment(&$comment, $op) {
  global $user;
  if (is_array($comment)) {
    $node = node_load($comment['nid']);
  }
  else {
    $node = node_load($comment->nid);
  }
  if ($node->type == 'support_ticket') {
    switch ($op) {
      case 'view':
        support_comment_view($comment);
        break;
      case 'validate':
        if (isset($comment['assigned']) && !is_numeric($comment['assigned'])) {
          $assigned = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $comment['assigned']));
          if ($node->assigned && !$assigned) {
            form_set_error('assigned', t('You must specify a valid user.'));
          }
          else {
            if ($assigned) {
              $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $node->client));
              $valid = _support_validate_assigned_user($assigned, $client);
              if (!$valid) {
                form_set_error('assigned', t('You must specify a user that has permission to view this ticket.'));
              }
            }
          }
        }
        break;
      case 'insert':
      case 'update':
        if (isset($comment['assigned']) && !is_numeric($comment['assigned'])) {
          $assigned = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $comment['assigned']));
          if ($assigned) {
            $comment['assigned'] = $assigned;
          }
          else {
            $comment['assigned'] = 0;
          }
        }
        db_query("UPDATE {support_ticket_comment} SET message_id = '%s', state = %d, priority = %d, client = %d, assigned = %d WHERE cid = %d", isset($comment['message_id']) ? $comment['message_id'] : '', $comment['state'], $comment['priority'], $comment['client'], $comment['assigned'], $comment['cid']);
        if (!db_affected_rows()) {
          @db_query("INSERT INTO {support_ticket_comment} (cid, message_id, state, priority, client, assigned) VALUES(%d, '%s', %d, %d, %d, %d)", $comment['cid'], isset($comment['message_id']) ? $comment['message_id'] : '', $comment['state'], $comment['priority'], $comment['client'], $comment['assigned']);

          // The first update to a ticket is not preserved in the database.
          // Store it in an array allowing other modules to dectect/respond to
          // ticket changes.
          $comment['previous'] = new stdClass();
          $comment['previous']->state = $node->state;
          $comment['previous']->priority = $node->priority;
          $comment['previous']->client = $node->client;
          $comment['previous']->assigned = $node->assigned;
        }
        _support_comment_update_node($comment['nid']);
        if ($op == 'insert') {

          // auto-subscribe ticket-comment creator
          if (variable_get('support_autosubscribe_creator', FALSE)) {
            support_subscribe_user($comment['nid'], $comment['uid']);
          }
          else {
            support_subscribe_user($comment['nid'], $comment['uid'], $comment['notification']);
          }

          // force auto-subscribe configured users
          if (variable_get('support_autosubscribe_force', FALSE)) {
            _support_autosubscribe($comment['nid'], $comment['client']);
          }

          // auto-subscribe assigned user
          if ($comment['assigned']) {
            support_subscribe_user($comment['nid'], $comment['assigned']);
          }

          // generate notification emails
          support_notification($comment, $comment['nid'], 'ticket_comment_new', isset($comment['suppress']) ? $comment['suppress'] : FALSE);
        }

        // if admin, can update who is assigned to the ticket
        if (user_access('administer support') && (!isset($comment['support_email']) || !$comment['support_email'])) {
          if (isset($comment['subscribed-users']) && !empty($comment['subscribed-users'])) {
            $array = drupal_explode_tags($comment['subscribed-users']);
            foreach ($array as $name) {
              $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $name));
              $comment["notify-{$uid}"] = 1;
            }
          }
          $available = _support_assigned(0, $node);
          foreach ($available as $uid => $name) {
            if (!$uid || $user->uid == $uid) {
              continue;
            }
            support_subscribe_user($node->nid, $uid, $comment["notify-{$uid}"]);
          }
        }
        break;
      case 'delete':
        db_query('DELETE FROM {support_ticket_comment} WHERE cid = %d', $comment->cid);
        _support_comment_update_node($comment->nid);
    }
  }
}

/**
 * Display state, priority and client when viewing comments for support nodes.
 */
function support_comment_view(&$comment) {
  static $state = 0;
  static $priority = 0;
  static $client = 0;
  static $assigned = 0;
  $current = db_fetch_object(db_query('SELECT state, priority, client, assigned FROM {support_ticket_comment} WHERE cid = %d', $comment->cid));
  if (!empty($current) && $assigned != $current->assigned) {
    $previous_account = user_load(array(
      'uid' => $assigned,
    ));
    $current_account = user_load(array(
      'uid' => $current->assigned,
    ));
    $comment->comment = '<div class="support-assigned">' . t('Assigned') . ': ' . ($previous_account->name ? check_plain($previous_account->name) : '<em>' . t('unassigned') . '</em>') . ' -> ' . ($current_account->name ? check_plain($current_account->name) : '<em>' . t('unassigned') . '</em>') . "</div>\n" . $comment->comment;
    $assigned = $current->assigned;
  }
  if (!empty($current) && $client != $current->client) {
    $comment->comment = '<div class="support-client">' . t('Client') . ': ' . check_plain(_support_client($client)) . ' -> ' . check_plain(_support_client($current->client)) . "</div>\n" . $comment->comment;
    $client = $current->client;
  }
  if (!empty($current) && $state != $current->state) {
    $comment->comment = '<div class="support-state">' . t('State') . ': ' . check_plain(_support_state($state)) . ' -> ' . check_plain(_support_state($current->state)) . "</div>\n" . $comment->comment;
    $state = $current->state;
  }
  if (!empty($current) && $priority != $current->priority) {
    $comment->comment = '<div class="support-priority">' . t('Priority') . ': ' . check_plain(_support_priorities($priority)) . ' -> ' . check_plain(_support_priorities($current->priority)) . "</div>\n" . $comment->comment;
    $priority = $current->priority;
  }
}

/**
 * Implementation of hook_mail().
 */
function support_mail($key, &$message, $params, $html = FALSE) {
  $language = $message['language'];
  $variables = support_mail_tokens($params['account'], $language, $params['nid'], isset($params['comment']) ? $params['comment'] : array(), $params['suppress'], $html);
  $message['subject'] .= _support_mail_text($key . '_subject', $language, $variables, $params['integrate_email']);
  $message['body'] = _support_mail_text($key . '_body', $language, $variables, $params['integrate_email']);

  // We generate message ids based on comment cids and support nids.
  // This allows readers to properly thread the messages coming from us,
  // as well as allow us to detect what comment an incoming email is a reply to,
  // so we can (optionally) handle threaded comments.
  $cid = $params['cid'];
  $references = array();
  while ($cid) {
    $cid = db_result(db_query('SELECT pid FROM {comments} WHERE cid = %d', $cid));

    // The last one will be cid 0.
    $references[] = _support_generate_message_id($params['nid'], $cid);
  }

  // Set up message id and threading.
  if (!isset($message['headers']['Message-ID'])) {
    $message['headers']['Message-ID'] = _support_generate_message_id($params['nid'], $params['cid']);
  }
  if (!empty($references)) {
    $message['headers']['In-Reply-To'] = $references[0];
    $message['headers']['References'] = implode(' ', array_reverse($references));
  }
}

/**
 * Generate an RFC2822 message ID.
 * @param $nid
 *   The support ticket nid.
 * @param $cid
 *   The cid of this followup, or 0 for a new ticket.
 */
function _support_generate_message_id($nid, $cid = 0) {
  global $base_url;
  $id_left = $cid . '.' . $nid;
  $id_right = preg_replace('|.+://([a-zA-Z0-9\\._-]+).*|', '\\1', $base_url);
  return "<{$id_left}@{$id_right}>";
}
function _support_node_move($node, $destination) {
  if (!user_access('move ticket') && user_access('administer support')) {
    drupal_set_message('Permission denied, unable to move ticket.');
  }

  // Move the ticket, making it an update on another ticket.
  $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $destination->nid));

  // Strip the "/" from the end of the thread.
  $max = rtrim($max, '/');

  // Finally, build the thread field for this new comment.
  $thread = int2vancode(vancode2int($max) + 1) . '/';
  $account = user_load(array(
    'uid' => $node->uid,
  ));
  db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $destination->nid, 0, $node->uid, $node->title, $node->body, 1, ip_address(), $node->changed, 0, $thread, $account->name, $account->mail, $account->homepage);

  // Tell the other modules a new comment has been submitted.
  $comment = array();
  $comment['cid'] = db_last_insert_id('comments', 'cid');
  $comment['subject'] = $node->title;
  $comment['comment'] = $node->body;
  $comment['nid'] = $destination->nid;
  $comment['uid'] = $account->uid;
  $comment['message_id'] = db_result(db_query('SELECT message_id FROM {support_ticket} WHERE nid = %d', $node->nid));
  $comment['support_email'] = TRUE;
  $comment['notification'] = db_result(db_query('SELECT 1 FROM {support_assigned} WHERE nid = %d AND uid = %d', $destination->nid, $account->uid));

  // Preserve the existing values for the support ticket status.
  $comment['state'] = $destination->state;
  $comment['priority'] = $destination->priority;
  $comment['client'] = $destination->client;
  $comment['assigned'] = $destination->assigned;
  comment_invoke_comment($comment, 'insert');

  // transfer attachments
  if (db_table_exists('upload')) {
    if (module_exists('comment_upload')) {

      // transfer attachments to new comment
      $result = db_query('SELECT fid, description, list, weight FROM {upload} WHERE nid = %d', $node->nid);
      while ($upload = db_fetch_object($result)) {
        db_query("INSERT INTO {comment_upload} (fid, nid, cid, description, list, weight) VALUES(%d, %d, %d, '%s', %d, %d)", $upload->fid, $destination->nid, $comment['cid'], $upload->description, $upload->list, $upload->weight);
      }
      db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid);
    }
    else {

      // transfer attachments to new node
      db_query('UPDATE {upload} SET nid = %d WHERE nid = %d', $destination->nid, $node->nid);
    }
  }

  // add an entry to the watchdog log
  watchdog('content', 'Comment: added %subject.', array(
    '%subject' => $comment['subject'],
  ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $comment['nid'], array(
    'fragment' => 'comment-' . $comment['cid'],
  )));
  $result = db_query('SELECT cid, uid, subject, comment, format, hostname, timestamp, timestamp, status, thread, name, mail, homepage FROM {comments} WHERE nid = %d', $node->nid);
  while ($update = db_fetch_array($result)) {
    $oldcid = $update['cid'];
    $max = rtrim($thread, '/');
    $thread = int2vancode(vancode2int($max) + 1) . '/';
    db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $destination->nid, 0, $update['uid'], $update['subject'], $update['comment'], $update['format'], $update['hostname'], $update['timestamp'], $update['status'], $thread, $update['name'], $update['mail'], $update['homepage']);

    // Tell the other modules a new comment has been submitted.
    $update['cid'] = db_last_insert_id('comments', 'cid');
    $update['nid'] = $destination->nid;
    $comment['message_id'] = db_query('SELECT message_id FROM {support_ticket_comment} WHERE cid = %d', $oldcid);
    $update['support_email'] = TRUE;
    $update['notification'] = $comment['notification'];

    // Preserve the existing values for the support ticket status.
    $update['state'] = $destination->state;
    $update['priority'] = $destination->priority;
    $update['client'] = $destination->client;
    $update['assigned'] = $destination->assigned;
    comment_invoke_comment($update, 'insert');

    // transfer attachments to new comment
    if (module_exists('comment_upload')) {
      db_query('UPDATE {comment_upload} SET nid = %d, cid = %d WHERE nid = %d AND cid = %d', $destination->nid, $update['cid'], $node->nid, $oldcid);
    }
  }
  _comment_update_node_statistics($comment['nid']);

  // remove the original node
  node_delete($node->nid);
  drupal_set_message(t('Successfully moved support ticket.'));
  watchdog('content', 'support_ticket: moved ticket %title from node/%old to node/%new.', array(
    '%title' => $node->title,
    '%old' => $node->nid,
    '%new' => $destination->nid,
  ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid, array(
    'fragment' => 'comment-' . $comment['cid'],
  )));
  cache_clear_all();
  drupal_goto("node/{$destination->nid}", NULL, 'comment-' . $comment['cid']);
}

/**
 * Return an array of token to value mappings for support e-mail messages.
 */
function support_mail_tokens($account, $language, $nid, $comment, $suppress, $html = FALSE) {
  global $base_url, $user;
  static $reset = TRUE;

  // force reload node from database to get updated state/priority information,
  // but no need to reset it multiple times when sending multiple notifications
  $node = node_load($nid, NULL, $reset);
  $assigned = user_load(array(
    'uid' => $node->assigned,
  ));
  $reset = FALSE;
  if (isset($comment['cid'])) {
    $cid = $comment['cid'];
    $update_account = user_load($comment['uid']);
  }
  else {
    $cid = 0;
    $update_account = user_load($node->uid);
  }
  $client = support_client_load($node->client);
  $ticket_unsubscribe_key = md5($account->uid . $node->nid);
  $all_unsubscribe_key = md5($account->uid);
  $previous_comment = db_result(db_query_range('SELECT cid FROM {comments} WHERE nid = %d ORDER BY cid DESC', $nid, 1, 1));
  if ($previous_comment) {
    $previous = db_fetch_object(db_query('SELECT * FROM {support_ticket_comment} WHERE cid = %d', $previous_comment));
  }
  if ($html) {
    $body = check_markup($node->body, variable_get('mimemail_format', FILTER_FORMAT_DEFAULT), FALSE) . _support_mail_list_attachments($node, $comment);
    $update = check_markup(isset($comment['comment']) ? $comment['comment'] : '', variable_get('mimemail_format', FILTER_FORMAT_DEFAULT), FALSE) . _support_mail_list_attachments($node, $comment);
  }
  else {
    $body = drupal_html_to_text(check_markup($node->body) . _support_mail_list_attachments($node, $comment));
    $update = drupal_html_to_text(check_markup(isset($comment['comment']) ? $comment['comment'] : '') . _support_mail_list_attachments($node, $comment));
  }
  $tokens = array(
    '!username' => $account->name,
    '!client_name' => $client->name,
    '!client_path' => $client->path,
    '!key' => '[' . variable_get('support_key', 'tkt') . ":{$nid}]",
    '!ticket_id' => $nid,
    '!ticket_update_id' => isset($comment['cid']) ? $comment['cid'] : 0,
    '!update_username' => isset($update_account->name) ? $update_account->name : '',
    '!update_realname' => theme('username', $user),
    '!site' => variable_get('site_name', 'Drupal'),
    '!uri' => $base_url,
    '!uri_brief' => preg_replace('!^https?://!', '', $base_url),
    '!uri_login' => url('user/register', array(
      'absolute' => TRUE,
      'alias' => !variable_get('support_use_aliased_urls', TRUE),
    )),
    '!mailto' => $account->mail,
    '!date' => format_date(time(), 'medium', '', NULL, $language->language),
    '!ticket_subject' => check_plain($node->title),
    '!ticket_body' => $suppress ? t('The text of this ticket was manually suppressed.  You must view the ticket online to see it.') . "\n" : $body,
    '!ticket_url' => url("node/{$nid}", array(
      'absolute' => TRUE,
      'language' => $language,
      'fragment' => "comment-{$cid}",
      'alias' => !variable_get('support_use_aliased_urls', TRUE),
    )),
    '!update_url' => url("node/{$nid}", array(
      'absolute' => TRUE,
      'language' => $language,
      'fragment' => "comment-form",
      'alias' => !variable_get('support_use_aliased_urls', TRUE),
    )),
    '!update' => $suppress ? t('The text of this ticket update was manually suppressed.  You must view the ticket online to see the update.') . "\n" : $update,
    '!state' => (isset($previous->state) && $previous->state != $node->state ? _support_state($previous->state) . ' -> ' : '') . _support_state($node->state),
    '!priority' => (isset($previous->priority) && $previous->priority != $node->priority ? _support_priorities($previous->priority) . ' -> ' : '') . _support_priorities($node->priority),
    '!assigned_username' => !empty($assigned) ? $assigned->name : '',
    '!assigned_realname' => !empty($assigned) ? theme('username', $assigned) : '',
    '!unsubscribe_ticket' => url("support/{$nid}/unsubscribe/{$account->uid}/{$ticket_unsubscribe_key}", array(
      'absolute' => TRUE,
      'language' => $language,
      'alias' => !variable_get('support_use_aliased_urls', TRUE),
    )),
    '!unsubscribe_all' => url("support/all/unsubscribe/{$account->uid}/{$all_unsubscribe_key}", array(
      'absolute' => TRUE,
      'language' => $language,
      'alias' => !variable_get('support_use_aliased_urls', TRUE),
    )),
  );
  if (!empty($account->password)) {
    $tokens['!password'] = $account->password;
  }
  drupal_alter('support_mail_tokens', $tokens);
  return $tokens;
}

/**
 * List all ticket attachments associated with this update, if any.
 */
function _support_mail_list_attachments($node, $comment) {
  $attachments = array();
  if (empty($comment) && is_object($node) && is_array($node->files)) {
    foreach ($node->files as $fid => $file) {
      $attachments[] = file_create_url($file->filepath);
    }
  }
  else {
    if (isset($comment['files']) && is_array($comment['files'])) {
      foreach ($comment['files'] as $fid => $file) {
        $attachments[] = file_create_url($file['filepath']);
      }
    }
  }
  return !empty($attachments) ? "<br />" . t('Attachments:') . "<br />" . implode("<br />", $attachments) : '';
}

/**
 * Returns the appropriate mail string for a given key.
 */
function _support_mail_text($key, $language = NULL, $variables = array(), $integrate_email) {
  $langcode = isset($language) ? $language->language : NULL;

  // Add one 'special unadvertised token' to $variables.
  // This part of the text is not stored as a multilingual variable
  // but as a 'common' translatable string.
  if ($integrate_email == TRUE) {
    $variables['!reply'] = t('You can reply to this email or visit the following URL to update this ticket');
  }
  else {
    $variables['!reply'] = t('You can visit the following URL to update this ticket');
  }

  /* Get text from (possibly multilingual) variable, if it's user-configured.
   * The following behavior provides backward compatibility with v1.3,
   * and only makes i18n.module a requirement when the web site needs
   * custom mail texts translated in multiple languages:
   *
   * - if a normal variable is set, return it untranslated.
   *   (because it might contain an already-translated text)
   * - if a multilingual variable is not set, do check the normal variable
   *   (for the same reason)
   * - if no variable is set, translate the default text using t()
   */
  if (module_exists('i18n')) {
    $admin_setting = i18n_variable_get('support_mail_' . $key, $langcode);
  }
  if (empty($admin_setting)) {
    $admin_setting = variable_get('support_mail_' . $key, '');
  }
  if (empty($admin_setting)) {
    return t(_support_mail_text_default($key), $variables, $langcode);
  }
  return strtr($admin_setting, $variables);
}

/**
 * Returns the appropriate untranslated default mail string for a given key.
 * It contains tokens which are not translated yet (see support_mail_tokens(),
 * plus "!reply")
 *
 * @param $key
 *   Key for the string to return (not prepended with 'support_mail_'),
 *   or NULL to return an array of all strings.
 */
function _support_mail_text_default($key) {
  $info = array(
    'ticket_deny_subject' => 'Support ticket creation denied',
    'ticket_deny_body' => "System message\n\nYou have tried to create a support ticket on the !site site. The creation of the ticket has been cancelled since the e-mail address you sent the message from is not registered at our site.\n\nYou have to be a registered user to be able to create support tickets via mail.\n\nYour feedback is important to us so please register at !uri_login and try again.\n\n!site Team",
    'ticket_new_subject' => '!key !ticket_subject',
    'ticket_new_body' => "!update_username has created the ticket '!ticket_subject':\n!ticket_url\n\nState: !state\nPriority: !priority\n\n!reply:\n!update_url\n\nTicket text:\n------------------------------\n!ticket_body------------------------------\n\nUnsubscribe from this ticket:\n!unsubscribe_ticket\n\nUnsubscribe from all tickets:\n!unsubscribe_all",
    'ticket_comment_new_subject' => '!key !ticket_subject',
    'ticket_comment_new_body' => "!update_username has updated the ticket '!ticket_subject':\n!ticket_url\n\nState: !state\nPriority: !priority\n\n!reply:\n!update_url\n\nUpdate text:\n------------------------------\n!update------------------------------\n\nUnsubscribe from this ticket:\n!unsubscribe_ticket\n\nUnsubscribe from all tickets:\n!unsubscribe_all",
  );
  drupal_alter('support_mail_text_default', $info);
  if (isset($key)) {
    return isset($info[$key]) ? $info[$key] : '';
  }
  else {
    return $info;
  }
}

/**
 * Save the message.
 */
function support_save_message($message, $client, $manual = FALSE) {
  if (isset($message['uid'])) {
    $account = user_load($message['uid']);
  }
  else {
    $account = support_account_load($message['from'], $message['nid'], $message['subject'], $client);
  }
  $ticket = support_ticket_load($message['nid']);
  if (is_object($message['headers']) && isset($message['headers']->message_id)) {
    $message_id = $message['headers']->message_id;
  }
  else {
    $message_id = NULL;
  }
  if (is_object($account) && is_object($ticket) && $ticket->nid) {

    // by retrieving the maximum thread level.
    $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $ticket->nid));

    // Strip the "/" from the end of the thread.
    $max = rtrim($max, '/');

    // Finally, build the thread field for this new comment.
    $thread = int2vancode(vancode2int($max) + 1) . '/';

    // TODO: format?
    // TODO: ip_address?
    db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $ticket->nid, 0, $account->uid, $message['subject'], $message['body'], 1, ip_address(), time(), 0, $thread, $account->name, $account->mail, isset($account->homepage) ? $account->homepage : '');

    // Tell the other modules a new comment has been submitted.
    $comment['cid'] = db_last_insert_id('comments', 'cid');
    $comment['subject'] = truncate_utf8(trim($message['subject']), 64, TRUE, TRUE);
    $comment['comment'] = $message['body'];
    $comment['nid'] = $ticket->nid;
    $comment['uid'] = $account->uid;
    $comment['message_id'] = $message_id;
    $comment['state'] = isset($message['state']) ? $message['state'] : $ticket->state;
    $comment['priority'] = isset($message['priority']) ? $message['priority'] : $ticket->priority;
    $comment['client'] = $ticket->client;
    $comment['assigned'] = isset($message['assigned']) ? $message['assigned'] : $ticket->assigned;
    $comment['notification'] = db_result(db_query('SELECT 1 FROM {support_assigned} WHERE nid = %d AND uid = %d', $ticket->nid, $account->uid));
    $comment['support_email'] = TRUE;
    $comment['files'] = _support_save_attachments($message['attachments'], $account, $manual);
    if (isset($message['suppress'])) {
      $comment['suppress'] = $message['suppress'];
    }

    // convert indivudal files to arrays to work with the comment_upload module
    foreach ($comment['files'] as $fid => $file) {
      $comment['files'][$fid] = (array) $file;
    }
    if (isset($message['_support_extra_fields'])) {
      foreach ($message['_support_extra_fields'] as $k => $v) {
        $comment[$k] = $v;
      }
    }
    comment_invoke_comment($comment, 'insert');

    // Add an entry to the watchdog log.
    watchdog('content', 'Comment: added %subject.', array(
      '%subject' => $comment['subject'],
    ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $comment['nid'], array(
      'fragment' => 'comment-' . $comment['cid'],
    )));
    _comment_update_node_statistics($comment['nid']);

    // Clear the cache so an anonymous user can see his comment being added.
    cache_clear_all();
    return TRUE;
  }
  else {
    if (is_object($account)) {
      $node = new stdClass();

      // Create new ticket if none matches and valid from address.
      $node->title = $message['subject'];
      $node->body = $message['body'];
      $node->type = 'support_ticket';
      $node->log = t('Support ticket created from email.');
      $node->comment = COMMENT_NODE_READ_WRITE;
      $node->uid = $account->uid;
      $node->message_id = $message_id;
      $node->state = isset($message['state']) ? $message['state'] : _support_state_default();
      $node->priority = isset($message['priority']) ? $message['priority'] : _support_priority_default();
      $node->client = $client->clid;
      $node->assigned = _support_autoassign($client->clid, $account->uid);
      $node->notification = TRUE;
      $node->support_email = TRUE;
      $node->language = $account->language;

      // Save/record attachments
      if (isset($message['attachments'])) {
        $node->files = _support_save_attachments($message['attachments'], $account, $manual);
      }
      $node->created_by_email = TRUE;
      if (isset($message['_support_extra_fields'])) {
        foreach ($message['_support_extra_fields'] as $k => $v) {
          $node->{$k} = $v;
        }
      }
      node_save($node);
      if ($manual) {
        drupal_set_message(t('Created new ticket: !title.', array(
          '!title' => l($node->title, 'node/' . $node->nid),
        )));
      }
      watchdog('content', 'Node: created %title.', array(
        '%title' => $node->title,
      ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid));
    }
  }
}
function _support_save_attachments($attachments, $account, $manual = FALSE) {
  $files = array();
  if (count($attachments)) {
    foreach ($attachments as $attachment) {
      $attachment = (object) $attachment;
      if (is_array($attachment->parameters)) {
        foreach ($attachment->parameters as $parm) {
          switch (strtoupper($parm->attribute)) {
            case 'NAME':
            case 'FILENAME':
              $attachment->filename = mb_decode_mimeheader($parm->value);
              break;
            case 'NAME*1*':
            case 'FILENAME*1*':
              $attachment->filename = urldecode(mb_decode_mimeheader($parm->value));
              break;
            default:

              // put everything else in the attributes array;
              $attachment->attributes[$parm->attribute] = mb_decode_mimeheader($parm->value);
          }
        }
      }
      if ($attachment->type != TYPETEXT && is_array($attachment->dparameters)) {
        foreach ($attachment->dparameters as $parm) {
          switch (strtoupper($parm->attribute)) {
            case 'NAME':
            case 'FILENAME':
              $attachment->filename = mb_decode_mimeheader($parm->value);
              break;
            default:

              // put everything else in the attributes array;
              $attachment->attributes[$parm->attribute] = mb_decode_mimeheader($parm->value);
          }
        }
      }
      if ($manual && !empty($attachment->filename)) {
        drupal_set_message(t('Saved attachment: %filename', array(
          '%filename' => $attachment->filename,
        )));
      }
      if (!isset($attachment->filename) || empty($attachment->filename)) {
        if ($attachment->subtype == 'HTML') {
          $attachment->filename = 'noname.html';
        }
        else {
          $attachment->filename = 'noname';
        }
      }

      // Transliterate special characters if transliteration is enabled.
      if (function_exists('transliteration_clean_filename')) {
        $attachment->filename = transliteration_clean_filename($attachment->filename, language_default());
      }
      $attachment->filepath = file_save_data($attachment->attachment, file_directory_path() . '/' . utf8_encode($attachment->filename));
      if ($attachment->filepath) {
        watchdog('support', 'Saved %size byte %type attachment %file to %path.', array(
          '%size' => number_format($attachment->bytes),
          '%type' => $attachment->filemime,
          '%file' => utf8_encode($attachment->filename),
          '%path' => $attachment->filepath,
        ), WATCHDOG_INFO);
        db_query("INSERT INTO {files} (uid, filename, filepath, filemime, filesize, status, timestamp) VALUES(%d, '%s', '%s', '%s', %d, %d, %d)", $account->uid, utf8_encode($attachment->filename), $attachment->filepath, $attachment->filemime, $attachment->bytes, 1, time());
        $attachment->fid = db_last_insert_id('files', 'fid');
        $attachment->new = $attachment->list = TRUE;
        $attachment->weight = 0;
        $attachment->description = $attachment->filename;
        $files[$attachment->fid] = $attachment;
      }
      else {
        watchdog('support', 'Failed to save attachment %file, file_save_data() returned error.', array(
          '%file' => utf8_encode($attachment->filename),
        ));
      }
    }
  }
  return $files;
}

/**
 * Retrieve MIME type of the message structure.
 */
function _support_get_filemime(&$structure) {
  static $primary_mime_type = array(
    'TEXT',
    'MULTIPART',
    'MESSAGE',
    'APPLICATION',
    'AUDIO',
    'IMAGE',
    'VIDEO',
    'OTHER',
  );
  $type_id = (int) $structure->type;
  if (isset($primary_mime_type[$type_id]) && !empty($structure->subtype)) {
    return $primary_mime_type[$type_id] . '/' . $structure->subtype;
  }
  return 'application/octet-stream';
}

/**
 * Find all attachments in the current message.
 */
function _support_get_attachments($stream, $message, $structure, $parts, $prefix = FALSE) {
  $attachments = array();
  for ($part = 2; $part <= $parts; $part++) {
    if ($prefix == FALSE) {
      $prefix = $part;
    }
    $attachment = imap_fetchbody($stream, $message, $prefix);
    $details = imap_bodystruct($stream, $message, $prefix);
    if (empty($details)) {
      continue;
    }

    // Extract all the parts in a multipart message.
    if ($details->type == TYPEMULTIPART) {
      if ($prefix) {
        $prefix = $prefix . '.';
      }
      $structure = imap_fetchstructure($stream, $message);
      while (list($index, $substructure) = each($structure->parts)) {
        $parts = count($substructure->parts);
        $attachments += _support_get_attachments($stream, $message, $substructure, $parts, $prefix . ($index + 1));
      }
      if (!empty($structure)) {
        foreach ($structure->parameters as $parameter) {
          if (strtoupper($parameter->attribute) == 'CHARSET') {
            $encoding = mb_decode_mimeheader($parameter->value);
            break;
          }
          if ($data) {
            return $data;
          }
        }
      }
    }

    // Decode as necessary
    if ($details->encoding == ENCBASE64) {
      $attachment = imap_base64($attachment);
    }
    else {
      if ($details->encoding == ENCQUOTEDPRINTABLE) {
        $attachment = quoted_printable_decode($attachment);
      }
      else {
        if ($details->type == TYPETEXT) {
          $attachment = imap_utf8($attachment);
        }
      }
    }
    $details->filemime = _support_get_filemime($details);
    if (!empty($attachment)) {
      $details->attachment = $attachment;
      $attachments[] = $details;
    }
    if (!empty($prefix)) {
      $pieces = explode('.', $prefix);
      $piece = count($pieces) - 1;
      $pieces[$piece]++;
      $prefix = implode('.', $pieces);
    }
  }
  return $attachments;
}
function _support_get_message_body_part($stream, $message, $mime_type, $structure = FALSE, $part = FALSE) {
  if (!$structure) {
    $structure = imap_fetchstructure($stream, $message);
  }
  if (!empty($structure)) {
    foreach ($structure->parameters as $parameter) {
      if (strtoupper($parameter->attribute) == 'CHARSET') {
        $encoding = mb_decode_mimeheader($parameter->value);
        break;
      }
    }
    if ($structure->type == TYPEMULTIPART) {
      $prefix = '';
      while (list($index, $sub_structure) = each($structure->parts)) {
        if ($part) {
          $prefix = $part . '.';
        }
        $mime_type = _support_get_filemime($sub_structure);
        $data[] = _support_get_message_body_part($stream, $message, $mime_type, $sub_structure, $prefix . ($index + 1));
      }
      if (!empty($data)) {

        // Grab the last element, which is HTML if existing.
        return array_shift($data);
      }
      if (!empty($data)) {
        return $data;
      }
    }
    else {
      if ($mime_type == _support_get_filemime($structure)) {
        if (!$part) {
          $part = 1;
        }
        $body = imap_fetchbody($stream, $message, $part);
        switch ($structure->encoding) {
          case ENCBASE64:
            return drupal_convert_to_utf8(imap_base64($body), $encoding);
            break;
          case ENCQUOTEDPRINTABLE:
            return drupal_convert_to_utf8(quoted_printable_decode($body), $encoding);
            break;
          default:
            return drupal_convert_to_utf8($body, $encoding);
            break;
        }
      }
    }
  }
}

/**
 * Subscribe a user to a ticket.
 */
function support_subscribe_user($nid, $uid, $subscribe = 1) {
  $clid = db_result(db_query('SELECT client FROM {support_ticket} WHERE nid = %d', $nid));
  $client = support_client_load($clid);
  $account = user_load(array(
    'uid' => $uid,
  ));
  if (support_access_clients($client, $account)) {
    if ($subscribe) {
      @db_query('INSERT INTO {support_assigned} (nid, uid) VALUES(%d, %d)', $nid, $uid);
    }
    else {
      db_query('DELETE FROM {support_assigned} WHERE nid = %d AND uid = %d', $nid, $uid);
    }
  }
  else {

    // If this user doesn't have permission to receive ticket updates,
    // be sure they are unsubscribed.
    db_query('DELETE FROM {support_assigned} WHERE nid = %d AND uid = %d', $nid, $uid);
  }
}

/**
 * Autosubscribe users to new client ticket.
 */
function _support_autosubscribe($nid, $client, $save = TRUE) {
  $accounts = array();
  $autosubscribe = db_result(db_query('SELECT autosubscribe FROM {support_client} WHERE clid = %d', $client));
  $autosubscribe = explode(',', $autosubscribe);
  foreach ($autosubscribe as $name) {
    $account = user_load(array(
      'name' => trim($name),
    ));
    if (is_object($account) && $account->uid) {
      $accounts[$account->uid] = $account->uid;
      if ($save) {
        support_subscribe_user($nid, $account->uid);
      }
    }
  }
  return $accounts;
}

/**
 * Send notification emails to everyone subscribed to the updated ticket.
 */
function support_notification($comment = array(), $nid, $op = 'ticket_comment_new', $suppress = FALSE) {
  if (variable_get('support_notifications', TRUE)) {
    $result = db_query('SELECT uid FROM {support_assigned} WHERE nid = %d', $nid);
    while ($account = db_fetch_object($result)) {
      $account = user_load(array(
        'uid' => $account->uid,
      ));

      // always send emails to admins, even if update was suppressed
      if ($account->status && $account->mail && (!$suppress || user_access('administer support', $account))) {
        _support_mail_notify($op, $account, $comment, $nid, $suppress);
        if (variable_get('support_admin_notify', FALSE)) {
          if (variable_get('support_admin_notify', FALSE) == 1 && user_access('administer support') || variable_get('support_admin_notify', FALSE) == 2) {
            drupal_set_message(t('Sent notification to %email.', array(
              '%email' => $account->mail,
            )));
          }
        }
      }
      else {
        if (!$account->mail) {
          watchdog('support', 'User !name (!uid) has no email address.', array(
            '!name' => $account->name,
            '!uid' => $account->uid,
          ), WATCHDOG_NOTICE);
        }
      }
    }
  }
}

/**
 * Using drupal_mail to send notification mail to user that is not registered
 * and tried to use mail support.
 */
function _support_mail_deny($to) {
  $language = language_default();
  $key = 'ticket_deny';
  drupal_mail('support', $key, $to, $language, NULL);
}

/**
 * Use drupal_mail to send email.
 */
function _support_mail_notify($op, $account, $comment = array(), $nid = NULL, $suppress = FALSE, $language = NULL) {
  $notify = variable_get('support_mail_' . $op . '_notify', TRUE);
  if ($notify) {
    $node = node_load($nid);
    $params['account'] = $account;
    $params['nid'] = $nid;
    if (isset($comment['cid'])) {
      $params['cid'] = $comment['cid'];
      $params['comment'] = $comment;
    }
    else {
      $params['cid'] = 0;
    }
    $params['suppress'] = $suppress;
    $language = $language ? $language : user_preferred_language($account);
    $params['integrate_email'] = db_result(db_query('SELECT integrate_email FROM {support_client} WHERE clid = %d', $node->client));
    if ($params['integrate_email'] == TRUE) {
      $mailfrom = db_result(db_query('SELECT mailfrom FROM {support_client} WHERE clid = %d', $node->client));
    }
    else {
      $mailfrom = variable_get('support_global_mailfrom', '');
    }
    if (function_exists('mimemail')) {
      $params['message_html_filter'] = variable_get('mimemail_format', FILTER_FORMAT_DEFAULT);
      $message['params'] = $params;

      // Build a message.
      support_mail($op, $message, $params, TRUE);
      $message['body'] = check_markup($message['body'], $params['message_html_filter'], FALSE);
      $mail = mimemail($mailfrom, $account->mail, $message['subject'], $message['body'], FALSE, $message['headers']);
    }
    else {
      $mail = drupal_mail('support', $op, $account->mail, $language, $params, $mailfrom);
    }
  }
  return empty($mail) ? NULL : $mail['result'];
}

/**
 * Helper function.
 */
function _support_comment_update_node($nid) {
  $cid = db_result(db_query('SELECT MAX(cid) FROM {comments} WHERE nid = %d', $nid));
  if ($cid) {
    $comment = db_fetch_object(db_query('SELECT state, priority, client, assigned FROM {support_ticket_comment} WHERE cid = %d', $cid));
    db_query('UPDATE {support_ticket} SET state = %d, priority = %d, client = %d, assigned = %d WHERE nid = %d', $comment->state, $comment->priority, $comment->client, $comment->assigned, $nid);
  }
}

/**
 * Customize comment form for ticket followups.
 */
function support_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'comment_form') {
    if (is_array($form) && isset($form['nid']) && is_array($form['nid'])) {
      $node = node_load($form['nid']['#value']);
    }
    if (isset($node) && is_object($node) && isset($node->type) && $node->type == 'support_ticket') {
      $reference = array();
      $form = array_merge(support_status_form($form_state, $form, ''), $form);
      $form = array_merge($form, support_subscribe_form($reference, $form, ''));
      $form['comment_filter']['comment']['#title'] = t('Update');
      unset($form['_author']);
    }
  }
  else {
    if ($form_id == 'search_form' && variable_get('support_remove_tickets', TRUE)) {
      unset($form['advanced']['type']['#options']['support_ticket']);
    }
    else {
      if ($form_id == 'search_theme_form' && variable_get('support_override_theme', FALSE)) {
        $form['#submit'] = array(
          'support_search_form_submit',
        );
      }
      else {
        if ($form_id == 'search_block_form' && variable_get('support_override_block', FALSE)) {
          $form['#submit'] = array(
            'support_search_form_submit',
          );
        }
      }
    }
  }
  if ($form_id == 'search_form' && $form['module']['#value'] == 'support' && user_access('use advanced search')) {

    // Keyword boxes:
    $form['advanced'] = array(
      '#type' => 'fieldset',
      '#title' => t('Advanced search'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#attributes' => array(
        'class' => 'search-advanced',
      ),
    );
    $form['advanced']['keywords'] = array(
      '#prefix' => '<div class="criterion">',
      '#suffix' => '</div>',
    );
    $form['advanced']['keywords']['or'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing any of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['phrase'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing the phrase'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['negative'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing none of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Advanced search'),
      '#prefix' => '<div class="action">',
      '#suffix' => '</div>',
    );

    // Clients
    $clients = _support_available_clients();
    if (sizeof($clients) > 1) {
      $form['advanced']['client'] = array(
        '#type' => 'select',
        '#multiple' => TRUE,
        '#title' => t('Search specific client(s)'),
        '#prefix' => '<div class="criterion">',
        '#suffix' => '</div>',
        '#options' => $clients,
      );
    }

    // States
    $states = _support_states();
    if (sizeof($states) > 1) {
      $form['advanced']['state'] = array(
        '#type' => 'select',
        '#multiple' => TRUE,
        '#title' => t('Search specific state(s)'),
        '#prefix' => '<div class="criterion">',
        '#suffix' => '</div>',
        '#options' => $states,
      );
    }

    // Priorities
    $priorities = _support_priorities();
    if (sizeof($priorities) > 1) {
      $form['advanced']['priority'] = array(
        '#type' => 'select',
        '#multiple' => TRUE,
        '#title' => t('Search specific priorities'),
        '#prefix' => '<div class="criterion">',
        '#suffix' => '</div>',
        '#options' => $priorities,
      );
    }
    $form['#validate'][] = 'support_search_validate';
  }
}

/**
 * Form API callback for the search form. Registered in support_form_alter().
 */
function support_search_validate($form, &$form_state) {

  // Initialise using any existing basic search keywords.
  $keys = $form_state['values']['processed_keys'];

  // Insert extra restrictions into the search keywords string.
  if (isset($form_state['values']['client']) && is_array($form_state['values']['client'])) {
    if (count($form_state['values']['client'])) {
      $keys = search_query_insert($keys, 'client', implode(',', array_keys($form_state['values']['client'])));
    }
  }
  if (isset($form_state['values']['state']) && is_array($form_state['values']['state'])) {
    if (count($form_state['values']['state'])) {
      $keys = search_query_insert($keys, 'state', implode(',', array_keys($form_state['values']['state'])));
    }
  }
  if (isset($form_state['values']['priority']) && is_array($form_state['values']['priority'])) {
    if (count($form_state['values']['priority'])) {
      $keys = search_query_insert($keys, 'priority', implode(',', array_keys($form_state['values']['priority'])));
    }
  }
  if ($form_state['values']['negative'] != '') {
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
      $keys .= ' -' . implode(' -', $matches[1]);
    }
  }
  if ($form_state['values']['phrase'] != '') {
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
  }
  if (!empty($keys)) {
    form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
  }
}
function support_search_form_submit($form, &$form_state) {
  $form_id = $form['form_id']['#value'];
  $form_state['redirect'] = 'search/support/' . trim($form_state['values'][$form_id]);
}

/**
 * Imeplementation of hook_db_rewrite_sql().
 * Optionally remove support tickets from content search.
 */
function support_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  global $user;

  // If enabled, remove support tickets from core search results.
  if (variable_get('support_remove_tickets', TRUE) && $query == '' && $primary_table == 'n' && ($primary_field = 'nid' && empty($args))) {
    return array(
      'where' => " n.type != 'support_ticket'",
    );
  }

  // Filter tickets user doesn't have access to from core tracker.
  if ($primary_table == 'n' and $primary_field == 'nid' && !strpos($query, '{support_ticket}')) {
    $clients = support_search_available_clients();
    if (!empty($clients)) {
      if (user_access('view other users tickets') || user_access('administer support') || user_access('edit any ticket') || user_access('delete any ticket')) {
        return array(
          'join' => ' LEFT JOIN {support_ticket} st ON n.nid = st.nid',
          'where' => ' (st.client IN (' . implode(',', $clients) . ')  OR st.client IS NULL)',
        );
      }
      else {
        return array(
          'join' => ' LEFT JOIN {support_ticket} st ON n.nid = st.nid',
          'where' => ' ((st.client IN (' . implode(',', $clients) . ') AND n.uid = ' . $user->uid . ') OR st.client IS NULL)',
        );
      }
    }
    else {
      return array(
        'where' => " n.type != 'support_ticket'",
      );
    }
  }
  else {
    if ($primary_table == 'nc' and $primary_field == 'nid' && !strpos($query, '{support_ticket_comment}')) {
      $clients = support_search_available_clients();
      if (!empty($clients)) {
        if (user_access('view other users tickets') || user_access('administer support') || user_access('edit any ticket') || user_access('delete any ticket')) {
          return array(
            'join' => ' LEFT JOIN {support_ticket} st ON nc.nid = st.nid',
            'where' => ' (st.client IN (' . implode(',', $clients) . ')  OR st.client IS NULL)',
          );
        }
        else {
          return array(
            'join' => ' LEFT JOIN {node} n ON nc.nid = n.nid LEFT JOIN {support_ticket} st ON nc.nid = st.nid',
            'where' => ' (st.client IN (' . implode(',', $clients) . ') AND (n.uid = ' . $user->uid . ' OR st.assigned = ' . $user->uid . ")) OR n.type != 'support_ticket'",
          );
        }
      }
      else {
        return array(
          'join' => ' LEFT JOIN {node} n ON nc.nid = n.nid',
          'where' => " n.type != 'support_ticket'",
        );
      }
    }
    else {
      if ($primary_table == 'n' && $primary_field == 'nid' && strpos($query, 'support_ticket_timer') && strpos($query, 't.client IN')) {
        $tid = isset($_GET['tid']) ? $_GET['tid'] : '';
        if ($tid) {
          $alter = array(
            'join' => ' LEFT JOIN {term_node} tn ON n.nid = tn.nid',
          );
          if ($tid == 'null') {
            $alter['where'] = "ISNULL(tn.tid)";
          }
          else {
            if ($tid && $tid == preg_replace('/[^0-9,]/', '', $tid)) {
              $alter['where'] = "tn.tid IN({$tid})";
            }
          }
          return $alter;
        }
      }
      else {
        if ($primary_table == 'c' && $primary_field == 'cid' && strpos($query, 'support_ticket_comment_timer') && strpos($query, 't.client IN')) {
          $tid = isset($_GET['tid']) ? $_GET['tid'] : '';
          if ($tid) {
            $alter = array(
              'join' => ' LEFT JOIN {term_node} tn ON c.nid = tn.nid',
            );
            if ($tid == 'null') {
              $alter['where'] = "ISNULL(tn.tid)";
            }
            else {
              if ($tid && $tid == preg_replace('/[^0-9,]/', '', $tid)) {
                $alter['where'] = "tn.tid IN({$tid})";
              }
            }
            return $alter;
          }
        }
      }
    }
  }
}

/**
 * Return array of client ids which current user has access to.
 */
function support_search_available_clients() {
  static $clids = NULL;
  if (!is_array($clids)) {
    $clids = array();
    $clients = _support_available_clients();
    foreach ($clients as $clid => $client) {
      $clids[] = $clid;
    }
  }
  return $clids;
}

/**
 * Implementation of hook _search.
 */
function support_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
  global $user;
  switch ($op) {
    case 'name':
      if ($skip_access_check || support_access_clients()) {
        return t('Tickets');
      }
      break;
    case 'search':

      // Build matching conditions
      $join = 'INNER JOIN {node} n ON n.nid = i.sid';
      $conditions = "n.status = 1 AND n.type = 'support_ticket'";
      $arguments = array();

      // clients
      if ($client = search_query_extract($keys, 'client')) {
        $clients = array();
        foreach (explode(',', $client) as $c) {
          $clients[] = "jt.client = %d";
          $arguments[] = $c;
        }
        $conditions .= ' AND (' . implode(' OR ', $clients) . ')';
        $keys = search_query_insert($keys, 'client');
      }

      // states
      if ($state = search_query_extract($keys, 'state')) {
        $states = array();
        foreach (explode(',', $state) as $s) {
          $states[] = "jt.state = %d";
          $arguments[] = $s;
        }
        $conditions .= ' AND (' . implode(' OR ', $states) . ')';
        $keys = search_query_insert($keys, 'state');
      }

      // priorities
      if ($priority = search_query_extract($keys, 'priority')) {
        $priorities = array();
        foreach (explode(',', $priority) as $p) {
          $priorities[] = "jt.priority = %d";
          $arguments[] = $p;
        }
        $conditions .= ' AND (' . implode(' OR ', $priorities) . ')';
        $keys = search_query_insert($keys, 'priority');
      }
      $keys = search_query_insert($keys, 'type');
      $clients = support_search_available_clients();
      if (!empty($clients)) {
        $join .= " LEFT JOIN {support_ticket} jt ON n.nid = jt.nid";
        $conditions .= ' AND jt.client IN (' . implode(',', support_search_available_clients()) . ')';
      }
      else {

        // User can not access any tickets
        $conditions .= " AND n.type != 'support_ticket'";
      }
      if (!user_access('view other users tickets') && !user_access('administer support') && !user_access('edit any ticket') && !user_access('delete any ticket')) {
        $conditions .= " AND n.uid = {$user->uid}";
      }

      // Build ranking expression (we try to map each parameter to a uniform
      // distribution in the range 0..1).
      // Tickets are nodes, so use the same ranking.
      $ranking = array();
      $arguments2 = array();
      $join2 = '';

      // Used to avoid joining on node_comment_statistics twice
      $stats_join = FALSE;
      $total = 0;
      if ($weight = (int) variable_get('node_rank_relevance', 5)) {

        // Average relevance values hover around 0.15
        $ranking[] = '%d * i.relevance';
        $arguments2[] = $weight;
        $total += $weight;
      }
      if ($weight = (int) variable_get('node_rank_recent', 5)) {

        // Exponential decay with half-life of 6 months, starting at last indexed node
        $ranking[] = '%d * POW(2, (GREATEST(MAX(n.created), MAX(n.changed), MAX(c.last_comment_timestamp)) - %d) * 6.43e-8)';
        $arguments2[] = $weight;
        $arguments2[] = (int) variable_get('node_cron_last', 0);
        $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
        $stats_join = TRUE;
        $total += $weight;
      }
      if (module_exists('comment') && ($weight = (int) variable_get('node_rank_comments', 5))) {

        // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
        $scale = variable_get('node_cron_comments_scale', 0.0);
        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(c.comment_count) * %f))';
        $arguments2[] = $weight;
        $arguments2[] = $scale;
        if (!$stats_join) {
          $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
        }
        $total += $weight;
      }
      if (module_exists('statistics') && variable_get('statistics_count_content_views', 0) && ($weight = (int) variable_get('node_rank_views', 5))) {

        // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
        $scale = variable_get('node_cron_views_scale', 0.0);
        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(nc.totalcount) * %f))';
        $arguments2[] = $weight;
        $arguments2[] = $scale;
        $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
        $total += $weight;
      }

      // When all search factors are disabled (ie they have a weight of zero),
      // the default score is based only on keyword relevance and there is no need to
      // adjust the score of each item.
      if ($total == 0) {
        $select2 = 'i.relevance AS score';
        $total = 1;
      }
      else {
        $select2 = implode(' + ', $ranking) . ' AS score';
      }

      // Do search.
      $find = do_search($keys, 'node', $join, $conditions, $arguments, $select2, $join2, $arguments2);

      // Load results.
      $results = array();
      foreach ($find as $item) {

        // Build the node body.
        $node = node_load($item->sid);
        $node->build_mode = NODE_BUILD_SEARCH_RESULT;
        $node = node_build_content($node, FALSE, FALSE);
        $node->body = drupal_render($node->content);

        // Fetch comments for snippet.
        $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');

        // Fetch terms for snippet.
        $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
        $extra = node_invoke_nodeapi($node, 'search result');
        if (!is_array($extra)) {
          $extra = array();
        }
        if (sizeof($clients) > 1) {
          $title = check_plain(_support_client($node->client)) . ': ' . $node->title;
        }
        else {
          $title = $node->title;
        }

        // change 'comments' to 'follow ups' for support tickets
        foreach ($extra as $key => $value) {
          $trans = array(
            ' comments' => ' follow ups',
          );
          $extra[$key] = strtr($value, $trans);
        }
        $extra[] = check_plain(_support_state($node->state));
        $extra[] = check_plain(_support_priorities($node->priority));
        $clients = support_search_available_clients();
        $results[] = array(
          'link' => url('node/' . $item->sid, array(
            'absolute' => TRUE,
          )),
          'type' => check_plain(node_get_types('name', $node)),
          'title' => $title,
          'user' => theme('username', $node),
          'date' => $node->changed,
          'node' => $node,
          'extra' => $extra,
          'score' => $item->score / $total,
          'snippet' => search_excerpt($keys, $node->body),
        );
      }
      return $results;
  }
}

/**
 * Generate form for adding update to ticket.  Enhances comment_form adding
 * a ticket status bar.
 */
function support_status_form(&$form_state, $edit, $title) {
  global $user;
  $cid = 0;
  if (is_array($edit)) {
    if (is_array($edit['nid'])) {
      $node = node_load($edit['nid']['#value']);
      $cid = $edit['cid']['#value'];
    }
    else {
      $node = node_load($edit['nid']);
    }
  }
  else {
    $node = $edit;
  }
  $autoassign = _support_autoassign(_support_current_client(), $user->uid);
  if ($autoassign && !isset($node->assigned) && (!isset($node->nid) || !$node->nid)) {

    // This ticket is being created, and this module is configured to
    // auto-assign new tickets.
    $node->assigned = $autoassign;
  }
  if ($cid) {
    $comment = db_fetch_object(db_query('SELECT state, priority FROM {support_ticket_comment} WHERE cid = %d', $cid));
    if ($comment->state && $comment->priority) {
      $node->state = $comment->state;
      $node->priority = $comment->priority;
    }
  }
  if (user_access('can select state') || user_access('can select priority') || user_access('can select client') || user_access('can assign tickets to self') || user_access('can assign tickets to any user') || user_access('administer support') || user_access('can administer state')) {
    $form['support'] = array(
      '#type' => 'fieldset',
      '#prefix' => '<div class="container-inline">',
      '#suffix' => '</div>',
      '#title' => t('Ticket properties'),
    );
  }
  $default = isset($node->state) ? $node->state : _support_state_default();

  /**
   * Disable auto-changing ticket from "new" to "active" state.
    if ($node->uid != $user->uid && $default == _support_state_default()) {
      // We did not create this ticket, but we're updating it.  Suggest that it
      // no longer be marked as new.
      $default = _support_state_secondary();
    }
  **/
  if (!user_access('can select state') && !user_access('administer support') && !user_access('can administer state')) {
    $form['support']['state'] = array(
      '#type' => 'hidden',
      '#value' => $default,
    );
  }
  else {
    if (isset($node->nid) && $node->nid && isset($node->state)) {
      $state = $node->state;
    }
    else {
      $state = 0;
    }
    $form['support']['state'] = array(
      '#type' => 'select',
      '#title' => t('State'),
      '#options' => _support_states(FALSE, $state),
      '#default_value' => $default,
    );
  }
  if (!user_access('can select priority') && !user_access('administer support')) {
    $form['support']['priority'] = array(
      '#type' => 'hidden',
      '#value' => $node->priority ? $node->priority : _support_priority_default(),
    );
  }
  else {
    $form['support']['priority'] = array(
      '#type' => 'select',
      '#prefix' => '&nbsp;&nbsp;',
      '#title' => t('Priority'),
      '#options' => _support_priorities(),
      '#default_value' => isset($node->priority) ? $node->priority : _support_priority_default(),
    );
  }
  $clients = _support_available_clients();
  if (!isset($node->client) || empty($node->client)) {
    if (sizeof($clients) == 1) {
      $node->client = key($clients);
    }
    else {
      if (is_numeric($_SESSION['support_client'])) {
        $node->client = $_SESSION['support_client'];
      }
      else {
        if (!user_access('can select client')) {

          // TODO: It should be possible to set a default client.  Perhaps allow
          // a weight to be assigned to clients -- then we select the heaviest
          // matching client...?
          $node->client = key($clients);
        }
      }
    }
  }
  $available = count($clients);
  if (!$available) {
    drupal_set_message(t('A site administrator must !create a client before you can create support tickets.', array(
      '!create' => l(t('create and enable'), 'admin/support/clients/add'),
    )), 'error');
  }
  $clients = array(
    '- select client -',
  ) + $clients;
  if ($available == 1 || !user_access('can select client') && !user_access('administer support')) {
    $form['support']['client'] = array(
      '#type' => 'hidden',
      '#value' => is_numeric($node->client) ? $node->client : 0,
    );
  }
  else {
    $form['support']['client'] = array(
      '#type' => 'select',
      '#required' => TRUE,
      '#prefix' => '&nbsp;&nbsp;',
      '#title' => t('Client'),
      '#options' => $clients,
      '#default_value' => is_numeric($node->client) ? $node->client : 0,
    );
  }
  if (!user_access('can assign tickets to self') && !user_access('can assign tickets to any user') && !user_access('administer support')) {
    $form['support']['assigned'] = array(
      '#type' => 'hidden',
      '#value' => $node->assigned ? $node->assigned : 0,
    );
  }
  else {
    $options = _support_assigned(isset($node->assigned) ? $node->assigned : 0, $node, variable_get('support_autocomplete_limit', 15));
    if ($options === FALSE) {
      if (isset($node->assigned)) {
        if (is_numeric($node->assigned)) {
          $account = user_load(array(
            'uid' => $node->assigned,
          ));
          $assigned = $account->name;
        }
        else {
          $assigned = $node->assigned;
        }
      }
      else {
        $assigned = '';
      }
      $form['support']['assigned'] = array(
        '#type' => 'textfield',
        '#prefix' => '&nbsp;&nbsp;',
        '#title' => t('Assigned'),
        '#autocomplete_path' => 'support/autocomplete/assigned/' . $node->client,
        '#default_value' => $assigned,
        '#size' => '15',
      );
    }
    else {
      $form['support']['assigned'] = array(
        '#type' => 'select',
        '#prefix' => '&nbsp;&nbsp;',
        '#title' => t('Assigned'),
        '#options' => $options,
        '#default_value' => isset($node->assigned) ? $node->assigned : 0,
      );
    }
  }
  return $form;
}

/**
 * Get list of available users to assign ticket to.
 */
function _support_assigned($assigned, $node, $limit = 9999) {
  global $user;
  static $available = array();
  $counter = 0;
  if (!$limit) {
    return FALSE;
  }
  if (!isset($node->nid)) {
    $node->nid = 0;
  }
  if (!isset($available["{$assigned}-{$node->nid}"])) {
    if ($assigned && $assigned != $user->uid) {
      $account = user_load($assigned);
      $available["{$assigned}-{$node->nid}"][$account->uid] = $account->name;
      $counter++;
    }

    // can always re-assign ticket to self
    $available["{$assigned}-{$node->nid}"][$user->uid] = $user->name;
    $counter++;
    if (is_numeric($node->client) && (user_access('administer support') || user_access('can assign tickets to any user'))) {
      $roles = array();
      $client = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $node->client));

      // retrieve all roles giving permission to access current tickets
      $result = db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%%s%%' OR perm LIKE '%%%s%%'", "access {$client} tickets", 'administer support');
      while ($role = db_fetch_object($result)) {
        $roles[$role->rid] = $role->rid;
      }

      // retrieve all users in appropriate roles
      $accounts = array();
      $all = FALSE;
      foreach ($roles as $rid) {
        if ($rid == DRUPAL_AUTHENTICATED_RID) {
          $all = TRUE;
          $result = db_query_range('SELECT uid FROM {users} WHERE status = 1', 0, $limit);
        }
        else {
          $result = db_query_range('SELECT r.uid FROM {users_roles} r LEFT JOIN {users} u ON r.uid = u.uid WHERE r.rid = %d AND u.status = 1', $rid, 0, $limit);
        }
        while ($account = db_fetch_object($result)) {
          $accounts[$account->uid] = $account->uid;
          $counter++;
          if (!$limit || $counter > $limit) {
            return FALSE;
          }
        }

        // we've already retrieved all active users, no need to search
        // additional roles
        if ($all) {
          break;
        }
      }

      // load users and allow them to be assigned
      foreach ($accounts as $uid) {
        $account = user_load(array(
          'uid' => $uid,
        ));
        $available["{$assigned}-{$node->nid}"][$account->uid] = $account->name;
      }

      // Filter uid1 if support_filter_uid1 enabled; however, don't filter if
      // the ticket is already assigned to uid1, or current user is uid1.
      if ($user->uid != 1 && $assigned != 1 && variable_get('support_filter_uid1', FALSE)) {
        unset($available["{$assigned}-{$node->nid}"][1]);
      }
    }

    // sort by name
    asort($available["{$assigned}-{$node->nid}"]);

    // can only unassign tickets if assigned to self, or have admin permissions
    // (always put this at the top of the array)
    if (!$assigned || $assigned == $user->uid || user_access('can assign tickets to any user') || user_access('administer support')) {
      $available["{$assigned}-{$node->nid}"] = array(
        0 => ' - ' . t('not assigned') . ' -',
      ) + $available["{$assigned}-{$node->nid}"];
    }
  }
  return $available["{$assigned}-{$node->nid}"];
}

/**
 * Provide option to subscribe/unsubscribe from ticket notification emails.
 */
function support_subscribe_form(&$form_state, $edit, $title) {
  global $user;
  $form = array();
  if (is_array($edit)) {
    $node = node_load($edit['nid']['#value']);
  }
  else {
    $node = $edit;
  }
  if (variable_get('support_notifications', TRUE)) {
    $form['subscribe'] = array(
      '#type' => 'fieldset',
      '#title' => t('Notifications'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    if (variable_get('support_autosubscribe_creator', FALSE)) {
      $notification = TRUE;
    }
    else {
      if (is_array($edit) && isset($edit['nid']) && $edit['nid']) {
        $notification = db_result(db_query('SELECT 1 FROM {support_assigned} WHERE nid = %d AND uid = %d', $edit['nid']['#value'], $user->uid));
      }
      else {
        if (is_object($edit) && isset($edit->notification)) {
          $notification = $edit->notification;
        }
        else {
          $notification = TRUE;
        }
      }
    }
    $form['subscribe']['notification'] = array(
      '#type' => 'checkbox',
      '#title' => t('Subscribe'),
      '#description' => t('Receive email notifications when this ticket is updated.'),
      '#default_value' => isset($notification) ? $notification : TRUE,
      '#disabled' => variable_get('support_autosubscribe_creator', FALSE) || variable_get('support_autosubscribe_assigned', FALSE) && isset($node->assigned) && $node->assigned == $user->uid,
    );
    if (user_access('can suppress notification')) {
      $form['subscribe']['suppress'] = array(
        '#type' => 'checkbox',
        '#title' => t('Suppress notification'),
        '#description' => t('By checking this box you will prevent notification emails from being sent for this ticket update.  It is recommended that you check this box if you are adding sensitive information such as passwords which should not be mailed out in plain text.%admin', array(
          '%admin' => user_access('administer support') ? t(' Users with "administer support" permission will still receive email notifications telling them the ticket was updated but with the body text suppressed; no notifications will be sent to users without "administer support" permissions.') : '',
        )),
        '#default_value' => isset($edit->suppress) ? $edit->suppress : 0,
      );
    }
    if (isset($edit) && (user_access('administer support') || user_access('can subscribe other users to notifications'))) {
      $form['subscribe']['subscribed'] = array(
        '#type' => 'fieldset',
        '#title' => t('Subscribed'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $available = _support_assigned(0, $node, variable_get('support_autocomplete_limit', 15));
      if ($available === FALSE) {
        $account = user_load(array(
          'uid' => $node->assigned,
        ));
        $accounts = array();
        if (isset($node->nid)) {
          $result = db_query('SELECT uid FROM {support_assigned} WHERE nid = %d', $node->nid);
          while ($a = db_fetch_object($result)) {
            $account = user_load(array(
              'uid' => $a->uid,
            ));
            $accounts[] = $account->name;
          }
        }
        if (!empty($accounts)) {
          $default = implode(', ', $accounts);
        }
        else {
          $default = '';
        }
        $form['subscribe']['subscribed']['subscribed-users'] = array(
          '#type' => 'textfield',
          '#autocomplete_path' => 'support/autocomplete/autosubscribe/' . $node->client,
          '#default_value' => $default,
          '#description' => t('Enter a comma separated list of users that should receive notifications when this ticket is updated.'),
        );
      }
      else {
        $notifications = array();
        foreach ($available as $uid => $name) {
          if (!$uid) {
            continue;
          }
          if (isset($node->nid) && $node->nid) {
            $enabled = db_result(db_query('SELECT 1 FROM {support_assigned} WHERE nid = %d AND uid = %d', $node->nid, $uid));
            if (!isset($enabled)) {
              $enabled = _support_enabled($node->client, $uid);
            }
          }
          else {
            if ($uid == $user->uid) {
              $enabled = TRUE;
            }
            else {
              $autoassign = _support_autoassign($node->client, $node->uid);
              if ($autoassign && $autoassign == $uid) {
                $enabled = TRUE;
              }
              else {
                $enabled = _support_enabled($node->client, $uid);
              }
            }
          }
          if (variable_get('support_autosubscribe_force', FALSE)) {
            $autosubscribed = _support_autosubscribe($node->nid, $node->client, FALSE);
          }
          else {
            $autosubscribed = array();
          }
          if (variable_get('support_autosubscribe_assigned', FALSE) && isset($node->assigned) && $node->assigned == $uid) {
            $enabled = TRUE;
            $disabled = TRUE;
          }
          else {
            $disabled = FALSE;
          }
          $notifications[] = $uid;
          $form['subscribe']['subscribed']["notify-{$uid}"] = array(
            '#type' => 'checkbox',
            '#title' => check_plain($name),
            '#default_value' => $enabled,
            '#disabled' => ($uid == $user->uid || isset($autosubscribed[$uid]) || $disabled) && $enabled ? TRUE : FALSE,
          );
        }
        $form['subscribe']['subscribed']['notifications'] = array(
          '#type' => 'hidden',
          '#value' => implode(',', $notifications),
        );
      }
    }
  }
  return $form;
}

/**
 * Load all active clients.
 */
function _support_clients_load($path = FALSE) {
  $result = db_query('SELECT clid, path, name FROM {support_client} WHERE status = 1 ORDER BY name');
  while ($client = db_fetch_object($result)) {
    if ($path) {
      $clients[$client->clid] = check_plain($client->path);
    }
    else {
      $clients[$client->clid] = check_plain($client->name);
    }
  }
  drupal_alter('support_clients_load', $clients);
  return $clients;
}

/**
 * Access callback for user support ticket pages.
 */
function support_page_user_access($account) {
  return $account->uid && user_access('access content') && (user_access('create tickets', $account) || _support_ticket_exists($account));
}

/**
 * Helper function to list available states.
 */
function _support_states($all = TRUE, $sid = NULL, $account = NULL) {
  static $states = array();
  $admin = user_access('can administer state', $account);
  if (!isset($states["{$admin}-{$all}-{$sid}"])) {
    if ($admin || $all) {
      $result = db_query("SELECT sid, state FROM {support_states} ORDER BY weight");
    }
    else {
      if (!$all && !$sid) {
        $result = db_query("SELECT sid, state FROM {support_states} WHERE phase1 = 1 ORDER BY weight");
      }
      else {
        if (!$all) {
          $result = db_query("SELECT sid, state FROM {support_states} WHERE phase2 = 1 ORDER BY weight");
        }
      }
    }
    while ($state = db_fetch_object($result)) {
      $states["{$admin}-{$all}-{$sid}"][$state->sid] = $state->state;
    }

    // include the current state, even if user doesn't actually have access
    if ($sid && !in_array($sid, $states["{$admin}-{$all}-{$sid}"])) {
      $states["{$admin}-{$all}-{$sid}"][$sid] = db_result(db_query("SELECT state FROM {support_states} WHERE sid = %d", $sid));
    }
  }
  return $states["{$admin}-{$all}-{$sid}"];
}

/**
 * Return default sid.
 */
function _support_state_default() {
  static $default = NULL;
  if (!$default) {
    $default = db_result(db_query_range('SELECT sid FROM {support_states} WHERE isdefault = 1 ORDER BY weight ASC', 0, 1));
  }
  return $default;
}

/**
 * Return secondary sid.
 */
function _support_state_secondary() {
  static $secondary = NULL;
  if (!$secondary) {
    $secondary = db_result(db_query_range('SELECT sid FROM {support_states} WHERE phase2 = 1 ORDER BY weight ASC', 0, 1));
  }
  return $secondary;
}

/**
 * Helper function to list available priorities.
 */
function _support_priorities($pid = NULL) {
  static $priorities = array();
  if (empty($priorities)) {
    $result = db_query('SELECT pid, priority FROM {support_priority} ORDER BY weight');
    while ($priority = db_fetch_object($result)) {
      $priorities[$priority->pid] = $priority->priority;
    }
  }
  if ($pid && isset($priorities[$pid])) {
    return $priorities[$pid];
  }
  if ($pid === 0) {
    return '';
  }
  else {
    return $priorities;
  }
}

/**
 * Return default pid.
 */
function _support_priority_default() {
  static $default = NULL;
  if (!$default) {
    $default = db_result(db_query_range('SELECT pid FROM {support_priority} WHERE isdefault = 1', 0, 1));
  }
  return $default;
}

/**
 * Helper function to determine if a user has support tickets already.
 */
function _support_ticket_exists($account) {
  return (bool) db_result(db_query_range(db_rewrite_sql("SELECT 1 FROM {node} n WHERE n.type = 'support_ticket' AND n.uid = %d AND n.status = 1"), $account->uid, 0, 1));
}

/**
 * Helper function, retrieve state name from database.
 */
function _support_state($state) {
  static $state_name = array();
  if (!isset($state_name[$state])) {
    $state_name[$state] = db_result(db_query('SELECT state FROM {support_states} WHERE sid = %d', $state));
  }
  return $state_name[$state];
}

/**
 * Find all clients we have permission to view/edit.
 */
function _support_available_clients($account = NULL) {
  global $user;
  static $valid = array();
  if (is_null($account) || !isset($account->uid)) {
    $account = $user;
  }
  if (!isset($valid[$account->uid])) {
    $clients = _support_clients_load();
    if (is_array($clients) && !empty($clients)) {
      foreach ($clients as $clid => $name) {
        if (user_access('administer support', $account) || user_access("access {$name} tickets", $account)) {
          $valid[$account->uid][$clid] = $name;
        }
      }
    }
    else {
      watchdog('support', t('There are no support clients configured/enabled.'), NULL, WATCHDOG_WARNING, l(t('add client'), 'admin/support/clients/add'));
    }
  }
  return isset($valid[$account->uid]) ? $valid[$account->uid] : array();
}

/**
 *
 */
function _support_get_state($state) {
  if ($state == 'all') {
    return 0;
  }
  else {
    if ($state == 'all open') {
      return -1;
    }
    else {
      if ($state == 'my open') {
        return -2;
      }
      else {
        if ($state == SUPPORT_STATE_CLOSED) {
          $result = db_query("SELECT sid FROM {support_states} WHERE isclosed = 1");
          $states = array();
          while ($state = db_fetch_object($result)) {
            $states[$state->sid] = $state->sid;
          }
          return $states;
        }
      }
    }
  }
  $sid = db_result(db_query("SELECT sid FROM {support_states} WHERE state = '%s'", $state));
  if (!$sid) {
    $sid = _support_state_default();
  }
  return $sid;
}
function _support_truncate($text, $maxlen = 64) {
  return truncate_utf8($text, $maxlen - 1, TRUE, TRUE);
}

/**
 * Form builder; Return form for support ticket listing filters.
 */
function support_filter_form($client, $filters, $state) {

  // Ignore these values when determining if a given filter is active.
  unset($filters['join']);
  unset($filters['where']);
  unset($filters['or']);
  $form = array();
  $form['filter'] = array(
    '#type' => 'fieldset',
    '#title' => t('Filter by'),
    '#collapsible' => TRUE,
    '#collapsed' => empty($filters),
  );
  $options = _support_client_terms($client, $state);
  if (count($options)) {

    // Filter by taxonomy.
    $form['filter']['tags'] = array(
      '#type' => 'fieldset',
      '#title' => t('Tags'),
      '#collapsible' => TRUE,
      '#collapsed' => empty($filters['tid']),
    );
    $form['filter']['tags']['tid'] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#options' => $options,
      '#size' => count($options) > 10 ? 10 : 5,
      '#default_value' => isset($filters['tid']) ? $filters['tid'] : 0,
    );
  }

  // No point in filtering by user if showing only self-assigned tickets.
  if ($state != -2) {
    $options = array(
      '-1' => '- not assigned -',
    );
    if ($users = _support_client_users($client)) {
      $options += $users;
    }
    unset($options[0]);
    if (count($options)) {

      // Filter by who's assigned.
      $form['filter']['assigned'] = array(
        '#type' => 'fieldset',
        '#title' => t('Assigned'),
        '#collapsible' => TRUE,
        '#collapsed' => empty($filters['uid']),
      );
      $form['filter']['assigned']['uid'] = array(
        '#type' => 'select',
        '#multiple' => TRUE,
        '#options' => $options,
        '#default_value' => isset($filters['uid']) ? $filters['uid'] : 0,
      );
    }
  }

  // No point in filtering by state if only showing tickets in a specific state.
  if ($state < 1) {

    // Filter by states.
    $form['filter']['state'] = array(
      '#type' => 'fieldset',
      '#title' => t('State'),
      '#collapsible' => TRUE,
      '#collapsed' => empty($filters['sid']),
    );
    $options = _support_states();
    $form['filter']['state']['sid'] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#options' => $options,
      '#default_value' => isset($filters['sid']) ? $filters['sid'] : 0,
    );
  }

  // Filter by priorities.
  $form['filter']['priority'] = array(
    '#type' => 'fieldset',
    '#title' => t('Priority'),
    '#collapsible' => TRUE,
    '#collapsed' => empty($filters['pid']),
  );
  $options = _support_priorities();
  $form['filter']['priority']['pid'] = array(
    '#type' => 'select',
    '#multiple' => TRUE,
    '#options' => $options,
    '#default_value' => isset($filters['pid']) ? $filters['pid'] : 0,
  );

  // Allow filtering by client if there's more than 1.
  if (count($client) > 1) {
    $form['filter']['client'] = array(
      '#type' => 'fieldset',
      '#title' => t('Client'),
      '#collapsible' => TRUE,
      '#collapsed' => empty($filters['clid']),
    );
    $options = _support_access_tickets(FALSE);
    $form['filter']['client']['clid'] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#options' => $options,
      '#default_value' => isset($filters['clid']) ? $filters['clid'] : 0,
    );
  }
  $form['filter']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Update'),
    '#validate' => array(
      'support_filter_form_validate',
    ),
    '#submit' => array(
      'support_filter_form_submit',
    ),
  );
  $form['filter']['markup'] = array(
    '#type' => 'markup',
    '#value' => l(t('Reset'), 'support'),
  );
  return $form;
}

/**
 * Validate the support_filter_form.
 */
function support_filter_form_validate($form, &$form_state) {

  // Nothing to do here at this time.
}

/**
 * Convert tid array to comma seperated list for URL.
 */
function support_filter_form_submit($form, &$form_state) {
  $query = array();
  if (!empty($form_state['values']['tid'])) {
    $query['tid'] = implode(',', $form_state['values']['tid']);
  }
  if (!empty($form_state['values']['uid'])) {
    $query['uid'] = implode(',', $form_state['values']['uid']);
  }
  if (!empty($form_state['values']['sid'])) {
    $query['sid'] = implode(',', $form_state['values']['sid']);
  }
  if (!empty($form_state['values']['pid'])) {
    $query['pid'] = implode(',', $form_state['values']['pid']);
  }
  if (!empty($form_state['values']['clid'])) {
    $query['clid'] = implode(',', $form_state['values']['clid']);
  }
  $form_state['support_filter_query'] = $query;
  drupal_alter('support_filter_form_submit', $form_state);
  $path = drupal_get_path_alias(isset($_GET['q']) ? $_GET['q'] : '');
  drupal_goto($path, $form_state['support_filter_query']);
}

/**
 * Display tickets
 */
function support_page_form(&$form_state, $client = NULL, $state = NULL) {
  global $user;
  $form = array();

  // Be sure a client is selected.  If not, select the last visited client.
  if (!$client) {
    if (isset($_SESSION['support_client']) && ($client = support_client_load($_SESSION['support_client']))) {
      unset($_SESSION['support_client']);
      if ($client->parent) {
        $parent = support_client_load($client->parent);
        drupal_goto("support/{$parent->path}/{$client->path}");
      }
      else {
        drupal_goto("support/{$client->path}");
      }
    }
    $clients = _support_available_clients();
    if (count($clients)) {
      foreach ($clients as $key => $name) {
        $client = support_client_load($key);
        if ($client->parent) {
          $parent = support_client_load($client->parent);
          drupal_goto("support/{$parent->path}/{$client->path}");
        }
        else {
          drupal_goto("support/{$client->path}");
        }
      }
    }
  }
  else {
    if (is_numeric($client)) {
      $_SESSION['support_client'] = $client;
    }
    else {
      drupal_set_message(t('Client does not exist or is not enabled.'), 'error');
      if (isset($_SESSION['support_client'])) {
        unset($_SESSION['support_client']);
      }
      drupal_goto('');
    }
  }
  if ($client == SUPPORT_ALL_CLIENTS) {
    $client = array_keys(_support_access_tickets(FALSE));
    $all_clients = TRUE;
  }
  else {
    $client = array(
      $client,
    );
    $all_clients = FALSE;
  }
  if (!$state) {
    $state = 'all open';
  }
  $state = _support_get_state($state);

  // Optionally filter ticket listing.
  $available = array(
    'tid',
    'uid',
    'sid',
    'pid',
    'clid',
  );
  $filters = array();
  foreach ($available as $filter) {
    $to_filter = isset($_GET[$filter]) ? $_GET[$filter] : '';
    $unsanitized = explode(',', $to_filter);
    foreach ($unsanitized as $element) {
      $element = (int) $element;
      if ($element) {
        $filters[$filter][$element] = $element;
      }
    }
  }
  $join = $where = $or = array();
  if (!empty($filters)) {
    if (isset($filters['tid']) && !empty($filters['tid'])) {
      $join[] = 'LEFT JOIN {term_node} tn ON n.nid = tn.nid';
      $where[] = strtr('tn.tid IN (!tid)', array(
        '!tid' => implode(',', $filters['tid']),
      ));
    }
    if (isset($filters['uid']) && !empty($filters['uid'])) {
      if (isset($filters['uid'][-1])) {
        $filters['uid'][-1] = 0;
      }
      $where[] = strtr('t.assigned IN (!uid)', array(
        '!uid' => implode(',', $filters['uid']),
      ));
      if (isset($filters['uid'][-1])) {
        $filters['uid'][-1] = -1;
      }
    }
    if (isset($filters['sid']) && !empty($filters['sid'])) {
      $where[] = strtr('t.state IN (!sid)', array(
        '!sid' => implode(',', $filters['sid']),
      ));
    }
    if (isset($filters['pid']) && !empty($filters['pid'])) {
      $where[] = strtr('t.priority IN (!pid)', array(
        '!pid' => implode(',', $filters['pid']),
      ));
    }
    if (isset($filters['clid']) && !empty($filters['clid'])) {
      $where[] = strtr('t.client IN (!clid)', array(
        '!clid' => implode(',', $filters['clid']),
      ));
    }
  }
  $filters['join'] = $join;
  $filters['where'] = $where;
  $filters['or'] = $or;
  drupal_alter('support_ticket_listing_filter', $filters);
  if (!empty($filters['where'])) {
    array_unshift($filters['where'], ' ');
  }

  // Allow filtering of ticket listing.
  $form += support_filter_form($client, $filters, $state);
  $form['post-ticket'] = array(
    '#type' => 'markup',
    '#value' => l(t('Post new support ticket'), 'node/add/support-ticket'),
  );
  $checkboxes = user_access('edit multiple tickets') || user_access('administer support') ? theme('table_select_header_cell') : array();
  $sort = variable_get('support_default_sort_tickets', SUPPORT_SORT_UPDATE);
  if (variable_get('support_default_sort_order', SUPPORT_SORT_DESC) == SUPPORT_SORT_DESC) {
    $order = 'desc';
  }
  else {
    $order = 'asc';
  }
  foreach (array(
    SUPPORT_SORT_UPDATE => array(
      'data' => t('Updated'),
      'field' => 'last_updated',
    ),
    SUPPORT_SORT_NID => array(
      'data' => t('Id'),
      'field' => 'n.nid',
    ),
    SUPPORT_SORT_STATE => array(
      'data' => t('State'),
      'field' => 't.state',
    ),
    SUPPORT_SORT_PRIORITY => array(
      'data' => t('Priority'),
      'field' => 'spri.weight',
    ),
  ) as $key => $array) {
    if ($sort == $key) {
      $headers[$key] = $array + array(
        'sort' => $order,
      );
    }
    else {
      $headers[$key] = $array;
    }
  }
  $form['header'] = array(
    '#type' => 'value',
    '#value' => array(
      $checkboxes,
      $headers[SUPPORT_SORT_NID],
      array(
        'data' => t('Ticket'),
        'field' => 'n.title',
      ),
      $headers[SUPPORT_SORT_UPDATE],
      array(
        'data' => t('Reported by'),
        'field' => 'n.uid',
      ),
      array(
        'data' => t('Assigned to'),
        'field' => 't.assigned',
      ),
      $headers[SUPPORT_SORT_STATE],
      $headers[SUPPORT_SORT_PRIORITY],
      array(
        'data' => t('Updates'),
        'field' => 'l.comment_count',
      ),
    ),
  );
  if ($all_clients) {
    $form['header']['#value'][] = array(
      'data' => t('Client'),
      'field' => 'sc.name',
    );
    $form['all_clients']['#value'] = TRUE;
  }
  if ($state == -2) {
    $my_open = "AND t.assigned = {$user->uid}";
  }
  else {
    $my_open = '';
  }
  if ($state < 0) {
    $state = 'AND t.state NOT IN (' . implode(', ', _support_get_state(SUPPORT_STATE_CLOSED)) . ')';
  }
  else {
    $state = $state ? "AND t.state = {$state}" : '';
  }
  $sql = "SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count, t.state, t.priority, t.assigned, t.client, sc.name FROM {node} n LEFT JOIN {support_ticket} t ON n.nid = t.nid LEFT JOIN {support_client} sc ON t.client = sc.clid LEFT JOIN {support_priority} spri ON t.priority = spri.pid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid " . implode(' ', $filters['join']) . " WHERE (n.status = 1 AND n.type = 'support_ticket' AND client IN(" . implode(',', $client) . ") {$state} {$my_open} " . implode(' AND ', $filters['where']);
  if (!user_access('view other users tickets') && !user_access('administer support') && !user_access('edit any ticket') && !user_access('delete any ticket')) {
    $sql .= " AND (n.uid = {$user->uid} OR t.assigned = {$user->uid})";
  }
  $sql .= ')';
  if (!empty($filters['or'])) {
    $sql .= ' OR (' . implode(' OR ', $filters['or']) . ')';
  }
  $sql = db_rewrite_sql($sql);
  $sql .= tablesort_sql($form['header']['#value']);
  if (variable_get('support_secondary_sort_order', SUPPORT_SORT_DESC) == SUPPORT_SORT_DESC) {
    $order = 'desc';
  }
  else {
    $order = 'asc';
  }
  switch (variable_get('support_secondary_sort_tickets', SUPPORT_SORT_NONE)) {
    case SUPPORT_SORT_UPDATE:
      $sql .= ", last_updated {$order}";
      break;
    case SUPPORT_SORT_NID:
      $sql .= ", n.nid {$order}";
      break;
    case SUPPORT_SORT_STATE:
      $sql .= ", t.state {$order}";
      break;
    case SUPPORT_SORT_PRIORITY:
      $sql .= ", t.priority {$order}";
      break;
  }
  $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = 0 OR c.status IS NULL) LEFT JOIN {support_ticket} t ON n.nid = t.nid ' . implode(' ', $filters['join']) . ' WHERE n.status = 1 AND client IN(' . implode(',', $client) . ") {$state} {$my_open} " . implode(' AND ', $filters['where']);
  $sql_count = db_rewrite_sql($sql_count);
  $result = pager_query($sql, 50, 0, $sql_count);
  $rows = array();
  $tickets = array();
  while ($ticket = db_fetch_object($result)) {
    drupal_alter('support_page_list_ticket', $ticket);
    $account = user_load(array(
      'uid' => $ticket->uid,
    ));
    $assigned = user_load(array(
      'uid' => $ticket->assigned,
    ));
    $comments = l($ticket->comment_count, "node/{$ticket->nid}", array(
      'fragment' => 'comments',
    ));
    if ($new = comment_num_new($ticket->nid)) {
      $node = node_load($ticket->nid);
      $comments .= '&nbsp;(' . l(format_plural($new, '1 new', '@count new'), "node/{$ticket->nid}", array(
        'query' => comment_new_page_count($node->comment_count, $new, $node),
        'fragment' => 'new',
      )) . ')';
    }
    $tickets[$ticket->nid] = '';
    $form['id'][$ticket->nid] = array(
      '#value' => l($ticket->nid, "node/{$ticket->nid}", array(
        'attributes' => array(
          'class' => 'ticket-id',
        ),
      )),
    );
    $form['title'][$ticket->nid] = array(
      '#value' => l(_support_truncate($ticket->title), "node/{$ticket->nid}", array(
        'attributes' => array(
          'class' => 'ticket-title',
        ),
      )),
    );
    $form['updated'][$ticket->nid] = array(
      '#value' => format_date($ticket->last_updated, 'small', array(
        'attributes' => array(
          'class' => 'ticket-updated',
        ),
      )),
    );
    $form['reported'][$ticket->nid] = array(
      '#value' => l(_support_truncate($account->name, 24), "user/{$account->uid}", array(
        'attributes' => array(
          'class' => 'ticket-reported',
        ),
      )),
    );

    // Assigned to
    if (user_access('edit multiple tickets') && user_access('can assign tickets to any user') || user_access('administer support')) {
      $node = node_load($ticket->nid);
      $options = _support_assigned(isset($assigned->uid) ? $assigned->uid : 0, $node, variable_get('support_autocomplete_limit', 15));
      if ($options === FALSE) {
        if (isset($ticket->assigned)) {
          if (is_numeric($ticket->assigned)) {
            $account = user_load(array(
              'uid' => $ticket->assigned,
            ));
            $assigned = $account->name;
          }
          else {
            $assigned = $ticket->assigned;
          }
        }
        else {
          $assigned = '';
        }
        $form['assigned']["assigned-{$ticket->nid}"] = array(
          '#type' => 'textfield',
          '#autocomplete_path' => 'support/autocomplete/assigned/' . $ticket->client,
          '#default_value' => $assigned,
          '#size' => '15',
          '#attributes' => array(
            'class' => 'ticket-assigned',
          ),
        );
      }
      else {
        $form['assigned']["assigned-{$ticket->nid}"] = array(
          '#type' => 'select',
          '#options' => $options,
          '#default_value' => isset($ticket->assigned) ? $ticket->assigned : 0,
          '#attributes' => array(
            'class' => 'ticket-assigned',
          ),
        );
      }
    }
    else {
      $form['assigned']["assigned-{$ticket->nid}"] = array(
        '#value' => l(_support_truncate($assigned->name, 24), "user/{$assigned->uid}", array(
          'attributes' => array(
            'class' => 'ticket-assigned',
          ),
        )),
      );
    }

    // State
    $states = _support_states(FALSE);
    if ((user_access('edit multiple tickets') || user_access('administer support')) && sizeof($states) > 1) {
      $form['state']["state-{$ticket->nid}"] = array(
        '#type' => 'select',
        '#options' => $states,
        '#default_value' => $ticket->state,
        '#attributes' => array(
          'class' => 'ticket-state',
        ),
      );
    }
    else {
      $form['state']["state-{$ticket->nid}"] = array(
        '#value' => _support_state($ticket->state),
        '#attributes' => array(
          'class' => 'ticket-state',
        ),
      );
    }

    // Priority
    if (user_access('administer support') || user_access('edit multiple tickets') && user_access('can select priority')) {
      $form['priority']["priority-{$ticket->nid}"] = array(
        '#type' => 'select',
        '#options' => _support_priorities(),
        '#default_value' => $ticket->priority,
        '#attributes' => array(
          'class' => 'ticket-priority',
        ),
      );
    }
    else {
      $form['priority']["priority-{$ticket->nid}"] = array(
        '#value' => _support_priorities($ticket->priority),
        '#attributes' => array(
          'class' => 'ticket-priority',
        ),
      );
    }
    $form['updates'][$ticket->nid] = array(
      '#value' => $comments,
      '#attributes' => array(
        'class' => "state-{$ticket->state} priority-{$ticket->priority}",
      ),
    );
    $form['class'][$ticket->nid] = array(
      '#value' => "state-{$ticket->state} priority-{$ticket->priority}",
    );
    if ($all_clients) {
      $form['client'][$ticket->nid] = array(
        '#value' => _support_client($ticket->client),
        '#attributes' => array(
          'class' => "state-{$ticket->state} priority-{$ticket->priority}",
        ),
      );
    }
  }
  $form['tickets'] = array(
    '#type' => 'checkboxes',
    '#options' => count($tickets) && (user_access('edit multiple tickets') || user_access('administer support')) ? $tickets : array(),
  );
  if (count($tickets) && (user_access('edit multiple tickets') || user_access('administer support'))) {
    $form['update'] = array(
      '#title' => t('Update'),
      '#type' => 'textarea',
      '#description' => t('This text will be added to all selected tickets.'),
    );
    if (user_access('can suppress notification') && variable_get('support_notifications', TRUE)) {
      $form['suppress'] = array(
        '#type' => 'checkbox',
        '#title' => t('Suppress notification'),
        '#description' => t('By checking this box you will prevent notification emails from being sent for this update.'),
        '#default_value' => isset($edit->suppress) ? $edit->suppress : 0,
      );
    }
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Update ticket'),
    );
  }
  $form['pager'] = array(
    '#value' => theme('pager', NULL, 50, 0),
  );
  return $form;
}

/**
 * Validate that at least one ticket is selected.
 */
function support_page_form_validate($form, &$form_state) {
  $form_state['values']['tickets'] = array_diff($form_state['values']['tickets'], array(
    0,
  ));
  if (count($form_state['values']['tickets']) == 0) {
    form_set_error('', t('Please select one or more tickets to perform the update on.'));
  }
  if (variable_get('support_require_comment', TRUE)) {
    if (!$form_state['values']['update']) {
      form_set_error('update', t('You must enter a ticket update.'));
    }
  }
}

/**
 * Update selected tickets.
 */
function support_page_form_submit($form, &$form_state) {
  global $user;
  foreach ($form_state['values']['tickets'] as $nid) {
    $message = array();
    $message['uid'] = $user->uid;
    $message['nid'] = $nid;

    // TODO: Allow the optional addition of a subject
    $message['subject'] = '';
    $message['body'] = $form_state['values']['update'];
    $message['attachments'] = array();
    $message['suppress'] = isset($form_state['values']['suppress']) ? $form_state['values']['suppress'] : FALSE;
    $message['state'] = $form_state['values']["state-{$nid}"];
    $message['priority'] = $form_state['values']["priority-{$nid}"];
    $message['assigned'] = $form_state['values']["assigned-{$nid}"];

    // TODO: Pass the client in rather than make unnecessary queries
    $clid = db_result(db_query('SELECT client FROM {support_ticket} WHERE nid = %d', $nid));
    $client = support_client_load($clid);

    // re-use email handling to save ticket updates
    support_save_message($message, $client);
  }
}

/**
 * Theme function for ticket list
 */
function theme_support_page_form($form) {
  drupal_add_css(drupal_get_path('module', 'support') . '/support-tickets.css');
  $output = drupal_render($form['filter']);
  $output .= drupal_render($form['post-ticket']);
  if (isset($form['title']) && is_array($form['title'])) {
    foreach (element_children($form['title']) as $key) {
      $row = array();
      $row[] = drupal_render($form['tickets'][$key]);
      $row[] = drupal_render($form['id'][$key]);
      $row[] = drupal_render($form['title'][$key]);
      $row[] = drupal_render($form['updated'][$key]);
      $row[] = drupal_render($form['reported'][$key]);
      $row[] = drupal_render($form['assigned']["assigned-{$key}"]);
      $row[] = drupal_render($form['state']["state-{$key}"]);
      $row[] = drupal_render($form['priority']["priority-{$key}"]);
      $row[] = drupal_render($form['updates'][$key]);
      if ($form['all_clients']) {
        $row[] = drupal_render($form['client'][$key]);
      }
      $rows[] = array(
        'data' => $row,
        'class' => $form['class'][$key]['#value'],
      );
      unset($form['class'][$key]);
    }
    unset($form['all_clients']);
  }
  else {
    $rows[] = array(
      array(
        'data' => t('No tickets available.'),
        'colspan' => '9',
      ),
    );
  }
  if ($form['pager']['#value']) {
    $output .= drupal_render($form['pager']);
  }
  $output .= theme('table', $form['header']['#value'], $rows, array(
    'class' => 'support',
  ));
  $output .= drupal_render($form['update']);
  $output .= drupal_render($form['suppress']);
  $output .= drupal_render($form['submit']);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Helper function, retrieve client name from database.
 */
function _support_client($clid) {
  static $client_name = array();
  if (!isset($client_name[$clid])) {
    $client_name[$clid] = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $clid));
  }
  return $client_name[$clid];
}

/**
 * Fetch mail for a specific client.
 */
function support_client_fetch($client, $manual = TRUE) {
  if (!isset($client->integrate_email) || $client->integrate_email != TRUE) {
    if ($manual) {
      drupal_set_message(t('Client not integrated with email, unable to fetch mail for !client.', array(
        '!client' => $client->name,
      )));
      drupal_goto('admin/support/clients');
    }
    return;
  }
  set_time_limit(0);
  if ($manual) {
    drupal_set_message(t('Fetching mail for !client...', array(
      '!client' => $client->name,
    )));
  }
  $connect = '{' . $client->server_name . ':' . $client->port;
  $username = $client->server_username;
  $password = $client->server_password;
  $extra = $client->extra;
  switch ($client->protocol) {
    case 0:

      // POP3
      $connect .= "/pop3{$extra}}" . $client->mailbox;
      break;
    case 1:

      // POP3S
      $connect .= "/pop3/ssl{$extra}}" . $client->mailbox;
      break;
    case 2:

      // IMAP
      $connect .= "{$extra}}" . $client->mailbox;
      break;
    case 3:

      // IMAPS
      $connect .= "/imap/ssl{$extra}}" . $client->mailbox;
      break;
    case 4:

      // Local file
      $connect = $client->mailbox;
      $username = $password = '';

      // sanity tests
      if (!file_exists($connect) && $manual) {
        drupal_set_message(t('Mail file "%connect" does not exist.', array(
          '%connect' => $connect,
        )), 'error');
      }
      else {
        if (!is_readable($connect) && $manual) {
          drupal_set_message(t('Mail file "%connect" is not readable.', array(
            '%connect' => $connect,
          )), 'error');
        }
        else {
          if (!is_writable($connect) && $manual) {
            drupal_set_message(t('Mail file "%connect" is not writable.', array(
              '%connect' => $connect,
            )));
          }
        }
      }
      break;
  }

  // Make a connection to the mail server.
  $stream = imap_open($connect, $username, $password);
  if ($stream === FALSE) {
    if ($manual) {
      drupal_set_message(t('Failed to download messages for %client, connection to mail server failed.', array(
        '%client' => $client->name,
      ), 'error'));
      if (user_access('administer support')) {

        // Dump additional debug if manually calling as administrator
        drupal_set_message(t('Mail server connection failure: connect(!connect), username(!username), password(!password)', array(
          '!connect' => $connect,
          '!username' => $username,
          '!password' => $password,
        )), 'error');
      }
    }
    if ($alerts = imap_alerts()) {
      foreach ($alerts as $alert) {
        watchdog('support', 'Imap alert: %alert for user %username', array(
          '%alert' => $alert,
          '%username' => $username,
        ));
        if ($manual) {
          drupal_set_message(t('Imap alert: %alert for user %username', array(
            '%alert' => $alert,
            '%username' => $username,
          )), 'error');
        }
      }
    }
    if ($errors = imap_errors()) {
      foreach ($errors as $error) {
        watchdog('support', 'Imap error: %error for user %username', array(
          '%error' => $error,
          '%username' => $username,
        ), WATCHDOG_NOTICE);
        if ($manual) {
          drupal_set_message(t('Imap error: %error for user %username', array(
            '%error' => $error,
            '%username' => $username,
          )), 'error');
        }
      }
    }
    if ($manual) {
      drupal_goto('admin/support/clients');
    }
    else {
      return -1;
    }
  }
  $messages_downloaded = 0;

  // check how many messages are available
  $messages_to_download = imap_num_msg($stream);
  $messages_limit = variable_get('support_download_limit', 1000);
  if ($messages_limit && $messages_limit < $messages_to_download) {

    // TODO: watchdog, there are more messages available
    $messages_to_download = $messages_limit;
  }
  for ($number = 1; $number <= $messages_to_download; $number++) {
    $message = array();
    $message['headers'] = imap_headerinfo($stream, $number);
    $message['headers_raw'] = imap_fetchbody($stream, $number, 0);
    if (is_array($message['headers']->from)) {
      $message['from'] = $message['headers']->from[0]->mailbox . '@' . $message['headers']->from[0]->host;
    }
    $strings = imap_mime_header_decode($message['headers']->subject);
    $message['subject'] = '';
    foreach ($strings as $string) {
      if ($string->charset == 'default') {
        $message['subject'] .= $string->text;
      }
      else {
        $message['subject'] .= drupal_convert_to_utf8($string->text, $string->charset);
      }
    }
    _support_identify_ticket($client, $message);
    $structure = imap_fetchstructure($stream, $number);
    $mime = _support_get_filemime($structure);
    $message['body'] = _support_get_message_body_part($stream, $number, $mime, $structure);
    if (isset($structure->parts)) {
      $parts = count($structure->parts);
      if ($parts > 1) {
        $message['attachments'] = _support_get_attachments($stream, $number, $structure, $parts);
      }
    }
    drupal_alter('support_fetch_message', $message, $client);
    $saved = support_save_message($message, $client, $manual);
    $messages_downloaded++;

    // mark message for deletion
    imap_delete($stream, $number);
  }
  imap_close($stream, CL_EXPUNGE);
  if ($manual) {
    drupal_set_message(t('Downloaded !count', array(
      '!count' => format_plural($messages_downloaded, '1 message', '@count messages'),
    )));
    drupal_goto('admin/support/clients');
  }
}

/**
 * Search for an existing ticket to associate an incoming message with.
 *
 * @param &$client
 *   Client object.
 * @param &$message
 *   Message being processed.
 */
function _support_identify_ticket(&$client, &$message) {
  global $base_url;

  // A) Check for ticket number defined in the Subject.
  $key = variable_get('support_key', 'tkt');
  $tickets = array();
  preg_match("/(\\[{$key}:)([0-9]*)(\\])/", $message['subject'], $tickets);
  if (isset($tickets[2])) {
    $message['nid'] = $tickets[2];
    return TRUE;
  }
  if (variable_get('support_thread_by_mail_headers', TRUE)) {
    $id_right = preg_replace('|.+://([a-zA-Z0-9\\._-]+).*|', '\\1', $base_url);

    // Search In-Reply-To and References...
    $check = '';
    if (isset($message['headers']->in_reply_to)) {
      $check .= $message['headers']->in_reply_to;
    }
    if (isset($message['headers']->references)) {

      // Turn references header around.
      $check .= implode(' ', array_reverse(explode(' ', $message['headers']->references)));
    }
    $message_ids = array();
    preg_match_all("/<[^<^>]*/", $check, $message_ids);
    foreach ($message_ids[0] as $message_id) {
      $message_id .= '>';

      // B) Check for a reply to one of the messages generated by us.
      $matches = array();
      if (preg_match('/^<(\\d+)\\.(\\d+)@' . $id_right . '>$/', $message_id, $matches)) {
        $cid = $matches[1];
        $nid = $matches[2];

        // Reply was directly to node.
        if (!$cid) {

          // Check message id against our records.
          if (db_result(db_query('SELECT 1 FROM {support_ticket} t WHERE t.nid = %d AND t.client = %d', $nid, $client->clid))) {
            $message['nid'] = $nid;
            return TRUE;
          }
        }
        else {

          // Check message id against our records.
          if (db_result(db_query('SELECT 1 FROM {comments} c INNER JOIN {support_ticket_comment} t ON c.cid = t.cid WHERE c.cid = %d AND c.nid = %d AND t.client = %d', $cid, $nid, $client->clid))) {
            $message['nid'] = $nid;
            return TRUE;
          }
        }
      }

      // C) Check for reply to the incoming message that created the ticket.
      $nid = db_result(db_query("SELECT nid FROM {support_ticket} WHERE message_id = '%s'", $message_id));
      if (isset($nid) && is_numeric($nid)) {
        $message['nid'] = $nid;
        return TRUE;
      }

      // D) Check for reply to an incoming message that created a followup.
      $nid = db_result(db_query("SELECT c.nid FROM {support_ticket_comment} j INNER JOIN {comments} c ON j.cid = c.cid WHERE j.message_id = '%s'", $message_id));
      if (isset($nid) && is_numeric($nid)) {
        $message['nid'] = $nid;
        return TRUE;
      }
    }
  }

  // E) Look for tickets with an identical subject.
  if (!$client->thread_subject) {
    $client->thread_subject = variable_get('support_thread_by_subject', 3);
  }
  switch ($client->thread_subject) {
    case 1:

      // No subject matching.
      break;
    case 2:

      // Match against new tickets.
      $message['nid'] = db_result(db_query_range("SELECT t.nid FROM {support_ticket} t LEFT JOIN {node} n ON t.nid = n.nid LEFT JOIN {support_states} s ON t.state = s.sid WHERE t.client = %d AND n.title = '%s' AND s.isdefault = 1 ORDER BY t.nid DESC", $client->clid, $message['subject'], 0, 1));
      break;
    case 3:

      // Match against open tickets.
      $message['nid'] = db_result(db_query_range("SELECT t.nid FROM {support_ticket} t LEFT JOIN {node} n ON t.nid = n.nid LEFT JOIN {support_states} s ON t.state = s.sid WHERE t.client = %d AND n.title = '%s' AND s.isclosed = 0 ORDER BY t.nid DESC", $client->clid, $message['subject'], 0, 1));
      break;
    case 4:

      // Match against any tickets.
      $message['nid'] = db_result(db_query_range("SELECT t.nid FROM {support_ticket} t LEFT JOIN {node} n ON t.nid = n.nid WHERE t.client = %d AND n.title = '%s' ORDER BY t.nid DESC", $client->clid, $message['subject'], 0, 1));
      break;
  }
  return (bool) $message['nid'];
}

/**
 *
 */
function _support_access_tickets($count = TRUE) {
  static $clients = NULL;
  if (is_null($clients)) {
    $clients = array();
    $result = db_query('SELECT clid, name FROM {support_client} WHERE status = 1');
    while ($client = db_fetch_object($result)) {
      if (user_access('administer support') || user_access("access {$client->name} tickets")) {
        $clients[$client->clid] = $client->name;
      }
    }
  }
  if ($count) {
    return count($clients);
  }
  else {
    return $clients;
  }
}

/**
 * Return an autoassigned user for a given client.
 */
function _support_autoassign($clid, $uid = 0) {
  static $autoassign = array();
  if ($clid && !isset($autoassign[$clid])) {
    $name = db_result(db_query('SELECT autoassign FROM {support_client} WHERE clid = %d', $clid));
    switch ($name) {
      case '<nobody>':
        $autoassign[$clid] = 0;
        break;
      case '<creator>':
        $autoassign[$clid] = $uid;
        break;
      default:

        // no per-client auto-assign, or an invalid auto-assign
        $account = user_load(array(
          'name' => trim($name),
        ));
        if (empty($account) || !$account->uid) {
          $assign = variable_get('support_autoassign_ticket', '<nobody>');
          switch ($assign) {
            case '<nobody>':
              $autoassign[$clid] = 0;
              break;
            case '<creator>':
              $autoassign[$clid] = $uid;
              break;
            default:
              $account = user_load(array(
                'name' => trim($assign),
              ));
              if (empty($account) || !$account->uid) {
                $autoassign[$clid] = 0;
              }
              else {
                $autoassign[$clid] = $account->uid;
              }
              break;
          }
        }
        else {
          $autoassign[$clid] = $account->uid;
        }
    }
  }
  return !empty($clid) ? $autoassign[$clid] : 0;
}

/**
 * Return the currently active client.
 */
function _support_current_client() {
  $clients = _support_available_clients();

  // Allow plug-in modules to affect which is the currently active client.
  drupal_alter('support_current_client', $clients);
  if (count($clients) == 1) {
    $client = key($clients);
  }
  else {
    if (count($clients)) {
      if (is_numeric($_SESSION['support_client'])) {
        $client = $_SESSION['support_client'];
      }
      else {
        if (!user_access('can select client')) {

          // TODO: It should be possible to set a default client.  Perhaps allow
          // a weight to be assigned to clients -- then we select the heaviest
          // matching client...?
          $client = key($clients);
        }
      }
    }
    else {
      return NULL;
    }
  }
  return $client;
}
function _support_enabled($clid, $uid) {
  static $enabled = array();
  if (!isset($enabled[$clid])) {
    $autosubscribe = db_result(db_query('SELECT autosubscribe FROM {support_client} WHERE clid = %d', $clid));
    $names = explode(',', $autosubscribe);
    foreach ($names as $name) {
      $account = user_load(array(
        'name' => trim($name),
      ));
      $enabled[$clid][$account->uid] = TRUE;
    }
  }
  return isset($enabled[$clid][$uid]);
}

/**
 * Callback access function for 'support/autocomplete/autosubscribe' menu item.
 *
 * @return <bool>
 */
function _support_autosubscribe_access() {
  return user_access('administer support') || user_access('can subscribe other users to notifications');
}

/**
 * Delete a client
 * 
 * @param $client
 *   Client object, needs only to define ->clid.
 */
function support_client_delete($client) {
  drupal_alter('support_client_delete', $client);
  if (!empty($client->clid)) {
    $name = db_result(db_query('SELECT name FROM {support_client} WHERE clid = %d', $client->clid));
    db_query('DELETE FROM {support_client} WHERE clid = %d', $client->clid);
    drupal_set_message(t('Client %client deleted.', array(
      '%client' => $name,
    )));
    module_invoke_all('support_client_deleted', $client);
  }
}

/**
 * Update a client
 *
 * @param $client
 *   Client object.
 */
function support_client_update($client) {
  drupal_alter('support_client_update', $client);
  if (isset($client->name)) {
    $client->name = check_plain($client->name);
  }
  if (empty($client->name)) {
    drupal_set_message(t('Valid client name required.'), 'error');
    return -1;
  }
  $rc = drupal_write_record('support_client', $client, 'clid');
  if ($rc == SAVED_UPDATED) {
    drupal_set_message(t('Client %client updated successfully.', array(
      '%client' => $client->name,
    )));
    module_invoke_all('support_client_updated', $client);
  }
  else {
    drupal_set_message(t('Client %client update failed.', array(
      '%client' => $client->name,
    )), 'error');
  }
}

/**
 * Create a client
 *
 * @param $client
 *   Client object.
 */
function support_client_create($client) {
  drupal_alter('support_client_create', $client);
  if (isset($client->name)) {
    $client->name = check_plain($client->name);
  }
  if (empty($client->name)) {
    drupal_set_message(t('Valid client name required.'), 'error');
    return -1;
  }
  $rc = drupal_write_record('support_client', $client);
  if ($rc == SAVED_NEW) {
    drupal_set_message(t('Client %client created successfully.', array(
      '%client' => $client->name,
    )));
    module_invoke_all('support_client_created', $client);
  }
  else {
    drupal_set_message(t('Client %client creation failed.', array(
      '%client' => $client->name,
    )), 'error');
  }
}

/**
 * Allow projects to be selected.
 */
function support_support_timer_client_report_alter(&$report) {
  $report->filters .= drupal_get_form('support_invoice_ui_form');
}
function _support_state_query_filter($state) {
  if ($state == -2) {
    global $user;
    $query_filter = " AND st.assigned = {$user->uid} ";
  }
  else {
    $query_filter = '';
  }
  if ($state < 0) {
    $query_filter .= ' AND st.state NOT IN (' . implode(', ', _support_get_state(SUPPORT_STATE_CLOSED)) . ') ';
  }
  else {
    $query_filter .= $state ? " AND st.state = {$state} " : '';
  }
  return $query_filter;
}

/**
 * Return an alphabetized list of all terms used with this client.
 */
function _support_client_terms($clid, $state = NULL) {
  static $tids = array();
  if (is_array($clid)) {
    $clid = implode(',', $clid);
  }
  else {
    $clid = (int) $clid;
  }
  if (!isset($tids["{$clid}"][$state])) {
    $query_filter = _support_state_query_filter($state);
    if ($state < 0) {
      $query_filter .= 'AND st.state NOT IN (' . implode(', ', _support_get_state(SUPPORT_STATE_CLOSED)) . ')';
    }
    else {
      $query_filter .= $state ? "AND st.state = {$state}" : '';
    }
    $result = db_query('SELECT DISTINCT(tn.tid) AS tid, td.name FROM {support_ticket} st LEFT JOIN {term_node} tn ON st.nid = tn.nid LEFT JOIN {term_data} td ON tn.tid = td.tid WHERE st.client IN(%s) AND !ISNULL(tn.nid) ' . $query_filter . ' ORDER BY td.name ASC', $clid);
    while ($term = db_fetch_object($result)) {
      $tids["{$clid}"][$state][$term->tid] = $term->name;
    }
  }
  if (isset($tids["{$clid}"][$state])) {
    return $tids["{$clid}"][$state];
  }
  return NULL;
}

/**
 *
 */
function _support_client_users($clid) {
  static $uids = array();
  if (is_array($clid)) {
    $clid = implode(',', $clid);
  }
  else {
    $clid = (int) $clid;
  }
  if (!isset($uids["{$clid}"])) {
    $result = db_query('SELECT DISTINCT(u.uid) AS uid, u.name FROM {support_ticket} st LEFT JOIN {users} u ON st.assigned = u.uid WHERE st.client IN(%s) ORDER BY u.name ASC', $clid);
    while ($account = db_fetch_object($result)) {
      $uids["{$clid}"][$account->uid] = $account->name;
    }
  }
  if (isset($uids["{$clid}"])) {
    return $uids["{$clid}"];
  }
  return NULL;
}

/**
 * Provide form for selecting projects.
 */
function support_invoice_ui_form() {
  $form = array();
  if (count(_support_client_terms(_support_current_client()))) {
    $tags = array(
      -1 => t('-- no tag --'),
      0 => t('-- all tags --'),
    ) + _support_client_terms(_support_current_client());
    $term = isset($_GET['tid']) ? $_GET['tid'] : '';
    if ($term && $term == preg_replace('/[^0-9,]/', '', $term)) {
      $selected = explode(',', $term);
    }
    else {
      if ($_GET['tid'] == 'null') {
        $selected = -1;
      }
      else {
        $selected = 0;
      }
    }
    $form['tags'] = array(
      '#type' => 'fieldset',
      '#title' => t('Tags'),
      '#collapsible' => TRUE,
      '#collapsed' => $selected ? FALSE : TRUE,
    );
    $form['tags']['tids'] = array(
      '#title' => t('Tag'),
      '#type' => 'select',
      '#multiple' => TRUE,
      '#options' => $tags,
      '#default_value' => $selected,
      '#description' => t('Filter report by selected tag(s).'),
    );
    $form['tags']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Update filter'),
    );
  }
  return $form;
}

/*
 * Add url filter when tags are selected.
 */
function support_invoice_ui_form_submit($form, &$form_state) {
  $tids = array();
  if (!empty($form_state['values']['tids'])) {
    foreach ($form_state['values']['tids'] as $tid) {
      if ($tid == -1) {
        $tids = array(
          'null',
        );
        break;
      }
      else {
        if ($tid == 0) {
          $tids = '';
          break;
        }
        else {
          $tids[$tid] = $tid;
        }
      }
    }
    if ($tids) {
      $tids = implode(',', $tids);
    }
  }
  $path = drupal_get_path_alias(isset($_GET['q']) ? $_GET['q'] : '');
  $query = array();
  foreach ($_GET as $key => $value) {
    if (!in_array($key, array(
      'q',
      'tid',
    ))) {
      $query[$key] = $value;
    }
  }
  if ($tids) {
    $query['tid'] = $tids;
  }
  drupal_goto($path, $query);
}

Functions

Namesort descending Description
support_access Implementation of hook_access().
support_access_clients Custom permissions function.
support_access_user_tickets Menu callback.
support_account_load Match up a user account with an incoming email. Create account if email doesn't match any. TODO: Make it possible to assign multiple email addresses to one account.
support_active_clients Load all active clients.
support_autocomplete_assigned Autocomplete usernames to assign to ticket.
support_autocomplete_autosubscribe Autocomplete usernames to assign to ticket.
support_block Implemtation of hook_block().
support_client_create Create a client
support_client_delete Delete a client
support_client_fetch Fetch mail for a specific client.
support_client_load Menu callback, load a client.
support_client_update Update a client
support_comment Implementation of hook_comment().
support_comment_view Display state, priority and client when viewing comments for support nodes.
support_comment_wrapper Implementation of theme_content_wrapper().
support_cron Implementation of hook_cron().
support_db_rewrite_sql Imeplementation of hook_db_rewrite_sql(). Optionally remove support tickets from content search.
support_fetch_client_mail Automatically download messages for all active clients.
support_filter_form Form builder; Return form for support ticket listing filters.
support_filter_form_submit Convert tid array to comma seperated list for URL.
support_filter_form_validate Validate the support_filter_form.
support_form Implementation of hook_form().
support_form_alter Customize comment form for ticket followups.
support_help Provide some inline documentation.
support_init Implementation of hook_init().
support_invoice_ui_form Provide form for selecting projects.
support_invoice_ui_form_submit
support_link_alter Implementation of hook_link_alter().
support_mail Implementation of hook_mail().
support_mail_tokens Return an array of token to value mappings for support e-mail messages.
support_menu Implementation of hook_menu().
support_nodeapi Implementation of hook_nodeapi().
support_node_info Implementation of hook_node_info().
support_notification Send notification emails to everyone subscribed to the updated ticket.
support_page_form Display tickets
support_page_form_submit Update selected tickets.
support_page_form_validate Validate that at least one ticket is selected.
support_page_user_access Access callback for user support ticket pages.
support_perm Implementation of hook_perm().
support_save_message Save the message.
support_search Implementation of hook _search.
support_search_available_clients Return array of client ids which current user has access to.
support_search_form_submit
support_search_validate Form API callback for the search form. Registered in support_form_alter().
support_status_form Generate form for adding update to ticket. Enhances comment_form adding a ticket status bar.
support_subscribe_form Provide option to subscribe/unsubscribe from ticket notification emails.
support_subscribe_user Subscribe a user to a ticket.
support_support_timer_client_report_alter Allow projects to be selected.
support_theme Implementation of hook_theme().
support_theme_registry_alter Implementation of hook_theme_registry_alter().
support_ticket_load Menu callback, load a ticket.
support_unsubscribe_user Unsubscribe user from tickets.
support_user Implementation of hook_user().
theme_support_page_form Theme function for ticket list
_support_access_tickets
_support_assigned Get list of available users to assign ticket to.
_support_autoassign Return an autoassigned user for a given client.
_support_autosubscribe Autosubscribe users to new client ticket.
_support_autosubscribe_access Callback access function for 'support/autocomplete/autosubscribe' menu item.
_support_available_clients Find all clients we have permission to view/edit.
_support_client Helper function, retrieve client name from database.
_support_clients_load Load all active clients.
_support_client_terms Return an alphabetized list of all terms used with this client.
_support_client_users
_support_comment_update_node Helper function.
_support_current_client Return the currently active client.
_support_domains Extract domains.
_support_enabled
_support_generate_message_id Generate an RFC2822 message ID.
_support_get_attachments Find all attachments in the current message.
_support_get_filemime Retrieve MIME type of the message structure.
_support_get_message_body_part
_support_get_state
_support_identify_ticket Search for an existing ticket to associate an incoming message with.
_support_mail_deny Using drupal_mail to send notification mail to user that is not registered and tried to use mail support.
_support_mail_list_attachments List all ticket attachments associated with this update, if any.
_support_mail_notify Use drupal_mail to send email.
_support_mail_text Returns the appropriate mail string for a given key.
_support_mail_text_default Returns the appropriate untranslated default mail string for a given key. It contains tokens which are not translated yet (see support_mail_tokens(), plus "!reply")
_support_node_move
_support_priorities Helper function to list available priorities.
_support_priority_default Return default pid.
_support_save_attachments
_support_state Helper function, retrieve state name from database.
_support_states Helper function to list available states.
_support_state_default Return default sid.
_support_state_query_filter
_support_state_secondary Return secondary sid.
_support_ticket_exists Helper function to determine if a user has support tickets already.
_support_truncate
_support_validate_assigned_user Be sure a valid user is being assigned to a ticket.

Constants