You are here

commons_q_a.module in Drupal Commons 7.3

File

modules/commons/commons_q_a/commons_q_a.module
View source
<?php

/**
 * @file
 * Code for the Commons Q&A feature.
 */
include_once 'commons_q_a.features.inc';

/**
 * Implements hook_theme().
 */
function commons_q_a_theme() {
  return array(
    'rate_template_commons_thumbs_up_down' => array(
      'variables' => array(
        'links' => NULL,
        'results' => NULL,
        'mode' => NULL,
        'just_voted' => FALSE,
        'entity_type' => NULL,
        'entity_id' => NULL,
        'display_options' => NULL,
      ),
      'template' => 'commons-thumbs-up-down',
    ),
  );
}

/**
 * Implements hook_node_view().
 *
 * Provides an answer button to bring a user down to the answer form.
 */
function commons_q_a_node_view($node, $view_mode) {
  if ($node->type == 'question' && $view_mode == 'full') {

    // Remove add comment link.
    unset($node->content['links']['comment']);

    // Add the answer link below.
    if (commons_q_a_check_answer_access($node)) {
      $node->content['links']['answer'] = array(
        '#theme' => 'links__node__answer',
        '#links' => array(
          'answer-add' => array(
            'title' => t('Answer'),
            'attributes' => array(
              'title' => t('Answer this question'),
            ),
            'href' => 'node/' . $node->nid,
            'fragment' => 'answer',
          ),
        ),
      );
    }
    return $node;
  }
}

/**
 * Implements hook_form_FROM_ID_alter().
 */
function commons_q_a_form_commons_bw_partial_node_form_alter(&$form, &$form_state) {
  if (empty($form['#entity']) || $form['#entity']->type != 'question') {
    return;
  }
  $form['title']['#markup'] = t('Ask a question');
  $language = $form['body']['#language'];
  $form['body'][$language][0]['#title_display'] = 'invisible';
  $form['body'][$language][0]['#required'] = TRUE;
  $form['body'][$language][0]['#placeholder'] = t('Ask anything. Let the community answer openly.');
  $form['body'][$language][0]['#resizable'] = FALSE;

  // Set fields as hideable so the forms can be compacted.
  $form['body']['#attributes']['class'][] = 'trigger-field';
  foreach (array(
    'field_media',
    'field_image',
    'og_group_ref',
    'choice_wrapper',
    'actions',
  ) as $field) {
    if (isset($form[$field])) {
      $form[$field]['#attributes']['class'][] = 'hideable-field';
    }
  }
  $form['actions']['submit']['#value'] = t('Ask');
  $form['#pre_render'][] = 'commons_q_a_form_commons_bw_partial_node_form_after_build';
}

/**
 * After-build call-back.
 * See commons_q_a_form_commons_bw_partial_node_form_alter().
 */
function commons_q_a_form_commons_bw_partial_node_form_after_build($form) {
  $language = $form['body']['#language'];
  $form['body'][$language][0]['#pre_render'] = array();
  $form['body'][$language][0]['format']['#access'] = FALSE;
  $form['body'][$language][0]['value']['#rows'] = 3;
  return $form;
}

/**
 * Run an access check to see if the current user can create answer nodes based
 * off of the question node.
 */
