You are here

support_substatus.module in Support Ticketing System 6

Support Substatus -- allows per-status sub-status values, so for example a "pending" ticket can be further marked with "needs review", etc. @author Jeremy Andrews <jeremy@tag1consulting.com> @package Support

File

support_substatus/support_substatus.module
View source
<?php

/**
* @file 
* Support Substatus -- allows per-status sub-status values, so for example a "pending" ticket
* can be further marked with "needs review", etc.
* @author Jeremy Andrews <jeremy@tag1consulting.com>
* @package Support
*/

// @todo: Fix permissions.

/**
 * Implementation of hook_perm();
 */
function support_substatus_perm() {
  return array(
    'administer support substatus',
    'view support substatus',
  );
}

/**
 * Implementation of hook_menu().
 */
function support_substatus_menu() {
  $items = array();
  $items['admin/support/substatus'] = array(
    'title' => 'Status',
    'description' => 'Configure support substatus fields',
    'page callback' => 'support_substatus_admin_overview',
    'access arguments' => array(
      'administer support substatus',
    ),
    'file' => 'support_substatus.admin.inc',
  );
  $items['admin/support/substatus/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/support/substatus/add'] = array(
    'title' => 'Add substatus',
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_substatus_admin_form',
    ),
    'access arguments' => array(
      'administer support substatus',
    ),
    'file' => 'support_substatus.admin.inc',
  );
  $items['admin/support/substatus/%support_substatus/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'support_substatus_admin_form',
      3,
    ),
    'access arguments' => array(
      'administer support substatus',
    ),
    'file' => 'support_substatus.admin.inc',
  );

  // TODO: Add paths to substatus-filtered listings
  return $items;
}

/**
 * Implementation of hook_nodeapi().
 */
