',
);
$form['issue_details']['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $node->title,
'#size' => 60,
'#maxlength' => 128,
);
$form['issue_details']['body'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => $node->body,
'#rows' => 10,
);
$form['issue_details']['format'] = filter_form($node->format);
$directory = file_create_path(variable_get('project_directory_issues', 'issues'));
if (!file_check_directory($directory, 0)) {
$msg = t('File attachments are disabled. The issue directory has not been properly configured.');
if (user_access('administer site configuration')) {
$msg .= ' '. t('Please visit the !admin-project-issue-settings page.', array('!admin-project-issue-settings' => l(t('Project issue settings'), 'admin/project/project-issue-settings')));
}
else {
$msg .= ' '. t('Please contact the site administrator.');
}
drupal_set_message($msg, 'warning');
}
}
else {
$form['pid'] = array(
'#type' => 'select',
'#title' => t('Project'),
'#options' => $projects,
'#required' => TRUE,
);
}
return $form;
}
function project_issue_form_pre_render($form_id, &$form) {
if ($form_id == 'project_issue_node_form') {
// Only move to the next page if there were no form errors.
// We don't want to keep incrementing page forever, since users
// can successfully preview many times. However, if we already
// have a value for 'Project' (via a submit with project in the
// URL), we'll already be on page 2 of the form and must increment
// our page or we'll display the wrong buttons.
if (!form_get_errors() && $form['page']['#value'] < 2 && (isset($_POST['page']) || isset($form['project_info']['pid']['#value']))) {
$form['page']['#value'] = $form['page']['#value'] + 1;
}
if ($form['page']['#value'] == 1) {
// If we're on the first page, we want to convert 'preview' into
// a 'next' button and hide 'submit'.
$form['preview'] = array(
'#type' => 'button',
'#value' => t('Next'),
'#weight' => 19,
);
$form['submit'] = array(
'#type' => 'value',
'#value' => 'hidden',
);
}
else {
/*
We're on page 2. In this case, we want all the default
buttons. Furthermore, we must now set the #required attribute
on all the fields that are now in the form (which are only
added once we know the project from page #1 or from a direct
link), so that when we now attempt to validate the input (and
display the form), these fields will be required.
*/
_project_issue_form_add_required_fields($form, TRUE);
}
}
}
/**
* Private helper method to set the '#required' attribute to TRUE for
* all the fields that should be required on the issue form. These
* fields can't be set to required from project_issue_form() because
* this is a 2 page form, and so the FAPI validation code would mark
* them all as an error as soon as the user lands on page 2.
* Therefore, they're set to required by the pre_render hook (after
* initially validating the page, but before it is displayed). This
* This is a separate method so that it can also be called from
* comment.inc, which shares the project_issue_form() code, but
* doesn't use it as a 2-page form, so we want these fields to be
* required right away in that case.
*
* @param $form
* Reference to the form to modify
* @param $needs_body
* Boolean that specifies if the 'body' field should be required
*/
function _project_issue_form_add_required_fields(&$form, $needs_body) {
$form['project_info']['rid']['#required'] = TRUE;
$form['project_info']['component']['#required'] = TRUE;
$form['issue_info']['category']['#required'] = TRUE;
$form['issue_details']['title']['#required'] = TRUE;
if ($needs_body) {
$form['issue_details']['body']['#required'] = TRUE;
}
}
function project_issue_node_form_validate($form_id, $form) {
global $form_values;
$edit = $_POST;
if (!$form_values['pid']) {
form_set_error('pid', t('You have to specify a valid project.'));
}
if ($form_values['page'] == 2) {
if ($form_values['pid'] && $project = node_load($form_values['pid'])) {
$node->title = $form_values['title'];
if (module_exists('project_release') &&
$releases = project_release_get_releases($project, 0)) {
empty($form_values['rid']) and form_set_error('rid', t('You have to specify a valid version.'));
}
if ($form_values['component'] && !in_array($form_values['component'], $project->components)) {
$form_values['component'] = 0;
}
empty($form_values['component']) && form_set_error('component', t('You have to specify a valid component.'));
empty($form_values['category']) && form_set_error('category', t('You have to specify a valid category.'));
empty($form_values['title']) && form_set_error('title', t('You have to specify a valid title.'));
empty($form_values['body']) && form_set_error('body', t('You have to specify a valid description.'));
}
}
}
function project_issue_view($node, $teaser = false, $page = false) {
$node = node_prepare($node, $teaser);
if (!$teaser && ($page || project_issue_is_comment_reply())) {
$node->content['#prefix'] = '
';
$node->content['#suffix'] = '
';
$project = node_load(array('nid' => $node->pid, 'type' => 'project_project'));
$release->nid = $node->rid;
if (module_exists('project_release')) {
$release = project_release_load($release);
}
$assigned = ($node->assigned && ($account = user_load(array('uid' => $node->assigned))) ? $account->name : t('Unassigned'));
$rows = array();
$rows[] = array('Project:', check_plain($project->title));
if ($release->version) {
$rows[] = array('Version:', check_plain($release->version));
}
$rows[] = array(t('Component:'), check_plain($node->component));
$rows[] = array(t('Category:'), check_plain($node->category));
$rows[] = array(t('Priority:'), project_issue_priority($node->priority));
$rows[] = array(t('Assigned:'), $assigned);
$rows[] = array(t('Status:'), project_issue_state($node->sid));
$node->content['project_issue_summary'] = array(
'#value' => theme('project_issue_summary', $rows, project_issue_internal_links($node)),
'#weight' => -5,
);
$node->content['project_issue_header'] = array(
'#value' => '',
'#weight' => -3,
);
project_issue_set_breadcrumb($node, $project);
}
return $node;
}
/**
* Themes the metadata table and internal page links for issue nodes.
*
* @param $summary_table_rows
* An array of row data for the metadata table.
* @param $summary_links
* An array of internal page links.
* @return
* An HTML string of the summary section.
*/
function theme_project_issue_summary($summary_table_rows, $summary_links) {
$output = '
';
$output .= '
'. theme('table', array(), $summary_table_rows) .'
';
$output .= '
'. theme('item_list', $summary_links, t('Jump to:'), 'ul', array('class' => 'internal-links')) .'
';
$output .= '
';
return $output;
}
/**
* Generates internal page links for issue pages.
*
* @param $node
* The issue node.
* @return
* An array of internal page links.
*/
function project_issue_internal_links($node) {
$links = array();
// Link to the first unread, or most recent comment.
if (comment_num_new($node->nid)) {
// There are unread replies; link to first unread comment.
$links[] = l(t('First unread comment'), "node/$node->nid", array(), NULL, 'new');
}
else {
// No unread replies; link to most recent comment.
$comment_cid = db_result(db_query_range("SELECT pic.cid FROM {project_issue_comments} pic INNER JOIN {node} n on pic.nid = n.nid WHERE n.status = 1 AND n.nid = %d ORDER BY pic.cid DESC", $node->nid, 0, 1));
if ($comment_cid) {
$links[] = l(t('Most recent comment'), "node/$node->nid", array(), NULL, "comment-$comment_cid");
}
}
// Link for most recent patch.
$file_cid = db_result(db_query_range("SELECT cu.cid FROM {comment_upload_files} cu INNER JOIN {node} n on cu.nid = n.nid WHERE n.status = 1 AND n.nid = %d ORDER BY cu.fid DESC", $node->nid, 0, 1));
if ($file_cid) {
$links[] = l(t('Most recent attachment'), "node/$node->nid", array(), NULL, "comment-$file_cid");
}
// Link straight to comment form.
if (user_access('post comments') || user_access('post comments without approval')) {
// TODO: This conditional needs to be ripped out in D6.
$comment_form_location = isset($node->comment_form_location) ? $node->comment_form_location : variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE);
// Comment form isn't on the page, link to the comment reply page.
if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) {
$links[] = l(t('Add new comment'), "comment/reply/$node->nid");
}
// Comment form is on the page, generate an internal page link for it.
else {
$links[] = l(t('Add new comment'), "node/$node->nid", array(), NULL, "comment-form");
}
}
return $links;
}
function project_issue_load($node) {
$project = db_fetch_object(db_query(db_rewrite_sql('SELECT pi.* FROM {project_issues} pi WHERE pi.nid = %d', 'pi'), $node->nid));
// TODO: This need to be ripped out in D6.
$project->comment_form_location = variable_get('project_issue_comment_form_location', NULL);
return $project;
}
function project_issue_insert($node) {
// Permanently store the original issue states in a serialized array. This is a bit
// yucky, but we need them for proper handling of states workflow. The current states
// need to be stored in {project_issues} as well for query efficiency in issue queue
// searches, and it seems too messy to add a bunch of new columns to the {project_issues}
// table for the original states.
$original_issue_data = new stdClass();
$fields = array(
'pid' => 0,
'rid' => 0,
'component' => '',
'category' => '',
'priority' => 0,
'assigned' => 0,
'sid' => 0,
'title' => '',
);
foreach ($fields as $field => $default) {
// Some of the incoming data may not have the correct default.
if (!$node->$field) {
$node->$field = $default;
}
$original_issue_data->$field = $node->$field;
}
db_query("INSERT INTO {project_issues} (nid, pid, category, component, priority, rid, assigned, sid, original_issue_data, last_comment_id, db_lock) VALUES (%d, %d, '%s', '%s', %d, %d, %d, %d, '%s', %d, %d)", $node->nid, $node->pid, $node->category, $node->component, $node->priority, $node->rid, $node->assigned, $node->sid, serialize($original_issue_data), 0, 0);
}
function project_issue_update($node) {
db_query("UPDATE {project_issues} SET pid = %d, category = '%s', component = '%s', priority = %d, rid = %d, assigned = %d, sid = %d WHERE nid = %d", $node->pid, $node->category, $node->component, $node->priority, $node->rid, $node->assigned, $node->sid, $node->nid);
}
function project_issue_delete($node) {
db_query('DELETE FROM {project_issues} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {project_issue_comments} WHERE nid = %d', $node->nid);
}
function project_issue_access($op, $node) {
global $user;
if (user_access('administer projects')) {
return TRUE;
}
switch ($op) {
case 'view':
if (user_access('access own project issues') && $node->uid == $user->uid) {
return TRUE;
}
if (!user_access('access project issues')) {
return FALSE;
}
break;
case 'create':
return user_access('create project issues');
case 'update':
if (user_access('edit own project issues') && $node->uid == $user->uid) {
return TRUE;
}
break;
case 'delete':
// Admin case already handled, no one else should be able to delete.
break;
}
}
// Support stuff
/**
* Return information about project issue state values.
*
* @param $sid
* Integer state id to return information about, or 0 for all states.
* @param $restrict
* Boolean to determine if states should be restricted based on the
* permissions of the current user or $account if that parameter
* is provided.
* @param $is_author
* Boolean that indicates if the current user is the author of the
* issue, which can potentially grant a wider selection of states.
* @param $current_sid
* The current integer state id for the issue.
* @param $defaults
* Boolean to request the states used for default issue queries.
* @param $account
* Account of a user to pass to user_access() for access checking.
* This parameter will have no effect unless $restrict is also
* set to TRUE.
*
* @return
* An array of states (sid as key, name as value) that match the
* given filters, or the name of the requested state.
*/
function project_issue_state($sid = 0, $restrict = false, $is_author = false, $current_sid = 0, $defaults = false, $account = NULL) {
static $options;
if (!$options) {
$result = db_query('SELECT * FROM {project_issue_state} ORDER BY weight');
while ($state = db_fetch_object($result)) {
$options[] = $state;
}
}
foreach($options as $state) {
if ($restrict) {
// Check if user has access,
// or if status is default status and therefore available to all,
// or if user is original issue author and author has access,
// or if the issue is already in a state, even if the user doesn't have
// access to that state themselves.
if (user_access('set issue status '. str_replace("'", "", $state->name), $account)
|| user_access('administer projects', $account)
|| ($state->sid == variable_get('project_issue_default_state', 1))
|| ($state->author_has && $is_author)
|| ($state->sid == $current_sid) ) {
$states[$state->sid] = $state->name;
}
}
else if ($defaults) {
if ($state->default_query) {
$states[$state->sid] = $state->name;
}
}
else {
$states[$state->sid] = $state->name;
}
}
return $sid ? $states[$sid] : $states;
}
/**
* Return an array of state ids that should be used for default queries.
*/
function project_issue_default_states() {
static $defaults;
if (empty($defaults)) {
$states = project_issue_state(0, false, false, 0, true);
$defaults = !empty($states) ? array_keys($states) : array(1, 2, 4);
}
return $defaults;
}
function project_issue_priority($priority = 0) {
$priorities = array(1 => 'critical', 'normal', 'minor');
return $priority ? $priorities[$priority] : $priorities;
}
function project_issue_category($category = 0, $plural = 1) {
if ($plural) {
$categories = array('bug' => t('bug reports'), 'task' => t('tasks'), 'feature' => t('feature requests'), 'support' => t('support requests'));
}
else {
$categories = array('bug' => t('bug report'), 'task' => t('task'), 'feature' => t('feature request'), 'support' => t('support request'));
}
return $category ? $categories[$category] : $categories;
}
function project_issue_count($pid) {
$state = array();
$result = db_query('SELECT p.sid, count(p.sid) AS count FROM {node} n INNER JOIN {project_issues} p ON n.nid = p.nid WHERE n.status = 1 AND p.pid = %d GROUP BY p.sid', $pid);
while ($data = db_fetch_object($result)) {
$state[$data->sid] = $data->count;
}
return $state;
}
function project_issue_search_page($project_name = NULL, $query = NULL) {
$project = project_project_retrieve($project_name);
if (isset($project) && $project->nid && node_access('view', $project)) {
return drupal_get_form('project_issue_query', $project, $query);
}
else {
return drupal_get_form('project_issue_query', 0, $query);
}
}
function project_issue_query($project = 0, $query = NULL) {
$categories = project_issue_category();
$projects = array();
$components = array();
if (!empty($project->nid)) {
drupal_set_title(t('Search issues for %name', array('%name' => $project->title)));
project_project_set_breadcrumb($project, TRUE);
foreach ($project->components as $component) {
$component = check_plain($component);
$components[$component] = $component;
}
if (module_exists('project_release')) {
$versions = project_release_get_releases($project, 0);
}
}
else {
$uris = NULL;
$projects = project_projects_select_options($uris);
drupal_set_title(t('Search issues for all projects'));
}
if (is_null($query)) {
$query = (object) array(
'projects' => '',
'summary' => '',
'attachment' => '',
'versions' => '',
'components' => '',
'categories' => '',
'priorities' => '',
'states' => '',
'submitted' => '',
'assigned' => '',
'users' => '',
);
}
$states = project_issue_state();
$priorities = project_issue_priority();
$form['#action'] = url(isset($project->uri) ? "project/issues/$project->uri" : 'project/issues');
$form['text'] = array(
'#type' => 'textfield',
'#title' => t('Search for'),
'#default_value' => $query->summary,
'#maxlength' => 255,
);
$form['attachment'] = array(
'#type' => 'checkbox',
'#title' => t('Has attachment.'),
'#default_value' => $query->attachment,
);
if ($projects) {
$form['projects'] = array(
'#type' => 'select',
'#title' => t('Projects'),
'#default_value' => $query->projects,
'#options' => $projects,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
$form['categories'] = array(
'#type' => 'select',
'#title' => t('Categories'),
'#default_value' => $query->categories,
'#options' => $categories,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
}
else {
if (!empty($versions)) {
$form['versions'] = array(
'#type' => 'select',
'#title' => t('Versions'),
'#default_value' => $query->versions,
'#options' => $versions,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
}
$form['components'] = array(
'#type' => 'select',
'#title' => t('Components'),
'#default_value' => $query->components,
'#options' => $components,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
$form['categories'] = array(
'#type' => 'select',
'#title' => t('Categories'),
'#default_value' => $query->categories,
'#options' => $categories,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
}
$form['priorities'] = array(
'#type' => 'select',
'#title' => t('Priorities'),
'#default_value' => $query->priorities,
'#options' => $priorities,
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
$form['states'] = array(
'#type' => 'select',
'#title' => t('Status'),
'#options' => $states,
'#default_value' => ($query->states) ? $query->states : project_issue_default_states(),
'#attributes' => array('size' => 5),
'#multiple' => TRUE,
);
$form['submitted'] = array(
'#type' => 'textfield',
'#title' => t('Submitted by'),
'#default_value' => $query->submitted,
'#autocomplete_path' => 'user/autocomplete',
'#size' => 20,
'#maxlength' => 255,
);
$form['assigned'] = array(
'#type' => 'textfield',
'#title' => t('Assigned'),
'#default_value' => $query->assigned,
'#autocomplete_path' => 'user/autocomplete',
'#size' => 20,
'#maxlength' => 255,
);
$form['participated'] = array(
'#type' => 'textfield',
'#title' => t('Participant'),
'#default_value' => $query->users,
'#autocomplete_path' => 'user/autocomplete',
'#size' => 20,
'#maxlength' => 255,
);
$form['submit'] = array(
'#type' => 'button',
'#value' => t('Search'),
);
return $form;
}
function theme_project_issue_query($form) {
$rows[] = array(
array('data' => drupal_render($form['text']), 'colspan' => 3)
);
$rows[] = array(
array('data' => drupal_render($form['attachment']), 'colspan' => 3)
);
if (isset($form['projects'])) {
$rows[] = array(
drupal_render($form['projects']),
drupal_render($form['categories']),
NULL,
);
}
elseif (isset($form['versions'])) {
$rows[] = array(
drupal_render($form['versions']),
drupal_render($form['components']),
drupal_render($form['categories']),
);
}
else {
$rows[] = array(
array('data' => drupal_render($form['components'])),
array('data' => drupal_render($form['categories']), 'colspan' => 2)
);
}
$rows[] = array(
array('data' => drupal_render($form['priorities'])),
array('data' => drupal_render($form['states']), 'colspan' => 2)
);
$rows[] = array(
drupal_render($form['submitted']),
drupal_render($form['assigned']),
drupal_render($form['participated'])
);
$rows[] = array(
array('data' => drupal_render($form['submit']), 'colspan' => 3)
);
$output = '
';
$output .= theme('table', array(), $rows);
$output .= '
';
$output .= drupal_render($form);
return $output;
}
function theme_project_issue_admin_states_form($form) {
$header = array(
array('data' => t('ID')),
array('data' => t('Name')),
array('data' => t('Weight')),
array('data' => t('Author may set')),
array('data' => t('In default queries')),
array('data' => t('Default status')),
array('data' => t('Operations'))
);
foreach (element_children($form['status']) as $key) {
$rows[] = array(
drupal_render($form['status'][$key]['sid']),
drupal_render($form['status'][$key]['name']),
drupal_render($form['status'][$key]['weight']),
drupal_render($form['status'][$key]['author_has']),
drupal_render($form['status'][$key]['default_query']),
drupal_render($form['default_state'][$key]),
drupal_render($form['delete'][$key])
);
}
$rows[] = array(
NULL,
drupal_render($form['status_add']['name']),
drupal_render($form['status_add']['weight']),
drupal_render($form['status_add']['author_has']),
drupal_render($form['status_add']['default_query']),
NULL, NULL,
);
$output = '
' . theme('table', $header, $rows) . '
';
$output .= drupal_render($form);
return $output;
}
function project_issue_admin_states_form() {
$result = db_query('SELECT * FROM {project_issue_state} ORDER BY weight');
$default_state = variable_get('project_issue_default_state', 1);
$default_states = project_issue_default_states();
$form['status']['#tree'] = TRUE;
while ($state = db_fetch_object($result)) {
$options[$state->sid] = '';
$form['status'][$state->sid]['sid'] = array(
'#type' => 'markup',
'#value' => $state->sid,
);
$form['status'][$state->sid]['name'] = array(
'#type' => 'textfield',
'#default_value' => $state->name,
'#size' => 20,
'#maxlength' => 255,
);
$form['status'][$state->sid]['weight'] = array(
'#type' => 'weight',
'#default_value' => $state->weight,
'#delta' => 15,
);
$form['status'][$state->sid]['author_has'] = array(
'#type' => 'checkbox',
'#default_value' => $state->author_has,
);
$form['status'][$state->sid]['default_query'] = array(
'#type' => 'checkbox',
'#default_value' => in_array($state->sid, $default_states),
);
$del_link = ($state->sid != $default_state) ? l(t('Delete'), 'admin/project/project-issue-status/delete/'. $state->sid) : '';
$form['delete'][$state->sid] = array(
'#type' => 'markup',
'#value' => $del_link,
);
}
$form['default_state'] = array(
'#type' => 'radios',
'#options' => $options,
'#default_value' => $default_state,
);
$form['status_add']['name'] = array(
'#type' => 'textfield',
'#size' => 20,
'#maxlength' => 255,
);
$form['status_add']['weight'] = array(
'#type' => 'weight',
'#default_value' => 0,
'#delta' => 15,
);
$form['status_add']['author_has'] = array(
'#type' => 'checkbox',
);
$form['status_add']['default_query'] = array(
'#type' => 'checkbox',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
$form['#tree'] = TRUE;
return $form;
}
/**
* Submit handler for project_issue_admin_states_form.
*/
function project_issue_admin_states_form_submit($form_id, $form_values) {
// Check for and apply changes or additions to project issue status options.
if (isset($form_values['default_state'])) {
variable_set('project_issue_default_state', $form_values['default_state']);
}
// Update existing status options.
if($form_values['status']) {
foreach ($form_values['status'] as $sid => $value) {
$state = db_fetch_object(db_query('SELECT name, weight, author_has, default_query FROM {project_issue_state} WHERE sid = %d', $sid));
// Check to see whether the record needs updating.
if (($state->name != $value['name']) || ($state->weight != $value['weight']) || ($state->author_has != $value['author_has']) || ($state->default_query != $value['default_query'])) {
db_query("UPDATE {project_issue_state} SET name = '%s', weight = %d, author_has = %d, default_query = %d WHERE sid = %d", $value['name'], $value['weight'], $value['author_has'], $value['default_query'], $sid);
}
}
}
// Add any new status options.
if (isset($form_values['status_add']) && !empty($form_values['status_add']['name'])) {
// Check to see whether the state already exists:
$query = db_query("SELECT name FROM {project_issue_state} WHERE name = '%s'", $form_values['status_add']['name']);
if (!db_num_rows($query)) {
db_query("INSERT INTO {project_issue_state} (name, weight, author_has, default_query) VALUES ('%s', %d, %d, %d)", $form_values['status_add']['name'], $form_values['status_add']['weight'], $form_values['status_add']['author_has'], $form_values['status_add']['default_query']);
}
else {
drupal_set_message(t('Status %status already exists.', array ('%status' => $form_values['status_add']['name'])));
}
}
}
function project_issue_delete_state_confirm($sid) {
$sid = arg(4);
$states = project_issue_state();
$name = $states[$sid];
$total = db_result(db_query('SELECT COUNT(nid) AS total FROM {project_issues} WHERE sid = %d', $sid));
if ($total > 0) {
$form['new_sid'] = array(
'#type' => 'select',
'#title' => t('Reassign status'),
'#default_value' => $sid,
'#options' => $states,
'#description' => t('There are !total existing issues with the status of @name. Please select a new status for these issues.', array('!total' => $total, '@name' => $name)),
);
}
$form['sid'] = array(
'#type' => 'value',
'#value' => $sid,
);
$form['name'] = array(
'#type' => 'hidden',
'#value' => $name,
);
return confirm_form(
$form,
t('Are you sure you want to delete the status option %name?', array('%name' => $name)),
'admin/project/project-issue-status',
t('This action cannot be undone.'),
t('Delete'), t('Cancel')
);
}
function project_issue_delete_state_confirm_submit($form_id, $form_values) {
if($form_values['new_sid'] == $form_values['sid']) {
form_set_error('new_sid', t('Choose a new issue status for existing issues of status %name.', array('%name' => $form_values['name'])));
} else {
if ($form_values['new_sid']) {
db_query('UPDATE {project_issues} SET sid = %d WHERE sid = %d', $form_values['new_sid'], $form_values['sid']);
}
db_query('DELETE FROM {project_issue_state} WHERE sid = %d', $form_values['sid']);
drupal_set_message(t('Project issue status %issue deleted.', array('%issue' => $form_values['name'])));
drupal_goto('admin/project/project-issue-status');
}
}
function project_issue_query_result($query = NULL, $format = 'html', $show_search = true, $set_title = true) {
global $user;
$query = project_issue_query_parse($query);
// $query must have a real value now, or the rest of this function fails.
$uris = NULL;
$projects = array(0 => t('
')) + project_projects_select_options($uris);
$states = array(implode(',', array_keys(project_issue_state())) => t('')) + project_issue_state();
$priorities = array(0 => t('')) + project_issue_priority();
// Load active project
if (count($query->projects) == 1) {
$project = node_load($query->projects[0]);
}
if ($project) {
if ($set_title) {
drupal_set_title(t('Issues for %name', array('%name' => $project->title)));
project_project_set_breadcrumb($project, TRUE);
}
if (module_exists('project_release')) {
$releases = project_release_get_releases($project, 0);
}
$query->projects = $project->nid;
$links = array();
if (node_access('create', 'project_issue')) {
$links[] = array(
'title' => t('Create'),
'href' => "node/add/project-issue/$project->uri",
'attributes' => array('title' => t('Create a new issue for @project.', array('@project' => $project->uri))),
);
}
else {
$links[] = array(
'title' => theme('project_issue_create_forbidden', $project->uri),
'html' => TRUE,
);
}
$links[] = array(
'title' => t('Statistics'),
'href' => "project/issues/statistics/$project->uri",
'attributes' => array('title' => t('See statistics about @project issues.', array('@project' => $project->uri))),
);
if ($user->uid) {
$links[] = array(
'title' => t('Subscribe'),
'href' => "project/issues/subscribe-mail/$project->uri",
'attributes' => array('title' => t('Receive email updates about @project issues.', array('@project' => $project->uri))),
);
}
$links[] = array(
'title' => t('Advanced search'),
'href' => "project/issues/search/$project->uri",
'attributes' => array('title' => t('Use the advanced search page to find @project issues.', array('@project' => $project->uri))),
);
}
else {
// only set a more descriptive title if we're not looking at "my
// issues" or "my projects", since those set their own title already.
if (!isset($_GET['q']) || !strpos($_GET['q'], '/user')) {
drupal_set_title(t('Issues for all projects'));
}
$links = array();
if (node_access('create', 'project_issue')) {
$links[] = array(
'title' => t('Create'),
'href' => "node/add/project-issue",
'attributes' => array('title' => t('Create a new issue.')),
);
}
else {
$links[] = array(
'title' => theme('project_issue_create_forbidden', $project->uri),
'html' => TRUE,
);
}
$links[] = array(
'title' => t('Statistics'),
'href' => "project/issues/statistics",
'attributes' => array('title' => t('See statistics about issues.')),
);
if ($user->uid) {
$links[] = array(
'title' => t('Subscribe'),
'href' => "project/issues/subscribe-mail",
'attributes' => array('title' => t('Receive email updates about issues.')),
);
}
$links[] = array(
'title' => t('Advanced search'),
'href' => "project/issues/search/",
'attributes' => array('title' => t('Use the advanced search page for finding issues.')),
);
}
$issues_per_page = variable_get('project_issues_per_page', PROJECT_ISSUES_PER_PAGE);
$header = array();
if (!$project->nid) {
$header[] = array('data' => t('Project'), 'field' => 'p.pid');
}
$header[] = array('data' => t('Summary'), 'field' => 'n.title');
$header[] = array('data' => t('Status'), 'field' => 'p.sid');
$header[] = array('data' => t('Priority'), 'field' => 'p.priority');
$header[] = array('data' => t('Category'), 'field' => 'p.category');
if (!empty($releases)) {
$header[] = array('data' => t('Version'), 'field' => 'p.rid');
}
$header[] = array('data' => t('Last updated'), 'field' => 'n.changed', 'sort' => 'desc');
$header[] = array('data' => t('Assigned to'), 'field' => 'u.name');
$sql = project_issue_query_sql($query);
$result = pager_query($sql['sql'] . tablesort_sql($header), $issues_per_page, 0, $sql['count']);
$search = '';
if ($show_search) {
// Action links:
$search .= theme('links', $links);
$search .= drupal_get_form('project_issue_query_result_quick_search', $query, $projects, $states, $priorities);
}
$rows = array();
// For the rest of this function, we need $projects to be a flat
// array to map pids to project names. If we're using the project
// taxonomy, it will really be an array of arrays, where each
// project type is itself an array. So, we flatten the array once
// and then can use it safely in the rest of this method.
if (project_use_taxonomy()) {
$flat_projects = array();
foreach ($projects as $tmp) {
if (is_array($tmp)) {
$flat_projects += $tmp;
}
}
$projects = $flat_projects;
}
$rss = '';
$link = '';
if ($format == 'rss') {
project_issue_query_rss($result, $project);
}
elseif (db_num_rows($result)) {
while ($node = db_fetch_object($result)) {
$node = node_load($node->nid);
$row = array();
$class = "state-$node->sid";
if (!$project->nid) {
$row[] = l($projects[$node->pid], "project/issues/$node->pid");
}
if (strlen($node->title) > 50) {
$title = l(drupal_substr($node->title, 0, 50), "node/$node->nid", array('title' => $node->title));
}
else {
$title = l($node->title, "node/$node->nid");
}
$row[] = $title . theme('mark', node_mark($node->nid, $node->changed));
$row[] = $states[$node->sid];
$row[] = $priorities[$node->priority];
$row[] = t($node->category);
if (count($releases)) {
$row[] = $releases[$node->rid];
}
$row[] = array('data' => format_interval(time() - $node->changed, 2), 'align' => 'right');
$row[] = ($node->assigned) ? theme('username', user_load(array('uid' => $node->assigned))) : '';
$row = array('data' => $row, 'class' => $class);
$rows[] = $row;
}
$query = project_issue_query_pager($query);
$rss = theme('feed_icon', url('project/issues/rss', project_issue_query_url($query)));
$link = l(t('#'), 'project/issues', array('title' => t('Permalink to search query.')), project_issue_query_url($query));
if ($pager = theme('pager', NULL, $issues_per_page, 0)) {
$rows[] = array(array('data' => $pager, 'colspan' => count($header)));
}
}
else {
$rows[] = array(array('data' => t('No issues found.'), 'colspan'=> 8));
}
$output = '';
$output .= '
';
$output .= $search;
$output .= '
';
$output .= theme('table', $header, $rows);
$output .= "$rss $link";
$output .= '
';
return $output;
}
/**
* Form builder for the quick-search form when filtering issues on the
* issue query result pages.
*/
function project_issue_query_result_quick_search($query, $projects, $states, $priorities) {
$categories = array(0 => t('')) + project_issue_category();
// Convert array fields to single select form items.
$fields = array('projects', 'states', 'priorities', 'categories', 'users');
foreach ($fields as $field) {
if (!isset($query->$field)) {
$query->$field = '';
}
elseif (is_array($query->$field)) {
$option = array();
// $query is untrusted, user submitted data
foreach ($query->$field as $key => $value) {
if (isset(${$field}[$value])) {
$option[] = ${$field}[$value];
}
else {
unset($query->{$field}[$key]);
}
}
$query->$field = implode(',', $query->$field);
// special case for 'states' so we don't lose
if (!isset(${$field}[$query->$field])) {
${$field}[$query->$field] = implode(',', $option);
}
}
}
// special case for states - we always want the defaults
$default_states = project_issue_default_states();
if ($query->states != implode(',', $default_states)) {
foreach ($default_states as $state) {
$options[] = $state;
$values[] = $states[$state];
}
$options = implode(',', $options);
$states[$options] = implode(',', $values);
}
$form['projects'] = array(
'#type'=> 'select',
'#title' => t('Project'),
'#default_value' => $query->projects,
'#options' => $projects,
);
$form['states'] = array(
'#type'=> 'select',
'#title' => t('Status'),
'#default_value' => $query->states,
'#options' => $states,
);
$form['categories'] = array(
'#type'=> 'select',
'#title' => t('Category'),
'#default_value' => $query->categories,
'#options' => $categories,
);
$form['priorities'] = array(
'#type'=> 'select',
'#title' => t('Priority'),
'#default_value' => $query->priorities,
'#options' => $priorities,
);
$form['users'] = array(
'#type'=> 'hidden',
'#value' => $query->users,
);
$form['js'] = array(
'#type'=> 'hidden',
'#value' => '0',
);
$form['submit'] = array(
'#type' => 'button',
'#value' => t('Search'),
);
return $form;
}
function theme_project_issue_query_result_quick_search($form) {
$rows[] = array(
drupal_render($form['projects']),
drupal_render($form['states']),
drupal_render($form['categories']),
drupal_render($form['priorities']),
drupal_render($form['users']) . drupal_render($form['submit']),
);
$output = theme('table', array(), $rows);
$output .= drupal_render($form);
return $output;
}
function project_issue_query_url($query = 0) {
static $url = NULL;
if (is_array($query) && $url === NULL) {
foreach ($query as $key => $value) {
$url[] = "$key=$value";
}
$url = implode('&', $url);
}
return $url;
}
/**
* Parses $_POST or $_GET and creates the appropriate query
* object based on whatever choices a user has made to filter the
* issue queue. This function *MUST* return a valid $query object,
* since the rest of project_issue_query_result() assumes that $query
* exists and contains some data. If the query was otherwise going to
* be empty, this function will automatically add the default set of
* issue states (active, fixed, patch needs work, etc).
*
* @param $query
* The existing query object we're adding to. (This should probably
* be a reference, instead of returning the object again).
*
* @return
* An object that describes what kind of query we're doing.
*
* @see project_issue_query_result()
*/
function project_issue_query_parse($query = NULL) {
$fields = array('projects', 'text', 'attachment', 'summary', 'comment', 'categories', 'components', 'versions', 'states', 'priorities', 'users', 'assigned', 'submitted', 'participated');
if ($_SERVER['REQUEST_METHOD'] == 'POST' && is_array($_POST)) {
foreach ($_POST as $key => $value) {
if (!empty($value) && in_array($key, $fields)) {
$query->$key = !is_array($value) ? explode(',', $value) : $value;
}
}
}
else {
foreach ($_GET as $key => $value) {
if (!empty($value) && in_array($key, $fields)) {
$query->$key = explode(',', $value);
}
}
}
if (empty($query->states)) {
$query->states = project_issue_default_states();
}
if ($query->states[0] == 'all') {
$query->states = array_keys(project_issue_state());
}
if (!empty($_REQUEST['participated']) && empty($query->participated)) {
$query->participated = (int)$_REQUEST['participated'];
}
return $query;
}
function project_issue_query_sql_field($field, $values, $like = 0, $operator = ' OR ', $callback = 0) {
$sql = array();
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $value) {
$value = db_escape_string($value);
if ($callback) {
$value = $callback($value);
}
$sql[] = ($like && $field != 'p.pid') ? "$field LIKE '". str_replace('%', '%%', "%$value%") ."'" : "$field = '$value'";
}
if ($sql) {
return '('. implode($operator, $sql) .')';
}
}
function project_issue_query_pager($query) {
$get = array();
if (count($query)) {
foreach ($query as $key => $value) {
$get[$key] = (is_array($value)) ? implode(',', $value) : $value;
}
}
return $get;
}
function project_issue_query_sql($query) {
$comments = 0;
foreach ($query as $key => $value) {
switch ($key) {
case 'projects':
$sql[] = project_issue_query_sql_field('p.pid', $value, 1);
break;
case 'text':
$comments = 1;
$sql[] = '('. project_issue_query_sql_field('n.title', $value, 1) .' OR '. project_issue_query_sql_field('r.body', $value, 1) .' OR '. project_issue_query_sql_field('c.comment', $value, 1) .')';
break;
case 'attachment':
$comments = 1;
$sql[] = "(f.filepath <> '' OR cu.filepath <> '')";
break;
case 'summary':
$sql[] = '('. project_issue_query_sql_field('n.title', $value, 1) .' OR '. project_issue_query_sql_field('r.body', $value, 1) .')';
break;
case 'comment':
$comments = 1;
$sql[] = project_issue_query_sql_field('c.comment', $value, 1);
break;
case 'categories':
$sql[] = project_issue_query_sql_field('p.category', $value);
break;
case 'components':
$sql[] = project_issue_query_sql_field('p.component', $value);
break;
case 'versions':
$sql[] = project_issue_query_sql_field('p.rid', $value);
break;
case 'states':
$sql[] = project_issue_query_sql_field('p.sid', $value);
break;
case 'priorities':
$sql[] = project_issue_query_sql_field('p.priority', $value);
break;
case 'users':
$_sql = array(
project_issue_query_sql_field('n.uid', $value, 0, ' OR ', 'project_issue_query_user'),
project_issue_query_sql_field('p.assigned', $value, 0, ' OR ', 'project_issue_query_user')
);
$sql[] = '('. implode(' OR ', $_sql) .')';
break;
case 'participated':
$comments = 1;
$_sql = array(
project_issue_query_sql_field('n.uid', $value, 0, ' OR ', 'project_issue_query_user'),
project_issue_query_sql_field('p.assigned', $value, 0, ' OR ', 'project_issue_query_user'),
project_issue_query_sql_field('c.uid', $value, 0, ' OR ', 'project_issue_query_user')
);
$sql[] = '('. implode(' OR ', $_sql) .')';
break;
case 'assigned':
$sql[] = project_issue_query_sql_field('p.assigned', $value, 0, ' OR ', 'project_issue_query_user');
break;
case 'submitted':
$sql[] = project_issue_query_sql_field('n.uid', $value, 0, ' OR ', 'project_issue_query_user');
break;
}
}
if (!$comments) {
return array(
'sql' => db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {project_issues} p ON p.nid = n.nid INNER JOIN {node_revisions} r ON r.vid = n.vid INNER JOIN {users} u ON p.assigned = u.uid WHERE n.status = 1 AND ('. implode(') AND (', $sql) .')'),
'count' => db_rewrite_sql('SELECT COUNT(n.nid) FROM {node} n INNER JOIN {project_issues} p ON p.nid = n.nid INNER JOIN {node_revisions} r ON r.vid = n.vid INNER JOIN {users} u ON p.assigned = u.uid WHERE n.status = 1 AND ('. implode(') AND (', $sql) .')')
);
}
else {
return array(
'sql' => db_rewrite_sql('SELECT DISTINCT(n.nid), n.changed FROM {node} n INNER JOIN {project_issues} p ON p.nid = n.nid INNER JOIN {node_revisions} r ON r.vid = n.vid INNER JOIN {users} u ON p.assigned = u.uid LEFT JOIN {comments} c ON c.nid = p.nid LEFT JOIN {comment_upload_files} cu ON p.nid = cu.nid LEFT JOIN {files} f ON n.nid = f.nid WHERE n.status = 1 AND ('. implode(') AND (', $sql) .')'),
'count' => db_rewrite_sql('SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {project_issues} p ON p.nid = n.nid INNER JOIN {node_revisions} r ON r.vid = n.vid INNER JOIN {users} u ON p.assigned = u.uid LEFT JOIN {comments} c ON c.nid = p.nid LEFT JOIN {comment_upload_files} cu ON p.nid = cu.nid LEFT JOIN {files} f ON n.nid = f.nid WHERE n.status = 1 AND ('. implode(') AND (', $sql) .')')
);
}
}
function project_issue_query_user($value) {
if (is_numeric($value)) {
return $value;
}
else {
$uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $value));
if (!$uid) {
drupal_set_message(t("Username '@user' not found.", array('@user' => $value)), 'error');
}
return $uid;
}
}
function project_issue_query_rss($result, $project) {
if ($project) {
$channel['title'] = variable_get('site_name', 'drupal') .' - '. t('issues for !name', array('!name' => $project->title));
}
else {
$channel['title'] = variable_get('site_name', 'drupal') .' - '. t('issues');
}
$channel['link'] = url('project/issues', NULL, NULL, TRUE); // not 100% correct
node_feed($result, $channel);
}
function theme_project_issue_follow_up_forbidden($nid) {
global $user;
if ($user->uid) {
return '';
}
// We cannot use drupal_get_destination() because these links sometimes appear on /node and taxo listing pages.
$destination = "destination=". drupal_urlencode("project/comments/add/$nid");
if (variable_get('user_register', 1)) {
return t('Login or register to follow up', array('@login' => url('user/login', $destination), '@register' => url('user/register', $destination)));
}
else {
return t('Login to follow up', array('@login' => url('user/login', $destination)));
}
}
function theme_project_issue_create_forbidden($uri) {
global $user;
if ($user->uid) {
return '';
}
// We cannot use drupal_get_destination() because these links sometimes appear on /node and taxo listing pages.
$destination = "destination=". drupal_urlencode("node/add/project-issue/$uri");
if (variable_get('user_register', 1)) {
return t('Login or register to create an issue', array('@login' => url('user/login', $destination), '@register' => url('user/register', $destination)));
}
else {
return t('Login to create an issue', array('@login' => url('user/login', $destination)));
}
}