You are here

answers.module in Answers 7.4

The Answers module.

File

answers.module
View source
<?php

/**
 * @file
 * The Answers module.
 */
define('ANSWERS_TRANS_UCQUESTIONS', 'answers_trans_ucquestions');
define('ANSWERS_TRANS_LCQUESTIONS', 'answers_trans_lcquestions');
define('ANSWERS_TRANS_UCQUESTION', 'answers_trans_ucquestion');
define('ANSWERS_TRANS_LCQUESTION', 'answers_trans_lcquestion');
define('ANSWERS_TRANS_UCASKED', 'answers_trans_ucasked');
define('ANSWERS_TRANS_LCASKED', 'answers_trans_lcasked');
define('ANSWERS_TRANS_UCANSWERS', 'answers_trans_ucanswers');
define('ANSWERS_TRANS_LCANSWERS', 'answers_trans_lcanswers');
define('ANSWERS_TRANS_UCANSWER', 'answers_trans_ucanswer');
define('ANSWERS_TRANS_LCANSWER', 'answers_trans_lcanswer');
define('ANSWERS_TRANS_UCANSWERED', 'answers_trans_ucanswered');
define('ANSWERS_TRANS_LCANSWERED', 'answers_trans_lcanswered');
module_load_include('inc', 'answers', 'includes/answers.lock');

/**
 * Implements hook_menu().
 */