function support_substatus_nodeapi(&$node, $op, $teaser, $page) {
  if ($node->type == 'support_ticket') {
    switch ($op) {

      // Notifications are sent before hook_nodeapi insert/update is invoked, so
      // we cache the value from op_presave.
      case 'presave':
        $substatus = isset($node->substatus) ? (int) $node->substatus : 0;
        if ($substatus) {
          _support_substatus_notification_static($substatus);
        }
        break;
      case 'view':
        if (user_access('view support substatus') && _support_substatus_client_active($node->client)) {
          if ($substatus = support_substatus_load_nid($node->nid)) {
            $node->content['support-substatus'] = array(
              '#value' => "<div class='support-priority'>Status: " . check_plain($substatus->substatus) . '</div>',
              '#weight' => -1,
            );
          }
        }
        break;
      case 'load':
        $node->substatus = support_substatus_load_nid($node->nid);
        break;
      case 'insert':
      case 'update':
        db_query("UPDATE {support_substatus_ticket} SET subid = %d WHERE type = 'node' AND id = %d", $node->substatus, $node->nid);
        if (!db_affected_rows()) {
          db_query("INSERT INTO {support_substatus_ticket} (subid, type, id, nid, current) VALUES(%d, 'node', %d, %d, %d)", $node->substatus, $node->nid, $node->nid);
        }
        _support_substatus_set_current($node->nid);
        break;
      case 'delete':
        db_query("DELETE FROM {support_substatus_ticket} WHERE nid = %d", $node->nid);
        break;
    }
  }
}
function _support_substatus_notification_static($new_substatus = NULL) {
  static $substatus = 0;
  if ($new_substatus) {
    $substatus = $new_substatus;
  }
  return $substatus;
}
function support_substatus_comment(&$comment, $op) {
  if (is_array($comment)) {
    $node = node_load($comment['nid']);
    $cid = $comment['cid'];
  }
  else {
    $node = node_load($comment->nid);
    $cid = $comment->cid;
  }
  if ($node->type == 'support_ticket') {
    switch ($op) {

      // Notifications are sent before hook_comment insert/update is invoked, so
      // we cache the value from op_validate.
      case 'validate':
        $substatus = isset($comment['substatus']) ? (int) $comment['substatus'] : 0;
        if ($substatus) {
          _support_substatus_notification_static($substatus);
        }
        break;
      case 'view':
        if (user_access('view support substatus') && _support_substatus_client_active($node->client)) {
          static $old_substatus = 0;
          if (!$old_substatus) {
            $ss = support_substatus_load_nid($node->nid);
            if (is_object($ss) && isset($ss->substatus)) {
              $old_substatus = $ss->substatus;
            }
          }
          $substatus = support_substatus_load_nid($node->nid, $cid);
          if ($new_substatus = check_plain($substatus->substatus)) {
            if ($new_substatus != $old_substatus) {
              $comment->comment = "<div class='support-priority'>Status: {$old_substatus} -> {$new_substatus}</div>" . $comment->comment;
            }
            else {
              if ($new_substatus) {
                $comment->comment = "<div class='support-priority'>Status: {$new_substatus}</div>" . $comment->comment;
              }
            }
            $old_substatus = $new_substatus;
          }
          else {
            if ($old_substatus) {
              $comment->comment = "<div class='support-priority'>Status: {$old_substatus}</div>" . $comment->comment;
            }
          }
        }
        break;
      case 'insert':
      case 'update':
        db_query("UPDATE {support_substatus_ticket} SET subid = %d, nid = %d WHERE type = 'comment' AND id = %d", $comment['substatus'], $comment['nid'], $comment['cid']);
        if (!db_affected_rows()) {
          @db_query("INSERT INTO {support_substatus_ticket} (subid, type, id, nid) VALUES(%d, 'comment', %d, %d)", $comment['substatus'], $comment['cid'], $comment['nid']);
        }
        _support_substatus_set_current($node->nid);
        break;
      case 'delete':
        db_query("DELETE FROM {support_substatus_ticket} WHERE type = 'comment' AND id = %d", $comment->cid);
        _support_substatus_set_current($comment->nid);
        break;
    }
  }
}
function support_substatus_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'support_ticket_node_form') {
    $node = $form['#node'];
    if (_support_substatus_client_active($node->client)) {

      // @todo: move into functions
      drupal_add_js(drupal_get_path('module', 'support_substatus') . '/support_substatus.js');
      $states = _support_states();
      $substatus = array();
      foreach ($states as $ssid => $state) {
        $substatus[$ssid] = support_substatus_load_client($node->client, $ssid);
      }
      drupal_add_js(array(
        'substatus' => $substatus,
      ), 'setting');
      $form['support']['state']['#id'] = 'select-state';
      $support_form = array();
      foreach ($form['support'] as $key => $value) {
        $support_form[$key] = $value;
        if ($key == 'state') {
          $options = support_substatus_load_client(_support_current_client());
          $support_form['substatus'] = array(
            '#type' => 'select',
            '#title' => t('Status'),
            '#prefix' => '&nbsp;&nbsp;',
            '#options' => $options,
            '#id' => 'select-substatus',
            '#default_value' => isset($node->substatus) && isset($node->substatus->ssid) ? $node->substatus->ssid : support_substatus_default($options),
          );
        }
      }
      $form['support'] = $support_form;
    }
  }
  else {
    if ($form_id == 'comment_form') {
      $nid = $form['nid']['#value'];
      $node = node_load($nid);
      if (_support_substatus_client_active($node->client)) {

        // @todo: move into function
        drupal_add_js(drupal_get_path('module', 'support_substatus') . '/support_substatus.js');
        $states = _support_states();
        $substatus = array();
        foreach ($states as $ssid => $state) {
          $substatus[$ssid] = support_substatus_load_client($node->client, $ssid);
        }
        drupal_add_js(array(
          'substatus' => $substatus,
        ), 'setting');
        $form['support']['state']['#id'] = 'select-state';
        $support_form = array();
        foreach ($form['support'] as $key => $value) {
          $support_form[$key] = $value;
          if ($key == 'state') {
            $cid = $form['cid']['#value'];
            $default_value = _support_substatus_last_value($nid);
            $options = support_substatus_load_client(_support_current_client());
            $support_form['substatus'] = array(
              '#type' => 'select',
              '#title' => t('Status'),
              '#prefix' => '&nbsp;&nbsp;',
              '#options' => $options,
              '#id' => 'select-substatus',
              '#default_value' => $default_value,
            );
          }
        }
        $form['support'] = $support_form;
      }
    }
    else {
      if ($form_id == 'support_page_form') {
        if (user_access('view support substatus') && _support_substatus_client_active(_support_current_client())) {

          // @todo: make "substate" field sortable, is it possible?
          // Insert "Substate" into the ticket listing
          foreach ($form['header']['#value'] as $key => $value) {
            if ($value['data'] == 'State') {
              array_splice($form['header']['#value'], ++$key, 0, array(
                array(
                  'data' => t('Status'),
                ),
              ));
              break;
            }
          }
          $substatus = array();
          if (isset($form['id'])) {
            foreach ($form['id'] as $id => $data) {
              $ssid = _support_substatus_last_value($id);
              if ($ssid) {
                $status = db_result(db_query('SELECT substatus FROM {support_substatus} WHERE ssid = %d', $ssid));
                $substatus["substatus-{$id}"] = array(
                  '#value' => $status,
                );
              }
              else {
                $substatus["substatus-{$id}"] = array(
                  '#value' => '',
                );
              }
            }
          }
          $ssid = isset($_GET['ssid']) ? $_GET['ssid'] : '';
          $ssids = array();
          $unsanitized = explode(',', $ssid);
          foreach ($unsanitized as $element) {
            $element = (int) $element;
            if ($element) {
              $ssids[$element] = $element;
            }
          }
          $new_filter = array();
          foreach ($form['filter'] as $key => $value) {
            $new_filter[$key] = $value;
            if ($key == 'state') {
              $new_filter['substatus'] = array(
                '#type' => 'fieldset',
                '#title' => t('Status'),
                '#collapsible' => TRUE,
                '#collapsed' => empty($ssids),
              );
              $options = _support_substatus_client_substatus(_support_current_client());
              $new_filter['substatus']['ssid'] = array(
                '#type' => 'select',
                '#multiple' => TRUE,
                '#options' => $options,
                '#default_value' => !empty($ssids) ? $ssids : 0,
              );
            }
          }
          $form['filter'] = $new_filter;

          // @todo: don't bother inserting into the precise form location, this is unnecessary
          $new_form = array();
          foreach ($form as $key => $value) {
            $new_form[$key] = $value;
            if ($key == t('state')) {
              $new_form['substatus'] = $substatus;
            }
          }
          $form = $new_form;
        }
      }
    }
  }
}
function support_substatus_theme_registry_alter(&$theme_registry) {
  $theme_registry['support_page_form']['function'] = 'theme_support_substatus_page_form';
  $theme_registry['support_page_user']['function'] = 'theme_support_substatus_page_user';
}
function theme_support_substatus_page_form($form) {
  if (!user_access('view support substatus') || !_support_substatus_client_active(_support_current_client())) {
    return 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['substatus']["substatus-{$key}"]);
      $row[] = drupal_render($form['priority']["priority-{$key}"]);
      $row[] = drupal_render($form['updates'][$key]);
      $rows[] = array(
        'data' => $row,
        'class' => $form['class'][$key]['#value'],
      );
      unset($form['class'][$key]);
    }
  }
  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;
}
function theme_support_substatus_page_user($header, $rows) {
  if (!user_access('view support substatus')) {

    // @todo fix; we likely shouldn't be calling with an empty array
    $form = array();
    return theme_support_page_form($form);
  }
  $new_header = array();
  $increment = $state_key = 0;
  foreach ($header as $key => $value) {
    $new_header[$key + $increment] = $value;
    if ($value['data'] == t('State')) {
      $state_key = $key;
      $increment++;
      $new_header[$key + $increment] = array(
        'data' => t('Status'),
      );
    }
  }
  if ($state_key) {
    $new_rows = array();
    foreach ($rows as $key => $value) {
      $new_row = array();
      $increment = 0;
      $ticket_url = preg_match('/>([0-9]+)</', $rows[$key]['data'][0]['data'], $matches);
      $ticket_id = $matches[1];
      $substatus = _support_substatus_last_value($ticket_id);
      foreach ($rows[$key]['data'] as $subkey => $subvalue) {
        $new_row['data'][$subkey + $increment] = $subvalue;
        if ($subkey == $state_key) {
          $increment++;
          $new_row['data'][$subkey + $increment] = array(
            'data' => _support_substatus_substatus($substatus),
            'class' => 'ticket-substatus',
          );
        }
      }
      $new_row['class'] = $rows[$key]['class'];
      $new_rows[] = $new_row;
    }
  }
  return theme('table', $new_header, $new_rows, array(
    'class' => 'support',
  )) . theme('pager');
}

