node_get_types('name', 'project_issue')))); return drupal_get_form('project_issue_pick_project_form'); } /** * Form builder for a simple form to select a project when creating a new * issue (as the first "page", but this is not really a multi-page form). */ function project_issue_pick_project_form(&$form_state) { $form = array(); // Fetch a list of all projects (nid => title, grouped by project type // categories if any), and also get an array of project shortnames. $short_names = array(); $projects = project_projects_select_options($short_names); if (empty($projects)) { drupal_set_message(t('You do not have access to any projects.'), 'error'); // No reason to return a project selector form with no elements, so just // return an empty array for the form. return array(); } // See if there's only one project the user has access to, and if so, // redirect directly to the issue form for that project. if (count($short_names) == 1) { drupal_goto('node/add/project-issue/' . key($short_names)); } // Otherwise, return a form to select which project to submit an issue for. $form['pid'] = array( '#type' => 'select', '#title' => t('Project'), '#options' => array(t('')) + $projects, '#required' => TRUE, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Next'), ); return $form; } function project_issue_pick_project_form_validate($form, &$form_state) { if (empty($form_state['values']['pid'])) { form_set_error('pid', t('You must select a project.')); } $node = node_load($form_state['values']['pid']); if (empty($node) || $node->type != 'project_project') { form_set_error('pid', t('Invalid project selected.')); } } function project_issue_pick_project_form_submit($form, &$form_state) { $project = node_load($form_state['values']['pid']); $form_state['redirect'] = 'node/add/project-issue/'. $project->project['uri']; } /** * Private helper to implement hook_form(). * * Create the project issue node form. * * @param $node * The project issue node object. * @param $include_metadata_fields * If set, metadata fields (eg. status, assigned, title) will * be included in the form regardless of whether $node->nid is set. * Otherwise, metadata fields will only be included in the form * if $node->nid is empty. */ function _project_issue_form($node, $form_state, $include_metadata_fields = FALSE) { global $user; $defaults = array( 'rid', 'component', 'category', 'priority', 'assigned', 'sid', ); // Set some defaults for new forms. if (!isset($node->nid)) { foreach ($defaults as $default) { $node->project_issue[$default] = 0; } } // In the case of an issue preview, get our defaults from the submitted form. // TODO: do we want to #tree our form so we don't need this hack? if (isset($form_state['node'])) { foreach ($defaults as $default) { if (isset($form_state['node'][$default])) { $node->project_issue[$default] = $form_state['node'][$default]; } } } // If this function is being called as a result of CCK building the form // in content_admin_field_overview_form(), just return an empty array, since // CCK only grabs the top level fields and this form has none of those anyway. if (!empty($node->cck_dummy_node_form)) { return array(); } if (arg(0) == 'node' && arg(1) == 'add') { $breadcrumb = array(); $breadcrumb[] = l(t('Home'), NULL); $breadcrumb[] = l(t('Create content'), 'node/add'); drupal_set_breadcrumb($breadcrumb); } $default_state = variable_get('project_issue_default_state', 1); // Fetch a list of all projects to test for access. $uris = NULL; $projects = array(t('')) + project_projects_select_options($uris); if (count($projects) == 1) { drupal_set_message(t('You do not have access to any projects.'), 'error'); drupal_goto('node/add/project-issue'); return; } // Figure out what project we should use for the issue metadata. if (!empty($form_state['values']['project_info']['pid'])) { // The project has been selected in the form itself (e.g. it's been // changed and we're previewing, etc.) $pid = $form_state['values']['project_info']['pid']; } elseif (!empty($node->project_issue['pid'])) { // The issue node already knows what project it belongs to. $pid = $node->project_issue['pid']; } else { // Fallback and try to learn the project from the URL -- evil. $pid = arg(3); if (!empty($pid)) { if (is_numeric($pid)) { $node->project_issue['pid'] = db_result(db_query(db_rewrite_sql('SELECT p.nid FROM {project_projects} p WHERE p.nid = %d', 'p'), $pid)); } else { $node->project_issue['pid'] = db_result(db_query(db_rewrite_sql("SELECT p.nid FROM {project_projects} p WHERE p.uri = '%s'", 'p'), $pid)); } } $pid = $node->project_issue['pid']; } if (empty($pid)) { drupal_set_message(t('Invalid project selected.'), 'error'); drupal_goto('node/add/project-issue'); } // If this issue has already been created and is just being // edited, we want to prevent any metadata changes. However, allow // the $include_metadata_fields parameter to override this check. if ($include_metadata_fields) { $allow_metadata_changes = TRUE; } else { $allow_metadata_changes = empty($node->nid); } // Load the project and initialize some support arrays. $project = node_load($pid); if ($project->type != 'project_project') { drupal_set_message(t('Invalid project selected.'), 'error'); // Not sure the best place to go here... drupal_goto('node/add/project-issue'); } if ($allow_metadata_changes) { if (module_exists('project_release') && isset($node->project_issue['rid']) && $releases = project_release_get_releases($project, 0, 'version', 'all', array($node->project_issue['rid']))) { $releases = array(t('')) + $releases; } // Remove releases marked as invalid release nodes for user selection. foreach (variable_get('project_issue_invalid_releases', array()) as $rid) { unset($releases[$rid]); } // Setup components and default component. if (!empty($node->project_issue['component'])) { $default_component = $node->project_issue['component']; } else { $default_component = $project->project_issue['default_component']; } $components = empty($default_component) ? array(t('')) : array(); if ($project->project_issue['components']) { foreach ($project->project_issue['components'] as $component) { $component = check_plain($component); $components[$component] = $component; } } $categories = array_merge(array(t('')), project_issue_category(0, 0)); $priorities = project_issue_priority(); $states = project_issue_state(0, TRUE, !empty($node->nid) && ($node->uid == $user->uid), $node->project_issue['sid']); $assigned = project_issue_assigned_choices($node); } // Display the site-wide and/or per-project help text. $site_help = trim(variable_get('project_issue_site_help', '')); if (!empty($site_help)) { $form['project_help']['site'] = array( '#prefix' => '
', '#value' => check_markup($site_help), '#suffix' => '
', ); } $project_help = trim($project->project_issue['help']); if (!empty($project_help)) { $form['project_help']['project'] = array( '#prefix' => '
', '#value' => check_markup($project_help), '#suffix' => '
', ); } if ($allow_metadata_changes) { $form['project_info'] = array( '#type' => 'fieldset', '#title' => t('Project information'), '#prefix' => '
', '#suffix' => '
', ); $form['project_info']['project_display'] = array( '#type' => 'item', '#title' => t('Project'), '#value' => check_plain($project->title), ); $form['project_info']['pid'] = array( '#type' => 'value', '#value' => $node->project_issue['pid'], ); if ($releases) { $form['project_info']['rid'] = array( '#type' => 'select', '#title' => t('Version'), '#default_value' => $node->project_issue['rid'], '#options' => $releases, '#required' => TRUE, ); } $form['project_info']['component'] = array( '#type' => 'select', '#title' => t('Component'), '#default_value' => $default_component, '#options' => $components, '#required' => TRUE, ); $form['issue_info'] = array( '#type' => 'fieldset', '#title' => t('Issue information'), '#prefix' => '
', '#suffix' => '
', ); $form['issue_info']['category'] = array( '#type' => 'select', '#title' => t('Category'), '#default_value' => $node->project_issue['category'] ? $node->project_issue['category'] : arg(4), '#options' => $categories, '#required' => TRUE, ); $form['issue_info']['priority'] = array( '#type' => 'select', '#title' => t('Priority'), '#default_value' => $node->project_issue['priority'] ? $node->project_issue['priority'] : 2, '#options' => $priorities, ); $form['issue_info']['assigned'] = array( '#type' => 'select', '#title' => t('Assigned'), '#default_value' => $node->project_issue['assigned'], '#options' => $assigned, ); if (count($states) > 1) { $form['issue_info']['sid'] = array( '#type' => 'select', '#title' => t('Status'), '#default_value' => $node->project_issue['sid'] ? $node->project_issue['sid'] : $default_state, '#options' => $states, ); } else { $form['issue_info']['sid'] = array( '#type' => 'value', '#value' => $default_state, ); $form['issue_info']['status'] = array( '#type' => 'item', '#title' => t('Status'), '#value' => check_plain(project_issue_state($default_state)), ); } } else { // If we're not allowing issue metadata changes, add all of these values // into the form so they show up in the $node->project_issue array during // validation and submit, so we're consistent with where they live. $form['project_issue'] = array('#tree' => TRUE); $form['project_issue']['pid'] = array( '#type' => 'value', '#value' => $node->project_issue['pid'], ); if (isset($node->project_issue['rid'])) { $form['project_issue']['rid'] = array( '#type' => 'value', '#value' => $node->project_issue['rid'], ); } $form['project_issue']['component'] = array( '#type' => 'value', '#value' => $node->project_issue['component'], ); $form['project_issue']['category'] = array( '#type' => 'value', '#value' => $node->project_issue['category'], ); $form['project_issue']['priority'] = array( '#type' => 'value', '#value' => $node->project_issue['priority'], ); $form['project_issue']['assigned'] = array( '#type' => 'value', '#value' => $node->project_issue['assigned'], ); $form['project_issue']['sid'] = array( '#type' => 'value', '#value' => $node->project_issue['sid'], ); } $form['issue_details'] = array( '#type' => 'fieldset', '#title' => t('Issue details'), '#prefix' => '
', ); if ($allow_metadata_changes) { $form['issue_details']['title'] = array( '#type' => 'textfield', '#title' => t('Title'), '#default_value' => $node->title, '#size' => 60, '#maxlength' => 128, '#required' => TRUE, ); } else { $form['issue_details']['title'] = array( '#type' => 'value', '#value' => $node->title, ); } $form['issue_details']['body'] = array( '#type' => 'textarea', '#title' => t('Description'), '#default_value' => $node->body, '#rows' => 10, '#required' => TRUE, ); $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, 'error'); } return $form; } /** * Private helper to implement hook_validate(). * * Ensures that the issue node form has valid values for all required fields. * We use hook_validate() here instead of a #validate handler or even defining * project_issue_node_form_validate() since if we did, node_form_validate() * itself would not be invoked, which would lead to all kinds of problems, * including hook_nodeapi('validate') never being invoked. * * @param $node * An object of form values from the project_issue node form, not a fully * loaded issue node object. Therefore, the fields are not in the usual * $node->project_issue array. */ function _project_issue_validate($node) { // If $node->nid is set, that means that the node was being // edited and not created. If that's the case, the user was // not presented with any of the metadata fields, so there's no // need to validate them here. if (empty($node->nid)) { if (empty($node->pid)) { form_set_error('pid', t('You have to specify a valid project.')); } elseif ($project = node_load($node->pid)) { if (module_exists('project_release') && $releases = project_release_get_releases($project, 0)) { if (empty($node->rid)) { form_set_error('rid', t('You have to specify a valid version.')); } } if (isset($node->component) && !in_array($node->component, $project->project_issue['components'])) { $node->component = 0; } if (empty($node->component)) { form_set_error('component', t('You have to specify a valid component.')); } if (empty($node->category)) { form_set_error('category', t('You have to specify a valid category.')); } } } } /** * Private helper to implement hook_insert(). * * @param $node * Object containing form values from the project_issue node form. This is * NOT a fully loaded $node object, so the issue-related values are directly * in $node, not in the $node->project_issue array. */ 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 (empty($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); // Invalidate the "Issue cockpit" block cache for this project, since the // new issue will have altered the summary totals. cache_clear_all('project_issue_cockpit_block:'. $node->pid, 'cache'); }