Skip to content
fivestar.module 55.2 KiB
Newer Older

/**
 * @file
 * A simple n-star voting widget, usable in other forms.
 */

include_once dirname(__FILE__) . '/includes/fivestar.field.inc';

/**
 * Implementation of hook_help().
 */
      $output = t('This page is used to configure site-wide features of the Fivestar module. To setup Fivestar to rate content:');
        t('Configure site-wide settings for Fivestar below.'),
        t('Go to <a href="!types">admin/structure/types</a> and edit the type you would like to rate.', array('!types' => url('admin/structure/types'))),
        t('On the settings page for the content type, a set of options is available for fivestar, where you can enable rating for that type and set rating options.'),
      $output .= theme('item_list', array('items' => $steps, 'type' => 'ol'));
    case 'admin/content/node-type/'. $arg[3] .'/fivestar':
      $arg[5] = 'vote';
    case 'admin/content/node-type/'. $arg[3] .'/fivestar/'. $arg[5]:
      $output = t('Use the settings on this page to set up Fivestar rating for the %type content type. These settings specifically affect the %axis rating axis. If needing to set up different criteria for voting, see the main <a href="@url">Fivestar settings page</a>.', array('%type' => node_get_types('name', $arg[3]), '%axis' => $arg[5], '@url' => url('admin/config/content/fivestar')));
 * Implementation of hook_menu().
  $items['admin/config/content/fivestar'] = array(
    'description'       => 'Configure site-wide widgets used for Fivestar rating.',
    'page callback'     => 'drupal_get_form',
    'page arguments'    => array('fivestar_settings'),
    'access callback'   => 'user_access',
    'access arguments'  => array('administer site configuration'),
    'type'              => MENU_NORMAL_ITEM,
    'file'              => 'includes/fivestar.admin.inc',
  );
  $items['fivestar/preview/node'] = array(
    'page callback'     => 'fivestar_preview',
    'access callback'   => 'user_access',
    'access arguments'  => array('administer content types'),
    'type'              => MENU_CALLBACK,
    'file'              => 'includes/fivestar.admin.inc',
  );
  $items['fivestar/preview/color'] = array(
    'page callback'     => 'fivestar_preview_color',
    'access callback'   => 'user_access',
    'access arguments'  => array('administer site configuration'),
    'type'              => MENU_CALLBACK,
    'file'              => 'includes/fivestar.color.inc',
  );
  $items['fivestar/vote'] = array(
    'page callback'     => 'fivestar_vote',
    'access callback'   => 'user_access',
    'access arguments'  => array('rate content'),
    'type'              => MENU_CALLBACK,
  );

  // Add a "fivestar" tab to each content type.
  // We can't yet add it to the "operations" column in content types, due to a TODO in CCK
  // (content.admin.inc line 32)

  $items['admin/structure/types/manage/%node_type/fivestar'] = array(
    'title' => 'Fivestar voting',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('fivestar_node_type_tag_form', 4),
    'access arguments' => array('administer content types'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 5,
    'file' => 'includes/fivestar.admin.inc',
  );
  foreach (fivestar_get_tags() as $tag) {
    $items['admin/structure/types/manage/%node_type/fivestar/' . urlencode($tag)] = array(
      'title' => $tag,
      'page callback' => 'drupal_get_form',
      'page arguments' => array('fivestar_node_type_tag_form', 4, $tag),
      'access arguments' => array('administer content types'),
      'type' => $tag == 'vote' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
      'weight' => $tag == 'vote' ? 0 : 1,
      'file' => 'includes/fivestar.admin.inc',
/**
 * Implementation of hook_init().
 * These includes do not need to be loaded for cached pages.
 */
function fivestar_init() {
  module_load_include('inc', 'fivestar', 'includes/fivestar.field');

  // Add necessary CSS and JS.
  // TODO: These shouldn't be loaded on every page, but block caching omits
  // CSS and JS files that would be otherwise added.
  fivestar_add_js();
  fivestar_add_css();
 * Exposes permissions for rating content, viewing aggregate ratings, and using PHP
 * snippets when configuring fivestar CCK fields.
 */
function fivestar_permission() {
  return array(
    'rate content' => array(
      'title' => t('rate content'),
      'description' => 'TODO: write description for rate content',
    ),
    'use PHP for fivestar target' => array(
      'title' => t('use PHP for fivestar target'),
      'description' => 'TODO: write description for use PHP for fivestar target',
    ),
  );
/**
 * Implementation of hook_theme().
 */
function fivestar_theme() {
  return array(
    // Fivestar theme functions.
    'fivestar' => array(
      'variables' => array('rating' => NULL, 'stars' => 5, 'tag' => 'vote'),
      'variables' => array('star_display' => NULL, 'title' => NULL, 'description' => NULL),
      'variables' => array('user_rating' => NULL, 'average_rating' => NULL, 'votes' => 0, 'stars' => 5, 'feedback_enable' => TRUE),
      'variables' => array('style' => NULL, 'text' => NULL, 'stars' => NULL, 'unvote' => NULL, 'title' => NULL, 'feedback_enable' => TRUE, 'labels_enable' => TRUE, 'labels' => array()),
      'file' => 'includes/fivestar.admin.inc',
    'fivestar_preview_widget' => array(
      'file' => 'includes/fivestar.admin.inc',
      'variables' => array('content' => NULL, 'type' => 'direct'),
      'file' => 'includes/fivestar.admin.inc',
      'file' => 'includes/fivestar.admin.inc',
    'fivestar_node_type_tag_form' => array(
      'file' => 'includes/fivestar.admin.inc',
    'fivestar_color_form' => array(
    'fivestar_formatter_default' => array(
      'file' => 'includes/fivestar.field.inc',
    ),
    'fivestar_formatter_rating' => array(
      'file' => 'includes/fivestar.field.inc',
    ),
    'fivestar_formatter_percentage' => array(
      'file' => 'includes/fivestar.field.inc',
  $type = $info->type;

  // Be responsible and cleanup unneeded variables.
  foreach (_fivestar_variables() as $variable) {
    foreach (fivestar_get_tags() as $tag) {
      $suffix = fivestar_get_suffix($type, $tag);
      variable_del($variable . '_' . $suffix);
}

/**
 * Implementation of hook_node_type_update().
 */
function fivestar_node_type_update($info) {
  // When changing the type name, update the variables.
  if (!empty($info->old_type) && $info->old_type != $info->type) {
    foreach (_fivestar_variables() as $variable) {
      foreach (fivestar_get_tags() as $tag) {
        $oldvarname = $variable . '_' . fivestar_get_suffix($info->old_type, $tag);
        $newvarname = $variable . '_' . fivestar_get_suffix($info->type, $tag);
        $value = variable_get($oldvarname, -1);
        if ($value != -1) {
          variable_del($oldvarname);
          variable_set($newvarname, $value);
        }
function _fivestar_variables() {
  return array('fivestar', 'fivestar_unvote', 'fivestar_style', 'fivestar_stars', 'fivestar_comment', 'fivestar_position', 'fivestar_position_teaser', 'fivestar_labels_enable', 'fivestar_labels', 'fivestar_text', 'fivestar_title', 'fivestar_feedback');
}

 * Callback function for fivestar/vote.
 *
 * @param type
 *   A content-type to log the vote to. 'node' is the most common.
 * @param cid
 *   A content id to log the vote to. This would be a node ID, a comment ID, etc.
 * @param tag
 *   Multi-axis tag to allow multiple votes per node. 'vote' is the most common.
 * @param value
 *   A value from 1-100, representing the vote cast for the content.
 * @return
 *  An XML chunk containing the results of the vote, for use by the client-side
 *  javascript code.
 */
function fivestar_vote($type, $cid, $tag, $value) {
  drupal_add_http_header("Content-Type", "text/xml");
  $output = '';
  $output .= '<?xml version="1.0" encoding="UTF-8"?>';
  // Rebuild the #auto_submit_path that was used as the token seed.
  $path = preg_replace('/\/'. $value .'$/', '', $_GET['q']);
  if (!isset($_GET['token']) || !fivestar_check_token($_GET['token'], $path)) {
    $output .= '<xml><error>'. t('Invalid token') .'</error></xml>';
  }
  $result = _fivestar_cast_vote($type, $cid, $value, $tag, NULL, TRUE);
  votingapi_recalculate_results($type, $cid);
  if ($type == 'node') {
    $node = node_load($cid);
  }
  $suffix = fivestar_get_suffix((!isset($node) ? 'default' : $node->type), $tag);
  $stars = variable_get('fivestar_stars_' . $suffix, 5);
  $feedback_enable = variable_get('fivestar_feedback_' . $suffix, 1);

  $output .= '<xml><result>';

  if (count($result)) {
    foreach ($result as $data) {
      if (isset($data['tag']) && $data['tag'] == $tag) {
        $output .= '<'. $data['function'] .'>'. $data['value'] .'</'. $data['function'] .'>';
        $summary[$data['tag']][$data['function']] = $data['value'];
  $arguments = array(
    'user_rating' => $value,
    'average_rating' => $summary[$tag]['average'],
    'votes' => $summary[$tag]['count'],
    'stars' => $stars,
    'feedback_enable' => $feedback_enable,
  );
  $skip_map = array(
    'average' => array('user_rating', 'votes'),
    'average_count' => array('user_rating'),
    'user' => array('average_rating', 'votes'),
    'user_count' => array('average_rating'),
    'combo' => array(),
    'count' => array('user_rating', 'average_rating'),
  );
  $output .= '<summary>';
    $output .= "<$tag><![CDATA[". theme('fivestar_summary', array_diff_key($arguments, array_flip($skip))) . "]]></$tag>";
  $output .= '</summary>';
  $output .= '</result>';

  $output .= '<vote>';
  $output .= '<value>'. $value .'</value>';
  $output .= '<type>'. $type .'</type>';
  $output .= '<id>'. $cid .'</id>';
  $output .= '<tag>'. $tag .'</tag>';
  $output .= '</vote></xml>';

  drupal_add_http_header("Content-Type", "text/xml");
  echo $output;
  drupal_exit();
/**
 * Internal function to handle vote casting, flood control, XSS, IP based
 * voting, etc...
 */
function _fivestar_cast_vote($entity_type, $id, $value, $tag = NULL, $uid = NULL, $result = FALSE, $skip_validation = FALSE) {
  $tag = empty($tag) ? 'vote' : $tag;
  $uid = empty($uid) ? $user->uid : $uid;
  // Bail out if the user's trying to vote on an invalid object.
  if (!$skip_validation && !fivestar_validate_target($entity_type, $id, $tag, $uid)) {
  if (is_numeric($id) && is_numeric($value)) {
    $criteria = array('entity_type' => $entity_type, 'entity_id' => $id, 'tag' => $tag, 'uid' => $uid);
    // Get the unique identifier for the user (IP Address if anonymous).
    $user_criteria = votingapi_current_user_identifier();
    $user_votes = votingapi_select_votes($criteria + $user_criteria);
      $votes = $criteria += array('value' => $value);
      votingapi_set_votes($votes, $user_votes);
    return fivestar_get_votes($entity_type, $id, $tag, $uid);
 * Utility function to retrieve VotingAPI votes.
 * Note that this should not be used for general vote retrieval, instead the
 * VotingAPI function votingapi_select_results() should be used, which is more
 * efficient when retrieving multiple votes.
 *
 * @param $entity_type
 *   The Entity type for which to retrieve votes.
 * @param $id
 *   The ID for which to retrieve votes.
 *   The VotingAPI tag for which to retrieve votes.
 *   Optional. A user ID for which to retrieve votes.
 * @return
 *   An array of the following keys:
 *   - average: An array of VotingAPI results, including the average 'value'.
 *   - count: An array of VotingAPI results, including the count 'value'.
 *   - user: An array of VotingAPI results, including the user's vote 'value'.
 */
function fivestar_get_votes($entity_type, $id, $tag = 'vote', $uid = NULL) {
    'entity_type' => $entity_type,
    'entity_id' => $id,
    'value_type' => 'percent',
    'tag' => $tag,
  );

  $votes = array(
    'average' => array(),
    'count' => array(),
    'user' => array(),
  );

  $results = votingapi_select_results($criteria);
  foreach ($results as $result) {
    if ($result['function'] == 'average') {
      $votes['average'] = $result;
    }
    if ($result['function'] == 'count') {
      $votes['count'] = $result;
    }
  }
  if ($uid) {
    $user_vote = votingapi_select_votes($criteria += array('uid' => $uid));
    if ($user_vote) {
      $votes['user'] = $user_vote[0];
      $votes['user']['function'] = 'user';
    }
  }
  else {
    // If the user is anonymous, we never bother loading their existing votes.
    // Not only would it be hit-or-miss, it would break page caching. Safer to always
    // show the 'fresh' version to anon users.
    $votes['user'] = array('value' => 0);
  }

  return $votes;
}

 * Check that an item being voted upon is a valid vote.
 *
 * @param $entity_type
 *   Type of target (currently only node is supported).
 * @param $id
 *   Identifier within the type (in this case nid).
 * @param $tag
 *   The VotingAPI tag string.
 * @param $uid
 *   The user trying to cast the vote.
 *
function fivestar_validate_target($entity_type, $id, $tag, $uid = NULL) {
    $uid = $GLOBALS['user']->uid;
  }

  $access = module_invoke_all('fivestar_access', $entity_type, $id, $tag, $uid);
  foreach ($access as $result) {
    if ($result == TRUE) {
      return TRUE;
    }
    if ($result === FALSE) {
/**
 * Implementation of hook_fivestar_access().
 *
 * This hook is called before every vote is cast through Fivestar. It allows
 * modules to allow voting on any type of content, such as nodes, users, or
 * comments, even though only nodes are supported by Fivestar directly.
 *
 * @param $entity_type
 *   Type of target (currently only node is supported).
 * @param $id
 *   Identifier within the type (in this case nid).
 * @param $tag
 *   The VotingAPI tag string.
 * @param $uid
 *   The user ID trying to cast the vote.
 *
 * @return boolean or NULL
 *   Returns TRUE if voting is supported on this object.
 *   Returns NULL if voting is not supported on this object by this module.
 *   If needing to absolutely deny all voting on this object, regardless
 *   of permissions defined in other modules, return FALSE. Note if all
 *   modules return NULL, stating no preference, then access will be denied.
 */
function fivestar_fivestar_access($entity_type, $id, $tag, $uid) {
  if ($entity_type == 'node' && $node = node_load($id)) {
    if (variable_get('fivestar_'. fivestar_get_suffix($node->type, $tag), 0)) {
 * Implementation of hook_fivestar_widgets().
 *
 * This hook allows other modules to create additional custom widgets for
 * the fivestar module.
 * @return array
 *   An array of key => value pairs suitable for inclusion as the #options in a
 *   select or radios form element. Each key must be the location of a css
 *   file for a fivestar widget. Each value should be the name of the widget.
 */
function fivestar_fivestar_widgets() {
  $widgets_directory = drupal_get_path('module', 'fivestar') .'/widgets';
  $files = file_scan_directory($widgets_directory, '/\.css$/');

  $widgets = array();
  foreach ($files as $file) {
    if (strpos($file->filename, '-rtl.css') === FALSE) {
      $widgets[$file->uri] = drupal_ucfirst(str_replace('-color', '', $file->name));
function fivestar_entity_view($entity, $type, $view_mode, $langcode) {
  $exclude_modes = array(
    'preview',
    'search_index',
    'search_result',
    'rss',
  );
  if ($type == 'node') {
    if (!in_array($view_mode, $exclude_modes) && !isset($entity->modr8_form_teaser) && variable_get('fivestar_'. $entity->type, 0)) {
      if ($view_mode == 'teaser') {
        $position = variable_get('fivestar_position_teaser_'. $entity->type, 'above');
      }
      else {
        $position = variable_get('fivestar_position_'. $entity->type, 'above');
      }
      switch ($position) {
        case 'above':
        case 'below':
          if (user_access('rate content')) {
            $content = '';
            foreach (fivestar_get_tags() as $tag) {
              if (fivestar_validate_target('node', $entity->nid, $tag)) {
                $entity->content['fivestar_widget_' . $tag]['form'] = fivestar_widget_form($entity, $tag);
                $entity->content['fivestar_widget_' . $tag]['#weight'] = ($position == 'above') ? -10 : 50;
              }
            }
            break;
          } // Fall through to static if not allowed to rate.
          $position .= '_static';
        case 'above_static':
        case 'below_static':
          $content = '';
          foreach (fivestar_get_tags() as $tag) {
            if (fivestar_validate_target('node', $entity->nid, $tag)) {
              $content .= fivestar_static('node', $entity->nid, $entity->type, $tag);
          if ($content) {
            $entity->content['fivestar_widget'] = array(
              '#markup' => $content,
              '#weight' => strpos($position, 'above') === 0 ? -10 : 50,
            );

          break;
        case 'link':
          $entity->content['links']['fivestar_widget'] = array(
            '#links' => array(
              'fivestar-rate' => array(
                'title' => t('Rate'),
                'href' => 'node/'. $entity->nid,
                'fragment' => 'fivestar-form-node-'. $entity->nid,
                'attributes' => array('title' => t('Rate this @type', array('@type' => _node_types_build()->types[$entity->type]->name))),
              ),
function fivestar_block_info() {
  $blocks[0]['info'] = t('Fivestar: Rate this node');
  return $blocks;
}
function fivestar_block_view($delta = 0) {
  if (user_access('access content') && user_access('rate content') && (arg(2) == '' || arg(2) == 'view') && ($node = menu_get_object())) {
    $block = array('subject' => t('Rate This'));
    foreach (fivestar_get_tags() as $tag) {
      if (fivestar_validate_target('node', $node->nid, $tag)) {
        $block['content'][$tag] = fivestar_widget_form($node, $tag);
function fivestar_widget_form($node, $tag = 'vote') {
  return drupal_get_form('fivestar_form_node_'. $node->nid .'_'. $tag, 'node', $node->nid, $tag);
 * Get a private token used to protect links from CSRF attacks.
function fivestar_get_token($value) {
  global $user;

  // Anonymous users don't get a session ID, which breaks page caching.
  $session_id = $user->uid ? session_id() : '';
  $private_key = drupal_get_private_key();
  return md5($session_id . $value . $private_key);
}

/**
 * Check to see if a token value matches the specified node.
 */
function fivestar_check_token($token, $value) {
  return fivestar_get_token($value) == $token;
}

/**
 * Implementation of hook_forms().
 *
 * This is necessary when multiple fivestar forms appear on the same page, each
 * requiring a separate form_id, but all using the same underlying callbacks.
function fivestar_forms($form_id, $args) {
  if (strpos($form_id, 'fivestar_form') !== FALSE) {
    if ($form_id == 'fivestar_form_'. $args[0] .'_'. $args[1] .'_'. $args[2]) {
      $forms[$form_id] = array('callback' => 'fivestar_form');
/**
 * Create the fivestar form for the current item.
 * Note that this is not an implementation of hook_form(). We should probably
 * change the function to reflect that.
 */
function fivestar_form($form, &$form_state, $content_type, $content_id, $tag) {

  if ($content_type == 'node') {
    if (is_numeric($content_id)) {
      $node = node_load($content_id);
    }
    else {
      return array();
    }
  }

  $suffix = fivestar_get_suffix($node->type, $tag);
  $star_display = variable_get('fivestar_style_' . $suffix, 'average');
  $text_display = variable_get('fivestar_text_' . $suffix, 'dual');
  if ($star_display == 'average' && ($text_display == 'average' || $text_display == 'none')) {
    // Save a query and don't retrieve the user vote unnecessarily.
    $votes = fivestar_get_votes($content_type, $content_id, $tag, 0);
  }
  else {
    $votes = fivestar_get_votes($content_type, $content_id, $tag);
  }
    'user' => isset($votes['user']['value']) ? $votes['user']['value'] : 0,
    'average' => isset($votes['average']['value']) ? $votes['average']['value'] : 0,
    'count' => isset($votes['count']['value']) ? $votes['count']['value'] : 0,
    'stars' => variable_get('fivestar_stars_' . $suffix, 5),
    'allow_clear' => variable_get('fivestar_unvote_' . $suffix, FALSE),
    'style' => $star_display,
    'text' => $text_display,
    'content_type' => $content_type,
    'content_id' => $content_id,
    'title' => variable_get('fivestar_title_' . $suffix, 1) ? NULL : FALSE,
    'feedback_enable' => variable_get('fivestar_feedback_' . $suffix, 1),
    'labels_enable' => variable_get('fivestar_labels_enable_' . $suffix, 1),
    'labels' => variable_get('fivestar_labels_' . $suffix, array()),
  return fivestar_custom_widget(array(), $form_state, $values, $settings);
/**
 * Retreive and print out a static display of stars for a piece of content.
 *
 * @param $content_type
 *   The type of content that will have its vote retreived. i.e. "node".
 * @param $content_id
 *   The ID of the content that will have its vote retreived.
 * @param $node_type
 *   Optional. If retreiving a node's rating, passing in the node type will
 *   prevent Fivestar from doing an additional query to find it.
 * @param $tag
 *   Optional. The voting tag that will be retreived. Defaults to "vote" if none
 *   is specified.
 */
function fivestar_static($content_type, $content_id, $node_type = NULL, $tag = 'vote') {
  $criteria = array(
    'content_type' => $content_type,
    'content_id' => $content_id,
    'value_type' => 'percent',
    'tag' => 'vote',
  );

  $votes = fivestar_get_votes($content_type, $content_id, $tag);

  if ($content_type == 'node') {
    // Content type should always be passed to avoid this node load.
    if (!isset($node_type)) {
      $node = node_load($content_id);
      $node_type = $node->type;
    }

    $settings = fivestar_get_settings($node_type, $tag);
    $stars = $settings['stars'];
    switch ($settings['star_display']) {
      case 'average':
      case 'dual':
        $star_value = (isset($votes['average']['value'])) ? $votes['average']['value']: '';
        $title = $settings['title_display'] ? t('Average') : NULL;
        $star_value = (isset($votes['user']['value'])) ? $votes['user']['value'] : '';
        $title = $settings['title_display'] ? t('Your rating') : NULL;
        $star_value = (isset($votes['user']['value'])) ? $votes['user']['value'] : $votes['average']['value'];
        $title = $settings['title_display'] ? $votes['user']['value'] ? t('Your rating') : t('Average') : NULL;
    if ($tag != 'vote') {
      $title .= ' (' . ucfirst($tag) . ')';
    }

    // Set all text values, then unset the unnecessary ones.
    $user_value = (isset($votes['user']['value'])) ? $votes['user']['value'] : NULL;
    $average_value = (isset($votes['average']['value'])) ? $votes['average']['value'] : NULL;
    $count_value = (isset($votes['count']['value'])) ? $votes['count']['value'] : NULL;
      case 'average':
        $user_value = NULL;
        break;
      case 'user':
        $average_value = NULL;
        break;
      case 'smart':
          $average_value = NULL;
        }
        else {
          $user_value = NULL;
        }
        break;
    }
  }
  // Possibly add other content types here (comment, user, etc).
  else {
    $stars = 5;
    $star_value = $votes['average']['value'];
    $user_value = $votes['user']['value'];
    $average_value = $votes['average']['value'];
    $count_value = $votes['count']['value'];
  $star_display = theme('fivestar_static', array(
    'rating' => $star_value,
    'stars' => $stars,
  ));
  $text_display = $settings['text_display'] == 'none' ? NULL : theme('fivestar_summary', array(
    'user_rating' => $user_value,
    'average_rating' => $average_value,
    'votes' => $count_value,
    'stars'=> $stars,
    'feedback_enable' =>FALSE,
  ));
  return theme('fivestar_static_element', array('star_display' => $star_display, 'title' => $title, 'description' => $text_display));
/**
 * Form builder; Build a custom Fivestar rating widget with arbitrary settings.
 *
 * This function is usually not called directly, instead call
 * drupal_get_form('fivestar_custom_widget', $values, $settings) when wanting
 * to display a widget.
 *
 * @param $form_state
 *   The form state provided by Form API.
 * @param $values
 *   An array of current vote values from 0 to 100, with the following array
 *   keys:
 *   - user: The user's current vote.
 *   - average: The average vote value.
 *   - count: The total number of votes so far on this content.
 * @param $settings
 *   An array of settings that configure the properties of the rating widget.
 *   Available keys for the settings include:
 *   - content_type: The type of content which will be voted upon.
 *   - content_id: The content ID which will be voted upon.
 *   - stars: The number of stars to display in this widget, from 2 to 10.
 *     Defaults to 5.
 *   - autosubmit: Whether the form should be submitted upon star selection.
 *     Defaults to TRUE.
 *   - allow_clear: Whether or not to show the "Clear current vote" icon when
 *     showing the widget. Defaults to FALSE.
 *   - required: Whether this field is required before the form can be
 *     submitted. Defaults to FALSE.
 *   - feedback_enable: Toggles the option to show the "Vote is being saved"
 *     text while a vote is being registered through AJAX. Defaults to TRUE.
 *   - labels_enable: Toggles the option to show the "Give it 2/5 stars" text
 *     while hovering over the stars with the mouse.
 *   - labels: An array of labels to be used. The number of labels should match
 *     the number of stars.
 *   - tag: The VotingAPI tag that will be registered by this widget. Defaults
 *     to "vote".
 */
function fivestar_custom_widget($form, &$form_state, $values, $settings) {
    '#attributes' => array('class' => array('fivestar-widget')),
    '#redirect' => FALSE,
    '#theme' => 'fivestar_widget',
  $form['#submit'][] = 'fivestar_form_submit';
  if (isset($settings['content_type'])) {
    $form['content_type'] = array(
      '#type' => 'hidden',
      '#value' => $settings['content_type'],
    );
  }

  if (isset($settings['content_id'])) {
    $form['content_id'] = array(
      '#type' => 'hidden',
      '#value' => $settings['content_id'],
    );
  }

  if (isset($settings['tag'])) {
    $form['tag'] = array(
      '#type' => 'hidden',
      '#value' => $settings['tag'],
    );
  }

  $form['vote'] = array(
    '#type' => 'fivestar',
    '#stars' => $settings['stars'],
    '#vote_count' => $values['count'],
    '#vote_average' => $values['average'],
    '#auto_submit' => isset($settings['autosubmit']) ? $settings['autosubmit'] : TRUE,
    '#auto_submit_path' => (!isset($settings['autosubmit']) || $settings['autosubmit']) ? 'fivestar/vote/'. $settings['content_type'] .'/'. $settings['content_id'] .'/' . $settings['tag'] : NULL,
    '#allow_clear' => $settings['allow_clear'],
    '#content_id' => isset($settings['content_id']) ? $settings['content_id'] : NULL,
    '#required' => isset($settings['required']) ? $settings['required'] : FALSE,
    '#feedback_enable' => isset($settings['feedback_enable']) ? $settings['feedback_enable'] : TRUE,
    '#labels_enable' => isset($settings['labels_enable']) ? $settings['labels_enable'] : TRUE,
    '#labels' => isset($settings['labels']) ? $settings['labels'] : NULL,
    '#tag' => isset($settings['tag']) ? $settings['tag'] : 'vote',
  $form['destination'] = array(
    '#type' => 'hidden',
    '#value' => $_GET['q'],
  );

  $form['fivestar_submit'] = array(
    '#value' => t('Rate'),
    '#attributes' => array('class' => array('fivestar-submit')),
  $form['vote']['#attributes']['class'] = isset($form['vote']['#attributes']['class']) ? $form['vote']['#attributes']['class'] : array();
  $settings['feedback_enable'] = isset($settings['feedback_enable']) ? $settings['feedback_enable'] : TRUE;
  switch ($settings['text']) {
    case 'user':
      $form['vote']['#description'] = theme('fivestar_summary', array(
        'user_rating' => $values['user'],
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
        'stars' => $settings['stars'],
        'feedback_enable' => $settings['feedback_enable'],
      ));
      $form['vote']['#attributes']['class'][] = 'fivestar-user-text';
      $form['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', array(
        'average_rating' => $values['average'],
        'votes' => $values['count'],
        'stars' => $settings['stars'],
        'feedback_enable' => $settings['feedback_enable'],
      ));
      $form['vote']['#attributes']['class'][] = 'fivestar-average-text';
      $form['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', array(
        'user_rating' => $values['user'],
        'average_rating' => $values['user'] ? NULL : $values['average'],
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
        'stars' => $settings['stars'],
        'feedback_enable' => $settings['feedback_enable'],
      ));
      $form['vote']['#attributes']['class'][] = 'fivestar-smart-text';
      $form['vote']['#attributes']['class'][] = $values['user'] ? 'fivestar-user-text' : 'fivestar-average-text';
      $form['vote']['#description'] = theme('fivestar_summary', array(
        'user_rating' => $values['user'],
        'average_rating' => $settings['style'] == 'dual' ? NULL : $values['average'],
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
        'stars' => $settings['stars'],
        'feedback_enable' => $settings['feedback_enable'],
      ));
      $form['vote']['#attributes']['class'][] = ' fivestar-combo-text';
      break;
  }

  switch ($settings['style']) {
    case 'average':
      $form['vote']['#title'] = t('Average');
      $form['vote']['#default_value'] = $values['average'];
      $form['vote']['#attributes']['class'][] = 'fivestar-average-stars';
      break;
    case 'user':
      $form['vote']['#title'] = t('Your rating');
      $form['vote']['#default_value'] = $values['user'];
      $form['vote']['#attributes']['class'][] = 'fivestar-user-stars';
      break;
    case 'smart':
      $form['vote']['#title'] = $values['user'] ? t('Your rating') : t('Average');
      $form['vote']['#default_value'] = $values['user'] ? $values['user'] : $values['average'];
      $form['vote']['#attributes']['class'][] = 'fivestar-smart-stars '. ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
      break;
    case 'dual':
      $form['vote']['#title'] = t('Your rating');
      $form['vote']['#default_value'] = $values['user'];
      $form['vote']['#attributes']['class'][] = 'fivestar-combo-stars';
      $form['#attributes']['class'][] = 'fivestar-combo-stars';
      $static_average = theme('fivestar_static', array(
        'rating' => $values['average'],
        'stars' => $settings['stars'],
        'tag' => $settings['tag'],
      ));
      if ($settings['text'] == 'none' && !$settings['labels_enable'] && !$settings['feedback_enable']) {
        $static_description = NULL;
      }
      elseif ($settings['text'] != 'none') {
        $static_description = theme('fivestar_summary', array(
          'averrage_rating' => $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0),
          'votes' => isset($values['count']) ? $values['count'] : 0,
          'stars' => $settings['stars'],
          'feedback_enable' => FALSE
        ));
      }
      else {
        $static_description = '&nbsp;';
      }
      $form['average'] = array(
        '#type' => 'markup',
        '#markup' => theme('fivestar_static_element', array(
          'star_display' => $static_average,
          'title' => $settings['title'] !== FALSE ? t('Average') : NULL,
          'description' => $static_description,
        )),
        '#weight' => -1,
      );
      break;
  }

  // Set an over-ridding title if passed in.
  // An empty title won't change the default, a string will set a new title,
  // and title === FALSE will unset the title entirely.
  if (isset($settings['title'])) {
    if ($settings['title'] !== FALSE) {
      $form['vote']['#title'] = $settings['title'];
    }
    else {
      unset($form['vote']['#title']);
      unset($form['average']['#title']);
    }
  }
  elseif ($settings['tag'] && $settings['tag'] != 'vote') {
    $form['vote']['#title'] .= ' (' . ucfirst($settings['tag']) . ')';
  }
/**
 * Submit handler for the above form (non-javascript version).
 */
function fivestar_form_submit($form, &$form_state) {
  if ($form_state['values']['form_id'] == 'fivestar_form_'. $form_state['values']['content_type'] .'_'. $form_state['values']['content_id'] . '_' . $form_state['values']['tag']) {
    _fivestar_cast_vote($form_state['values']['content_type'], $form_state['values']['content_id'], $form_state['values']['vote'], $form_state['values']['tag']);
    votingapi_recalculate_results($form_state['values']['content_type'], $form_state['values']['content_id']);

    // Set a message that the vote was received.
    if ($form_state['values']['vote'] === '0') {