/**
 * Use '!optional_status' to only add "Status: " when applicable to a client, for example:
 *   State: !state !optional_status
 *   Priority: !priority
 *
 * Use '!substatus' to always add the Status transition if you always have a status value.
 */
function support_substatus_support_mail_tokens_alter($tokens) {
  $ticket_id = (int) $tokens['!ticket_id'];
  $ticket_update_id = (int) $tokens['!ticket_update_id'];
  $previous_substatus_id = (int) _support_substatus_last_value($ticket_id);
  $current_substatus_id = (int) _support_substatus_notification_static();
  $tokens['!previous_substatus_id'] = $previous_substatus_id;
  $tokens['!current_substatus_id'] = $current_substatus_id;
  $tokens['!substatus'] = ($previous_substatus_id && $previous_substatus_id != $current_substatus_id ? _support_substatus_substatus($previous_substatus_id) . ' -> ' : '') . _support_substatus_substatus($current_substatus_id);
  if (function_exists('mimemail')) {
    $tokens['!optional_status'] = !empty($current_substatus_id) ? "<br />Status: " . $tokens['!substatus'] : '';
  }
  else {
    $tokens['!optional_status'] = !empty($current_substatus_id) ? "\nStatus: " . $tokens['!substatus'] : '';
  }
}
function support_substatus_support_ticket_listing_filter_alter(&$filters) {
  if (_support_substatus_client_active(_support_current_client())) {
    $ssid = isset($_GET['ssid']) ? $_GET['ssid'] : '';
    $unsanitized = explode(',', $ssid);
    foreach ($unsanitized as $element) {
      $element = (int) $element;
      if ($element) {
        $filters['ssid'][$element] = $element;
      }
    }
    if (isset($filters['ssid']) && !empty($filters['ssid'])) {
      $filters['join'][] = 'LEFT JOIN {support_substatus_ticket} sst ON t.nid = sst.nid';
      $filters['where'][] = strtr('sst.subid IN (!ssid) AND sst.current = 1', array(
        '!ssid' => implode(',', $filters['ssid']),
      ));
    }
  }
}
function support_substatus_support_filter_form_submit_alter(&$form_state) {
  if (!empty($form_state['values']['ssid'])) {
    $form_state['support_filter_query']['ssid'] = implode(',', $form_state['values']['ssid']);
  }
}

