Skip to content
question.module 22.6 KiB
Newer Older
<?php
/**
 * @file
 * Allows users to submit questions to a queue that may be answered by site administrators
 * @todo
 * - Create upgrade path in hook_install_N
/************************************************
 *                                              *
 * MENU ITEMS, PERMISSIONS, AND ACCESS CONTROL  *
 *                                              *
 ***********************************************/
 * Implements hook_permission().
 * Defines the permissions that users can have related to this module
function question_permission() {
  return array(
    'ask questions' => array(
      'title' => t('Ask questions'),
      'description' => t('Submit a question to the site question queue.'),
    ),
    'view questions' => array(
      'title' => t('View questions'),
      'description' => t('View all questions in the queue.'),
    ),
    'answer questions' => array(
      'title' => t('Answer questions'),
      'description' => t('Respond to questions in the queue.'),
    ),
    'delete questions' => array(
      'title' => t('Delete questions'),
      'description' => t('Delete questions from the queue.'),
    'edit own answers' => array(
      'title' => t('Edit own answers'),
      'description' => t('Edit own answers.'),
    ),
    'edit any answers' => array(
      'title' => t('Edit any answers'),
      'description' => t('Edit any answers.'),
    ),
    'delete own answers' => array(
      'title' => t('Delete own answers'),
      'description' => t('Delete own answers.'),
    ),
    'delete any answers' => array(
      'title' => t('Delete any answers'),
      'description' => t('Delete any answers.'),
    ),
  );
 * Implements hook_node_access().
function question_node_access($node, $op, $account) {
  // Get the type of node being accessed
  $type = is_string($node) ? $node : $node->type;
  if ($type != 'question') {
    return NODE_ACCESS_IGNORE;
  }
  if ($op == 'create' && user_access('answer questions', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  if ($op == 'update' && user_access('edit any answers', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  if ($op == 'update' && ($account->uid == $node->uid) && user_access('edit own answers', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  if ($op == 'delete' && user_access('delete any answers', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  if ($op == 'delete' && ($account->uid == $node->uid) && user_access('delete own answers', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  return NODE_ACCESS_IGNORE;
}
 * Implements hook_menu().
 * Adds all the necessary menu items, and controls access
 * to those items based on user permissions.
function question_menu() {
  $items = array();
  // The page for users to ask a question
    'title' => 'Ask a question', // note don't wrap in t() function from 6.x
    'description' => 'Post a question to be answered.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('question_ask_form'),
    'access arguments' => array('ask questions'),
  );
  // The queue of unanswered questions
  $items['admin/content/question'] = array(
    'title' => 'Questions',
    'description' => 'Manage the sites question queue.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('question_queue_admin'),
    'access callback' => 'user_access',
    'access arguments' => array('view questions'),
    'type' => MENU_LOCAL_TASK,
  );
  // The settings page
  $items['admin/config/content/question'] = array(
    'title' => 'Question',
    'description' => 'Edit Question settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('question_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );
  // Callback to delete a question
  $items['question/%/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('question_delete_confirm', 1),
    'access callback' => 'user_access',
    'access arguments' => array('delete questions'),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
  );
 * Implements hook_menu_alter().
 * Removes the default link to create a question/answer page from the
 * "Add new content" menu at node/add.
 * Instead, we only want users to create answers by promoting questions from the queue.
function question_menu_alter(&$items) {
  $items['node/add/question']['type'] = MENU_CALLBACK;
/************************************************
 *                                              *
 *      SETTINGS, HELP, AND INFORMATION         *
 *                                              *
 ***********************************************/

 * Implements hook_help().
function question_help($page, $arg) {
  switch ($page) {
    case 'node/add#question': // This description shows up when users click "create content."
      return t('A question/answer node.');
    case 'admin/content/questions':
      return t('Here is a list of the questions that have been submitted to your site. Each question can be answered by selecting "promote". Once submitted, this question becomes a node. Choose "delete" to delete this question without answering.');
    case 'admin/structure/trigger/question':
      $explanation = t(
      "A trigger is a system event. For the question module, you can choose to
      associate actions when a question is submitted to the queue, or when a
      question is answered.");
      return "<p>$explanation</p>";
  }
}

function question_settings() {
  // Redirect path following question submit
  $form['question_ask_form_redirect'] = array(
codepoet's avatar
codepoet committed
    '#type' => 'textfield',
codepoet's avatar
codepoet committed
    '#weight' => -3,
    '#title' => t('Path to redirect node'),
    '#default_value' => variable_get('question_ask_form_redirect', FALSE),
codepoet's avatar
codepoet committed
    '#size' => 40,
    '#maxlength' => 100,
    '#description' => t('This is where users will end up after they submit the question form. Example: "node/454".<br/>Leave blank and the user will be returned to the front page of the site.'),
  );
  // Message to display after question submitted
  $form['question_submission_message'] = array(
    '#type' => 'textarea',
    '#weight' => -2,
    '#title' => t('Message to display'),
    '#default_value' => variable_get('question_submission_message', FALSE),
    '#size' => 40,
    '#description' => t('This message will be shown to the user after submitting a question.<br/>Leave blank to display no message.'),
codepoet's avatar
codepoet committed
  );
  // Instructions on the question ask form
codepoet's avatar
codepoet committed
  $form['question_instructions'] = array(
    '#type' => 'textarea',
    '#weight' => -1,
    '#title' => t('Instructions for the user'),
    '#default_value' => variable_get('question_instructions', FALSE),
    '#size' => 40,
    '#description' => t('This message will appear above the question form to provide the user instructions.'),
  );
codepoet's avatar
codepoet committed
  return system_settings_form($form);
/************************************************
 *                                              *
 *     USER QUESTION SUBMISSION                 *
 *                                              *
 ************************************************/
/**
 * Form for users to ask a question
 */
function question_ask_form($node, &$form_state) {
  // Display the instructions (if applicable)
  $instructions = variable_get('question_instructions', FALSE);
  if ($instructions) {
    $form['instructions'] = array(
      '#type' => 'markup',
      '#markup' => check_plain($instructions),
    );
  $form['question'] = array(
    '#type' => 'textarea',
    '#title' => t('Question'),
    '#cols' => 50,
    '#rows' => 5,
    '#description' => NULL,
    '#attributes' => NULL,
    '#default_value' => isset($form_state['storage']['question']) ? $form_state['storage']['question'] : '[please enter a value]',
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit Question'),
  );
  $form['#method'] = 'post';
  $form['#theme'] = 'question_ask_form';
  return $form;
}

/**
 * Implements _form_validate.
 */
function question_ask_form_validate(&$form, &$form_state) {
  if ($form_state['values']['question'] == '') {
    form_set_error('question', t('Please enter a question.'));
/**
 * Implements _form_submit.
 */
function question_ask_form_submit($form, &$form_state) {
  // Make an array containing all the values to insert
  $row = array(
    'question' => $form_state['values']['question'],
  );
  // Write the question to the database
  drupal_write_record('question_queue', $row);
  // Retrieve the question id assigned by the database to be used in future form processing
  $form_state['storage']['qid'] = $row['qid'];
  // Determine where the browser should now go to
  $path = variable_get('question_ask_form_redirect', '');
  if (!empty($path)) {
    // Redirect to the specified path
    $form_state['redirect'] = $path;
  }
  else {
    // Return to the site frontpage
    $form_state['redirect'] = '<front>';
  }
  // Display message to user
  $message = variable_get('question_submission_message', '');
  if (!empty($message)) {
    drupal_set_message(check_plain($message));
  }
  // Add an entry to the watchdog log
  watchdog('Question', 'Question submitted: %question.', array('%question' => $form_state['values']['question']), WATCHDOG_NOTICE);
}

/**
 * Themes the question form
 */
function theme_question_ask_form($variables) {
  $form = $variables['form'];
  
  $output = '';
  $output .= drupal_render($form['instructions']);
  $output .= drupal_render($form['questioner']);
  $output .= drupal_render($form['age']);
  $output .= drupal_render($form['sex']);
  $output .= drupal_render($form['question']);
  
  $output .= drupal_render_children($form);

  return $output;

}

/**
 * Implements hook_theme().
 */
function question_theme() {
  return array(
    'question_ask_form' => array('render element' => 'form'),
  );
codepoet's avatar
codepoet committed
}


/************************************************
 *                                              *
 *                                              *
 ************************************************ /
 * Provide an array of the bulk operations that can be
 * perfomed on items in the question queue.
function question_question_queue_operations() {
  $operations = array();
  
  if (user_access('delete questions')) {
    $operations = array(
      'delete' => array(
        'label' => t('Delete selected questions'),
        'callback' => NULL,
      ),
    );
 * Admin form for questions in the queue.
function question_queue_admin($form, &$form_state) {
  if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
    return question_queue_multiple_delete_confirm($form, $form_state, array_filter($form_state['values']['questions']));
  }
  $form['admin']  = question_queue_admin_form();
  return $form;
 * Return an array of fields to be included in the question queue.
 * This is implemented as a separate hook so that it can be extended
 * by other modules that add additional fields to the question queue.
function question_question_queue_fields() {
  return array(
    // Defines the columns of data in the table
    'header' => array(
      'question' => array('data' => t('Question'), 'field' => 'question', 'sort' => 'desc'),
      'operations' => array('data' => t('Operations')),
    ),
    // Defines any functions that should be performed on items of data in each column
    'options' => array(
      'question' => array('#markup' => 'check_plain'),
    )
  );
 * Lists all questions currently in the queue, and allows
 * bulk operations to be performed against those questions.
function question_queue_admin_form() {

  $form['instructions'] = array(
    '#type' => 'markup',
    '#markup' => t('To respond to a question, click on the title.'),
  );

  // Build the 'Update options' dropdown.
  $form['options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Update options'),
    '#prefix' => '<div class="container-inline">',
    '#suffix' => '</div>',
  );
  $options = array();
  $operations = array();
  // Invoke all modules that define operations that can be perfomed on the question queue
  foreach (module_invoke_all('question_queue_operations') as $operation => $array) {
    $options[$operation] = $array['label'];
  }
  $form['options']['operation'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => 'delete',
  );
  $form['options']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Update'),
    '#submit' => array('question_queue_admin_form_submit'),
  );

  // Invoke all modules that define fields that should be included
  // in the question queue page
  $question_queue_fields = module_invoke_all('question_queue_fields');
  // Build the table header.
  $header = $question_queue_fields['header'];  
  
  // Retrieve all questions currently in the queue
  $query = db_select('question_queue', 'q')->extend('PagerDefault')->extend('TableSort');
  $query->fields('q'); // Retrieve every field from the queue table
  $query->addTag('question_queue'); // Add a tag so that other modules can alter this query
  $query->orderByHeader($header);
  $result = $query->execute();

  $options = array();
  // Loop through all the questions
  foreach ($result as $question) {
    // Loop through every field on information about this question
    foreach ($question as $column => $value) {
      // Determine how the value in this field should be displayed
      $markup = "";
      if (isset($question_queue_fields['options'][$column]['#markup'])) {
        $markup = call_user_func($question_queue_fields['options'][$column]['#markup'], $value);
      }
      else {
        $markup = check_plain($value);
      }
      // Add this column to the options array
      $options[$question->qid][$column] = array('data' => array('#markup' => $markup));
    }
    
    // Build a list of all the accessible operations for the current question.
    $operations = array();
    if (user_access('answer questions')) {
      $operations['respond'] = array(
        'title' => t('respond'),
        'href' => 'node/add/question/' . $question->qid,
        'query' => $destination,
      );
    }
    if (user_access('delete questions')) {
      $operations['delete'] = array(
        'title' => t('delete'),
        'href' => 'question/' . $question->qid . '/delete',
        'query' => $destination,
      );
    $options[$question->qid]['operations'] = array();
    if (count($operations) > 1) {
      // Render an unordered list of operations links.
      $options[$question->qid]['operations'] = array(
        'data' => array(
          '#theme' => 'links__node_operations',
          '#links' => $operations,
          '#attributes' => array('class' => array('links', 'inline')),
        ),
      );
    }
    elseif (!empty($operations)) {
      // Render the first and only operation as a link.
      $link = reset($operations);
      $options[$question->qid]['operations'] = array(
        'data' => array(
          '#type' => 'link',
          '#title' => $link['title'],
          '#href' => $link['href'],
          '#options' => array('query' => $link['query']),
        ),
      );
    }
  }
  // Create the table
  $form['questions'] = array(
    '#type' => 'tableselect',
    '#header' => $header,
    '#options' => $options,
    '#empty' => t('No questions available.'),


/**
 * Callback to delete a single question
 */
function question_delete_confirm($form, &$form_state, $qid) {
  $form['qid'] = array('#type' => 'value', '#value' => $qid);
  $question = db_query('SELECT question FROM {question_queue} WHERE qid = :qid', array(':qid' => $qid))->fetchField();
  return confirm_form($form,
    t('Are you sure you want to delete %question?', array('%question' => $question)),
    'admin/content/question',
    t('This action cannot be undone.'),
    t('Delete'),
    t('Cancel')
  );
}

/**
 * Execute node deletion
 */
function question_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    $qid = $form_state['values']['qid'];
    $num_deleted = db_delete('question_queue')
        ->condition('qid', $qid)
        ->execute();
    watchdog('question', 'Deleted question %qid.', array('%qid' => $qid));
    drupal_set_message(t('The question has been deleted.'));
  }
}

 * Perform updates against multiple question queue items.
function question_queue_mass_update($questions, $updates) {
  foreach ($questions as $qid) {
    _question_queue_mass_update_helper($qid, $updates);
codepoet's avatar
codepoet committed
  }
  drupal_set_message(t('The update has been performed.'));
/**
 * Question Queue Mass Update - helper function.
 *
 */
function _question_queue_mass_update_helper($qid, $updates) {
  foreach ($updates as $name => $value) {
    $query = db_update('question_queue')
      ->fields(array(
          $name => $value,
      ))
      ->condition('qid', $qid, '=')
      ->execute();
/**
 * Validate the question queue bulk update form.
 *
 * @see node_admin_nodes_validate
 */
function question_queue_admin_form_validate($form, &$form_state) {
  $questions = array_filter($form_state['values']['questions']);
  if (count($questions) == 0) {
    form_set_error('', t('No questions selected.'));
codepoet's avatar
codepoet committed
  }
/**
 * Submit the question queue bulk update form.
 *
 */
function question_queue_admin_form_submit($form, &$form_state) {
  $operations =  module_invoke_all('question_queue_operations');
  $operation = $operations[$form_state['values']['operation']];
  // Filter out unchecked questions
  $questions = array_filter($form_state['values']['questions']);
  if ($function = $operation['callback']) {
    // Add in callback arguments if present.
    if (isset($operation['callback arguments'])) {
      $args = array_merge(array($questions), $operation['callback arguments']);
    }
    else {
      $args = array($questions);
    }
    call_user_func_array($function, $args);
codepoet's avatar
codepoet committed

    // We need to rebuild the form to go to a second step. For example, to
    // show the confirmation form for the deletion of questions.
    $form_state['rebuild'] = TRUE;
/**
 * Confirm multiple deletion from the question queue bulk update form.
 */
function question_queue_multiple_delete_confirm($form, &$form_state, $questions) {
  $form['questions'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
  // array_filter returns only elements with TRUE values
  foreach ($questions as $qid => $value) {
    $title = db_query('SELECT question FROM {question_queue} WHERE qid = :qid', array(':qid' => $qid))->fetchField();
    $form['questions'][$qid] = array(
      '#type' => 'hidden',
      '#value' => $qid,
      '#prefix' => '<li>',
      '#suffix' => check_plain($title) . "</li>\n",
    );
codepoet's avatar
codepoet committed
  }
  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
  $form['#submit'][] = 'question_queue_multiple_delete_confirm_submit';
  $confirm_question = format_plural(count($questions),
                                  'Are you sure you want to delete this question?',
                                  'Are you sure you want to delete these questions?');
  return confirm_form($form,
                    $confirm_question,
                    'admin/content/question', t('This action cannot be undone.'),
                    t('Delete'), t('Cancel'));
/**
 * Submit confirmation of multiple deletion from the question queue bulk update form.
 */
function question_queue_multiple_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    foreach ($form_state['values']['questions'] as $qid => $value) {
      $num_deleted = db_delete('question_queue')
        ->condition('qid', $qid)
        ->execute();
codepoet's avatar
codepoet committed
    }
    $count = count($form_state['values']['questions']);
    watchdog('content', 'Deleted @count questions.', array('@count' => $count));
    drupal_set_message(format_plural($count, 'Deleted 1 question', 'Deleted @count questions.', array('@count' => $count)));
codepoet's avatar
codepoet committed
  }
}
/************************************************
 *                                              *
 *     RESPOND TO QUESTIONS                     *
 *                                              *
 ************************************************/
 * Implements hook_form().
 *
 * This is the node form called when a question has been promoted from the queue.
 * The $qid is passed as arg(3) in the url (node/add/question/3).
 * This is used to prepopulate the node form with information from the question queue.
 */
function question_form_question_node_form_alter(&$form, &$form_state) {
codepoet's avatar
codepoet committed

  // If we've arrived here from the question queue, retrieve and pre-populate the information we already know
  if (arg(2)=='question' && is_numeric(arg(3))) {
    $result = db_query('SELECT * FROM {question_queue} WHERE qid = :qid', array(':qid' => arg(3)))->fetchObject();
    $form['qid'] = array(
      '#type' => 'hidden',
      '#value' => arg(3),
    );
    $form['question_questioner']['und']['0']['value']['#default_value'] = isset($result->questioner) ? $result->questioner : '';
    $form['question_question']['und']['0']['value']['#default_value'] = isset($result->question) ? $result->question : '';
    $form['title']['#default_value'] = isset($result->question) ? truncate_utf8($result->question, 64, $wordsafe = TRUE, $dots  = TRUE) : '';
codepoet's avatar
codepoet committed
  }
  $form['#validate'][] = 'question_form_validate'; //add additional validation handler for our new fields
  $form['#submit'][] = 'question_form_submit'; //add additional validation handler for our new fields
 * Implements _form_validate().
 *
 * Validation functions called when a new response is submitted
 */
function question_form_validate(&$elements, &$form_state, $form_id = NULL) {

  if ($elements['question_question']['und'][0]['value']['#value'] == '') {
    form_set_error('question_question', t('Please enter a question.'));
  if ($elements['title']['#value'] == '') {
    form_set_error('title', t('Please enter a short title for the question.'));
  if ($elements['question_answer']['und'][0]['value']['#value'] == '') {
    form_set_error('question_answer', t('Please enter the answer.'));
codepoet's avatar
codepoet committed
/**
 * Implements _form_submit().
codepoet's avatar
codepoet committed
 *
 * Submit functions called when a new response is submitted
codepoet's avatar
codepoet committed
 */
function question_form_submit(&$elements, &$form_state, $form_id = NULL) {
  // If this node came from the queue, delete the queue item...
  if (isset($elements['qid'])) {
    $num_deleted = db_delete('question_queue')
      ->condition('qid', $elements['qid']['#value'])
      ->execute();
codepoet's avatar
codepoet committed
  }
}