You are here

faq_ask.module in FAQ_Ask 6.2

Same filename and directory in other branches
  1. 8 faq_ask.module
  2. 6 faq_ask.module
  3. 7 faq_ask.module

This module is an add-on to the FAQ module that allows users with the 'ask question' permission to create a question which will be queued for an 'expert' to answer.

File

faq_ask.module
View source
<?php

/**
 * @file
 * This module is an add-on to the FAQ module that allows users with the 'ask question'
 * permission to create a question which will be queued for an 'expert' to answer.
 */

/**
 * Display help and module information
 * @param section which section of the site we're displaying help
 * @return help text for section
 */
function faq_ask_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/help#faq_ask':
      $output .= '<p>' . t("This module is an add-on to the FAQ module that allows users with the 'ask question' permission to create a question which will be queued for an 'expert' to answer.") . '</p>' . '<p>' . t("The module shows an abbreviated version of the FAQ form without an answer field. The node is created without the 'published' attribute. There is a block that will show the unanswered questions to the 'expert' (generally, this requires a separate role).") . '</p>' . '<p>' . t("Viewing of the completed question and answer pair is done by the FAQ module.") . '</p>' . '<p>' . t("Simply adding the 'FAQ' content type to a vocabulary will not make it eligible for experts; you must go to the settings page and add it there.") . '</p>';
      return $output;
    case 'admin/settings/faq/ask':
      return theme('box', NULL, '<big>' . t('In order for the Faq_Ask module to operate, you must, at the least,: 1) Define at least one vocabulary for use with the "faq" content type; 2) select one or more roles as experts (and you may have to "Save configuration"); 3) select at least one category and expert combination; 4) click the "Save configuration" button.') . '</big>');
    case 'faq_ask/unanswered':
      $output = '<p>' . filter_xss_admin(variable_get('faq_ask_expert_advice', _faq_ask_advice_default('expert'))) . '</p>';
      if (user_access('administer blocks')) {
        $output .= '<p><em>' . t('You may go <a href="!setting">here</a> to change the block limit.', array(
          '!setting' => url('admin/build/block/configure/faq_ask/0'),
        )) . '</em></p>';
      }
      return $output;
    case 'faq_ask/%':
    case 'faq_ask':
      return filter_xss_admin(variable_get('faq_ask_help_text', _faq_ask_help_default()));
  }
}

/**
 * Implementation of hook_perm().
 * Define the permissions this module uses
 */
function faq_ask_perm() {
  return array(
    'ask question',
    'answer question',
  );
}

/**
 * Implementation of hook_access().
 */
function faq_ask_access($op, $node, $account) {

  // If node is already published, it's not ours any more.
  if ($node->status == 1) {
    return NULL;
  }
  if ($op == 'create') {
    return user_access('ask question') || user_access('answer question');
  }
  else {

    //    return user_access('answer question') || user_access('edit_own_faq');
    // We don't include "edit own" because the intent is they can edit their own until it's published.
    return user_access('answer question') || $account->uid == $node->uid;
  }
}

/**
 * Determines whether the current user has one of the given permissions.
 */
function faq_ask_user_access_or($string1, $string2) {
  return user_access($string1) || user_access($string2);
}

/**
 * Implementation of hook_menu().
 */