function commons_q_a_check_answer_access($question) {
  $group_ref = array();
  if (!empty($question->og_group_ref[LANGUAGE_NONE])) {
    foreach ($question->og_group_ref[LANGUAGE_NONE] as $key => $value) {

      // Check to see the user has access to the group the question is in, only attach to those groups they have permission to post in.
      if (og_user_access('node', $value['target_id'], 'create answer content')) {
        $group_ref[] = $value['target_id'];
        return TRUE;
      }
    }
  }
  else {
    if (node_access('create', 'answer')) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Implements hook_views_pre_render().
 */
function commons_q_a_views_pre_render(&$view) {

  // Improve the browsing widget empty text when displayed outside of a group.
  // TODO: Enable og_context and check group context instead of looking for an
  // empty first argument.
  if (empty($view->args[0]) && $view->name == 'commons_bw_q_a') {
    $view->display_handler->handlers['empty']['area']->options['content'] = t('No questions have been created.');
  }
  if ($view->name == 'commons_question_answers' && !empty($view->args[0])) {

    // If the user has access to post into any of the groups associated
    // with the question, embed a simplified answer node form.
    global $user;
    if (user_is_anonymous()) {
      $account = drupal_anonymous_user();
    }
    else {
      $account = $user;
    }
    $question_nid = $view->args[0];
    $question = node_load($question_nid);
    $answer_access = commons_q_a_check_answer_access($question);

    // Check global user access before showing the answer form.
    if ($answer_access) {
      $view->empty['area']->options['content'] = t("This question hasn't been answered yet. You can be the first to answer it!");
      module_load_include('inc', 'node', 'node.pages');
      $types = node_type_get_types();
      $node = (object) array(
        'uid' => $account->uid,
        'name' => isset($account->name) ? $account->name : '',
        'type' => 'answer',
        'language' => LANGUAGE_NONE,
      );

      // Prepopulate the Related question field
      // with Entityreference Prepopulate, which looks strictly at $_GET.
      $_GET['field_related_question'] = $view->args[0];
      if (!empty($group_ref)) {
        $_GET['og_group_ref'] = implode(',', $group_ref);
      }
      $answer_form = drupal_get_form('answer_node_form', $node);
      $answer_form['header'] = array(
        '#markup' => '<h3 id="answer" name="answer">' . t('Add a new answer') . '</h3>',
        '#weight' => -10,
      );

      // Hide any vertical tabs that might be present.
      $answer_form['additional_settings']['#access'] = FALSE;

      // Hide the Related question field.
      $answer_form['field_related_question']['#access'] = FALSE;

      // Add the form to the attachment_after part of the view,
      $view->attachment_after .= drupal_render($answer_form);

      // We only need to add the form once if the user has access to
      // post questions into any of the groups associated with the parent.
      return;
    }
    elseif ($account->uid === 0) {
      $view->empty['area']->options['content'] = t("This question hasn't been answered yet. !create to be the first to answer it!", array(
        '!create' => l(t("Login or create an account"), 'user'),
      ));
      return;
    }
    else {
      $view->empty['area']->options['content'] = t("This question hasn't been answered yet.");
      return;
    }
  }
}

/**
 * Implements hook_module_implements_alter().
 * Set commons_q_a form alter to happen after node so the title doesn't get reset.
 */
function commons_q_a_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter') {
    $group = $implementations['commons_q_a'];
    unset($implementations['commons_q_a']);
    $implementations['commons_q_a'] = $group;
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function commons_q_a_form_node_form_alter(&$form, &$form_state, $form_id) {
  $node = $form['#node'];
  list(, , $bundle) = entity_extract_ids('node', $node);
  if ($bundle == 'question' && empty($node->nid)) {
    drupal_set_title(t('Ask a question'));
  }
}

/**
* Implements hook_form_alter().
*/
function commons_q_a_form_alter(&$form, &$form_state, $form_id) {

  // Unset the groups audience field. Answers programatically inherit
  // the group membership of their respective questions.
  if ($form_id == 'answer_node_form') {
    $form['og_group_ref']['#access'] = FALSE;
    $form['actions']['submit']['#submit'][] = 'commons_q_a_answer_submit';

    // Ensure that the answer node inherits group membership from the
    // parent question by preventing users from changing the audience through
    // the Trusted Contacts toggle when commons_trusted_contacts.module
    // is enabled.
    $form_state['hide_audience_toggle'] = TRUE;
  }
  if ($form_id == 'comment_node_question_form' || $form_id == 'comment_node_answer_form') {
    $form['container'] = array(
      '#type' => 'fieldset',
      '#title' => t('Add new comment'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 2,
    );
    $form['container']['author'] = $form['author'];
    $form['container']['comment_body'] = $form['comment_body'];
    $form['container']['actions'] = $form['actions'];
    unset($form['author']);
    unset($form['subject']);

    // We don't need a subject, they're pointless within comments.
    unset($form['comment_body']);
    unset($form['actions']);
  }
}

/**
 * Implements hook_field_attach_submit().
 */
function commons_q_a_field_attach_submit($entity_type, &$entity, $form, &$form_state) {

  // Questions should inherit the group membership of the related question.
  if ($entity_type == 'node' && $entity->type == 'answer' && empty($entity->nid)) {
    $question = node_load($entity->field_related_question[LANGUAGE_NONE][0]['target_id']);
    $entity->og_group_ref = $question->og_group_ref;
  }
}

/**
 * Submit handler for the submit button on the answer node form.
 */
function commons_q_a_answer_submit($form, &$form_state) {

  // Redirect the user back to the related question.
  $form_state['redirect'] = 'node/' . $form_state['values']['field_related_question'][LANGUAGE_NONE][0]['target_id'];
}

/**
 * Implements hook_commons_activity_streams_message_selection_alter().
 */
function commons_q_a_commons_activity_streams_message_selection_alter(&$message_type, $hook, $node) {

  // Provide a special message type that uses "User asked the question"
  // phrasing when a new question node is created.
  if ($hook == 'node_insert' && $node->type == 'question') {
    $message_type = 'commons_q_a_question_asked';
  }

  // Provide a special message type that uses "User answered the question"
  // phrasing when a new answer node is created.
  if ($hook == 'node_insert' && $node->type == 'answer') {
    $message_type = 'commons_q_a_question_answered';
  }
}

/**
 * Preprocess function for the commons_like template.
 */
function commons_q_a_preprocess_rate_template_commons_thumbs_up_down(&$variables) {
  extract($variables);
  $up_classes = 'rate-number-up-down-btn-up';
  $down_classes = 'rate-number-up-down-btn-down';
  if (isset($results['user_vote'])) {
    switch ($results['user_vote']) {
      case $links[0]['value']:
        $up_classes .= ' rate-voted';
        break;
      case $links[1]['value']:
        $down_classes .= ' rate-voted';
        break;
    }
  }
  $variables['up_button'] = theme('rate_button', array(
    'text' => $links[0]['text'],
    'href' => $links[0]['href'],
    'class' => $up_classes,
  ));
  $variables['down_button'] = theme('rate_button', array(
    'text' => $links[1]['text'],
    'href' => $links[1]['href'],
    'class' => $down_classes,
  ));
  if ($results['rating'] > 0) {
    $score = $results['rating'];
    $score_class = 'positive';
  }
  elseif ($results['rating'] < 0) {
    $score = $results['rating'];
    $score_class = 'negative';
  }
  else {
    $score = 0;
    $score_class = 'neutral';
  }
  $variables['score'] = $score;
  $variables['score_class'] = $score_class;
  $info = array();
  if ($mode == RATE_CLOSED) {
    $info[] = t('Voting is closed.');
  }
  if ($mode != RATE_COMPACT && $mode != RATE_COMPACT_DISABLED) {
    if (isset($results['user_vote'])) {
      $info[] = t('You voted \'@option\'.', array(
        '@option' => $results['user_vote'] == 1 ? $links[0]['text'] : $links[1]['text'],
      ));
    }
  }
  $variables['info'] = implode(' ', $info);
}

/**
 * Implements hook_rate_templates().
 */
function commons_q_a_rate_templates() {
  $templates = array();
  $templates['commons_thumbs_up_down'] = new stdClass();
  $templates['commons_thumbs_up_down']->value_type = 'points';
  $templates['commons_thumbs_up_down']->options = array(
    array(
      1,
      'up',
    ),
    array(
      -1,
      'down',
    ),
  );
  $templates['commons_thumbs_up_down']->theme = 'rate_template_commons_thumbs_up_down';
  $templates['commons_thumbs_up_down']->css = drupal_get_path('module', 'commons_q_a') . '/commons-thumbs-up-down.css';
  $templates['commons_thumbs_up_down']->customizable = FALSE;
  $templates['commons_thumbs_up_down']->translate = TRUE;
  $templates['commons_thumbs_up_down']->use_source_translation = TRUE;
  $templates['commons_thumbs_up_down']->template_title = t('Commons Thumbs up / down');
  return $templates;
}

/**
 * Implements hook_strongarm_alter().
 */
function commons_q_a_strongarm_alter(&$items) {

  // The Rate module stores all of its configuration data in a single variable.
  // Add a thumbs up/down-style 'Answer' rate widget by altering the rate
  // variable.
  if (!empty($items['rate_widgets']->value)) {
    $items['rate_widgets']->value[] = commons_q_a_rate_widget();
  }
}

/**
 * Return a Rate module widget configuration used for thumbs up/down
 * rating of answers.
 */
function commons_q_a_rate_widget() {
  return (object) array(
    'name' => 'commons_answer',
    'tag' => 'commons_thumbs_up_down',
    'title' => 'Answer',
    'node_types' => array(
      0 => 'answer',
    ),
    'comment_types' => array(),
    'options' => array(
      0 => array(
        0 => 1,
        1 => 'up',
      ),
      1 => array(
        0 => -1,
        1 => 'down',
      ),
    ),
    'template' => 'commons_thumbs_up_down',
    'node_display' => '2',
    'teaser_display' => FALSE,
    'comment_display' => '2',
    'node_display_mode' => '1',
    'teaser_display_mode' => '1',
    'comment_display_mode' => '1',
    'roles' => array(
      2 => '2',
      1 => 0,
    ),
    'allow_voting_by_author' => 1,
    'noperm_behaviour' => '3',
    'displayed' => '1',
    'displayed_just_voted' => '1',
    'description' => '',
    'description_in_compact' => TRUE,
    'delete_vote_on_second_click' => '1',
    'value_type' => 'points',
    'theme' => 'rate_template_commons_thumbs_up_down',
    'css' => 'profiles/commons/modules/contrib/commons_q_a/commons-thumbs-up-down.css',
    'translate' => TRUE,
    'use_source_translation' => TRUE,
  );
}

Functions

Namesort descending Description
commons_q_a_answer_submit Submit handler for the submit button on the answer node form.
commons_q_a_check_answer_access Run an access check to see if the current user can create answer nodes based off of the question node.
commons_q_a_commons_activity_streams_message_selection_alter Implements hook_commons_activity_streams_message_selection_alter().
commons_q_a_field_attach_submit Implements hook_field_attach_submit().
commons_q_a_form_alter Implements hook_form_alter().
commons_q_a_form_commons_bw_partial_node_form_after_build After-build call-back. See commons_q_a_form_commons_bw_partial_node_form_alter().
commons_q_a_form_commons_bw_partial_node_form_alter Implements hook_form_FROM_ID_alter().
commons_q_a_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter().
commons_q_a_module_implements_alter Implements hook_module_implements_alter(). Set commons_q_a form alter to happen after node so the title doesn't get reset.
commons_q_a_node_view Implements hook_node_view().
commons_q_a_preprocess_rate_template_commons_thumbs_up_down Preprocess function for the commons_like template.
commons_q_a_rate_templates Implements hook_rate_templates().
commons_q_a_rate_widget Return a Rate module widget configuration used for thumbs up/down rating of answers.
commons_q_a_strongarm_alter Implements hook_strongarm_alter().
commons_q_a_theme Implements hook_theme().
commons_q_a_views_pre_render Implements hook_views_pre_render().