/**
 * Load substatus from database.
 */
function support_substatus_load($ssid) {
  static $substatus = array();
  if (!isset($substatus[$ssid])) {
    $substatus[$ssid] = db_fetch_object(db_query('SELECT * FROM {support_substatus} WHERE ssid = %d', $ssid));
    $substatus[$ssid]->clids = array();
    $result = db_query('SELECT stid FROM {support_substatus_state} WHERE ssid = %d', $ssid);
    while ($state = db_fetch_object($result)) {
      $substatus[$ssid]->state[] = $state->stid;
    }
    $result = db_query('SELECT clid FROM {support_substatus_client} WHERE ssid = %d', $ssid);
    while ($client = db_fetch_object($result)) {
      $substatus[$ssid]->clids[] = $client->clid;
    }
    drupal_alter('support_substatus_load', $substatus[$ssid]);
  }
  return $substatus[$ssid];
}
function support_substatus_load_nid($nid, $cid = 0) {
  static $substatus = array();
  if (!isset($substatus[$nid][$cid])) {
    if ($cid) {
      $substatus[$nid][$cid] = db_fetch_object(db_query("SELECT * FROM {support_substatus_ticket} sst LEFT JOIN {support_substatus} ss ON sst.subid = ss.ssid WHERE sst.type = 'comment' AND sst.id = %d", $cid));
    }
    else {
      $substatus[$nid][$cid] = db_fetch_object(db_query('SELECT * FROM {support_substatus_ticket} sst LEFT JOIN {support_substatus} ss ON sst.subid = ss.ssid WHERE sst.id = %d', $nid));
    }
    drupal_alter('support_substatus_load_nid', $substatus[$nid][$cid]);
  }
  return $substatus[$nid][$cid];
}

/**
 * Load substatus assigned to a given client.
 */
