support_pm.module in Support Ticketing System 7
Same filename and directory in other branches
Support Project Management. @author Jeremy Andrews <jeremy@tag1consulting.com> @package Support
File
support_pm/support_pm.moduleView source
<?php
/**
* @file
* Support Project Management.
* @author Jeremy Andrews <jeremy@tag1consulting.com>
* @package Support
*/
/**
* TODO:
* - JavaScript to calculate support plan totals
* - Combined "calendar" view showing all plans on one page
*/
/**
* Implements hook_permission().
*/
function support_pm_permission() {
return array(
'create plans' => array(
'title' => t('Create support plans'),
),
'view all plans' => array(
'title' => t('View all support plans'),
),
'administer plans' => array(
'title' => t('Administer support plans'),
),
'administer support projects' => array(
'title' => t('Administer support projects'),
),
);
}
/**
* Implements hook_menu().
* TODO: Include date in 'view' and 'edit' tabs
*/
function support_pm_menu() {
$items = array();
$items['user/%user/support_plan'] = array(
'title' => 'Support plan',
'description' => 'Support planning',
'page callback' => 'support_pm_plan_overview_weekly',
'page arguments' => array(
1,
),
'access arguments' => array(
'create plans',
),
'type' => MENU_LOCAL_TASK,
);
$items['user/%user/support_plan/view'] = array(
'title' => 'Show plan',
'description' => 'Create support plans',
'page callback' => 'support_pm_plan_overview_weekly',
'page arguments' => array(
1,
),
'access arguments' => array(
'create plans',
),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['user/%user/support_plan/edit'] = array(
'title' => 'Edit plan',
'description' => 'Create support plans',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_user_week',
1,
),
'access arguments' => array(
'create plans',
),
'weight' => 2,
'type' => MENU_LOCAL_TASK,
);
$items['admin/support/plan_report'] = array(
'title' => 'Plan reports',
'description' => 'Generate support plan reports',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_admin_reports',
),
'access arguments' => array(
'administer support',
),
'file' => 'support_pm.admin.inc',
);
$items['admin/support/plan_settings'] = array(
'title' => 'Plan report settings',
'description' => 'Configure support plans.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_admin_settings',
),
'access arguments' => array(
'administer support',
),
'file' => 'support_pm.admin.inc',
);
$items['admin/support/project'] = array(
'title' => 'Projects',
'description' => 'Configure support projects.',
'page callback' => 'support_pm_admin_project_overview',
'access arguments' => array(
'administer support projects',
),
'file' => 'support_pm.admin.inc',
);
$items['admin/support/project/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/support/project/add'] = array(
'title' => 'Add project',
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_admin_project_form',
),
'access arguments' => array(
'administer support projects',
),
'file' => 'support_pm.admin.inc',
);
$items['admin/support/project/%support_pm_project/edit'] = array(
'title' => 'Edit',
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_admin_project_form',
3,
),
'access arguments' => array(
'administer support projects',
),
'file' => 'support_pm.admin.inc',
);
$items['admin/support/project/%support_pm_project/delete'] = array(
'title' => 'Delete',
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_pm_admin_project_delete_form',
3,
),
'access arguments' => array(
'administer support projects',
),
'file' => 'support_pm.admin.inc',
);
$items['support_pm/image/%'] = array(
'page callback' => 'support_pm_display_swatch',
'page arguments' => array(
2,
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
// Add project sub-menu
$states = array(
-3 => 'all',
-2 => 'all open',
-1 => 'my open',
) + _support_states();
$result = db_query('SELECT sp.projid, sp.project, sp.path, sp.weight, spc.clid FROM {support_project} sp LEFT JOIN {support_project_client} spc ON sp.projid = spc.projid WHERE disabled = 0 ORDER BY spc.clid, sp.weight ASC, sp.project');
foreach ($result as $project) {
if ($client = support_client_load($project->clid)) {
$clients = array(
$client,
);
}
else {
$clients = array();
$result2 = db_query('SELECT clid FROM {support_client} WHERE status = 1');
foreach ($result2 as $client) {
$clients[] = support_client_load($client->clid);
}
}
foreach ($clients as $client) {
foreach ($states as $sid => $state) {
if ($client->parent == 0) {
$items["support/{$client->path}/{$state}/{$project->path}"] = array(
'title' => check_plain($project->project),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_page_form',
$client->clid,
$state,
),
'access callback' => 'support_access_clients',
'access arguments' => array(
$client,
),
'weight' => $project->weight,
'type' => MENU_LOCAL_TASK,
);
}
else {
$parent = support_client_load($client->parent);
$items["support/{$parent->path}/{$client->path}/{$state}/{$project->path}"] = array(
'title' => check_plain($project->project),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'support_page_form',
$client->clid,
$state,
),
'access callback' => 'support_access_clients',
'access arguments' => array(
$client,
),
'weight' => $project->weight,
'type' => MENU_LOCAL_TASK,
);
}
}
}
}
return $items;
}
/**
* Implements hook_theme().
*/
function support_pm_theme() {
return array(
'support_pm_user_week' => array(
'render element' => 'form',
),
'support_pm_pager' => array(
'variables' => array(
'text' => NULL,
'op' => NULL,
'parameters' => array(),
'attributes' => array(),
),
),
'support_pm_user_client_hours_details' => array(
'variables' => array(
'day' => array(),
'scale' => 12,
),
),
'support_pm_user_hours_summary' => array(
'variables' => array(
'totals' => array(),
'load_callback' => 'support_client_load',
'max' => NULL,
'message' => 'Mismatch',
),
),
'support_pm_plan_diff' => array(
'variables' => array(
'diff' => NULL,
'plan' => NULL,
),
),
);
}
/**
* Implements hook_node_view().
*/
function support_pm_node_view($node, $view_mode, $langcode) {
if ($node->type != 'support_ticket') {
return;
}
if (user_access('administer support projects')) {
if (isset($node->project->project)) {
$node->content['support-project'] = array(
'#markup' => "<div class='support-priority'>Project: " . check_plain($node->project->project) . '</div>',
'#weight' => -1,
);
}
}
}
/**
* Implements hook_node_load().
*/
function support_pm_node_load($nodes, $types) {
if (!in_array('support_ticket', $types)) {
return;
}
$result = db_query('SELECT * FROM {support_project_ticket} spt LEFT JOIN {support_project} sp ON spt.projid = sp.projid WHERE spt.nid IN (:nids)', array(
':nids' => array_keys($nodes),
));
foreach ($result as $additions) {
$nodes[$additions->nid]->project = $additions;
// @@@ Deprecate.
drupal_alter('support_pm_project_load_nid', $nodes[$additions->nid]->project);
}
}
/**
* Implements hook_node_insert().
*/
function support_pm_node_insert($node) {
return support_pm_node_update($node);
}
/**
* Implements hook_node_update().
*/
function support_pm_node_update($node) {
if ($node->type != 'support_ticket') {
return;
}
if (!isset($node->project)) {
db_delete('support_project_ticket')
->condition('nid', $node->nid)
->execute();
}
else {
db_merge('support_project_ticket')
->key(array(
'nid' => $node->nid,
))
->fields(array(
'projid' => $node->project,
))
->execute();
}
}
/**
* Implements hook_node_delete().
*/
function support_pm_node_delete($node) {
if ($node->type != 'support_ticket') {
return;
}
db_delete('support_project_ticket')
->condition('nid', $node->nid)
->execute();
}
/**
* Implements hook_comment_view().
*/
function support_pm_comment_view($comment, $view_mode, $langcode) {
if ($comment->node_type == 'comment_node_support_ticket' && user_access('administer support projects') && ($project = support_pm_project_load_nid($comment->nid))) {
// @@@ Why is this being appended to every comment? It seems weird for something that doesn't change state via ticket updates... --Bdragon
if (!empty($project->project)) {
$comment->content['support']['project'] = array(
'#markup' => '<div class="support-project">' . t('Project') . ': ' . check_plain($project->project) . '</div>',
);
// Make sure the weight is correct.
$comment->content['support']['#weight'] = -1;
}
}
}
function support_pm_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'support_ticket_node_form') {
$node = $form['#node'];
$client = FALSE;
if (!empty($node->client)) {
$client = $node->client;
}
if (!empty($form_state['values']['client'])) {
$client = $form_state['values']['client'];
}
if (!$client) {
$client = _support_current_client();
}
// @todo: include disabled project if already set to it
$options = support_pm_load_projects($client);
$form['support']['client_dependencies']['project'] = array(
'#type' => 'select',
'#title' => t('Project'),
'#prefix' => ' ',
'#options' => $options,
'#default_value' => isset($node->project) && isset($node->project->projid) ? $node->project->projid : support_pm_default_project($options),
);
}
}
/**
* Alter the ticket listing query to consider projects.
*/
function support_pm_query_support_pager_alter($query) {
$client = $query
->getMetaData('support_client');
if ($project = $client->parent == 0 ? arg(3) : arg(4)) {
$project = db_query('SELECT p.projid FROM {support_project} p LEFT JOIN {support_project_client} c ON p.projid = c.projid WHERE (c.clid = :clid OR c.clid = 0) AND p.path = :path', array(
':clid' => $client->clid,
':path' => $project,
))
->fetchField();
if ($project) {
$query
->leftJoin('support_project_ticket', 'spt', 'n.nid = spt.nid');
$query
->condition('spt.projid', $project);
}
}
}
/**
* Alter the timer report for nodes to consider projects.
*/
function support_pm_query_support_timer_node_alter($query) {
$project = isset($_GET['project']) ? $_GET['project'] : '';
if ($project && $project == preg_replace('/[^0-9a-zA-Z_-]/', '', $project)) {
$query
->leftJoin('support_project_ticket', 'spt', 'n.nid = spt.nid');
$query
->leftJoin('support_project', 'sp', 'spt.projid = sp.projid');
if (strtolower($project) == 'null') {
$query
->isNull('sp.path');
}
else {
$query
->condition('sp.path', $project);
}
}
}
/**
* Alter the timer report for comments to consider projects.
*/
function support_pm_query_support_timer_comment_alter($query) {
$project = isset($_GET['project']) ? $_GET['project'] : '';
if ($project && $project == preg_replace('/[^0-9a-zA-Z_-]/', '', $project)) {
$query
->leftJoin('support_project_ticket', 'spt', 'c.nid = spt.nid');
$query
->leftJoin('support_project', 'sp', 'spt.projid = sp.projid');
if (strtolower($project) == 'null') {
$query
->isNull('sp.path');
}
else {
$query
->condition('sp.path', $project);
}
}
}
/**
* Allow projects to be selected.
*/
function support_pm_support_timer_client_report_alter(&$report) {
$report->filters .= drupal_render(drupal_get_form('support_pm_invoice_ui_form'));
}
/**
* Provide form for selecting projects.
*/
function support_pm_invoice_ui_form($form, &$form_state) {
if (user_access('administer support projects')) {
drupal_add_js('jQuery(document).ready(function() { jQuery("form#support-pm-invoice-ui-form select").change(function() { jQuery("form#support-pm-invoice-ui-form").submit(); }); });', 'inline');
$projects = array(
-1 => t('-- no project --'),
0 => t('-- all projects --'),
) + support_pm_load_projects(_support_current_client());
$project = isset($_GET['project']) ? $_GET['project'] : '';
if ($project && $project == preg_replace('/[^0-9a-zA-Z_-]/', '', $project)) {
if (strtolower($project) == 'null') {
$selected = -1;
}
else {
$selected = (int) db_query('SELECT projid FROM {support_project} WHERE path = :path', array(
':path' => $project,
))
->fetchField();
}
}
else {
$selected = 0;
}
$form['pm'] = array(
'#type' => 'fieldset',
'#title' => t('Project'),
'#collapsible' => TRUE,
'#collapsed' => $selected ? FALSE : TRUE,
);
$form['pm']['projects'] = array(
'#title' => t('Project'),
'#type' => 'select',
'#options' => $projects,
'#default_value' => $selected,
'#description' => t('Filter report by selected project.'),
);
$form['pm']['submit'] = array(
'#type' => 'submit',
'#value' => t('Update filter'),
);
}
return $form;
}
/**
* Add url filter when projects are selected.
*/
function support_pm_invoice_ui_form_submit($form, &$form_state) {
$project = NULL;
if (!empty($form_state['values']['projects'])) {
$projects = support_pm_load_projects(_support_current_client());
if (isset($projects[$form_state['values']['projects']])) {
$project = support_pm_project_load($form_state['values']['projects']);
}
else {
if ($form_state['values']['projects'] == -1) {
$project = new stdClass();
$project->path = 'null';
}
}
}
$path = drupal_get_path_alias(isset($_GET['q']) ? $_GET['q'] : '');
$query = array();
foreach ($_GET as $key => $value) {
if (!in_array($key, array(
'q',
'project',
))) {
$query[$key] = $value;
}
}
if (is_object($project)) {
$query['project'] = $project->path;
}
drupal_goto($path, array(
'query' => $query,
));
}
/**
*
*/
function support_pm_plan_overview_weekly($account) {
$output = NULL;
$week = isset($_GET['week']) ? _support_pm_first_day((int) $_GET['week']) : _support_pm_first_day(time());
// Set the page title (keep it consisten with the editing page)
drupal_set_title(t('@start - @end', array(
'@start' => format_date($week, 'medium'),
'@end' => format_date($week + 86400 * 6, 'medium'),
)));
$header = array(
'',
);
$row = array(
'<strong>' . t('Plan') . '</strong>',
);
$row2 = array(
'<strong>' . t('Actual') . '</strong>',
);
$totals = array(
'plan' => array(),
);
$hours = array();
// TODO: Allow for 5 day weeks, etc
for ($i = 0; $i < 7; $i++) {
$date = $week + 86400 * $i;
$header[] = t('!day<br />!date', array(
'!day' => format_date($date, 'custom', 'l'),
'!date' => format_date($date, 'custom', 'M d'),
));
$day = support_pm_day_load($date, $account);
if (isset($day[$account->uid]) && is_array($day[$account->uid])) {
$row[] = theme('support_pm_user_client_hours_details', array(
'day' => $day[$account->uid],
));
}
else {
$row[] = '';
}
// Add up totals
if (isset($day[$account->uid]) && is_array($day[$account->uid])) {
foreach ($day[$account->uid] as $clid => $data) {
if (!isset($totals['plan'][$clid])) {
$totals['plan'][$clid] = 0;
}
$totals['plan'][$clid] += $data->hours;
}
}
else {
if (!is_array($totals['plan'])) {
$totals['plan'] = array();
}
}
// Integrate with the support_timer module, if enabled
$hour = array();
if (module_exists('support_timer')) {
// The support_timer module uses a slightly different date format
$convert = strtotime(date('d M Y', $date));
$result = db_query('SELECT tt.time, t.client FROM {support_ticket_timer} tt LEFT JOIN {support_ticket} t ON tt.nid = t.nid LEFT JOIN {node} n ON t.nid = n.nid WHERE tt.date = :date AND n.uid = :uid', array(
':date' => $convert,
':uid' => $account->uid,
));
foreach ($result as $timer) {
if (!isset($hour[$timer->client])) {
$hour[$timer->client] = new stdClass();
}
if (!isset($hours[$timer->client])) {
$hours[$timer->client] = new stdClass();
}
$hour[$timer->client]->hours += support_pm_timer_to_hours($timer->time);
$hours[$timer->client]->hours += support_pm_timer_to_hours($timer->time);
}
$result = db_query('SELECT tt.time, t.client FROM {support_ticket_comment_timer} tt LEFT JOIN {support_ticket_comment} t ON tt.cid = t.cid LEFT JOIN {comment} c ON t.cid = c.cid WHERE tt.date = :date AND c.uid = :uid', array(
':date' => $convert,
':uid' => $account->uid,
));
foreach ($result as $timer) {
if (!isset($hour[$timer->client])) {
$hour[$timer->client] = new stdClass();
}
if (!isset($hours[$timer->client])) {
$hours[$timer->client] = new stdClass();
}
$hour[$timer->client]->hours += support_pm_timer_to_hours($timer->time);
$hours[$timer->client]->hours += support_pm_timer_to_hours($timer->time);
}
$row2[] = theme('support_pm_user_client_hours_details', array(
'day' => $hour,
));
}
}
$rows = array(
$row,
);
// Only display actual data if support_timer is enabled to collect it
if (count($row2) > 1) {
$rows[] = $row2;
foreach ($hours as $clid => $data) {
// Add up totals
$totals['actual'][$clid] = $data->hours;
}
}
$output = theme('support_pm_pager', array(
'text' => t('‹ previous'),
'op' => '<',
));
$header2 = array(
t('Plan'),
);
$plan_sum = is_array($totals['plan']) ? array_sum($totals['plan']) : 0;
$actual_sum = isset($totals['actual']) && is_array($totals['actual']) ? array_sum($totals['actual']) : 0;
$max = $plan_sum > $actual_sum ? $plan_sum : $actual_sum;
$row = array(
theme('support_pm_user_hours_summary', array(
'totals' => $totals['plan'],
'load_callback' => 'support_client_load',
'max' => $max,
'message' => t('Not scheduled'),
)),
);
if (count($row2) > 1) {
$header2[] = t('Actual');
$row[] = theme('support_pm_user_hours_summary', array(
'totals' => isset($totals['actual']) ? $totals['actual'] : NULL,
'load_callback' => 'support_client_load',
'max' => $max,
'message' => t('Not worked'),
));
}
$rows2 = array(
$row,
);
$output .= theme('table', array(
'header' => $header2,
'rows' => $rows2,
'attributes' => array(
'id' => 'support_pm_summary',
),
));
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array(
'id' => 'support_pm_week',
),
));
$header = array(
t('Client'),
);
if (function_exists('imagecreatetruecolor')) {
$header[] = t('Color');
}
$header[] = t('Planned');
$header[] = t('Worked');
$header[] = t('Difference');
if (isset($totals['plan']) || isset($totals['actual'])) {
if (isset($totals['plan']) && isset($totals['actual'])) {
$all_clients = $totals['plan'] + $totals['actual'];
}
else {
$all_clients = isset($totals['plan']) ? $totals['plan'] : $totals['actual'];
}
}
else {
$all_clients = array();
}
$rows = $clients = array();
foreach ($all_clients as $clid => $data) {
$client = support_client_load($clid);
$clients[$clid] = $client->name;
}
asort($clients);
$rows = array();
foreach ($clients as $clid => $name) {
$row = array();
$client = support_client_load($clid);
$row[] = l($name, "support/{$client->path}");
$comments = db_query('SELECT comment FROM {support_plan} WHERE clid = :client AND uid = :uid AND day = :day', array(
':client' => $clid,
':uid' => $account->uid,
':day' => $week,
))
->fetchField();
if (!empty($comments)) {
$row[0] .= '<br />' . check_plain($comments);
}
if (function_exists('imagecreatetruecolor')) {
$row[] = "<img src='" . url('support_pm/image/') . support_pm_chartapi_color($clid) . "' alt='swatch' height='15' width='15' />";
}
$row[] = isset($totals['plan'][$clid]) ? number_format($totals['plan'][$clid], 2) : "0.00";
$row[] = isset($totals['actual'][$clid]) ? number_format($totals['actual'][$clid], 2) : "0.00";
if (isset($totals['plan'][$clid])) {
if (isset($totals['actual'][$clid])) {
$diff = $totals['actual'][$clid] - $totals['plan'][$clid];
}
else {
$diff = -$totals['plan'][$clid];
}
}
else {
if (isset($totals['actual'][$clid])) {
$diff = $totals['actual'][$clid];
}
else {
$diff = 0.0;
}
}
$plan = isset($totals['plan'][$clid]) ? $totals['plan'][$clid] : 0;
$row[] = theme('support_pm_plan_diff', array(
'diff' => $diff,
'plan' => $plan,
));
$rows[] = $row;
}
$row = array(
'<strong>' . t('Total') . '</strong>',
);
if (function_exists('imagecreatetruecolor')) {
$row[] = "<img src='" . url('support_pm/image/DDDDDD') . "' alt='swatch' height='15' width='15' />";
}
$plan = isset($totals['plan']) ? array_sum($totals['plan']) : 0;
$actual = isset($totals['actual']) ? array_sum($totals['actual']) : 0;
$row[] = '<strong>' . number_format($plan, 2) . '</strong>';
$row[] = '<strong>' . number_format($actual, 2) . '</strong>';
$diff = $actual - $plan;
$row[] = '<strong>' . theme('support_pm_plan_diff', array(
'diff' => $diff,
'plan' => $plan,
));
$rows[] = $row;
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array(
'id' => 'support_pm_clients',
),
));
$output .= theme('support_pm_pager', array(
'text' => t('next ›'),
'op' => '>',
));
return $output;
}
function support_pm_display_swatch($color) {
$color = preg_replace('/[^0-9a-fA-F]/', '', $color);
if (strlen($color)) {
if (strlen($color) == 3) {
$color = $color . $color;
}
if (strlen($color) > 6) {
$color = substr($color, 0, 6);
}
else {
if (strlen($color) < 6) {
$color = 'FFFFFF';
}
}
}
$image = imagecreatetruecolor(15, 15);
$swatch = imagecolorallocate($image, hexdec(substr($color, 0, 2)), hexdec(substr($color, 2, 2)), hexdec(substr($color, 4, 2)));
imagefill($image, 0, 0, $swatch);
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
}
/**
* Page callback.
*/
function support_pm_user_week($form, &$form_state, $user) {
$week = isset($_GET['week']) ? (int) $_GET['week'] : time();
$dates = _support_pm_dates($week);
drupal_add_js(array(
'support_pm' => array(
'unload_warning' => variable_get('support_pm_unload_warning', TRUE),
'elapsed' => 0,
),
), 'setting');
drupal_add_js(drupal_get_path('module', 'support_pm') . '/support_pm.js');
$clients = _support_available_clients($user);
$clients['totals'] = '<strong>' . t('Totals') . '</strong>';
foreach ($clients as $clid => $name) {
$form['client'][$clid] = array(
'#markup' => $name,
);
foreach ($dates as $date => $day) {
$hours = db_query('SELECT hours FROM {support_plan} WHERE clid = :client AND uid = :uid AND day = :day', array(
':client' => $clid,
':uid' => $user->uid,
':day' => $date,
))
->fetchField();
$form['textfields'][$clid]["{$clid}:{$date}"] = array(
'#type' => 'textfield',
'#size' => '2',
'#default_value' => $hours ? $hours : 0,
'#disabled' => $clid == 'totals' ? TRUE : FALSE,
);
}
$form['textfields'][$clid]["{$clid}:totals"] = array(
'#type' => 'textfield',
'#size' => '2',
'#disabled' => TRUE,
'#default_value' => 0,
);
if ($clid != 'totals') {
$comment = db_query('SELECT comment FROM {support_plan} WHERE clid = :client AND uid = :uid AND day = :day', array(
':client' => $clid,
':uid' => $user->uid,
':day' => _support_pm_first_day($week),
))
->fetchField();
$form['textfields'][$clid]["{$clid}:comment"] = array(
'#type' => 'textfield',
'#size' => '20',
'#disabled' => FALSE,
'#default_value' => $comment,
);
}
}
$form['uid'] = array(
'#type' => 'hidden',
'#value' => $user->uid,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save plan'),
);
return $form;
}
/**
* Save user's week plan.
*/
function support_pm_user_week_submit($form, &$form_state) {
$week = isset($_GET['week']) ? _support_pm_first_day((int) $_GET['week']) : _support_pm_first_day(time());
$dates = _support_pm_dates($week);
$dates['totals'] = t('Totals');
$user = user_load($form_state['values']['uid']);
$clients = _support_available_clients($user);
foreach ($form_state['values'] as $key => $value) {
$key = explode(':', $key);
// Skip values that aren't part of the matrix.
if (count($key) != 2) {
continue;
}
$clid = $key[0];
$day = $key[1];
if (!empty($day)) {
if (isset($clients[$clid]) && $day != 'totals') {
if ($day == 'comment') {
db_merge('support_plan')
->key(array(
'clid' => $clid,
'uid' => $user->uid,
'day' => $week,
))
->fields(array(
'comment' => $value,
))
->execute();
}
else {
db_merge('support_plan')
->key(array(
'clid' => $clid,
'uid' => $user->uid,
'day' => $day,
))
->fields(array(
'hours' => $value,
))
->execute();
}
}
}
}
drupal_goto("user/{$user->uid}/support_pm", array(
'query' => array(
'week' => $week,
),
));
}
/**
* TODO: Auto-calculate totals whenever a field is updated.
*/
function theme_support_pm_user_week($variables) {
$form = $variables['form'];
$week = isset($_GET['week']) ? _support_pm_first_day((int) $_GET['week']) : _support_pm_first_day(time());
drupal_set_title(t('@start - @end', array(
'@start' => format_date($week, 'medium'),
'@end' => format_date($week + 86400 * 6, 'medium'),
)), PASS_THROUGH);
$dates = _support_pm_dates($week);
$dates['totals'] = t('Totals');
$dates['comment'] = t('Comments');
$rows = array();
foreach (element_children($form['client']) as $key) {
// Don't take form control structures
if (isset($form['client'][$key]) && is_array($form['client'][$key])) {
$row = array();
$row[] = array(
'data' => drupal_render($form['client'][$key]),
'class' => 'support-client',
'id' => drupal_clean_css_identifier('client-' . $form['client'][$key]['#markup']),
);
if (is_array($form['textfields'][$key])) {
foreach ($dates as $date => $name) {
$row[] = array(
'data' => drupal_render($form['textfields'][$key]["{$key}:{$date}"]),
'class' => 'textfield',
'title' => $name,
);
}
}
}
$rows[] = $row;
}
$header[] = t('Client');
foreach ($dates as $date => $name) {
if (is_numeric($date) && $name == format_date($date, 'custom', 'l')) {
$header[] = t('!day<br />!date', array(
'!day' => format_date($date, 'custom', 'l'),
'!date' => format_date($date, 'custom', 'M d'),
));
}
else {
$header[] = $name;
}
}
$output = theme('support_pm_pager', array(
'text' => t('‹ previous'),
'op' => '<',
));
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array(
'id' => 'permissions',
),
));
$output .= drupal_render_children($form);
$output .= theme('support_pm_pager', array(
'text' => t('next ›'),
'op' => '>',
));
return $output;
}
/**
* Bar graph details.
*/
function theme_support_pm_user_client_hours_details($variables) {
$day = $variables['day'];
$scale = $variables['scale'];
static $comment;
$clients = array();
$chart = array();
$hours = array();
$colors = array();
foreach ($day as $clid => $data) {
$hours[$clid] = $data->hours;
$colors[$clid] = support_pm_chartapi_color($clid);
}
if (empty($hours)) {
$hours[] = 0;
$colors[] = support_pm_chartapi_color(0);
}
// TODO: Allow attribute overrides
$attributes = array(
'class' => 'chart',
);
$output = support_pm_chartapi_render(array(
'cht' => 'bvs',
'chd' => 't:' . implode('|', $hours),
'chco' => implode(',', $colors),
'chs' => '45x70',
'chl' => format_plural(array_sum($hours), '1 hr', '@count hrs'),
'chma' => '7,0,7',
'chds' => "0,{$scale}",
), $attributes);
return $output;
}
/**
* Pie chart summary.
*/
function theme_support_pm_user_hours_summary($variables) {
$output = '';
$totals = $variables['totals'];
$load_callback = $variables['load_callback'];
$max = $variables['max'];
$message = $variables['message'];
if (!is_array($totals)) {
return;
}
arsort($totals);
$data = array();
foreach ($totals as $id => $total) {
if ($id) {
if (!isset($data['totals'][$id])) {
$data['totals'][$id] = 0;
}
$data['totals'][$id] += $total;
$data['labels'][$id] = format_plural($total, '1 hr', '@count hrs');
$data['colors'][$id] = support_pm_chartapi_color($id, 'user');
$details = $load_callback($id);
$data['legend'][$id] = check_plain($details->name);
}
}
if (isset($data['totals']) && is_array($data['totals'])) {
foreach ($data['totals'] as $id => $total) {
if (!$total) {
unset($data['totals'][$id]);
unset($data['labels'][$id]);
unset($data['colors'][$id]);
unset($data['legend'][$id]);
}
}
}
if (!empty($data)) {
$total = array_sum($data['totals']);
if ($max && $total < $max) {
if (!isset($data['totals']['filler'])) {
$data['totals']['filler'] = 0;
}
$data['totals']['filler'] += $max - $total;
$data['labels']['filler'] = format_plural($max - $total, '1 hr', '@count hrs');
$data['colors']['filler'] = 'DDDDDD';
$data['legend']['filler'] = $message;
}
// TODO: Allow attribute overrides
$attributes = array(
'class' => 'chart',
);
$output = support_pm_chartapi_render(array(
'cht' => 'p',
'chs' => '450x245',
'chtt' => format_plural($total, '1 hr', '@count hrs'),
'chd' => 't:' . implode(',', $data['totals']),
'chdl' => implode('|', $data['legend']),
'chco' => implode(',', $data['colors']),
'chl' => implode('|', $data['labels']),
'chma' => '25,0,15,15',
), $attributes);
}
return $output;
}
/**
* Helper function to show previous and next weeks.
*/
function theme_support_pm_pager($variables) {
$text = $variables['text'];
$op = $variables['op'];
$parameters = $variables['parameters'];
$attributes = $variables['attributes'];
$first_day = isset($_GET['week']) ? _support_pm_first_day((int) $_GET['week']) : _support_pm_first_day(time());
$week = $first_day;
$prepend = $append = '';
switch ($op) {
case '<':
$week -= 86400 * 7;
$prepend = '‹ ';
break;
case '>':
$week += 86400 * 7;
$append = ' ›';
break;
default:
$append = '';
$prepend = '';
break;
}
$dates = t('@start - @end', array(
'@start' => format_date($week, 'medium'),
'@end' => format_date($week + 86400 * 6, 'medium'),
));
$text = t('@prepend@dates@append', array(
'@prepend' => $prepend,
'@dates' => $dates,
'@append' => $append,
));
$query = array();
if (!isset($parameters['week'])) {
$parameters['week'] = $week;
}
if (!isset($attributes['title'])) {
$attributes['title'] = t('View plan for week of @week', array(
'@week' => $dates,
));
}
return l($text, $_GET['q'], array(
'attributes' => $attributes,
'query' => $parameters,
));
}
function theme_support_pm_plan_diff($variables) {
$diff = $variables['diff'];
$plan = $variables['plan'];
$diff = number_format($diff, 2);
$percent = $plan ? number_format($diff / $plan * 100, 2) : 0;
if ($diff < 0) {
return "<font color='red'>{$diff} ({$percent}%)</font>";
}
else {
if ($diff > 0) {
if ($plan) {
return "<font color='green'>{$diff} (+{$percent}%)</font>";
}
else {
return "<font color='brown'>{$diff}</font>";
}
}
}
return $diff;
}
/**
* TODO: Proper timezone support
*/
function _support_pm_first_day($time = 0) {
// Use Drupal core's configurable first day of the week.
$date_first_day = variable_get('date_first_day', 0);
if (!$time) {
$time = time();
}
// Determine what day of the week $time is
$day = date('w', $time);
// Now calculate a timestamp for the first day of this week,
// respecting the configured first day of the week.
if ($day >= $date_first_day) {
$days = $day - $date_first_day;
}
else {
$days = $day + 7 - $date_first_day;
}
return strtotime(date('M d, Y 12:00', $time - 86400 * $days));
}
/**
* Return array of days, $timestamp => $day where $timestamp is the first
* second of the named $day.
*/
function _support_pm_dates($start = 0) {
// TODO: Make configurable (ie, disable weekends)
$day = array(
t('Sunday'),
t('Monday'),
t('Tuesday'),
t('Wednesday'),
t('Thursday'),
t('Friday'),
t('Saturday'),
);
$date = _support_pm_first_day($start);
$day_of_week = variable_get('date_first_day', 0);
// Build an array of $timestamp => $day pairs
for ($i = 0; $i < 7; $i++) {
$dates[$date] = $day[$day_of_week];
$date += 86400;
if (++$day_of_week > 6) {
$day_of_week = 0;
}
}
return $dates;
}
function support_pm_day_load($date = NULL, $user = NULL, $client = NULL) {
if (is_null($date)) {
// default to this week
$date = _support_pm_first_day();
}
$query = db_select('support_plan')
->fields('support_plan', array(
'clid',
'uid',
'day',
'hours',
'comment',
))
->condition('day', $date)
->condition('hours', 0, '>');
if (is_object($user) && isset($user->uid)) {
$query
->condition('uid', $user->uid);
}
if (is_object($client) && isset($client->clid)) {
$query
->condition('clid', $client->clid);
}
$day = array();
$result = $query
->execute();
foreach ($result as $row) {
$day[$row->uid][$row->clid] = $row;
}
return $day;
}
/**
* Google ChartAPI calls.
* TODO: Move into a helper module? (chartapi module seems abandoned)
**/
/**
* In order to optimize displaying multiple charts on one page,
* we generate unique urls for the charts.
* TODO: Why does this break?
*/
function support_pm_chartapi_uri() {
return 'http://chart.apis.google.com/chart';
static $i = 0;
$uri = "http://{$i}.chart.apis.google.com/chart";
if (++$i > 9) {
$i = 0;
}
return $uri;
}
function support_pm_chartapi_color($id, $type = 'client') {
static $color = 0;
static $values = NULL;
$update = FALSE;
if (!isset($values)) {
$values = variable_get('support_pm_color_values', array());
$color = variable_get('support_pm_color', 0);
}
$colors = array(
'66FF99',
'6699CC',
'FFCCFF',
'FFFF99',
'FFFF00',
'6633CC',
'666600',
'FFCC00',
'666666',
'66FF00',
'66CC66',
'66FFFF',
'669933',
'FF6600',
'6666FF',
'FF3300',
'66CCFF',
'663333',
'FF0000',
);
if (!isset($values[$type][$id])) {
$values[$type][$id] = $colors[$color++];
$update = TRUE;
}
if ($color > count($colors)) {
$color = 0;
}
if ($update) {
variable_set('support_pm_color_values', $values);
variable_set('support_pm_color', $color);
}
return $values[$type][$id];
}
function support_pm_chartapi_render($chart, $attributes = array()) {
return '<img src="' . support_pm_chartapi_uri() . '?' . drupal_http_build_query(drupal_get_query_parameters($chart)) . '"' . drupal_attributes($attributes) . " />\n";
}
/**
* Convert from timer time format to decimal.
*/
function support_pm_timer_to_hours($time) {
$time = explode(':', $time);
if (!isset($time[1])) {
$time[1] = 0;
}
$hours = (int) $time[0];
$minutes = round((int) $time[1] / 60, 2);
return $hours + $minutes;
}
/**
* Load project from database.
*/
function support_pm_project_load($projid) {
static $projects = array();
if (!isset($projects[$projid])) {
$projects[$projid] = db_query('SELECT * FROM {support_project} WHERE projid = :project', array(
':project' => $projid,
))
->fetchObject();
if (empty($projects[$projid])) {
return FALSE;
}
$projects[$projid]->clids = array();
$result = db_query('SELECT clid FROM {support_project_client} WHERE projid = :project', array(
':project' => $projid,
));
foreach ($result as $client) {
$projects[$projid]->clids[] = $client->clid;
}
drupal_alter('support_pm_project_load', $projects[$projid]);
}
return $projects[$projid];
}
function support_pm_project_load_nid($nid) {
static $projects = array();
if (!isset($projects[$nid])) {
$projects[$nid] = db_query('SELECT * FROM {support_project_ticket} spt LEFT JOIN {support_project} sp ON spt.projid = sp.projid WHERE spt.nid = :nid', array(
':nid' => $nid,
))
->fetchObject();
drupal_alter('support_pm_project_load_nid', $projects[$nid]);
}
return $projects[$nid];
}
/**
* Load projects assigned to a given client.
*/
function support_pm_load_projects($clid) {
$projects = array();
$result = db_query('SELECT sp.projid, sp.project FROM {support_project} sp INNER JOIN {support_project_client} spc ON sp.projid = spc.projid WHERE (spc.clid = :client OR spc.clid = 0) AND disabled = 0', array(
':client' => $clid,
));
foreach ($result as $project) {
$projects[$project->projid] = $project->project;
}
return $projects;
}
/**
* Determine default for a list of projects.
*/
function support_pm_default_project($projects) {
$projids = array();
foreach ($projects as $projid => $project) {
$projids[] = $projid;
}
if (empty($projids)) {
return 0;
}
else {
return db_query_range('SELECT projid FROM {support_project} WHERE projid IN (:projects) AND disabled = 0 ORDER BY weight ASC', 0, 1, array(
':projects' => $projids,
))
->fetchField();
}
}
Functions
Name | Description |
---|---|
support_pm_chartapi_color | |
support_pm_chartapi_render | |
support_pm_chartapi_uri | In order to optimize displaying multiple charts on one page, we generate unique urls for the charts. TODO: Why does this break? |
support_pm_comment_view | Implements hook_comment_view(). |
support_pm_day_load | |
support_pm_default_project | Determine default for a list of projects. |
support_pm_display_swatch | |
support_pm_form_alter | |
support_pm_invoice_ui_form | Provide form for selecting projects. |
support_pm_invoice_ui_form_submit | Add url filter when projects are selected. |
support_pm_load_projects | Load projects assigned to a given client. |
support_pm_menu | Implements hook_menu(). TODO: Include date in 'view' and 'edit' tabs |
support_pm_node_delete | Implements hook_node_delete(). |
support_pm_node_insert | Implements hook_node_insert(). |
support_pm_node_load | Implements hook_node_load(). |
support_pm_node_update | Implements hook_node_update(). |
support_pm_node_view | Implements hook_node_view(). |
support_pm_permission | Implements hook_permission(). |
support_pm_plan_overview_weekly | |
support_pm_project_load | Load project from database. |
support_pm_project_load_nid | |
support_pm_query_support_pager_alter | Alter the ticket listing query to consider projects. |
support_pm_query_support_timer_comment_alter | Alter the timer report for comments to consider projects. |
support_pm_query_support_timer_node_alter | Alter the timer report for nodes to consider projects. |
support_pm_support_timer_client_report_alter | Allow projects to be selected. |
support_pm_theme | Implements hook_theme(). |
support_pm_timer_to_hours | Convert from timer time format to decimal. |
support_pm_user_week | Page callback. |
support_pm_user_week_submit | Save user's week plan. |
theme_support_pm_pager | Helper function to show previous and next weeks. |
theme_support_pm_plan_diff | |
theme_support_pm_user_client_hours_details | Bar graph details. |
theme_support_pm_user_hours_summary | Pie chart summary. |
theme_support_pm_user_week | TODO: Auto-calculate totals whenever a field is updated. |
_support_pm_dates | Return array of days, $timestamp => $day where $timestamp is the first second of the named $day. |
_support_pm_first_day | TODO: Proper timezone support |