admin/structure/types 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')); break; 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 Fivestar settings page.', array('%type' => node_get_types('name', $arg[3]), '%axis' => $arg[5], '@url' => url('admin/config/content/fivestar'))); break; } return $output; } /** * Implementation of hook_menu(). */ function fivestar_menu() { $items = array(); $items['admin/config/content/fivestar'] = array( 'title' => 'Fivestar', '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', ); } return $items; } /** * 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(); } /** * Implementation of hook_permission(). * * 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( 'render element' => 'element', ), 'fivestar_select' => array( 'render element' => 'element', ), 'fivestar_static' => array( 'variables' => array('rating' => NULL, 'stars' => 5, 'tag' => 'vote'), ), 'fivestar_static_element' => array( 'variables' => array('star_display' => NULL, 'title' => NULL, 'description' => NULL), ), 'fivestar_summary' => array( 'variables' => array('user_rating' => NULL, 'average_rating' => NULL, 'votes' => 0, 'stars' => 5, 'feedback_enable' => TRUE), ), 'fivestar_widget' => array( 'render element' => 'form', ), // fivestar.admin.inc. 'fivestar_preview' => array( '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( 'variables' => array('css_file' => NULL), 'file' => 'includes/fivestar.admin.inc', ), 'fivestar_preview_wrapper' => array( 'variables' => array('content' => NULL, 'type' => 'direct'), 'file' => 'includes/fivestar.admin.inc', ), 'fivestar_settings' => array( 'render element' => 'form', 'file' => 'includes/fivestar.admin.inc', ), 'fivestar_node_type_tag_form' => array( 'render element' => 'form', 'file' => 'includes/fivestar.admin.inc', ), // fivestar.color.inc. 'fivestar_color_form' => array( 'render element' => 'form', ), // fivestar.field.inc. 'fivestar_formatter_default' => array( 'render element' => 'element', ), 'fivestar_formatter_rating' => array( 'render element' => 'element', ), 'fivestar_formatter_percentage' => array( 'render element' => 'element', ), ); } /** * Implementation of hook_node_type_delete(). */ function fivestar_node_type_delete($info) { $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 .= ''; // 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 .= ''. t('Invalid token') .''; echo $output; drupal_exit(); } $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 .= ''; if (count($result)) { foreach ($result as $data) { if (isset($data['tag']) && $data['tag'] == $tag) { $output .= '<'. $data['function'] .'>'. $data['value'] .''; $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 .= ''; foreach ($skip_map as $tag => $skip) { $output .= "<$tag>![CDATA[". theme('fivestar_summary', array_diff_key($arguments, array_flip($skip))) . "]]>"; } $output .= ''; $output .= ''; $output .= ''; $output .= ''. $value .''; $output .= ''. $type .''; $output .= ''. $cid .''; $output .= ''. $tag .''; $output .= ''; 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($type, $cid, $value, $tag = NULL, $uid = NULL, $result = FALSE, $skip_validation = FALSE) { global $user; $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($type, $cid, $tag, $uid)) { return array(); } // Sanity-check the incoming values. if (is_numeric($cid) && is_numeric($value)) { if ($value > 100) { $value = 100; } // Get the user's current vote. $criteria = array('entity_type' => $type, 'entity_id' => $cid, '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); if ($value == 0) { votingapi_delete_votes($user_votes); } else { $votes = $criteria += array('value' => $value); votingapi_set_votes($votes, $user_votes); } return fivestar_get_votes($type, $cid, $tag, $uid); } } /** * Utility function to retreive VotingAPI votes. * * Note that this should not be used for general vote retreival, instead the * VotingAPI function votingapi_select_results() should be used, which is more * efficient when retrieving multiple votes. * * @param $type * The content type for which to retreive votes. * @param $cid * The content ID for which to retreive votes. * @param $tag * The VotingAPI tag for which to retreive votes. * @param $uid * Optional. A user ID for which to retreive 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($type, $cid, $tag = 'vote', $uid = NULL) { global $user; if (!isset($uid)) { $uid = $user->uid; } $criteria = array( 'entity_type' => $type, 'entity_id' => $cid, '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 $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. * * @return boolean */ function fivestar_validate_target($type, $id, $tag, $uid = NULL) { if (!isset($uid)) { $uid = $GLOBALS['user']->uid; } $access = module_invoke_all('fivestar_access', $type, $id, $tag, $uid); foreach ($access as $result) { if ($result == TRUE) { return TRUE; } if ($result === FALSE) { return 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 $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($type, $id, $tag, $uid) { if ($type == 'node' && $node = node_load($id)) { if (variable_get('fivestar_'. fivestar_get_suffix($node->type, $tag), 0)) { return TRUE; } } } /** * 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)); } } return $widgets; } /** * Implementation of hook_node_view(). * * Adds the fievestar widget to the node view. */ function fivestar_node_view($node, $view_mode) { $exclude_modes = array( 'preview', 'search_index', 'search_result', 'rss', ); if (!in_array($view_mode, $exclude_modes) && !isset($node->modr8_form_teaser) && variable_get('fivestar_'. $node->type, 0)) { if ($view_mode == 'teaser') { $position = variable_get('fivestar_position_teaser_'. $node->type, 'above'); } else { $position = variable_get('fivestar_position_'. $node->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', $node->nid, $tag)) { $node->content['fivestar_widget_' . $tag]['form'] = fivestar_widget_form($node, $tag); $node->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', $node->nid, $tag)) { $content .= fivestar_static('node', $node->nid, $node->type, $tag); } } if ($content) { $node->content['fivestar_widget'] = array( '#markup' => $content, '#weight' => strpos($position, 'above') === 0 ? -10 : 50, ); } break; case 'link': $node->content['links']['fivestar_widget'] = array( '#links' => array( 'fivestar-rate' => array( 'title' => t('Rate'), 'href' => 'node/'. $node->nid, 'fragment' => 'fivestar-form-node-'. $node->nid, 'attributes' => array('title' => t('Rate this @type', array('@type' => _node_types_build()->types[$node->type]->name))), ), ), ); break; } } } 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); } } return $block; } } 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'); return $forms; } } } /** * 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) { global $user; 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); } $values = array( '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, ); $settings = array( '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, 'tag' => 'vote', 'autosubmit' => TRUE, '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()), 'tag' => $tag, ); 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') { global $user; $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; break; case 'user': $star_value = (isset($votes['user']['value'])) ? $votes['user']['value'] : ''; $title = $settings['title_display'] ? t('Your rating') : NULL; break; case 'smart': $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; break; } 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['user']['value'] : NULL; $count_value = (isset($votes['count']['value'])) ? $votes['user']['value'] : NULL; switch ($settings['text_display']) { case 'average': $user_value = NULL; break; case 'user': $average_value = NULL; break; case 'smart': if ($votes['user']['value']) { $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) { $form = array( '#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( '#type' => 'submit', '#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'; break; case 'average': $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'; break; case 'smart': $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'; break; case 'dual': $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 = ' '; } $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']) . ')'; } return $form; } /** * 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']) { // Cast the vote. _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') { drupal_set_message(t('Your vote has been cleared.')); } elseif (is_numeric($form_state['values']['vote'])) { drupal_set_message(t('Thank you for your vote.')); } // Regenerate the page with a drupal_goto() to update the current values. drupal_goto(); } } /** * Implementation of hook_elements(). * * Defines 'fivestar' form element type */ function fivestar_element_info() { $type['fivestar'] = array( '#input' => TRUE, '#stars' => 5, '#widget' => 'stars', '#allow_clear' => FALSE, '#auto_submit' => FALSE, '#auto_submit_path' => '', '#labels_enable' => TRUE, '#feedback_enable' => TRUE, '#process' => array('fivestar_expand'), ); return $type; } /** * Theme the fivestar form element by adding necessary css and javascript. */ function theme_fivestar($variables) { $element = $variables['element']; if (empty($element['#description'])) { if ($element['#feedback_enable']) { $element['#description'] = '
 
'; } elseif ($element['#labels_enable']) { $element['#description'] = '
 
'; } } return theme('form_element', array('element' => $element)); } /** * Theme the straight HTML version of the fivestar select list. This is used * to remove the wrapping 'form-item' div from the select list. */ function theme_fivestar_select($variables) { $element = $variables['element']; element_set_attributes($element, array('id', 'name', 'size')); _form_set_class($element, array('form-select')); return '' . form_select_options($element) . ''; } /** * Theme an entire fivestar widget, including the submit button and the normal * fivestar widget themed in the theme_fivestar() function. */ function theme_fivestar_widget($variables) { $form = $variables['form']; // Only print out the summary if text is being displayed or using rollover text. if (empty($form['vote']['#description']) && strpos($form['vote']['#prefix'], 'fivestar-labels-hover') === FALSE) { unset($form['vote']['#description']); } $class = 'fivestar-form'; $class .= '-'. (isset($form['vote']['#tag']) ? $form['vote']['#tag'] : 'vote'); $class .= '-'. (isset($form['content_id']['#value']) ? $form['content_id']['#value'] : 0); $output = ''; $output .= '
'; $output .= drupal_render_children($form); $output .= '
'; return $output; } /** * Display a plain HTML view-only version of the widget with a specified rating. * * @param $rating * The desired rating to display out of 100 (i.e. 80 is 4 out of 5 stars). * @param $stars * The total number of stars this rating is out of. * @param $tag * Allows multiple ratings per node. * @return * A themed HTML string representing the star widget. */ function theme_fivestar_static($variables) { $rating = $variables['rating']; $stars = $variables['stars']; $tag = $variables['tag']; $output = ''; $output .= '
'; if (empty($stars)) { $stars = 5; } $numeric_rating = $rating/(100/$stars); for ($n=1; $n <= $stars; $n++) { $star_value = ceil((100/$stars) * $n); $prev_star_value = ceil((100/$stars) * ($n-1)); $zebra = ($n % 2 == 0) ? 'even' : 'odd'; $first = $n == 1 ? ' star-first' : ''; $last = $n == $stars ? ' star-last' : ''; $output .= '
'; if ($rating < $star_value && $rating > $prev_star_value) { $percent = (($rating - $prev_star_value) / ($star_value - $prev_star_value)) * 100; $output .= ''; } elseif ($rating >= $star_value) { $output .= ''; } else { $output .= ''; } if ($n == 1)$output .= $numeric_rating; $output .= '
'; } $output .= '
'; return $output; } /** * Display the text associated with a static star display. * * Note that passing in explicit data types is extremely important when using * this function. A NULL value will exclude the value entirely from display, * while a 0 value indicates that the text should be shown but it has no value * yet. * * All ratings are from 0 to 100. * * @param $user_rating * The current user's rating. * @param $average * The average rating. * @param $votes * The total number of votes. * @param $stars * The number of stars being displayed. * @param $feedback * A toggle that enables AJAX indicator message when a vote is being saved. * @return * A themed HTML string representing the star widget. */ function theme_fivestar_summary($variables) { extract($variables, EXTR_SKIP); $output = ''; $div_class = ''; if (isset($user_rating)) { $div_class = isset($votes) ? 'user-count' : 'user'; $user_stars = round(($user_rating * $stars) / 100, 1); $output .= ''. t('Your rating: !stars', array('!stars' => $user_rating ? $user_stars : t('None'))) .''; } if (isset($user_rating) && isset($average_rating)) { $output .= ' '; } if (isset($average_rating)) { $div_class = isset($votes) ? 'average-count' : 'average'; $average_stars = round(($average_rating * $stars) / 100, 1); $output .= ''. t('Average: !stars', array('!stars' => $average_stars)) .''; } if (isset($user_rating) && isset($average_rating)) { $div_class = 'combo'; } if (isset($votes) && !(isset($user_rating) || isset($average_rating))) { $output .= ' '. format_plural($votes, '@count vote', '@count votes') .''; $div_class = 'count'; } elseif (isset($votes)) { $output .= ' ('. format_plural($votes, '@count vote', '@count votes') .')'; } if ($votes === 0) { $output = ''. t('No votes yet') .''; } $output = '
'. $output .'
'; return $output; } /** * Display a static fivestar value as stars with a title and description. */ function theme_fivestar_static_element($variables) { $output = ''; $output .= '
'; $element = array( '#type' => 'item', '#title' => $variables['title'], '#description' => $variables['description'], '#children' => $variables['star_display'], ); $output .= theme('form_element', array('element' => $element)); $output .= '
'; return $output; } /** * Fetch the necessary CSS files to render the fivestar widget. */ function fivestar_add_css($widget_css = NULL) { // Add fivestar CSS. drupal_add_css(drupal_get_path('module', 'fivestar') .'/css/fivestar.css'); // Add widget specific CSS. if (!isset($widget_css)) { $widget_css = variable_get('fivestar_widget', 'default'); } if ($widget_css != 'default') { drupal_add_css($widget_css); } } /** * Add necessary JS files and settings to render the fivestar widget. */ function fivestar_add_js() { static $js_added = FALSE; // Add necessary javascript only once per page. if (!$js_added) { $settings = array( 'titleUser' => t('Your rating') .': ', 'titleAverage' => t('Average') .': ', 'feedbackSavingVote' => t('Saving your vote...'), 'feedbackVoteSaved' => t('Your vote has been saved.'), 'feedbackDeletingVote' => t('Deleting your vote...'), 'feedbackVoteDeleted' => t('Your vote has been deleted.'), ); drupal_add_js(drupal_get_path('module', 'fivestar') .'/js/fivestar.js'); drupal_add_js(array('fivestar' => $settings), 'setting'); $js_added = TRUE; } } /** * Add Inline CSS to the page, only used on admin/config/content/fivestar page. */ function fivestar_add_inline_css($widget_key = NULL, $css = NULL, $reset = FALSE) { static $inline_css; if (!isset($inline_css) || $reset) { $inline_css = array(); } if (isset($widget_key) && isset($inline_css)) { $inline_css[$widget_key] = $css; } return $inline_css; } /** * Retrieve a list of all inline CSS to be added to the page. */ function fivestar_get_inline_css() { $inline_css = fivestar_add_inline_css(); return implode("\n", $inline_css); } /** * Process callback for fivestar_element -- see fivestar_element() */ function fivestar_expand($element) { if (isset($element['#vote_count'])) { $element['vote_count'] = array( '#type' => 'hidden', '#value' => $element['#vote_count'], ); } if (isset($element['#vote_average'])) { $element['vote_average'] = array( '#type' => 'hidden', '#value' => $element['#vote_average'], ); } if ($element['#auto_submit'] && !empty($element['#auto_submit_path'])) { $element['auto_submit_path'] = array( '#type' => 'hidden', '#value' => url($element['#auto_submit_path']), '#attributes' => array('class' => 'fivestar-path'), ); $element['auto_submit_token'] = array( '#type' => 'hidden', '#value' => fivestar_get_token($element['#auto_submit_path']), '#attributes' => array('class' => 'fivestar-token'), ); } if (!isset($element['#default_value'])) { $element['#default_value'] = 0; } $options = array('-' => t('Select rating')); $default_value = $element['#default_value']; for ($i = 0; $i <= $element['#stars']; $i++) { $this_value = ceil($i * 100/$element['#stars']); $next_value = ceil(($i+1) * 100/$element['#stars']); // Display clear button only if enabled. if ($element['#allow_clear'] == TRUE && $i == 0) { $options[$this_value] = isset($element['#labels'][$i]) ? t(filter_xss_admin($element['#labels'][$i])) : t('Cancel rating'); } // Display a normal star value. if ($i > 0) { if (isset($element['#labels'][$i])) { $options[$this_value] = $element['#labels'][$i] == '' ? $i : t(filter_xss_admin($element['#labels'][$i]), array('@star' => $i, '@count' => $element['#stars'])); } else { $options[$this_value] = t('Give it @star/@count', array('@star' => $i, '@count' => $element['#stars'])); } } // Round up the default value to the next exact star value if needed. if ($this_value < $element['#default_value'] && $next_value > $element['#default_value']) { $default_value = $next_value; } } $element['vote'] = array( '#type' => 'select', '#options' => $options, '#required' => $element['#required'], '#default_value' => $default_value, '#parents' => $element['#parents'], '#theme' => 'fivestar_select', '#weight' => $element['#weight'], ); // If a default value is not exactly on a radio value, round up to the next one if ($element['#default_value'] > $this_value && $element['#default_value'] <= $next_value) { $element['vote']['#default_value'] = $next_value; } $class = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array(); $class[] = 'fivestar-form-item'; if ($element['#labels_enable']) { // Set a class for the display of label text on hover. $class[] = 'fivestar-labels-hover'; } $element['#prefix'] = '
$class)) . '>'; $element['#suffix'] = '
'; // Add validation function that considers a 0 value as empty. $element['#element_validate'] = array('fivestar_validate'); return $element; } /** * An #element_validate function for "fivestar" elements. */ function fivestar_validate($element, &$form_state) { if ($element['#required'] && (empty($element['#value']) || $element['#value'] == '-')) { form_error($element, t('!name field is required.', array('!name' => $element['#title']))); } } /** * Implementation of hook_votingapi_views_formatters(). */ function fivestar_votingapi_views_formatters($details = array()) { if ($details->field == 'value') { return array( 'fivestar_views_value_display_handler' => t('Fivestar Stars (display only)'), 'fivestar_views_value_text_handler' => t('Fivestar Stars (text star count)'), 'fivestar_views_widget_compact_handler' => t('Fivestar Stars (clickable, no text)'), 'fivestar_views_widget_normal_handler' => t('Fivestar Stars (clickable, with text)'), ); } } /** * VotingAPI Views formatter for displaying static stars. */ function fivestar_views_value_display_handler($value, $field, $columns) { // Determine number of stars to display // TODO need to get the tag here to support >1 tag // For now we're only going to support one tag ("vote"). // We'll add the multiple tag once the module is somewhat stable. $tag = 'vote'; if ($field->view->base_table == 'node') { if (isset($columns->node_type)) { $stars = variable_get('fivestar_stars_'. $columns->node_type, 5); } else { $node_type = db_query("SELECT type FROM {node} WHERE nid = :nid", array(':nid' => $columns->nid))->fetchField(); $stars = variable_get('fivestar_stars_'. (!isset($node_type) ? 'default' : $node_type), 5); } // Find the VotingAPI tag for this field. foreach ($field->query->table_queue[$field->relationship]['join']->extra as $votingapi_setting) { if ($votingapi_setting['field'] == 'tag') { $tag = $votingapi_setting['value']; } } } else { $stars = 5; $tag = 'vote'; } return theme('fivestar_static', array( 'rating' => $value, 'stars' => $stars, 'tag' => $tag, )); } /** * VotingAPI Views formatter for displaying number of stars as text. */ function fivestar_views_value_text_handler($value, $field, $columns) { // Get the number of stars for this node type. $node_type = isset($columns->node_type) ? $columns->node_type : db_query("SELECT type FROM {node} WHERE nid = :nid", array(':nid' => $columns->nid))->fetchField(); $stars = variable_get('fivestar_stars_'. $node_type, 5); // If displaying a user's vote, always display a whole value. if ($field->table == 'votingapi_vote') { return ceil(($value / 100) * $stars); } else { if ($field->options['set_precision']) { return round(($value / 100) * $stars, $field->options['precision']); } return ($value / 100) * $stars; } } /** * VotingAPI Views formatter for displaying rating widget without text. */ function fivestar_views_widget_compact_handler($value, $field, $columns) { return fivestar_views_widget_handler($value, $field, $columns, FALSE); } /** * VotingAPI Views formatter for displaying rating widget with text. */ function fivestar_views_widget_normal_handler($value, $field, $columns) { return fivestar_views_widget_handler($value, $field, $columns, TRUE); } /** * Generic VotingAPI Views formatter for displaying rating widget. */ function fivestar_views_widget_handler($value, $field, $columns, $summary) { // For now we're only supporting one tag. $tag = 'vote'; // If the user can't rate, use the display handler. if (!user_access('rate content')) { return fivestar_views_value_display_handler($value, $field, $columns); } if ($field->view->base_table == 'node') { // Find the VotingAPI tag for this field. foreach ($field->query->table_queue[$field->relationship]['join']->extra as $votingapi_setting) { if ($votingapi_setting['field'] == 'tag') { $tag = $votingapi_setting['value']; } } $content_type = 'node'; $content_id = $columns->nid; $node_type = isset($columns->node_type) ? $columns->node_type : db_query("SELECT type FROM {node} WHERE nid = :nid", array(":nid" => $columns->nid))->fetchField(); $values = array( 'user' => 0, 'average' => 0, 'count' => 0, ); if ($field->table == 'votingapi_vote') { $values['user'] = $value; } if ($field->table == 'votingapi_cache') { $values['average'] = $value; } // Only pull in all the votes if we need to display the summary text. if ($summary) { $votes = fivestar_get_votes($content_type, $content_id, $tag); if ($field->table != 'votingapi_vote') { $values['user'] = isset($votes['user']['value']) ? $votes['user']['value'] : 0; } if ($field->table != 'votingapi_cache') { $values['average'] = isset($votes['average']['value']) ? $votes['average']['value'] : 0; } $values['count'] = isset($votes['count']['value']) ? $votes['count']['value'] : 0; } $settings = array( 'stars' => variable_get('fivestar_stars_'. $node_type, 5), 'allow_clear' => variable_get('fivestar_unvote_'. $node_type, FALSE), 'style' => $field->table == 'votingapi_vote' ? 'user' : 'average', 'text' => $summary ? variable_get('fivestar_text_'. $node_type, 'dual') : 'none', 'content_type' => $content_type, 'content_id' => $content_id, 'tag' => $tag, 'autosubmit' => TRUE, 'title' => FALSE, 'feedback_enable' => $summary ? variable_get('fivestar_feedback_'. $node_type, 1) : FALSE, 'labels_enable' => $summary ? variable_get('fivestar_labels_enable_'. $node_type, 1) : FALSE, 'labels' => $summary ? variable_get('fivestar_labels_'. $node_type, array()) : array(), ); return drupal_get_form('fivestar_custom_widget', $values, $settings); } else { return theme('fivestar_static', array( 'rating' => $value, 'stars' => 5, )); } } function fivestar_get_tags() { $tags_txt = variable_get('fivestar_tags', 'vote'); $tags_exploded = explode(',', $tags_txt); $tags = array(); $got_vote = false; foreach ($tags_exploded as $tag) { $tag_trimmed = trim($tag); if ($tag_trimmed) { $tags[] = $tag_trimmed; if ($tag_trimmed == 'vote') { $got_vote = true; } } } if (!$got_vote) { $tags[] = 'vote'; } return $tags; } /** * Gets the variable suffix for node/tag-specific variables. * * Note that for backwards compatibility, the tag suffix is omitted for the tag * "vote". So instead of "story_vote", this just returns "story". */ function fivestar_get_suffix($node_type, $tag) { return $node_type . ($tag == 'vote' ? '' : '_' . $tag); } /** * Get all the settings set for a specific node type. */ function fivestar_get_settings($node_type, $tag) { $suffix = fivestar_get_suffix($node_type, $tag); $settings = array( 'fivestar' => variable_get('fivestar_' . $suffix, 0), 'stars' => variable_get('fivestar_stars_' . $suffix, 5), 'unvote_enable' => variable_get('fivestar_unvote_' . $suffix, 0), 'feedback_enable' => variable_get('fivestar_feedback_'. $suffix, 1), 'labels_enable' => variable_get('fivestar_labels_enable_'. $suffix, 1), 'labels' => variable_get('fivestar_labels_'. $suffix, array()), 'star_display' => variable_get('fivestar_style_' . $suffix, 'average'), 'text_display' => variable_get('fivestar_text_' . $suffix, 'dual'), 'title_display' => variable_get('fivestar_title_' . $suffix, 1), 'position' => variable_get('fivestar_position_' . $suffix, 'below'), 'position_teaser' => variable_get('fivestar_position_teaser_' . $suffix, 'hidden'), ); // Set default labels. if ($settings['stars'] == 5 && empty($settings['labels'])) { $settings['labels'] = array(t('Cancel rating'), t('Poor'), t('Okay'), t('Good'), t('Great'), t('Awesome')); } for ($n = 0; $n <= 10; $n++) { $settings['labels'][$n] = isset($settings['labels'][$n]) ? $settings['labels'][$n] : t('Give it @star/@count'); } return $settings; } /** * Implementation of hook_votingapi_metadata_alter(). */ function fivestar_votingapi_metadata_alter(&$data) { // TODO: This needs porting // static $tags; // if (!isset($tags)) { // $tags = array(); // // // Grab the list of fields to update. // foreach (content_types_install() as $type_name => $fields) { // foreach ($fields as $field) { // if ($field['type'] == 'fivestar') { // if (!empty($field['axis'])) { // $data['tags'][$field['axis']] = array( // 'name' => drupal_ucfirst($field['axis']), // 'description' => t('Used by %field in the %type content type.', array('%type' => $type_name, '%field' => $field['field_name'])), // 'module' => 'fivestar', // This is optional; we can add it for our own purposes. // ); // } // } // } // } // } // return $tags; }