function answers_menu() {
  $items = array();
  $items['admin/config/content/answers'] = array(
    'title' => 'Answers',
    'description' => 'Configure how the question/answer service operates',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'answers_settings',
    ),
    'access arguments' => array(
      'administer content types',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/content/answers/settings'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'title' => 'Settings',
  );
  $items['admin/config/content/answers/orphan'] = array(
    'title' => 'Orphans',
    'description' => 'Report of answers without questions.',
    'page callback' => 'answers_orphan_report',
    'file' => 'includes/answers.display.inc',
    'access arguments' => array(
      'administer content types',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 15,
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function answers_menu_alter(&$items) {

  // Remove 'answers' from the 'add content' menu item in the 'navigation' menu.
  // Thanks to http://drupal.stackexchange.com/questions/17643/how-to-hide-a-content-type-on-the-node-add-page
  if (isset($items['node/add/answers-answer'])) {
    $items['node/add/answers-answer']['type'] = MENU_DEFAULT_LOCAL_TASK;
  }
}

/**
 * Implements hook_permission().
 */
function answers_permission() {
  return array(
    'manage answers content' => array(
      'title' => t('Manage !answers content', answers_translation()),
      'description' => t('Edit any !question or !answer content.', answers_translation()),
    ),
  );
}

/**
 * Returns the form definition for answers configuration page.
 */
function answers_settings() {
  $form = array();
  $form['additional_settings'] = array(
    '#type' => 'vertical_tabs',
    '#attached' => array(
      'js' => array(
        drupal_get_path('module', 'answers') . '/js/answers_admin.js',
      ),
    ),
  );
  $form['answers_question_lock_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Lock Settings'),
    '#weight' => -100,
    '#collapsible' => TRUE,
    '#group' => 'additional_settings',
  );
  $form['answers_question_lock_settings']['answers_question_lock_message'] = array(
    '#type' => 'textfield',
    '#title' => t('!Question lock message', answers_translation()),
    '#description' => t('Text to use to notify user that a !question is locked', answers_translation()),
    '#default_value' => variable_get('answers_question_lock_message', t('Note: This question is locked.')),
  );
  $form['renaming'] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#title' => t('Branding'),
    '#group' => 'additional_settings',
  );
  $form['renaming']['question'] = array(
    '#type' => 'fieldset',
    '#title' => t('Questions'),
    '#weight' => -1,
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['renaming']['question'][ANSWERS_TRANS_UCQUESTIONS] = array(
    '#type' => 'textfield',
    '#title' => t('Questions'),
    '#description' => t('Word to use in the interface for the upper case plural word !Questions', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCQUESTIONS, 'Questions'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['question'][ANSWERS_TRANS_LCQUESTIONS] = array(
    '#type' => 'textfield',
    '#title' => t('questions'),
    '#description' => t('Word to use in the interface for the lower case plural word !questions', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCQUESTIONS, 'questions'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['question'][ANSWERS_TRANS_UCQUESTION] = array(
    '#type' => 'textfield',
    '#title' => t('Question'),
    '#description' => t('Word to use in the interface for the upper case singular word !Question', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCQUESTION, 'Question'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['question'][ANSWERS_TRANS_LCQUESTION] = array(
    '#type' => 'textfield',
    '#title' => t('question'),
    '#description' => t('Word to use in the interface for the lower case singular word !question', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCQUESTION, 'question'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['question'][ANSWERS_TRANS_UCASKED] = array(
    '#type' => 'textfield',
    '#title' => t('Submitted', answers_translation()),
    '#description' => t('Word to use in the interface for the upper case word !Question_submitted', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCASKED, 'Asked'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['question'][ANSWERS_TRANS_LCASKED] = array(
    '#type' => 'textfield',
    '#title' => t('submitted', answers_translation()),
    '#description' => t('Word to use in the interface for the lower case word !question_submitted', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCASKED, 'asked'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'] = array(
    '#type' => 'fieldset',
    '#title' => t('Answers'),
    '#weight' => -1,
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_UCANSWERS] = array(
    '#type' => 'textfield',
    '#title' => t('Answers'),
    '#description' => t('Word to use in the interface for the upper case plural word !Answers', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCANSWERS, 'Answers'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_LCANSWERS] = array(
    '#type' => 'textfield',
    '#title' => t('answers'),
    '#description' => t('Word to use in the interface for the lower case plural word !answers', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCANSWERS, 'answers'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_UCANSWER] = array(
    '#type' => 'textfield',
    '#title' => t('Answer'),
    '#description' => t('Word to use in the interface for the upper case singular word !Answer', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCANSWER, 'Answer'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_LCANSWER] = array(
    '#type' => 'textfield',
    '#title' => t('answer'),
    '#description' => t('Word to use in the interface for the lower case singular word !answer', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCANSWER, 'answer'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_UCANSWERED] = array(
    '#type' => 'textfield',
    '#title' => t('Submitted', answers_translation()),
    '#description' => t('Word to use in the interface for the upper case word !Answer_submitted', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_UCANSWERED, 'Answered'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['renaming']['answer'][ANSWERS_TRANS_LCANSWERED] = array(
    '#type' => 'textfield',
    '#title' => t('submitted', answers_translation()),
    '#description' => t('Word to use in the interface for the lower case word !answer_submitted', answers_translation()),
    '#default_value' => variable_get(ANSWERS_TRANS_LCANSWERED, 'answered'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  return system_settings_form($form);
}

/**
 * Implements hook_init().
 *
 * Redirect to its related question when visiting an answer page, scrolling to
 * the answer.
 */
function answers_init() {
  if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '') {
    $node = node_load(arg(1));
    if ($node != '') {
      $node = entity_metadata_wrapper('node', $node);
      if ($node->type
        ->value() == 'answers_answer') {
        drupal_goto('node/' . $node->answers_related_question
          ->value()->nid, array(
          'fragment' => 'node-' . $node->nid
            ->value(),
          'alias' => TRUE,
        ));
      }
    }
  }
}

/**
 * Implements hook_node_info().
 */
function answers_node_info() {
  return array(
    'answers_question' => array(
      'name' => t('!Question', answers_translation()),
      'base' => 'answers',
      'description' => t('A !question which can be !answer_submitted by other users.', answers_translation()),
      'title_label' => t('Title'),
      'locked' => TRUE,
    ),
    'answers_answer' => array(
      'name' => t('!Answer', answers_translation()),
      'base' => 'answers',
      'description' => t('An !answer provided to !question !question_submitted by a member of the community.', answers_translation()),
      'has_title' => FALSE,
      'locked' => TRUE,
    ),
  );
}

/**
 * Implements hook_node_access().
 *
 * Grant users with 'manage answers content' the ability to perform any op on
 * any question or answer.
 */
function answers_node_access($node, $op, $account) {
  $type = is_string($node) ? $node : $node->type;
  if (($type == 'answers_answer' || $type == 'answers_question') && user_access('manage answers content', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Implements hook_form().
 */
function answers_form($node, $form_state) {
  return node_content_form($node, $form_state);
}

/**
 * Implements hook_form_alter().
 */
function answers_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'answers_answer_node_form') {

    // Disallow adding an answer that is not attached to a question.
    $node = $form['#node'];
    if (!isset($node->answers_related_question[LANGUAGE_NONE][0]['target_id'])) {
      drupal_set_message(t('You cannot post an !answer without a !question.', answers_translation()), 'error');
      drupal_not_found();
      exit;
    }

    // Disallow manually assigning an answer to a question.
    hide($form['answers_related_question']);
    $form['body'][LANGUAGE_NONE][0]['#title'] = '';
  }
  elseif ($form_id == 'answers_question_node_form') {

    // Disallow manually setting the question lock.
    hide($form['question_locks']);
  }
}

/**
 * Implements hook_node_view().
 *
 * Add the view for answers to a question.
 * Add the new answer form.
 * Add the operations links to an answer node.
 */
function answers_node_view($node, $view_mode = 'full') {
  module_load_include('inc', 'answers', 'includes/answers.lock');
  if ($node->type == 'answers_question') {
    if ($view_mode == 'full') {
      if (answers_question_locked_p($node)) {
        $node->content['lock_message'] = array(
          '#markup' => variable_get('answers_question_lock_message', ''),
          '#prefix' => '<div class="answers-question-locked-message">',
          '#suffix' => '</div>',
          '#weight' => -100,
        );
      }
      if (answers_create_answer_permission_p($node)) {
        global $user;

        // Create an empty placeholder for an answer node.
        $node_answer = array(
          'uid' => $user->uid,
          'name' => isset($user->name) ? $user->name : '',
          'type' => 'answers_answer',
          'language' => LANGUAGE_NONE,
        );

        // Set the node reference field of the answer to point to its question.
        $node_answer['answers_related_question'][LANGUAGE_NONE][0]['target_id'] = $node->nid;
        $form_state['build_info']['args'][] = (object) $node_answer;
        form_load_include($form_state, 'inc', 'node', 'node.pages');

        // Create a form to edit/save the placeholder answer.
        $answer_form = drupal_build_form('answers_answer_node_form', $form_state);

        // Add the form to the question page.
        $node->content['new_answer_form'] = $answer_form;
        $node->content['new_answer_form']['#weight'] = 150;
        $node->content['new_answer_form']['new_answer_form_title'] = array(
          '#theme' => 'html_tag',
          '#tag' => 'h2',
          '#attributes' => array(
            'class' => 'new-answer-form-title',
          ),
          '#value' => t('Your !Answer', answers_translation()),
          '#weight' => -100,
        );
      }
    }

    // Include the answers in the content when view_mode is
    // either 'search_index' or 'full'.
    if ($view_mode == 'search_index' || $view_mode == 'full') {
      $node->content['answers_list'] = array(
        '#type' => 'markup',
        '#markup' => views_embed_view('question_answers', 'default', $node->nid),
        '#weight' => 49,
      );
    }
  }
  if ($view_mode == 'full' && $node->type == 'answers_answer') {
    if (node_access('update', $node)) {
      $node->content['links']['#links']['answers-answer-edit'] = array(
        'title' => t('Edit'),
        'href' => 'node/' . $node->nid . '/edit',
      );
    }
    if (node_access('delete', $node)) {
      $node->content['links']['#links']['answers-answer-delete'] = array(
        'title' => t('Delete'),
        'href' => 'node/' . $node->nid . '/delete',
      );
    }
  }
}

/**
 * Checks permissions access.
 *
 * Return NULL if the current user does not have permission to create an answer
 * to the question.
 */
function answers_create_answer_permission_p($question) {
  module_load_include('inc', 'answers', 'includes/answers.lock');
  global $user;
  $locked = answers_question_locked_p($question);
  return node_access('create', 'answers_answer') && (!$locked || user_access('manage answers content', $user));
}

/**
 * Implements hook_node_delete().
 *
 * Delete answers of some question when this one is deleted. On cascade.
 *
 * TODO: This is going to be obsolete (hopefully) when the patch been worked in:
 * http://drupal.org/node/1368386 is commited to entityreference
 */
function answers_node_delete($node) {
  if ($node->type == 'answers_question') {
    $nids = db_query('SELECT entity_id FROM {field_data_answers_related_question} WHERE answers_related_question_target_id = :nid', array(
      ':nid' => $node->nid,
    ))
      ->fetchCol();
    node_delete_multiple($nids);
  }
}

/**
 * Return all questions.
 */
function answers_all_questions() {
  $query = new EntityFieldQuery();
  $entities = $query
    ->entityCondition('entity_type', 'node')
    ->entityCondition('bundle', 'answers_question')
    ->execute();
  return empty($entities) ? $entities : entity_load('node', array_keys($entities['node']));
}

/**
 * Return all answers to a question.
 *
 * @param object $question
 *   A fully loaded question node.
 */
function answers_question_answers($question) {
  $query = new EntityFieldQuery();
  $entities = $query
    ->entityCondition('entity_type', 'node')
    ->entityCondition('bundle', 'answers_answer')
    ->fieldCondition('answers_related_question', 'target_id', $question->nid, '=')
    ->execute();
  return empty($entities) ? array() : entity_load('node', array_keys($entities['node']));
}

/**
 * Return the question to which an answer responds.
 *
 * @param object $answer
 *   Either an answer node or an answer nid.
 */
function answers_answer_question($answer) {
  return entity_metadata_wrapper('node', $answer)->answers_related_question
    ->value();
}

/**
 * Implements hook_views_api().
 */
function answers_views_api() {
  return array(
    'api' => 3.0,
    'path' => drupal_get_path('module', 'answers'),
  );
}

/**
 * Implements hook_theme_registry_alter().
 *
 * See:
 * http://www.metachunk.com/blog/adding-module-path-drupal-7-theme-registry.
 */
function answers_theme_registry_alter(&$theme_registry) {
  $mod_path = drupal_get_path('module', 'answers');

  // Munge on a copy.
  $theme_registry_copy = $theme_registry;
  _theme_process_registry($theme_registry_copy, 'phptemplate', 'theme_engine', 'pow', $mod_path);
  $theme_registry += array_diff_key($theme_registry_copy, $theme_registry);
  $hooks = array(
    'node',
  );
  foreach ($hooks as $h) {
    _answers_insert_after_first_element($theme_registry[$h]['theme paths'], $mod_path);
  }
}

/**
 * Helper function for re-ordering arrays (needed by theme_registry_alter).
 */
function _answers_insert_after_first_element(&$a, $element) {
  if (is_array($a)) {
    $first_element = array_shift($a);
    array_unshift($a, $first_element, $element);
  }
}

/**
 * Implements hook_preprocess_node().
 */
function answers_preprocess_node(&$vars) {
  _answers_check_type_theming_suggestion($vars, 'node__answers');
}

/**
 * Implements hook_preprocess_comment().
 */
function answers_preprocess_comment(&$vars) {
  _answers_check_type_theming_suggestion($vars, 'comment__node_answers');
}

/**
 * Implements hook_preprocess_comment_wrapper().
 */
function answers_preprocess_comment_wrapper(&$vars) {
  _answers_check_type_theming_suggestion($vars, 'comment_wrapper__node_answers');
}

/**
 * Implements hook_views_data().
 */
function answers_views_data() {
  $data['views']['answers_count'] = array(
    'title' => t('!Answers Count', answers_translation()),
    'help' => t('Shows the number of answers to a question.'),
    'area' => array(
      'handler' => 'AnswersCountViewsHandler',
    ),
  );
  $data['answers']['table']['group'] = t('!Answers', answers_translation());
  $data['answers']['table']['join'] = array(
    '#global' => array(),
  );
  $data['answers']['new_answers'] = array(
    'title' => t('Has new content'),
    'field' => array(
      'handler' => 'AnswersNewContentViewsHandler',
      'help' => t('Show a marker if the !question is new or has new !answers since last access.', answers_translation()),
    ),
  );
  $data['answers']['new_answers_since_last_login'] = array(
    'title' => t('Has new content since last login'),
    'field' => array(
      'handler' => 'AnswersNewContentSinceLastLoginViewsHandler',
      'help' => t('Show a marker if the !question is new or has new !answers since last login.', answers_translation()),
    ),
  );
  return $data;
}

/**
 * Helper function for preprocess hooks.
 */
function _answers_check_type_theming_suggestion(&$vars, $theme_hook_suggestions) {
  if ($vars['node']->type == 'answers_answer' || $vars['node']->type == 'answers_question') {
    $vars['theme_hook_suggestions'][] = $theme_hook_suggestions;
  }
}

/**
 * Implements hook_views_post_render().
 */
function answers_views_post_render(&$view, &$output, &$cache) {
  if ($view->name == 'question_answers') {
    if (count($view->result)) {
      foreach ($view->result as $object) {
        node_tag_new($object);
      }
    }
  }
}

/**
 * Returns an array of common translation placeholders.
 */
function answers_translation() {
  static $trans;
  if (!isset($trans)) {
    $trans = array(
      '!Questions' => check_plain(variable_get(ANSWERS_TRANS_UCQUESTIONS, 'Questions')),
      '!questions' => check_plain(variable_get(ANSWERS_TRANS_LCQUESTIONS, 'questions')),
      '!Question' => check_plain(variable_get(ANSWERS_TRANS_UCQUESTION, 'Question')),
      '!question' => check_plain(variable_get(ANSWERS_TRANS_LCQUESTION, 'question')),
      '!Question_submitted' => check_plain(variable_get(ANSWERS_TRANS_UCASKED, 'Asked')),
      '!question_submitted' => check_plain(variable_get(ANSWERS_TRANS_LCASKED, 'asked')),
      '!Answers' => check_plain(variable_get(ANSWERS_TRANS_UCANSWERS, 'Answers')),
      '!answers' => check_plain(variable_get(ANSWERS_TRANS_LCANSWERS, 'answers')),
      '!Answer' => check_plain(variable_get(ANSWERS_TRANS_UCANSWER, 'Answer')),
      '!answer' => check_plain(variable_get(ANSWERS_TRANS_LCANSWER, 'answer')),
      '!Answer_submitted' => check_plain(variable_get(ANSWERS_TRANS_UCANSWERED, 'Answered')),
      '!answer_submitted' => check_plain(variable_get(ANSWERS_TRANS_LCANSWERED, 'answered')),
    );

    // Calling all modules implementing hook_answers_translation_alter():
    drupal_alter('answers_translation', $trans);
  }
  return $trans;
}

Functions

Namesort descending Description
answers_all_questions Return all questions.
answers_answer_question Return the question to which an answer responds.
answers_create_answer_permission_p Checks permissions access.
answers_form Implements hook_form().
answers_form_alter Implements hook_form_alter().
answers_init Implements hook_init().
answers_menu Implements hook_menu().
answers_menu_alter Implements hook_menu_alter().
answers_node_access Implements hook_node_access().
answers_node_delete Implements hook_node_delete().
answers_node_info Implements hook_node_info().
answers_node_view Implements hook_node_view().
answers_permission Implements hook_permission().
answers_preprocess_comment Implements hook_preprocess_comment().
answers_preprocess_comment_wrapper Implements hook_preprocess_comment_wrapper().
answers_preprocess_node Implements hook_preprocess_node().
answers_question_answers Return all answers to a question.
answers_settings Returns the form definition for answers configuration page.
answers_theme_registry_alter Implements hook_theme_registry_alter().
answers_translation Returns an array of common translation placeholders.
answers_views_api Implements hook_views_api().
answers_views_data Implements hook_views_data().
answers_views_post_render Implements hook_views_post_render().
_answers_check_type_theming_suggestion Helper function for preprocess hooks.
_answers_insert_after_first_element Helper function for re-ordering arrays (needed by theme_registry_alter).

Constants