function faq_ask_menu() {
  $items = array();
  $items['admin/settings/faq/ask'] = array(
    'title' => 'Experts',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'faq_ask_settings_form',
    ),
    'access arguments' => array(
      'administer faq',
    ),
    'description' => 'Allows the user to configure the Ask_FAQ module.',
    'type' => MENU_LOCAL_TASK,
    'weight' => -2,
  );
  $items['faq_ask'] = array(
    'title' => 'Ask a question',
    'page callback' => 'faq_ask_page',
    'access callback' => 'user_access',
    'access arguments' => array(
      'ask question',
    ),
    'weight' => 1,
  );
  $items['faq_ask/%'] = array(
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'ask question',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['faq_ask/answer/%node'] = array(
    'title' => 'Answer a question',
    'page callback' => 'faq_ask_answer',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'answer question',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['faq_ask/edit/%node'] = array(
    'title' => 'Edit a question',
    'page callback' => 'faq_ask_edit',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['faq_ask/unanswered'] = array(
    'title' => 'List more unanswered questions',
    'page callback' => 'faq_ask_list_more',
    'access callback' => 'faq_ask_user_access_or',
    'access arguments' => array(
      'answer question',
      'ask question',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Get the ask question form.
 */
function faq_ask_page($tid = NULL) {

  // Issue #957414 by wvanbusk: Can shortcut select category?
  // In case of a term added, modify query string to add it.
  if ($tid && is_numeric($tid)) {
    $term_object = taxonomy_get_term($tid);
    if (is_object($term_object)) {
      drupal_goto('node/add/faq', array(
        'ask' => TRUE,
        'term' => $term_object->tid,
      ));
    }
  }

  // Jump to question form
  drupal_goto('node/add/faq', array(
    'ask' => TRUE,
  ));
}

/**
 * Get the edit question form.
 */
function faq_ask_edit($node) {
  global $user;
  if ($node->status == 1) {
    drupal_set_message(t('That question has already been answered.'), 'status');
  }
  else {
    if ($user->uid == $node->uid) {

      //      $form = drupal_get_form('faq_ask_form', NULL, $node);
      //      return $form;
      drupal_goto("node/{$node->nid}/edit", 'ask=TRUE');
    }
    else {
      drupal_set_message(t('You are not allowed to edit that question.'), 'error');
    }
  }
  drupal_goto('node');
}

/**
 * Implementation of hook_form_alter().
 *  This is how we build the "ask question" form.
 */
function faq_ask_form_alter(&$form, $form_state, $form_id) {
  global $user;

  // If this is question form and user is allowed to ask a question but not allowed to answer question - this must be a question form
  if ($form_id == 'faq_node_form' && user_access('ask question', $user) && !user_access('answer question', $user)) {
    $_GET['ask'] = 1;
  }

  // If this is not the question form OR question flag is not set - return from here
  if ($form_id != 'faq_node_form' || !isset($_GET['ask'])) {
    return;
  }

  // If question flag is not set - return too
  if ($_GET['ask'] != 1 && $_GET['ask'] != 'TRUE' && $_GET['ask'] != 'true') {
    return;
  }

  // ELSE: Show question form!
  drupal_set_title(t('Ask a Question'));

  // Set the published field off and make sure they can't override it.
  $form['options']['status']['#default_value'] = FALSE;
  $form['options']['status']['#disabled'] = TRUE;

  // Get rid of the body elements (we'll dummy one later) and the menu elements.
  $form['body_field']['#type'] = 'hidden';
  $form['menu']['#type'] = 'hidden';

  // Check if only experts can categorize the question.
  if (variable_get('faq_ask_categorize', FALSE)) {
    unset($form['taxonomy']);
  }
  $form['body'] = array(
    '#type' => 'value',
    '#value' => variable_get('faq_ask_unanswered', t('Not answered yet.')),
  );

  //dpm($form);

  // if we're supposed to notify asker on answer, add form item for this
  if (variable_get('faq_ask_notify_asker', FALSE)) {
    global $user;
    $mail = '';
    if (isset($form['nid']['#value'])) {
      $mail = _faq_ask_get_faq_notification_email($form['nid']['#value']);
    }

    // If asker is anonymous, add an optional e-mail field that may be used for notification when question is answered
    if ($user->uid == 0) {

      // Form field for e-mail.
      $form['faq_email'] = array(
        '#type' => 'textfield',
        '#title' => t('Notification E-mail (optional)'),
        // Issue #1471114 by jlea9378: Email address isn't retained after clicking Preview
        '#default_value' => isset($form_state['values']['faq_email']) ? $form_state['values']['faq_email'] : $mail,
        '#weight' => 0,
        '#description' => t('Write your e-mail here if you would like to be notified when the question is answered.'),
      );
    }
    else {

      // Checkbox for notification
      $form['faq_notify'] = array(
        '#type' => 'checkbox',
        '#title' => t('Notify by E-mail (optional)'),
        // Issue #1471114 by jlea9378: Email address isn't retained after clicking Preview
        '#default_value' => isset($form_state['values']['faq_notify']) ? $form_state['values']['faq_notify'] : $mail != '' ? TRUE : FALSE,
        '#weight' => 0,
        '#description' => t('Check this box if you would like to be notified when the question is answered.'),
      );
    }
  }

  // Issue #947402 by verta: PHP error on saving new question, warning: function_exists() expects parameter 1 to be string, includes\form.inc on line 769
  // Add validation of the e-mail field
  if (!isset($form['#validate'])) {
    $form['#validate'] = array();
  }
  $form['#validate'][] = 'faq_ask_form_validate';

  // Make sure we know we came from here.
  $form['faq_ask'] = array(
    '#type' => 'value',
    '#value' => TRUE,
  );
  $form['buttons']['submit']['#submit'][] = 'faq_ask_submit';

  // Sean Corales: Redirect to faq page if user is anonymous or cannot edit own faq nodes
  global $user;
  if (!user_access('edit own faq') && !user_access('edit faq') || $user->uid == 0) {
    $form['#redirect'] = 'faq';
  }

  // Issue #957414 by wvanbusk: Can shortcut select category?
  if (isset($_GET['term']) && ($t = $_GET['term'])) {
    if (is_numeric($t)) {
      $term = taxonomy_get_term($t);
      if (is_object($term)) {

        // Make sure we have loaded a proper object
        if (isset($form['taxonomy']['tags'])) {

          // Using tags
          $vid = key($form['taxonomy']['tags']);
          $form['taxonomy']['tags'][$vid]['#default_value'] = $term->name;
        }
        else {
          $vid = key($form['taxonomy']);
          $form['taxonomy'][$vid]['#default_value'] = $term->tid;
        }
      }
    }
  }

  // Handle special cases if this is a block form
  if (isset($_GET['block'])) {
    if ($_GET['block']) {
      if (!variable_get('faq_ask_categorize', FALSE)) {

        // Issue #1127744 by j.r: Categories don't show up in Ask a Question Block
        if (isset($form['taxonomy']['tags'])) {
          $tags = array_keys($form['taxonomy']['tags']);
          $form['taxonomy']['tags'][$tags[0]]['#description'] = t('A comma-separated list of terms.');
        }
      }
      else {
        unset($form['taxonomy']);
      }

      // Shorter description on Qestion field + move it higher
      $form['title']['#description'] = t('Question to be answered.');
      $form['title']['#weight'] = '-5';

      // Shorter description on detailed question field
      $form['detailed_question']['#description'] = t('Longer question text.');
    }
  }
}

/**
 * Validation form for the FAQ Ask form
 * Thanks to http://hokuten.net/2010/drupal-creating-an-e-mail-subscription-block/
 */
function faq_ask_form_validate($form, &$form_state) {
  if (isset($form_state['values']['faq_email'])) {
    $email = $form_state['values']['faq_email'];
    if (strlen($email) > 1 && !eregi('^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.([a-zA-Z]{2,4})$', $email)) {
      form_set_error('email', t('That is not a valid e-mail address.'));
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 *
 * Checks if the node being updated is a question that has been answered
 */
function faq_ask_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if ($node->type == 'faq') {
    switch ($op) {
      case 'update':

        // If node is not to be published by default, then issue a message with instructions to publish when ready
        $options = variable_get('node_options_faq', array(
          'status',
          'promote',
        ));
        if (!in_array('status', $options) && $node->status == '0') {
          $token = _faq_ask_get_token('faq_ask/answer/' . $node->nid);
          $options = array(
            'query' => "token={$token}&forced=TRUE",
          );
          $url = url("faq_ask/answer/" . $node->nid, $options);
          drupal_set_message(t('This question is by default NOT published. To publish the answered question when you are ready,
                        <a href="@url">click this link</a>', array(
            '@url' => $url,
          )), 'warning');
        }

        // return if the asker notification should be done by cron
        if (variable_get('faq_ask_notify_by_cron', TRUE)) {
          return;
        }

        // Check if the node is published and asker notified
        $email = _faq_ask_get_faq_notification_email($node->nid);
        if ($node->status == '1' && $email != '') {

          // Get the asker account
          $account = user_load(array(
            'mail' => $email,
          ));

          // If the account is not anonymous
          if ($account) {
            $params['account'] = $account;
          }
          else {
            $params['account'] = NULL;
          }
          $params['question'] = $node->title;
          $params['nid'] = $node->nid;

          // Send the e-mail to the asker. Drupal calls hook_mail() via this
          $mail_sent = drupal_mail('faq_ask', 'notify_asker', $email, user_preferred_language($params['account']), $params);

          // Handle sending result
          if ($mail_sent) {
            watchdog('FAQ_Ask', 'Asker notification email sent to @to for question @quest', array(
              '@to' => $email,
              '@quest' => check_plain($node->title),
            ), WATCHDOG_NOTICE);

            // If email sent, remove the notification from the queue
            _faq_ask_delete_faq_notification($node->nid);
          }
          else {
            watchdog('FAQ_Ask', 'Asker notification email to @to failed for the "@quest" question.', array(
              '@to' => $email,
              '@quest' => check_plain($node->title),
            ), WATCHDOG_ERROR);
            drupal_set_message(t('Asker notification email to @to failed for the "@quest" question.', array(
              '@to' => $email,
              '@quest' => check_plain($node->title),
            )));
          }
        }
        break;
      case 'delete':

        // Remove notification if the question is deleted
        _faq_ask_delete_faq_notification($node->nid);
        break;
      case 'prepare':

        // Node is being shown on the add/edit form
        // Check if the node nid exists. It does not when creating a new question
        if (isset($node->nid) && ($mail = _faq_ask_get_faq_notification_email($node->nid))) {
          drupal_set_message(t('Notification will be sent to <strong>@mail</strong> when answered.', array(
            '@mail' => $mail,
          )));
        }
        break;
      default:
        break;
    }
  }
}
function faq_ask_submit($form, &$form_state) {

  // Use only the first term entered in the correct vocabulary.
  // OK, so here we have a set of vocabualries with associated array of selected terms
  // Get the vocab first
  $terms = array();
  $vocabs = variable_get('faq_ask_vocabularies', 0);

  // Get the vocabularies used by faq experts
  // Issue #879442 comment 13 by mishutka90: Invalid argument due to only expert can categorize
  // If expert categorizes - no handling of terms in saving unanswered question
  if (FALSE == variable_get('faq_ask_categorize', FALSE)) {

    // Issue #879442 by Anita Sølver (soelver): Mails for experts
    // for every vocabulary in the form submitted
    foreach ($form_state['values']['taxonomy'] as $vid => $term_array) {
      if (in_array($vid, $vocabs)) {

        // If vocabulary is used by faq ask experts
        $terms = array_merge($terms, $term_array);
      }
      elseif ($vid == 'tags') {

        // If we're using Tags - there will be no vocabulary id on it
        foreach ($term_array as $termname) {
          $t = taxonomy_get_term_by_name($termname);
          foreach ($t as $term_obj) {
            $terms[$term_obj->tid] = $term_obj->tid;
          }
        }
      }
    }
  }

  // All valid terms are found
  // Are we notifying the expert(s)?
  if (variable_get('faq_ask_notify', FALSE)) {

    // Prepare the data structure for sending messages to experts
    $params = array(
      'category' => -1,
      // Default expert if no terms
      'question' => $form_state['values']['title'],
      'question_details' => $form_state['values']['detailed_question'],
      'nid' => $form_state['nid'],
      'creator' => theme('username', node_load($form_state['nid']), array(
        'plain' => TRUE,
      )),
    );
    if (empty($terms)) {

      // If no terms, send to default expert
      $expert = db_fetch_object(db_query("SELECT uid FROM {faq_expert} WHERE tid='0'"));
      faq_ask_notify_expert($expert->uid, $params);
    }
    else {

      // Find out who the experts are.
      $result = db_query("SELECT uid FROM {faq_expert} WHERE tid IN ('%s')", implode(",", $terms));
      while ($expert = db_fetch_object($result)) {

        // Find all terms belonging to this expert
        $expert_terms = db_query("SELECT tid FROM {faq_expert} WHERE uid='%d'", $expert->uid);
        $params['category'] = '';

        // Empty term name
        // Find the first term that is both assigned to this expert and is tagged to this question
        while ($params['category'] == '' && ($term = db_fetch_object($expert_terms))) {
          if (in_array($term->tid, $terms)) {
            $term_node = taxonomy_get_term($term->tid);
            $params['category'] = $term_node->name;
          }
        }

        // Send the message
        faq_ask_notify_expert($expert->uid, $params);
      }
    }
  }

  // Handle the notification of asker
  if (isset($form_state['values']['faq_email'])) {
    if (strlen($form_state['values']['faq_email']) > 4) {

      // length of e-mail must be more than a@b.c
      _faq_ask_set_faq_notification($form_state['nid'], $form_state['values']['faq_email']);
      drupal_set_message(t('Your question has been submitted. An e-mail will be sent to <i>@mail</i> when answered.', array(
        '@mail' => $form_state['values']['faq_email'],
      )), 'status');

      // If this user is not registered as a user before - check if all asking anonymous users should be added to the newsletter list
      if (module_exists('simplenews') && ($tid = variable_get('faq_ask_notify_asker_simplenews_tid', '0'))) {

        // If we have selected a newsletter to add
        if (function_exists('simplenews_subscribe_user')) {
          simplenews_subscribe_user($form_state['values']['faq_email'], $tid, variable_get('faq_ask_notify_asker_simplenews_confirm', 1), 'FAQ-Ask');
        }
      }
    }
  }
  elseif (isset($form_state['values']['faq_notify'])) {
    if ($form_state['values']['faq_notify']) {
      global $user;
      $email = $user->mail;
      _faq_ask_set_faq_notification($form_state['nid'], $email);
      drupal_set_message(t('Your question has been submitted. An e-mail will be sent to <i>@mail</i> when answered.', array(
        '@mail' => $email,
      )), 'status');
    }
    else {

      // Issue #1479382 by stenjo: Not able to turn off asker notification when editing own question
      _faq_ask_delete_faq_notification($form_state['nid']);
    }
  }
  else {
    drupal_set_message(t('Your question has been submitted. It will appear in the FAQ listing as soon as it has been answered.'), 'status');
  }
}

/**
 * Send an expert notification
 *
 * @param int $uid
 *   The user id of the expert
 * @param array $data
 *   Structure containing the parameters for the e-mail
 */
function faq_ask_notify_expert($uid, $data) {
  $account = user_load(array(
    'uid' => $uid,
  ));
  $data['account'] = $account;
  $mail_sent = drupal_mail('faq_ask', 'notify_expert', $account->mail, user_preferred_language($account), $data);
  if ($mail_sent) {
    watchdog('FAQ_Ask', 'Expert notification email sent to @to', array(
      '@to' => $account->mail,
    ), WATCHDOG_NOTICE);

    //    drupal_set_message(t('Expert notification email sent to @to',  array('@to' => $account->mail)));
  }
  else {
    watchdog('FAQ_Ask', 'Expert notification email to @to failed for the "@cat" category.', array(
      '@to' => $account->mail,
      '@cat' => check_plain($data['category']),
    ), WATCHDOG_ERROR);

    //    drupal_set_message(t('Expert notification email failed for the "!cat" category.', array('!cat' => check_plain($data['category']))));
  }
}

/**
 * Implementation of hook_cron().
 *
 * Checks the que for asker notifications and sends a notification to the asker when the question is published
 *
 */
function faq_ask_cron() {

  // If the asker notification should be done by cron
  if (!variable_get('faq_ask_notify_by_cron', TRUE)) {
    return;
  }

  // Get all the waiting notifications
  $notifications = _faq_ask_get_faq_notifications();
  foreach ($notifications as $nid => $email) {

    // With the notification record, check if status of the question is published
    if ($result = db_query('SELECT title,status FROM {node} WHERE nid=%d', $nid)) {
      $notification = db_fetch_array($result);
      if ($notification['status'] == '1') {
        $params = array(
          'question' => $notification['title'],
          'nid' => $nid,
          'account' => user_load(array(
            'mail' => $email,
          )),
        );

        // Send the e-mail to the asker. Drupal calls hook_mail() via this
        $mail_sent = drupal_mail('faq_ask', 'notify_asker', $email, user_preferred_language($params['account']), $params);

        // Handle sending result
        if ($mail_sent) {
          watchdog('FAQ_Ask', 'Asker notification email sent to @to for question: "@quest"', array(
            '@to' => $email,
            '@quest' => check_plain($notification['title']),
          ), WATCHDOG_NOTICE);

          // If email sent, remove the notification from the queue
          _faq_ask_delete_faq_notification($nid);
        }
        else {
          watchdog('FAQ_Ask', 'Asker notification email to @to failed for the "@quest" question.', array(
            '@to' => $email,
            '@quest' => check_plain($notification['title']),
          ), WATCHDOG_ERROR);
          drupal_set_message(t('Asker notification email to @to failed for the "@quest" question.', array(
            '@to' => $email,
            '@quest' => check_plain($notification['title']),
          )));
        }
      }
    }
  }
}

/**
 * Helper function to fetch an email for notification assigned to an faq node
 *
 */
function _faq_ask_get_faq_notification_email($nid) {
  if ($nid != '') {
    $result = db_query('SELECT email FROM {faq_ask_notify} WHERE nid=%d', $nid);
    $mail = db_fetch_array($result);
    return $mail['email'];
  }
  else {
    return '';
  }
}

/**
 * Helper function fetching all notifications
 *
 * @return array
 *   Array containing all outstanding notifications
 *
 */
function _faq_ask_get_faq_notifications() {
  $notifications = array();
  $result = db_query('SELECT nid, email FROM {faq_ask_notify}');
  while ($row = db_fetch_array($result)) {
    $notifications[$row['nid']] = $row['email'];
  }
  return $notifications;
}

/**
 * Set a notification with an e-mail address attached to a node
 *
 * @param int $nid
 *   Node id of question where there is a notification
 *
 * @param string $email
 *   Address to send the notification
 */
function _faq_ask_set_faq_notification($nid, $email) {
  _faq_ask_delete_faq_notification($nid);
  $data = array(
    'nid' => $nid,
    'email' => $email,
  );
  drupal_write_record('faq_ask_notify', $data);
}

/**
 * Delete the notification belonging to a node
 *
 * @param int $nid
 *   Node id of which to delete the corresponding notification
 */
function _faq_ask_delete_faq_notification($nid) {
  $delete = db_query("DELETE FROM {faq_ask_notify} WHERE nid=%d", $nid);
  if ($delete === FALSE) {
    drupal_set_message(t('Attempt to delete email notification failed.'), 'error');
  }
}

/**
 * Block "Ask a Question" form implementation
 * This implements the form displayed in a block where the user may ask a question
 *
 */
function faq_ask_a_question_blockform() {

  // Include page handler for node_add()
  module_load_include('inc', 'node', 'node.pages');

  // If user is allowed to create a faq content type
  if (node_access('create', 'faq')) {

    // Fool the hook_form_alter function to think we're in an faq-ask page
    $saved_get = '';
    if (isset($_GET['ask'])) {
      $saved_get = $_GET['ask'];
    }
    $_GET['ask'] = '1';
    $_GET['block'] = 'TRUE';

    // Note title before rendering of form.
    $title = drupal_get_title();

    // Create the form
    $form = node_add('faq');

    // Restore title, which will have been overridden.
    drupal_set_title(check_plain($title));

    // Restore the $_GET['ask'] variable status
    if ($saved_get != '') {
      $_GET['ask'] = $saved_get;
    }
    else {
      unset($_GET['ask']);
    }
    unset($_GET['block']);
    return $form;
  }
  else {
    return '';
  }
}

/**
 * Implementation of hook_mail().
 * This function completes the email, allowing for placeholder substitution.
 * @TODO: notify_asker.
 * @TODO: define messages & subjects on settings page, with list of tokens. how to handle newlines?
 */
function faq_ask_mail($key, &$message, $params) {
  $newline = "\n<br/><br/>";
  $body = array();
  $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed';
  $language = $message['language'];

  // Initiate text variables
  $variables = array(
    '@question' => $params['question'],
    '@question_details' => isset($params['question_details']) ? $params['question_details'] : '',
    '@site-name' => variable_get('site_name', 'Drupal'),
  );

  // Find category name
  if (isset($params['category']) && $params['category']) {
    $term = '';
    $tid = $params['category'];
    if (is_array($params['category'])) {
      $term = taxonomy_get_term(array_shift($params['category']));
    }
    else {
      $term = taxonomy_get_term($params['category']);
    }
    if (is_object($term)) {
      $variables['!cat'] = $term->name;
    }
    else {
      $params['category'] = -1;
    }
  }
  else {
    $params['category'] = -1;
  }

  // Handle user names
  if (!isset($variables['!username']) || $variables['!username'] == '') {
    if (isset($params['account']) && is_object($params['account'])) {
      $variables['!username'] = $params['account']->name;
    }
    else {
      $variables['!username'] = t('user');
    }
  }
  switch ($key) {
    case 'notify_expert':
      $url_options = array(
        'absolute' => TRUE,
        'query' => array(
          'token' => _faq_ask_get_token('faq_ask/answer/' . $params['nid']),
        ),
      );
      $variables = array_merge($variables, array(
        '!cat' => $params['category'],
        '@question' => $params['question'],
        '!answer_uri' => url('faq_ask/answer/' . $params['nid'], $url_options),
        '!asker' => $params['creator'],
        '!login_uri' => url('user', array(
          'absolute' => TRUE,
        )),
      ));
      $subject = t('You have a question waiting on @site-name', $variables, $language->language);
      $body[] = t('Dear !username,', $variables, $language->language);
      if ($params['category'] == -1) {
        $body[] = t('The following question has been posted.', NULL, $language->language);
      }
      else {
        $body[] = t('The following question has been posted in the "!cat" category by !asker.', $variables, $language->language);
      }
      $body[] = t('<strong><i>@question</i></strong>', $variables, $language->language);
      if ($variables['@question_details']) {
        $body[] = t('<i>@question_details</i>', $variables, $language->language);
      }

      //      $body[] = t('In order to answer it you will first need to <a href="!login_uri">login</a> to the site.', $variables, $language->language);
      //      $body[] = t('Once logged in, you may proceed <a href="!answer_uri">directly to the question</a> to answer it.', $variables, $language->language);
      //      $body[] = t('By clicking on the above question link you will be redirected to the login form if you are currently logged out.', $variables, $language->language);
      $body[] = t('You may proceed directly to the question to answer it by clicking this link: !answer_uri', $variables, $language->language);
      $body[] = t('Note: Clicking the link will automatically publish the question, making it available for users immediately.', $variables, $language->language);
      break;
    case 'notify_asker':
      $variables = array_merge($variables, array(
        //'!cat' => $params['category'],
        '@question' => $params['question'],
        '!question_uri' => url('node/' . $params['nid'], array(
          'absolute' => TRUE,
        )),
      ));
      if ($variables['!username'] == '') {
        $variables['!username'] = t('user');
      }
      $subject = t('A question you asked has been answered on @site-name', $variables, $language->language);
      $body[] = t('Dear !username,', $variables, $language->language);
      $body[] = t('The question: "@question" you asked on @site-name has been answered.', $variables, $language->language);
      $body[] = t('To view the answer, please visit the question you created on !question_uri.', $variables, $language->language);
      $body[] = t('Thank you for visiting.', $variables, $language->language);
      break;
  }
  $message['body'] = drupal_wrap_mail(implode($newline, $body));
  $message['subject'] = $subject;
}

/**
 * Implementation of hook_form().
 * This form allows the users to select the expert roles and to which categories the users in those roles are assigned.
 * Note, the expert/category table attempts to use the least horizontal space,
 * so it can "flip" based on whether there are more categories or experts.
 */
function faq_ask_settings_form(&$form_state, $op = NULL, $aid = NULL) {
  $form = array();

  // Set a basic message that will be unset once we pass the error checking.
  $form['error'] = array(
    '#value' => t('Errors were found, please correct them before proceeding.'),
    '#weight' => -10,
  );
  $faq_use_categories = variable_get('faq_use_categories', FALSE);
  if (!$faq_use_categories) {
    drupal_set_message(t('The Faq_Ask module requires that FAQ "Categorize questions."') . ' ' . t('Please go to the <a href="@url">settings page</a> to configure this module.', array(
      '@url' => url('admin/settings/faq/categories'),
    )), 'error');
    return $form;
  }

  // Get the list of vocabularies that apply to FAQ s.
  $vocabs = taxonomy_get_vocabularies('faq');
  if (count($vocabs) == 0) {
    drupal_set_message(t('The Faq_Ask module requires that at least one vocabulary apply to the "faq" content type. Please go to the Taxonomy <a href="@taxo_uri">configuration page</a> to do this.', array(
      '@taxo_uri' => url('admin/content/taxonomy'),
    )), 'error');
    return $form;
  }

  // Get the admin's name.
  $admin = ucwords(db_result(db_query('SELECT name FROM {users} WHERE uid=1')));

  // Get the Simplenews newsletters if they exists
  $newsletters = array(
    '0' => t('No newsletter'),
  );
  if (module_exists('simplenews')) {
    if (!function_exists('simplenews_get_newsletters')) {
      drupal_set_message(t('The Simplenews integration is not compatible with this version of Simplenews. Please download a later version.'), 'error');
    }
    else {
      $list = simplenews_get_newsletters(variable_get('simplenews_vid', ''));
      foreach ($list as $key => $object) {
        $list[$key] = $object->name;
      }
      $newsletters += $list;
    }
  }
  $form['notification'] = array(
    '#type' => 'fieldset',
    '#title' => t('Notifications'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['notification']['faq_ask_notify'] = array(
    '#type' => 'checkbox',
    '#title' => t('Notify experts'),
    '#description' => t('If this box is checked, the expert(s) for the question will be notified via email that a question awaits them. If you do not choose this option, the "Unanswered Questions" block will be the only way they will know they have questions to answer.'),
    '#default_value' => variable_get('faq_ask_notify', 0),
  );
  $form['notification']['notify_asker'] = array(
    '#type' => 'fieldset',
    '#title' => T('Asker notification'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['notification']['notify_asker']['faq_ask_asker_notify'] = array(
    '#type' => 'checkbox',
    '#title' => t('Notify askers'),
    '#description' => t('If this box is checked, the asker creating the question will be notified via email that their question is answered.'),
    '#default_value' => variable_get('faq_ask_notify_asker', 0),
  );

  //Issue #1479362 by stenjo: Indication of asker notification
  $form['notification']['notify_asker']['faq_ask_asker_notify_indication'] = array(
    '#type' => 'textfield',
    '#title' => t('Notify asker indication'),
    '#description' => t('If this box is not blank the titles on the unanswered list will be added the string in this box to indicate an asker notification.'),
    '#size' => 5,
    '#maxlength' => 5,
    '#default_value' => variable_get('faq_ask_asker_notify_indication', ' [n]'),
  );
  $form['notification']['notify_asker']['faq_ask_asker_notify_cron'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use cron for asker notification'),
    '#description' => t('If this box is checked, the asker notifications will be sendt via cron.'),
    '#default_value' => variable_get('faq_ask_notify_by_cron', TRUE),
    '#disabled' => !variable_get('faq_ask_notify_asker', 0),
  );

  // If the Simplenews module is loaded we can add functionality to add anonymous askers to a newsletter
  $form['notification']['notify_asker']['faq_ask_notify_asker_simplenews'] = array(
    '#type' => 'select',
    '#title' => t('Add anonymous asker to newsletter'),
    '#default_value' => variable_get('faq_ask_notify_asker_simplenews_tid', '0'),
    '#options' => $newsletters,
    '#description' => module_exists('simplenews') ? t('Select a newsletter you want anonymous askers to be assigned to.') : t('This functionality needs the <a href="http://drupal.org/project/simplenews">Simplenews module</a> to be activated.'),
    '#disabled' => !module_exists('simplenews'),
  );
  $form['notification']['notify_asker']['faq_ask_notify_asker_simplenews_confirm'] = array(
    '#type' => 'checkbox',
    '#title' => t('Confirm subscription to newsletter'),
    '#description' => t('If this box is checked, the asker creating the question will be asked to confirm the subscription of the newsletter.'),
    '#default_value' => variable_get('faq_ask_notify_asker_simplenews_confirm', 1),
    '#disabled' => !module_exists('simplenews'),
  );
  $form['options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Options'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['options']['faq_ask_categorize'] = array(
    '#type' => 'checkbox',
    '#title' => t('Only expert can categorize'),
    '#description' => t('If this box is checked, only an expert answering a question can add a category.'),
    '#default_value' => variable_get('faq_ask_categorize', FALSE),
  );

  // Issue #1479352 by stenjo: Compact display of unanswered questions
  $form['options']['faq_ask_compact_limit'] = array(
    '#type' => 'textfield',
    '#title' => t('Display compact unanswered list threshold'),
    '#description' => t('Set this to a number that will cause the unanswered list to appear in compact form. Leave this blank or set to zero for no compact display.'),
    '#size' => 5,
    '#maxlength' => 5,
    '#default_value' => variable_get('faq_ask_compact_limit', ''),
  );
  $give_options = array(
    0 => t('Asker retains ownerhsip'),
    1 => t('Anonymous questions reassigned to expert'),
    2 => t('All questions reassigned to expert'),
  );
  $form['options']['faq_ask_expert_own'] = array(
    '#type' => 'radios',
    '#options' => $give_options,
    '#title' => t('Give ownership to the expert'),
    '#description' => t('This determines if questions will be reassigned to the expert when answered.'),
    '#default_value' => variable_get('faq_ask_expert_own', 0),
  );
  $form['options']['faq_ask_unanswered'] = array(
    '#type' => 'textarea',
    '#title' => t('Default unanswered body text'),
    '#cols' => 60,
    '#rows' => 1,
    '#description' => t('This text will be inserted into the body of questions when they are asked. This helps make editing easier'),
    '#default_value' => variable_get('faq_ask_unanswered', t('Not answered yet.')),
  );
  $form['options']['faq_ask_expert_advice'] = array(
    '#type' => 'textarea',
    '#title' => t('Answer advice for the expert'),
    '#cols' => 60,
    '#rows' => 1,
    '#description' => t('This text will be shown at the bottom of the "Unanswered questions" block.'),
    '#default_value' => variable_get('faq_ask_expert_advice', _faq_ask_advice_default()),
  );
  $form['options']['advice']['faq_ask_admin_advice'] = array(
    '#type' => 'textarea',
    '#title' => t('Advice for an administrator/editor'),
    '#cols' => 60,
    '#rows' => 1,
    '#default_value' => variable_get('faq_ask_admin_advice', _faq_ask_advice_default('admin')),
  );
  $form['options']['advice']['faq_ask_asker_advice'] = array(
    '#type' => 'textarea',
    '#title' => t('Advice for an asker'),
    '#cols' => 60,
    '#rows' => 1,
    '#default_value' => variable_get('faq_ask_asker_advice', _faq_ask_advice_default('asker')),
  );
  $help_default = variable_get('faq_ask_help_text', _faq_ask_help_default());
  $form['options']['faq_ask_help_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Help text for the asker'),
    '#cols' => 60,
    '#rows' => drupal_strlen($help_default) / 60,
    '#description' => t('This text will be shown at the top of the "Ask a Question" page.'),
    '#default_value' => $help_default,
  );
  $form['experts'] = array(
    '#type' => 'fieldset',
    '#title' => t('Experts'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );

  // Use the list of vocabularies from above.
  if (count($vocabs) == 1) {

    // Single vocabulary, don't bother with a selection box, just set it.
    $vid = key($vocabs);
    $def_vid = $vid;
    variable_set('faq_ask_vocabularies', array(
      $vid => $vid,
    ));
    $vobj = $vocabs[$vid];
    $free = $vobj->tags;
  }
  else {

    // Multiple vocabs available.
    $voc_list = array();
    $def_vid = 0;

    // variable_get('faq_ask_vocabularies', 0)?
    foreach ($vocabs as $vid => $vobj) {
      $voc_list[$vid] = $vobj->name;
      if ($vobj->name == 'FAQ') {
        $def_vid = $vid;
      }
    }
    $form['experts']['faq_ask_vocabularies'] = array(
      '#type' => 'select',
      '#options' => $voc_list,
      '#title' => t('Use these vocabularies'),
      '#multiple' => TRUE,
      '#default_value' => variable_get('faq_ask_vocabularies', $def_vid),
      '#description' => t('Only the terms from the selected vocabularies will be included in the list below.') . ' ' . t("Simply adding the 'FAQ' content type to a vocabulary will not make it eligible for experts; you must return to here to add it.") . '<br/><big>' . t('If you select different vocabularies, you must save the configuration BEFORE selecting users below.') . '</big>',
    );
  }

  // End multiple vocabs.
  // Changed query and loop because it failed if 'answer' was the first perm in list.
  // This should be faster any way.
  $role_list = array();
  $r_res = db_query("SELECT r.rid, r.name FROM {role} r JOIN {permission} p USING (rid) WHERE p.perm LIKE '%%answer question%%'");
  while ($row = db_fetch_array($r_res)) {
    $role_list[$row['rid']] = $row['name'];
  }
  if (empty($role_list)) {
    drupal_set_message(t('No roles with "answer question" permission were found; only @admin is currently eligible to be an expert. You may want to go to the <a href="@access">Permissions page</a> to update your permissions.', array(
      '@access' => url('admin/user/permissions'),
      '@admin' => $admin,
    )), 'error');
  }

  // Get all terms associated with FAQ.

  //$vocabs = variable_get('faq_ask_vocabularies', $def_vid);
  $vocabs_array = array();
  foreach ($vocabs as $vocab) {
    $vocabs_array[$vocab->vid] = $vocab->vid;
  }
  $result = db_query('SELECT td.tid, td.name, td.description FROM {term_data} td WHERE td.vid IN (' . db_placeholders($vocabs_array) . ') ORDER BY td.weight ASC, td.name ASC', $vocabs_array);

  //  $result = db_query("SELECT td.tid, td.name, td.description FROM {term_data} td WHERE td.vid IN (". db_placeholders($vocabs) .") ORDER BY td.weight ASC, td.name ASC", $vocabs);
  $faq_terms = array();
  while ($term = db_fetch_array($result)) {

    // Show term hierarchy?
    $term_name = check_plain($term['name']);
    if (substr($term['description'], 0, 9) == 'suggested') {
      $faq_terms[$term['tid']] = $term_name . '<br/>--<small>' . strip_tags($term['description']) . '</small>';
    }
    else {
      $faq_terms[$term['tid']] = $term_name;
    }
  }
  if (count($faq_terms) == 0) {
    drupal_set_message(t('No vocabularies or terms were found for the "faq" content type . Please go to the <a href="@access">Categories page</a> to update your vocabulary.', array(
      '@access' => url('admin/content/taxonomy'),
    )), 'error');
    return $form;
  }

  // Get all users associated with the roles.
  $faq_expert_names = array();

  // User/1 typically is not assigned roles, but should be in the list.
  $faq_expert_names[1] = $admin;
  $rids = variable_get('faq_expert_role', array());
  if (!empty($rids)) {
    if (in_array(DRUPAL_AUTHENTICATED_RID, $rids)) {

      // Authenticated users may be experts, so get all active users.
      // No other roles matter.
      $result = db_query("SELECT u.uid, u.name FROM {users} u WHERE status=1");
    }
    else {

      // Only specific roles may be experts.
      $result = db_query('SELECT DISTINCT(u.uid), u.name FROM {users_roles} ur JOIN {users} u USING (uid) WHERE ur.rid IN (' . db_placeholders($rids) . ')', $rids);
    }
    while ($user = db_fetch_array($result)) {
      if ($user['uid'] != 1) {
        $faq_expert_names[$user['uid']] = ucwords($user['name']);
      }
    }

    // Put them in alphabetical order.
    asort($faq_expert_names);
  }
  if (!empty($role_list)) {
    $form['experts']['faq_expert_role'] = array(
      '#type' => 'select',
      '#title' => t('Expert Roles'),
      '#options' => $role_list,
      '#multiple' => TRUE,
      '#default_value' => variable_get('faq_expert_role', '2'),
      '#description' => t('User 1 (@admin) will always be in the list, regardless of roles.', array(
        '@admin' => $admin,
      )) . '<br/><big>' . t('If you select different roles, you must save the configuration BEFORE selecting users below.') . '</big>',
    );
  }
  $more_experts_than_terms = count($faq_expert_names) > count($faq_terms);

  // If there is only one eligible expert, we might as well preset all categories.
  $expert_msg = NULL;
  $only_one_expert = count($faq_expert_names) == 1;
  $count = 0;
  if ($more_experts_than_terms) {

    // Experts go down the left; terms go across the top.
    $top = NULL;
    if ($only_one_expert) {
      $top .= '<p>' . t('Note: Even though the check boxes below are checked, you must still click the "Save configuration" button to save the expert settings.') . '</p>';
    }
    $top .= '<table id="faq_experts"><tr><th> </th><th>' . implode('</th><th>', $faq_terms) . '</th></tr>';
    if ($only_one_expert) {
      $top .= '<tr><td colspan="100">' . t('Note: Even though the check boxes below are checked, you must still click the "Save configuration" button to save the expert settings.') . '</td></tr>';
    }
    foreach ($faq_expert_names as $uid => $name) {
      ++$count;
      $class = $count & 1 ? 'odd' : 'even';
      $left = '<tr class="' . $class . '"><td><strong>' . $name . '</strong></td>';
      foreach ($faq_terms as $tid => $term_name) {
        $box_name = 'expert_' . $uid . '_' . $tid;
        $form['experts'][$box_name] = array(
          '#type' => 'checkbox',
          '#default_value' => $only_one_expert,
          '#prefix' => $top . $left . '<td align="center">',
          '#suffix' => '</td>',
        );
        $top = NULL;
        $left = NULL;
      }
      $form['experts'][$box_name]['#suffix'] .= '</tr>';
    }
    $form['experts'][$box_name]['#suffix'] .= '</table>';
  }
  else {

    // Experts go across the top; terms go down the left.
    $top = NULL;
    if ($only_one_expert) {
      $top .= '<p>' . t('Note: Even though the check boxes below are checked, you must still click the "Save configuration" button to save the expert settings.') . '</p>';
    }
    $top .= '<table id="faq_experts"><tr><th> </th><th>' . implode('</th><th>', $faq_expert_names) . '</th></tr>';
    foreach ($faq_terms as $tid => $term_name) {
      ++$count;
      $class = $count & 1 ? 'odd' : 'even';
      $left = '<tr class="' . $class . '"><td><strong>' . $term_name . '</strong></td>';
      foreach ($faq_expert_names as $uid => $name) {
        $box_name = 'expert_' . $uid . '_' . $tid;
        $form['experts'][$box_name] = array(
          '#type' => 'checkbox',
          '#default_value' => $only_one_expert,
          '#prefix' => $top . $left . '<td align="center">',
          '#suffix' => '</td>',
        );
        $top = NULL;
        $left = NULL;
      }
      $form['experts'][$box_name]['#suffix'] .= '</tr>';
    }
    $form['experts'][$box_name]['#suffix'] .= '</table>';
  }
  $form['experts'][$box_name]['#suffix'] .= t('Those who will be answering questions will need both "answer question" and "edit faq" permissions.');
  $result = db_query("SELECT * FROM {faq_expert}");
  while ($expert = db_fetch_array($result)) {
    $box_name = 'expert_' . $expert['uid'] . '_' . $expert['tid'];
    if (isset($form['experts'][$box_name])) {

      // Might not be present any more.
      $form['experts'][$box_name]['#default_value'] = TRUE;
    }
    else {

      // Expert 0 means default expert; overlook it.
      if ($expert['tid'] != 0) {
        drupal_set_message(t("@name doesn't exist. If you have just changed your role selections this may be okay.", array(
          '@name' => $box_name,
        )), 'warning');
      }
    }
  }
  if ($only_one_expert) {

    // Create a form value to set default expert to admin.
    $form['experts']['faq_ask_default_expert'] = array(
      '#type' => 'value',
      '#value' => 1,
    );
  }
  else {
    $form['experts']['faq_ask_default_expert'] = array(
      '#type' => 'select',
      '#options' => $faq_expert_names,
      '#multiple' => FALSE,
      '#title' => t('Default expert'),
      '#description' => t('The selected user will be assigned as the expert for all terms that are added to the selected vocabularies until you return to this page and update it.'),
      '#default_value' => variable_get('faq_ask_default_expert', 1),
    );
  }

  // Get rid of error element.
  unset($form['error']);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
    '#weight' => 5,
  );
  return $form;
}

/**
 * Implementation of hook_form_submit().
 * It saves the expert roles that were selected, then rebuilds the expert/category table.
 */
function faq_ask_settings_form_submit($form, &$form_state) {

  // Save the simple stuff.
  if (isset($form_state['values']['faq_expert_role'])) {
    variable_set('faq_expert_role', $form_state['values']['faq_expert_role']);
  }
  if (isset($form_state['values']['faq_ask_vocabularies'])) {
    variable_set('faq_ask_vocabularies', $form_state['values']['faq_ask_vocabularies']);
  }
  variable_set('faq_ask_categorize', $form_state['values']['faq_ask_categorize']);
  variable_set('faq_ask_compact_limit', $form_state['values']['faq_ask_compact_limit']);
  variable_set('faq_ask_expert_own', $form_state['values']['faq_ask_expert_own']);
  variable_set('faq_ask_notify', $form_state['values']['faq_ask_notify']);
  variable_set('faq_ask_notify_asker', $form_state['values']['faq_ask_asker_notify']);
  variable_set('faq_ask_asker_notify_indication', $form_state['values']['faq_ask_asker_notify_indication']);
  variable_set('faq_ask_notify_asker_simplenews_tid', $form_state['values']['faq_ask_notify_asker_simplenews']);
  variable_set('faq_ask_notify_asker_simplenews_confirm', $form_state['values']['faq_ask_notify_asker_simplenews_confirm']);
  variable_set('faq_ask_notify_by_cron', $form_state['values']['faq_ask_asker_notify_cron']);
  variable_set('faq_ask_unanswered', $form_state['values']['faq_ask_unanswered']);
  variable_set('faq_ask_default_expert', $form_state['values']['faq_ask_default_expert']);
  variable_set('faq_ask_expert_advice', $form_state['values']['faq_ask_expert_advice']);
  variable_set('faq_ask_help_text', $form_state['values']['faq_ask_help_text']);
  variable_set('faq_ask_admin_advice', $form_state['values']['faq_ask_admin_advice']);
  variable_set('faq_ask_asker_advice', $form_state['values']['faq_ask_asker_advice']);

  // Get all the selected expert/category options.
  // First, we'll include the default expert for tid=0.
  $values = array(
    '(' . $form_state['values']['faq_ask_default_expert'] . ', 0)',
  );
  foreach ($form_state['values'] as $name => $value) {
    if (substr($name, 0, 7) == 'expert_') {
      list($junk, $uid, $tid) = explode('_', $name);
      if ($value) {
        $values[] = '(' . $uid . ', ' . $tid . ')';
      }
    }
  }
  $vals = implode(', ', $values);

  // Delete the current values and save the new ones.
  if (!empty($values)) {
    db_query('TRUNCATE {faq_expert}');

    // Issue #1559240 by stenjo: Saving experts on multi-lingual sites
    // Issue #1828148 by Andriy Podranetskyy (apodran): Wrong use of db_rewrite_sql in the code
    db_query("INSERT INTO {faq_expert} (uid, tid) VALUES " . $vals, 'fe', 'uid');
  }
  drupal_set_message(t('Configuration has been updated.'), 'status');
}

/**
 * This function is called when an expert selects a question to answer.
 * It changes the status option to "published" then goes to the regular FAQ edit function.
 */
function faq_ask_answer($node) {
  global $user;

  // Validate the request.
  if (!isset($_REQUEST['token']) || !_faq_ask_valid_token($_REQUEST['token'], "faq_ask/answer/{$node->nid}")) {
    watchdog('Faq_Ask', 'Received an invalid answer request (@query_string) from @user_ip.', array(
      '@query_string' => $_SERVER['QUERY_STRING'],
      '@user_ip' => $_SERVER['REMOTE_ADDR'],
    ), WATCHDOG_ALERT);
    drupal_access_denied();
    return;
  }
  $reassign_opt = variable_get('faq_ask_expert_own', 0);

  // Check if we need to reassign to the expert.
  switch ($reassign_opt) {
    case 0:

      // Do not reassign.
      break;
    case 1:

      // Reassign if anonymous.
      if ($node->uid == 0) {
        faq_ask_reassign($node);
      }
      break;
    case 2:

      // Always reassign.
      faq_ask_reassign($node);
      break;
  }

  // Change the status to published and the user id, if needed.
  $options = variable_get('node_options_faq', array(
    'status',
    'promote',
  ));
  if (in_array('status', $options) || isset($_REQUEST['forced']) && $_REQUEST['forced'] == TRUE) {
    db_query("UPDATE {node} SET status=1, uid=%d WHERE nid=%d", $node->uid, $node->nid);
    $node->status = 1;
  }
  if (!in_array('status', $options) && $node->status == '1') {

    // If this is default NOT published AND node published
    // Need to display published node.
    drupal_goto("node/{$node->nid}");
  }

  // Need to invoke node/##/edit.
  drupal_goto("node/{$node->nid}/edit");
}

/**
 *  Reassign the node to the current user and display a message.
 */
function faq_ask_reassign(&$node) {
  global $user;
  $node->uid = $user->uid;
  drupal_set_message(t('This question is being assigned to @user.', array(
    '@user' => theme('username', $user),
  )));
}

/**
 * Implementation of hook_taxonomy().
 *
 *  @param:
 *    op:  'insert', 'update, 'delete'
 *    type: 'term', 'vocabulary'
 *    array: depends on other two.
 */
function faq_ask_taxonomy($op, $type, $edit = NULL) {
  global $user;
  $default_expert = variable_get('faq_ask_default_expert', 1);
  $my_vocs = variable_get('faq_ask_vocabularies', array());
  $vid = $edit['vid'];

  // See if it's one of our vocabularies.
  $our_vocab = in_array($vid, $my_vocs);
  switch ($op) {
    case 'insert':
      switch ($type) {
        case 'term':

          // term: set default expert.
          if ($our_vocab) {
            $values = array(
              $default_expert,
              $edit['tid'],
            );
            $insert = db_query("INSERT INTO {faq_expert} (uid, tid) VALUES (" . db_placeholders($values) . ")", $default_expert, $edit['tid']);
            if ($insert === FALSE) {
              drupal_set_message(t('Attempt to assign expert failed.'), 'error');
            }
            else {
              drupal_set_message(t('Assigned expert @expert to @name (@tid).', array(
                '@expert' => $default_expert,
                '@name' => $edit['name'],
                '@tid' => $edit['tid'],
              )), 'status');
            }
          }
          break;
        case 'vocabulary':

          // New vocabulary created. It will not show on the ask page until the user
          // goes to the settings page, so we don't need to do anything.
          break;
      }

      // End insert switch type.
      break;
    case 'delete':
      switch ($type) {
        case 'term':

          // Delete term: remove experts.
          if ($our_vocab) {
            _faq_ask_delete_expert($edit['tid'], $edit['name']);
          }
          break;
        case 'vocabulary':

          // Each term gets deleted first, so all we have to do is remove it from our vocab list.
          if ($our_vocab) {
            _faq_ask_delete_vocabulary($vid, $edit, $my_vocs);
          }
          break;
      }

      // End delete switch type.
      break;
    case 'update':

      //  Two cases for vocabulary:
      //    1) FAQ is added to the vocab. -- see insert comment.
      //    2) FAQ is removed from the vocab. -- need to delete all experts for all terms and remove from voc list?
      //  $edit['nodes'] contains an array of content types for the vocab.
      switch ($type) {
        case 'term':

          // Term update: nothing to do.
          break;
        case 'vocabulary':
          if (in_array('faq', $edit['nodes'])) {

            // If it's there now, we're done.
            break;
          }

          // Not there now, so we need to see if it was.
          if ($our_vocab) {
            $tree = taxonomy_get_tree($vid);
            foreach ($tree as $term) {
              $my_tid = $term->tid;
              $my_tname = $term->name;
              _faq_ask_delete_expert($my_tid, $my_tname);
            }

            // End foreach tree.
            _faq_ask_delete_vocabulary($vid, $edit, $my_vocs);
          }
          break;
      }

      // End update switch type.
      break;
    default:
      drupal_set_message(t('Faq_ask_taxonomy: Unknown op (@op) encountered', array(
        '@op' => $op,
      )), 'status');
  }

  // End switch $op
}

/**
 * Helper function to delete a vocabulary.
 *  @param.
 *    $vid - the taxonomy vocabulary id.
 *    $edit - the array provided in hook_taxonomy.
 *    $my_vocs - the array of faq_ask vocabularies.
 *
 *  @return: none.
 */
function _faq_ask_delete_vocabulary($vid, $edit, $my_vocs) {
  global $user;
  $name = check_plain($edit['name']);
  $uname = $user->name;
  drupal_set_message(t("Vocabulary '@name' is being removed from the Faq_Ask list.", array(
    '@name' => $name,
  )), 'status');
  watchdog('Faq_Ask', 'Vocabulary @voc was deleted from Faq_Ask by @name.', array(
    '@voc' => $name,
    '@name' => $uname,
  ), WATCHDOG - NOTICE);
  unset($my_vocs[$vid]);
  variable_set('faq_ask_vocabularies', $my_vocs);
}

/**
 * Helper function to delete experts.
 *  @param.
 *    $tid - the taxonomy term id for the experts.
 *
 *  @return: none.
 */
function _faq_ask_delete_expert($tid, $name = NULL) {
  $delete = db_query("DELETE FROM {faq_expert} WHERE tid=%d", $tid);
  if ($delete === FALSE) {
    drupal_set_message(t('Attempt to delete expert failed.'), 'error');
  }
  else {
    drupal_set_message(t("Deleted experts for '@name'.", array(
      '@name' => $name,
    )), 'status');
  }
}

/**
 *  Special function to get the advice text default.
 */
function _faq_ask_advice_default($type = 'expert') {
  switch ($type) {
    case 'expert':
      return t('If you select a question, you must answer it.');
    case 'admin':
      return t('You are allowed to edit unanswered questions.');
    case 'asker':
      return t('You may edit your own questions until they are answered.');
  }
}

/**
 *  Special function to get the help text default.
 */
function _faq_ask_help_default() {
  return t("Add a question for our expert to answer. After being answered, your question and the answer will be displayed in the FAQ pages. If the question will not fit in the box below, please try to rephrase it.");
}

/**
 * Implementation of hook_block().
 *
 * This creates and populates the "unanswered questions" block.
 */
function faq_ask_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;
  $block = array();
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('Unanswered Questions');
      $blocks[1]['info'] = t('Ask a Question');
      return $blocks;
    case 'view':
      switch ($delta) {
        case 0:

          // Unanswered Questions.
          $block['subject'] = t('Unanswered questions');
          $block['content'] = _faq_ask_list_unanswered(variable_get('faq_unanswered_count', 3));
          break;
        case 1:

          // Ask a question block.
          if (user_access('ask question')) {
            $block['subject'] = t('Ask a Question');
            $block['content'] = faq_ask_a_question_blockform();
          }
      }

      // end switch($delta).
      return $block;
    case 'configure':
      switch ($delta) {
        case 0:

          // Unanswered Questions.
          $block['faq_unanswered_count'] = array(
            '#type' => 'select',
            '#title' => t('Number of questions to show'),
            '#description' => t("This controls the number of questions that appear in the 'Unanswered Questions' block."),
            '#options' => array(
              1 => 1,
              2 => 2,
              3 => 3,
              4 => 4,
              5 => 5,
              6 => 6,
              7 => 7,
              8 => 8,
              9 => 9,
              10 => 10,
              15 => 15,
              20 => 20,
              25 => 25,
              50 => 50,
              100 => 100,
            ),
            '#default_value' => variable_get('faq_unanswered_count', 3),
          );
          break;
      }

      // end switch($delta)
      return $block;
    case 'save':
      switch ($delta) {
        case 0:
          variable_set('faq_unanswered_count', $edit['faq_unanswered_count']);
          break;
      }

      // end switch($delta)
      return;
  }

  // end switch($op)
}

/**
 * This is the code to select the Unanswered Questions for the block.
 * It is also used by the "unanswered" page by setting a very high limit.
 */
function _faq_ask_list_unanswered($limit) {
  global $user;

  // Bounce anonymous users.
  if ($user->uid == 0) {
    if ($limit < 1000) {

      // If this is a block
      return NULL;

      // Return empty content
    }
    else {

      // Snached from http://drupal.org/node/60148
      drupal_set_message("Access Denied: Please Login");
      $dest = drupal_get_destination();
      drupal_goto('user/login', $dest);

      // this remembers where the user is coming from
    }
  }

  // What permissions does this user have?
  $can_edit = user_access('administer faq') || user_access('administer nodes');
  $is_expert = user_access('answer question');
  $mode = 'edit';
  $output = $extra_msg = NULL;

  // A high limit means we are doing the "unanswered" page.
  if ($limit > 1000) {
    $order = 'tn.tid ASC, n.created ASC';
  }
  else {
    $order = 'n.created ASC';
  }

  // Note: If the admin is also an expert, the expert-ness prevails.
  if ($is_expert) {
    $mode = 'answer';
    if ($limit < 1000) {
      $extra_msg = '<p class="faq_ask_expert_advice">' . filter_xss_admin(variable_get('faq_ask_expert_advice', _faq_ask_advice_default('expert'))) . '</p>';
    }

    // Get the expert's terms.
    $terms = array();
    $tresult = db_query('SELECT tid FROM {faq_expert} WHERE uid=%d', $user->uid);
    while ($row = db_fetch_object($tresult)) {
      $terms[$row->tid] = $row->tid;
    }

    // Check if this expert has any categories.
    if (count($terms) == 0) {
      if ($limit > 1000) {
        return '<p>' . t("For some strange reason, I couldn't find any categories for you.") . '</p>';
      }
      else {
        return NULL;
      }
    }

    // Join the term_data table to select based on tid.
    $result = db_query($qry = db_rewrite_sql("SELECT n.title, n.nid, tn.tid FROM {node} n LEFT JOIN {term_node} tn USING (nid) WHERE n.type='faq' AND n.status=0 AND (tn.tid IN (" . db_placeholders($terms) . ") OR tn.tid IS NULL) ORDER BY " . $order), $terms);
  }
  elseif ($can_edit) {
    $result = db_query(db_rewrite_sql("SELECT n.title, n.nid, tn.tid FROM {node} n JOIN {term_node} tn USING (nid) WHERE n.type='faq' AND n.status=0 ORDER BY " . $order));
    if ($limit < 1000) {
      $extra_msg = '<p class="faq_ask_expert_advice">' . filter_xss_admin(variable_get('faq_ask_admin_advice', _faq_ask_advice_default('admin'))) . '</p>';
    }
  }
  else {

    // Edit own.
    $result = db_query(db_rewrite_sql("SELECT n.title, n.nid, tn.tid FROM {node} n JOIN {term_node} tn USING (nid) WHERE n.type='faq' AND n.status=0 AND n.uid=" . $user->uid . " ORDER BY " . $order));
    if ($limit < 1000) {
      $extra_msg = '<p class="faq_ask_expert_advice">' . filter_xss_admin(variable_get('faq_ask_asker_advice', _faq_ask_advice_default('asker'))) . '</p>';
    }
  }

  // Get unpublished nodes that are type='faq'.
  $items = array();
  $i = 0;
  $prev_cat = -1;
  $voc_list = variable_get('faq_ask_vocabularies', array());

  // Issue #1479352 by stenjo: Compact display of unanswered questions
  // Issue #1559296 by stenjo: Display compact unanswered list threshold does not work
  global $db_type;
  $count = 1;
  if (is_object($result)) {

    // If this is an object
    $count = $result->num_rows;
  }
  else {
    if ($db_type == 'mysql') {
      $count = mysql_num_rows($result);
    }
    else {
      if ($db_type == 'pgsql') {

        // Issue #1559296 by stenjo: Display compact unanswered list threshold does not work
        $count = pg_num_rows($result);
      }
    }
  }

  // Make this postgresql safe!!!
  $compact_limit = variable_get('faq_ask_compact_limit', '');

  // What is the limit for compact display?
  $compact = $count > $compact_limit && $compact_limit > 0 ? TRUE : FALSE;

  // If in a block
  if ($limit < 1000) {
    $node = NULL;
    while ($i < $limit && ($node = db_fetch_array($result))) {
      $tid = $node['tid'];
      if ($tid) {

        // We need to skip terms that are not in our vocabularies.
        $term = taxonomy_get_term($tid);
        if (!in_array($term->vid, $voc_list)) {
          --$i;
          continue;
        }
      }
      $token = _faq_ask_get_token('faq_ask/answer/' . $node['nid']);
      $options = array(
        'query' => "token={$token}",
      );
      if ($mode == 'edit') {
        $options['query'] .= '&ask=TRUE';
      }
      $items[] = l($node['title'], "faq_ask/{$mode}/" . $node['nid'], $options);
      $i++;
    }
    if (db_fetch_array($result)) {
      $i++;
    }
  }
  else {

    // We're on a page
    while ($node = db_fetch_array($result)) {
      ++$i;

      // If we've reached the limit, then quit.
      if ($i > $limit) {
        break;
      }

      //Issue #1479362 by stenjo: Indication of asker notification
      $notify = _faq_ask_get_faq_notification_email($node['nid']) ? variable_get('faq_ask_asker_notify_indication', ' [n]') : '';
      $tid = $node['tid'];
      if ($tid) {

        // We need to skip terms that are not in our vocabularies.
        $term = taxonomy_get_term($tid);
        if (!in_array($term->vid, $voc_list)) {
          --$i;
          continue;
        }
      }
      $token = _faq_ask_get_token('faq_ask/answer/' . $node['nid']);
      $options = array(
        'query' => "token={$token}",
      );
      if ($mode == 'edit') {
        $options['query'] .= '&ask=TRUE';
      }
      if ($prev_cat == $tid) {
        $edit_link = l($node['title'], "faq_ask/{$mode}/" . $node['nid'], $options);

        // Issue #1468970 by jlea9378: Access Denied error when picking up unanswered question
        $node_html = node_view(node_load($node['nid']));

        // Modify link in node title to point to the answer page
        $node_html = str_replace(url('node/' . $node['nid']), url("faq_ask/{$mode}/" . $node['nid'], $options), $node_html);

        //Issue #1479362 by stenjo: Indication of asker notification
        $node_html = str_replace($node['title'], $node['title'] . $notify, $node_html);
        if ($compact) {
          $items[] = l($node['title'] . $notify, "faq_ask/{$mode}/" . $node['nid'], $options);
        }
        else {
          $items[] = $node_html;
        }
      }
      else {
        if (count($items) > 0) {

          // If this is first item under a new term
          $output .= theme('item_list', $items);

          // Output what's created so far
          $items = array();

          // Clear list of items
        }
        if ($tid) {

          // If term exists
          $term = taxonomy_get_term($tid);
          $output .= '<br/><h3>' . check_plain(faq_tt("taxonomy:term:{$term->tid}:name", $term->name)) . '</h3>';
        }
        else {

          // Else this is not categorized at all
          $output .= '<br/><h3>' . t('Uncategorized') . '</h3>';
        }
        $prev_cat = $tid;
        $edit_link = l($node['title'], "faq_ask/{$mode}/" . $node['nid'], $options);
        $node_html = node_view(node_load($node['nid']));
        $node_html = str_replace(url('node/' . $node['nid']), url("faq_ask/{$mode}/" . $node['nid'], $options), $node_html);

        //Issue #1479362 by stenjo: Indication of asker notification
        $node_html = str_replace($node['title'], $node['title'] . $notify, $node_html);
        if ($compact) {
          $items[] = l($node['title'] . $notify, "faq_ask/{$mode}/" . $node['nid'], $options);
        }
        else {
          $items[] = $node_html;
        }
      }
    }
  }
  if ($i) {
    $output .= ($limit < 1000 ? $extra_msg : NULL) . theme('item_list', $items) . ($i > $limit ? l(t('more...'), 'faq_ask/unanswered', array(
      'class' => 'faq_ask_more_link',
    )) : NULL);
  }
  elseif ($limit > 1000) {
    $output .= '<p>' . t('Currently there are no unanswered questions for you to view.') . '</p>';
  }
  return $output;
}

/**
 *  This function lists all the unanswered questions the user is allowed to see.
 *  It is used by the "more..." link from the block, but can also be called independently.
 */
function faq_ask_list_more() {
  drupal_set_title(t('All Unanswered Questions'));
  $output = '<br/>';
  return _faq_ask_list_unanswered(9999999);
}

/**
* Identical to drupal_get_token() but without the session variable and the salt
*
* @param string $value
*/
function _faq_ask_get_token($value = '') {
  $private_key = drupal_get_private_key();
  return md5($value . $private_key);
}
function _faq_ask_valid_token($token, $value = '', $skip_anonymous = FALSE) {
  global $user;
  return $skip_anonymous && $user->uid == 0 || $token == _faq_ask_get_token($value);
}

Functions

Namesort descending Description
faq_ask_access Implementation of hook_access().
faq_ask_answer This function is called when an expert selects a question to answer. It changes the status option to "published" then goes to the regular FAQ edit function.
faq_ask_a_question_blockform Block "Ask a Question" form implementation This implements the form displayed in a block where the user may ask a question
faq_ask_block Implementation of hook_block().
faq_ask_cron Implementation of hook_cron().
faq_ask_edit Get the edit question form.
faq_ask_form_alter Implementation of hook_form_alter(). This is how we build the "ask question" form.
faq_ask_form_validate Validation form for the FAQ Ask form Thanks to http://hokuten.net/2010/drupal-creating-an-e-mail-subscription-block/
faq_ask_help Display help and module information
faq_ask_list_more This function lists all the unanswered questions the user is allowed to see. It is used by the "more..." link from the block, but can also be called independently.
faq_ask_mail Implementation of hook_mail(). This function completes the email, allowing for placeholder substitution. @TODO: notify_asker. @TODO: define messages & subjects on settings page, with list of tokens. how to handle newlines?
faq_ask_menu Implementation of hook_menu().
faq_ask_nodeapi Implementation of hook_nodeapi().
faq_ask_notify_expert Send an expert notification
faq_ask_page Get the ask question form.
faq_ask_perm Implementation of hook_perm(). Define the permissions this module uses
faq_ask_reassign Reassign the node to the current user and display a message.
faq_ask_settings_form Implementation of hook_form(). This form allows the users to select the expert roles and to which categories the users in those roles are assigned. Note, the expert/category table attempts to use the least horizontal space, so it can "flip"…
faq_ask_settings_form_submit Implementation of hook_form_submit(). It saves the expert roles that were selected, then rebuilds the expert/category table.
faq_ask_submit
faq_ask_taxonomy Implementation of hook_taxonomy().
faq_ask_user_access_or Determines whether the current user has one of the given permissions.
_faq_ask_advice_default Special function to get the advice text default.
_faq_ask_delete_expert Helper function to delete experts. @param. $tid - the taxonomy term id for the experts.
_faq_ask_delete_faq_notification Delete the notification belonging to a node
_faq_ask_delete_vocabulary Helper function to delete a vocabulary. @param. $vid - the taxonomy vocabulary id. $edit - the array provided in hook_taxonomy. $my_vocs - the array of faq_ask vocabularies.
_faq_ask_get_faq_notifications Helper function fetching all notifications
_faq_ask_get_faq_notification_email Helper function to fetch an email for notification assigned to an faq node
_faq_ask_get_token Identical to drupal_get_token() but without the session variable and the salt
_faq_ask_help_default Special function to get the help text default.
_faq_ask_list_unanswered This is the code to select the Unanswered Questions for the block. It is also used by the "unanswered" page by setting a very high limit.
_faq_ask_set_faq_notification Set a notification with an e-mail address attached to a node
_faq_ask_valid_token