function support_substatus_load_client($clid, $state = 0) {
  $substatus = array();
  if ($state) {
    $result = db_query('SELECT ss.ssid, ss.substatus FROM {support_substatus} ss LEFT JOIN {support_substatus_client} ssc ON ss.ssid = ssc.ssid LEFT JOIN {support_substatus_state} sss ON ss.ssid = sss.ssid WHERE (ssc.clid = %d OR ISNULL(ssc.clid)) AND stid = %d AND disabled = 0 ORDER BY ss.weight ASC', $clid, $state);
  }
  else {
    $result = db_query('SELECT ss.ssid, ss.substatus FROM {support_substatus} ss LEFT JOIN {support_substatus_client} ssc ON ss.ssid = ssc.ssid WHERE (ssc.clid = %d OR ISNULL(ssc.clid)) AND disabled = 0 ORDER BY ss.weight ASC', $clid);
  }
  while ($ss = db_fetch_object($result)) {
    $substatus[$ss->ssid] = $ss->substatus;
  }
  return $substatus;
}

/**
 * Determine default for a list of substatus fields.
 */
function support_substatus_default($substatus) {
  $ssids = array();
  foreach ($substatus as $ssid => $stuff) {
    $ssids[] = $ssid;
  }
  if (empty($ssids)) {
    return 0;
  }
  else {
    return db_result(db_query_range('SELECT ssid FROM {support_substatus} WHERE ssid IN (%s) AND disabled = 0 ORDER BY weight ASC', implode(',', $ssids), 0, 1));
  }
}

/**
 * Implementation of hook_apachesolr_update_index().
 */
function support_substatus_apachesolr_update_index(&$document, $node) {
  if (!isset($node->substatus->ssid)) {
    $document->is_support_substatus = 0;
  }
  else {
    $document->is_support_substatus = (int) $node->substatus->ssid;
  }
}

/**
 * Implementation of hook_support_solr_info().
 */
function support_substatus_support_solr_info() {
  return array(
    'support_substatus' => array(
      'facet' => array(
        'info' => t('Support: Filter by Status'),
        'facet_field' => 'is_support_substatus',
      ),
      'filter_by' => t('Filter by Support Status'),
      'facet_callback' => '_support_substatus_substatus',
      'block' => array(
        'info' => t('Support: Status'),
        'cache' => BLOCK_CACHE_PER_PAGE,
      ),
    ),
  );
}
function _support_substatus_substatus($ssid) {
  if (!$ssid) {
    return t('none');
  }
  return check_plain(db_result(db_query('SELECT substatus FROM {support_substatus} WHERE ssid = %d', $ssid)));
}
function _support_substatus_client_active($clid) {
  return (int) db_result(db_query_range('SELECT 1 FROM {support_substatus} ss LEFT JOIN {support_substatus_client} ssc ON ss.ssid = ssc.ssid WHERE ss.disabled = 0 AND ssc.clid = %d', $clid, 0, 1));
}
function _support_substatus_last_value($nid) {
  return (int) db_result(db_query('SELECT subid FROM {support_substatus_ticket} WHERE nid = %d AND current = 1', $nid));
}

/**
 * Update current flag to allow for filtering and sorting.
 */
function _support_substatus_set_current($nid) {
  $trid = db_result(db_query_range('SELECT trid FROM {support_substatus_ticket} WHERE nid = %d ORDER BY trid DESC', $nid, 0, 1));
  db_query('UPDATE {support_substatus_ticket} SET current = 0 WHERE nid = %d', $nid);
  db_query('UPDATE {support_substatus_ticket} SET current = 1 WHERE trid = %d', $trid);
}

/**
 *
 */
function _support_substatus_client_substatus($clid) {
  static $ssids = array();
  if (!isset($ssids[$clid])) {
    $result = db_query('SELECT DISTINCT(ss.ssid) AS ssid, ss.substatus FROM {support_substatus} ss LEFT JOIN {support_substatus_client} ssc ON ss.ssid = ssc.ssid WHERE ssc.clid = %d ORDER BY ss.substatus ASC', $clid);
    while ($substatus = db_fetch_object($result)) {
      $ssids[$clid][$substatus->ssid] = $substatus->substatus;
    }
  }
  if (isset($ssids[$clid])) {
    return $ssids[$clid];
  }
  return NULL;
}

Functions