/[drupal]/contributions/modules/fivestar/fivestar.module
ViewVC logotype

Contents of /contributions/modules/fivestar/fivestar.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.32 - (show annotations) (download) (as text)
Tue Nov 3 04:34:29 2009 UTC (3 weeks, 2 days ago) by quicksketch
Branch: MAIN
CVS Tags: HEAD
Changes since 1.31: +3 -3 lines
File MIME type: text/x-php
Consistently using the word "Fivestar" to refer to the module instead of "Five Star" or "fivestar".
1 <?php
2 // $Id: fivestar.module,v 1.31 2009/11/03 04:24:50 quicksketch Exp $
3
4 /**
5 * @file
6 * A simple n-star voting widget, usable in other forms.
7 */
8
9 /**
10 * Implementation of hook_help().
11 */
12 function fivestar_help($path, $arg) {
13 $output = '';
14 switch ($path) {
15 case 'admin/settings/fivestar':
16 $output = t('This page is used to configure site-wide features of the Fivestar module. To setup Fivestar to rate content:');
17 $steps = array(
18 t('Configure site-wide settings for Fivestar below.'),
19 t('Go to <a href="!types">admin/content/types</a> and edit the type you would like to rate.', array('!types' => url('admin/content/types'))),
20 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.'),
21 );
22 $output .= theme('item_list', $steps, NULL, 'ol');
23 break;
24 case 'admin/content/node-type/'. $arg[3] .'/fivestar':
25 $arg[5] = 'vote';
26 case 'admin/content/node-type/'. $arg[3] .'/fivestar/'. $arg[5]:
27 $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/settings/fivestar')));
28 break;
29 }
30 return $output;
31 }
32
33 /**
34 * Implementation of hook_menu().
35 */
36 function fivestar_menu() {
37 $items = array();
38 $items['admin/settings/fivestar'] = array(
39 'title' => 'Fivestar',
40 'description' => 'Configure site-wide widgets used for Fivestar rating.',
41 'page callback' => 'drupal_get_form',
42 'page arguments' => array('fivestar_settings'),
43 'access callback' => 'user_access',
44 'access arguments' => array('administer site configuration'),
45 'type' => MENU_NORMAL_ITEM,
46 'file' => 'includes/fivestar.admin.inc',
47 );
48 $items['fivestar/preview/node'] = array(
49 'page callback' => 'fivestar_preview',
50 'access callback' => 'user_access',
51 'access arguments' => array('administer content types'),
52 'type' => MENU_CALLBACK,
53 'file' => 'includes/fivestar.admin.inc',
54 );
55 $items['fivestar/preview/color'] = array(
56 'page callback' => 'fivestar_preview_color',
57 'access callback' => 'user_access',
58 'access arguments' => array('administer site configuration'),
59 'type' => MENU_CALLBACK,
60 'file' => 'includes/fivestar.color.inc',
61 );
62 $items['fivestar/vote'] = array(
63 'page callback' => 'fivestar_vote',
64 'access callback' => 'user_access',
65 'access arguments' => array('rate content'),
66 'type' => MENU_CALLBACK,
67 );
68
69 // Add a "fivestar" tab to each content type.
70 // We can't yet add it to the "operations" column in content types, due to a TODO in CCK
71 // (content.admin.inc line 32)
72
73 foreach (node_get_types() as $type) {
74 $type_name = $type->type;
75 $type_url_str = str_replace('_', '-', $type_name);
76 $items['admin/content/node-type/'. $type_url_str .'/fivestar'] = array(
77 'title' => 'Fivestar voting',
78 'page callback' => 'drupal_get_form',
79 'page arguments' => array('fivestar_node_type_tag_form', $type_name),
80 'access arguments' => array('administer content types'),
81 'type' => MENU_LOCAL_TASK,
82 'weight' => 5,
83 'file' => 'includes/fivestar.admin.inc',
84 );
85 foreach (fivestar_get_tags() as $tag) {
86 $items['admin/content/node-type/'. $type_url_str .'/fivestar/' . urlencode($tag)] = array(
87 'title' => $tag,
88 'page callback' => 'drupal_get_form',
89 'page arguments' => array('fivestar_node_type_tag_form', $type_name, $tag),
90 'access arguments' => array('administer content types'),
91 'type' => $tag == 'vote' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
92 'weight' => $tag == 'vote' ? 0 : 1,
93 'file' => 'includes/fivestar.admin.inc',
94 );
95
96 }
97 }
98
99 return $items;
100 }
101
102 /**
103 * Implementation of hook_init().
104 *
105 * These includes do not need to be loaded for cached pages.
106 */
107 function fivestar_init() {
108 if (module_exists('content')) {
109 module_load_include('inc', 'fivestar', 'includes/fivestar.field');
110 }
111
112 // Add necessary CSS and JS.
113 // TODO: These shouldn't be loaded on every page, but block caching omits
114 // CSS and JS files that would be otherwise added.
115 fivestar_add_js();
116 fivestar_add_css();
117 }
118
119 /**
120 * Implementation of hook_perm().
121 *
122 * Exposes permissions for rating content, viewing aggregate ratings, and using PHP
123 * snippets when configuring fivestar CCK fields.
124 */
125 function fivestar_perm() {
126 return array('rate content', 'use PHP for fivestar target');
127 }
128
129 /**
130 * Implementation of hook_theme().
131 */
132 function fivestar_theme() {
133 return array(
134 // Fivestar theme functions.
135 'fivestar' => array(
136 'arguments' => array('element' => NULL),
137 ),
138 'fivestar_select' => array(
139 'arguments' => array('element' => NULL),
140 ),
141 'fivestar_static' => array(
142 'arguments' => array('rating' => NULL, 'stars' => 5),
143 ),
144 'fivestar_static_element' => array(
145 'arguments' => array('star_display' => NULL, 'title' => NULL, 'description' => NULL),
146 ),
147 'fivestar_summary' => array(
148 'arguments' => array('user_rating' => NULL, 'average_rating' => NULL, 'votes' => 0, 'stars' => 5),
149 ),
150 'fivestar_widget' => array(
151 'arguments' => array('form' => NULL),
152 ),
153 // fivestar.admin.inc.
154 'fivestar_preview' => array(
155 'arguments' => array('style' => NULL, 'text' => NULL, 'stars' => NULL, 'unvote' => NULL, 'title' => NULL, 'labels_enable' => TRUE, 'labels' => array()),
156 'file' => 'includes/fivestar.admin.inc',
157 ),
158 'fivestar_preview_widget' => array(
159 'arguments' => array('css_file' => NULL),
160 'file' => 'includes/fivestar.admin.inc',
161 ),
162 'fivestar_preview_wrapper' => array(
163 'arguments' => array('content' => NULL, 'type' => 'direct'),
164 'file' => 'includes/fivestar.admin.inc',
165 ),
166 'fivestar_settings' => array(
167 'arguments' => array('form' => NULL),
168 'file' => 'includes/fivestar.admin.inc',
169 ),
170 'fivestar_node_type_tag_form' => array(
171 'arguments' => array('form' => NULL),
172 'file' => 'includes/fivestar.admin.inc',
173 ),
174 // fivestar.color.inc.
175 'fivestar_color_form' => array(
176 'arguments' => array('form' => NULL),
177 ),
178 // fivestar.field.inc.
179 'fivestar_formatter_default' => array(
180 'arguments' => array('element' => NULL),
181 ),
182 'fivestar_formatter_rating' => array(
183 'arguments' => array('element' => NULL),
184 ),
185 'fivestar_formatter_percentage' => array(
186 'arguments' => array('element' => NULL),
187 ),
188 );
189 }
190
191 /**
192 * Implementation of hook_node_types().
193 */
194 function fivestar_node_type($op, $info) {
195 $type = $info->type;
196 $variables = 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');
197
198 // Be responsible and cleanup unneeded variables.
199 if ($op == 'delete') {
200 foreach ($variables as $variable) {
201 foreach (fivestar_get_tags() as $tag) {
202 $suffix = fivestar_get_suffix($type, $tag);
203 variable_del($variable . '_' . $suffix);
204 }
205 }
206 }
207 // When changing the type name, update the variables.
208 elseif ($op == 'update' && !empty($info->old_type) && $info->old_type != $info->type) {
209 foreach ($variables as $variable) {
210 foreach (fivestar_get_tags() as $tag) {
211 $oldvarname = $variable . '_' . fivestar_get_suffix($info->old_type, $tag);
212 $newvarname = $variable . '_' . fivestar_get_suffix($info->type, $tag);
213 $value = variable_get($oldvarname, -1);
214 if ($value != -1) {
215 variable_del($oldvarname);
216 variable_set($newvarname, $value);
217 }
218 }
219 }
220 }
221 }
222
223 /**
224 * Callback function for fivestar/vote.
225 *
226 * @param type
227 * A content-type to log the vote to. 'node' is the most common.
228 * @param cid
229 * A content id to log the vote to. This would be a node ID, a comment ID, etc.
230 * @param tag
231 * Multi-axis tag to allow multiple votes per node. 'vote' is the most common.
232 * @param value
233 * A value from 1-100, representing the vote cast for the content.
234 * @return
235 * An XML chunk containing the results of the vote, for use by the client-side
236 * javascript code.
237 */
238 function fivestar_vote($type, $cid, $tag, $value) {
239 drupal_set_header("Content-Type: text/xml");
240 $output = '';
241 $output .= '<?xml version="1.0" encoding="UTF-8"?>';
242
243 // Rebuild the #auto_submit_path that was used as the token seed.
244 $path = preg_replace('/\/'. $value .'$/', '', $_GET['q']);
245 if (!isset($_GET['token']) || !fivestar_check_token($_GET['token'], $path)) {
246 $output .= '<xml><error>'. t('Invalid token') .'</error></xml>';
247 exit($output);
248 }
249
250 $result = _fivestar_cast_vote($type, $cid, $value, $tag, NULL, TRUE);
251 votingapi_recalculate_results($type, $cid);
252
253 if ($type == 'node') {
254 $node = node_load($cid);
255 }
256
257 $suffix = fivestar_get_suffix((!isset($node) ? 'default' : $node->type), $tag);
258
259 $stars = variable_get('fivestar_stars' . $suffix, 5);
260 $feedback_enable = variable_get('fivestar_feedback' . $suffix, 1);
261
262 $output .= '<xml><result>';
263
264 if (count($result)) {
265 foreach ($result as $data) {
266 if (isset($data['tag']) && $data['tag'] == $tag) {
267 $output .= '<'. $data['function'] .'>'. $data['value'] .'</'. $data['function'] .'>';
268 $summary[$data['tag']][$data['function']] = $data['value'];
269 }
270 }
271 }
272 $output .= '<summary>';
273 $output .= '<average><![CDATA['. theme('fivestar_summary', NULL, $summary[$tag]['average'], NULL, $stars, $feedback_enable) .']]></average>';
274 $output .= '<average_count><![CDATA['. theme('fivestar_summary', NULL, $summary[$tag]['average'], $summary[$tag]['count'], $stars, $feedback_enable) .']]></average_count>';
275 $output .= '<user><![CDATA['. theme('fivestar_summary', $value, NULL, NULL, $stars, $feedback_enable) .']]></user>';
276 $output .= '<user_count><![CDATA['. theme('fivestar_summary', $value, NULL, $summary[$tag]['count'], $stars, $feedback_enable) .']]></user_count>';
277 $output .= '<combo><![CDATA['. theme('fivestar_summary', $value, $summary[$tag]['average'], $summary[$tag]['count'], $stars, $feedback_enable) .']]></combo>';
278 $output .= '<count><![CDATA['. theme('fivestar_summary', NULL, NULL, $summary[$tag]['count'], $stars, $feedback_enable) .']]></count>';
279 $output .= '</summary>';
280 $output .= '</result>';
281
282 $output .= '<vote>';
283 $output .= '<value>'. $value .'</value>';
284 $output .= '<type>'. $type .'</type>';
285 $output .= '<id>'. $cid .'</id>';
286 $output .= '<tag>'. $tag .'</tag>';
287 $output .= '</vote></xml>';
288
289 drupal_set_header("Content-Type: text/xml");
290 exit($output);
291 }
292
293 /**
294 * Internal function to handle vote casting, flood control, XSS, IP based
295 * voting, etc...
296 */
297 function _fivestar_cast_vote($type, $cid, $value, $tag = NULL, $uid = NULL, $result = FALSE, $skip_validation = FALSE) {
298 global $user;
299 $tag = empty($tag) ? 'vote' : $tag;
300 $uid = empty($uid) ? $user->uid : $uid;
301
302 // Bail out if the user's trying to vote on an invalid object.
303 if (!$skip_validation && !fivestar_validate_target($type, $cid, $tag, $uid)) {
304 return array();
305 }
306
307 // Sanity-check the incoming values.
308 if (is_numeric($cid) && is_numeric($value)) {
309 if ($value > 100) {
310 $value = 100;
311 }
312
313 // Get the user's current vote.
314 $criteria = array('content_type' => $type, 'content_id' => $cid, 'tag' => $tag, 'uid' => $uid);
315 // Get the unique identifier for the user (IP Address if anonymous).
316 $user_criteria = votingapi_current_user_identifier();
317 $user_votes = votingapi_select_votes($criteria + $user_criteria);
318
319 if ($value == 0) {
320 votingapi_delete_votes($user_votes);
321 }
322 else {
323 $votes = $criteria += array('value' => $value);
324 votingapi_set_votes($votes, $user_votes);
325 }
326 return fivestar_get_votes($type, $cid, $tag, $uid);
327 }
328 }
329
330 /**
331 * Utility function to retreive VotingAPI votes.
332 *
333 * Note that this should not be used for general vote retreival, instead the
334 * VotingAPI function votingapi_select_results() should be used, which is more
335 * efficient when retrieving multiple votes.
336 *
337 * @param $type
338 * The content type for which to retreive votes.
339 * @param $cid
340 * The content ID for which to retreive votes.
341 * @param $tag
342 * The VotingAPI tag for which to retreive votes.
343 * @param $uid
344 * Optional. A user ID for which to retreive votes.
345 * @return
346 * An array of the following keys:
347 * - average: An array of VotingAPI results, including the average 'value'.
348 * - count: An array of VotingAPI results, including the count 'value'.
349 * - user: An array of VotingAPI results, including the user's vote 'value'.
350 */
351 function fivestar_get_votes($type, $cid, $tag = 'vote', $uid = NULL) {
352 global $user;
353
354 if (!isset($uid)) {
355 $uid = $user->uid;
356 }
357
358 $criteria = array(
359 'content_type' => $type,
360 'content_id' => $cid,
361 'value_type' => 'percent',
362 'tag' => $tag,
363 );
364
365 $votes = array(
366 'average' => array(),
367 'count' => array(),
368 'user' => array(),
369 );
370
371 $results = votingapi_select_results($criteria);
372 foreach ($results as $result) {
373 if ($result['function'] == 'average') {
374 $votes['average'] = $result;
375 }
376 if ($result['function'] == 'count') {
377 $votes['count'] = $result;
378 }
379 }
380 if ($uid) {
381 $user_vote = votingapi_select_votes($criteria += array('uid' => $uid));
382 if ($user_vote) {
383 $votes['user'] = $user_vote[0];
384 $votes['user']['function'] = 'user';
385 }
386 }
387 else {
388 // If the user is anonymous, we never bother loading their existing votes.
389 // Not only would it be hit-or-miss, it would break page caching. Safer to always
390 // show the 'fresh' version to anon users.
391 $votes['user'] = array('value' => 0);
392 }
393
394 return $votes;
395 }
396
397 /**
398 * Check that an item being voted upon is a valid vote.
399 *
400 * @param $type
401 * Type of target (currently only node is supported).
402 * @param $id
403 * Identifier within the type (in this case nid).
404 * @param $tag
405 * The VotingAPI tag string.
406 * @param $uid
407 * The user trying to cast the vote.
408 *
409 * @return boolean
410 */
411 function fivestar_validate_target($type, $id, $tag, $uid = NULL) {
412 if (!isset($uid)) {
413 $uid = $GLOBALS['user']->uid;
414 }
415
416 $access = module_invoke_all('fivestar_access', $type, $id, $tag, $uid);
417 foreach ($access as $result) {
418 if ($result == TRUE) {
419 return TRUE;
420 }
421 if ($result === FALSE) {
422 return FALSE;
423 }
424 }
425 }
426
427 /**
428 * Implementation of hook_fivestar_access().
429 *
430 * This hook is called before every vote is cast through Fivestar. It allows
431 * modules to allow voting on any type of content, such as nodes, users, or
432 * comments, even though only nodes are supported by Fivestar directly.
433 *
434 * @param $type
435 * Type of target (currently only node is supported).
436 * @param $id
437 * Identifier within the type (in this case nid).
438 * @param $tag
439 * The VotingAPI tag string.
440 * @param $uid
441 * The user ID trying to cast the vote.
442 *
443 * @return boolean or NULL
444 * Returns TRUE if voting is supported on this object.
445 * Returns NULL if voting is not supported on this object by this module.
446 * If needing to absolutely deny all voting on this object, regardless
447 * of permissions defined in other modules, return FALSE. Note if all
448 * modules return NULL, stating no preference, then access will be denied.
449 */
450 function fivestar_fivestar_access($type, $id, $tag, $uid) {
451 if ($type == 'node' && $node = node_load($id)) {
452 if (variable_get('fivestar_'. fivestar_get_suffix($node->type, $tag), 0)) {
453 return TRUE;
454 }
455 }
456 }
457
458 /**
459 * Implementation of hook_fivestar_widgets().
460 *
461 * This hook allows other modules to create additional custom widgets for
462 * the fivestar module.
463 *
464 * @return array
465 * An array of key => value pairs suitable for inclusion as the #options in a
466 * select or radios form element. Each key must be the location of a css
467 * file for a fivestar widget. Each value should be the name of the widget.
468 */
469 function fivestar_fivestar_widgets() {
470 $widgets_directory = drupal_get_path('module', 'fivestar') .'/widgets';
471 $files = file_scan_directory($widgets_directory, '\.css$');
472
473 $widgets = array();
474 foreach ($files as $file) {
475 if (strpos($file->filename, '-rtl.css') === FALSE) {
476 $widgets[$file->filename] = drupal_ucfirst(str_replace('-color', '', $file->name));
477 }
478 }
479 return $widgets;
480 }
481
482 /**
483 * Implementation of hook_nodeapi().
484 *
485 * Adds the fievestar widget to the node view.
486 */
487 function fivestar_nodeapi(&$node, $op, $teaser, $page) {
488 switch ($op) {
489 case 'view':
490 $exclude_modes = array(
491 NODE_BUILD_PREVIEW,
492 NODE_BUILD_SEARCH_INDEX,
493 NODE_BUILD_SEARCH_RESULT,
494 NODE_BUILD_RSS,
495 );
496 if (!in_array($node->build_mode, $exclude_modes) && !isset($node->modr8_form_teaser) && variable_get('fivestar_'. $node->type, 0)) {
497 if ($teaser) {
498 $position = variable_get('fivestar_position_teaser_'. $node->type, 'above');
499 }
500 else {
501 $position = variable_get('fivestar_position_'. $node->type, 'above');
502 }
503
504 switch ($position) {
505 case 'above':
506 case 'below':
507 if (user_access('rate content')) {
508 $content = '';
509 foreach (fivestar_get_tags() as $tag) {
510 if (fivestar_validate_target('node', $node->nid, $tag)) {
511 $content .= fivestar_widget_form($node, $tag);
512 }
513 }
514 if ($content) {
515 $node->content['fivestar_widget'] = array(
516 '#value' => $content,
517 '#weight' => $position == 'above' ? -10 : 50,
518 );
519 }
520 break;
521 } // Fall through to static if not allowed to rate.
522 $position .= '_static';
523 case 'above_static':
524 case 'below_static':
525 $content = '';
526 foreach (fivestar_get_tags() as $tag) {
527 if (fivestar_validate_target('node', $node->nid, $tag)) {
528 $content .= fivestar_static('node', $node->nid, $node->type, $tag);
529 }
530 }
531 if ($content) {
532 $node->content['fivestar_widget'] = array(
533 '#value' => $content,
534 '#weight' => strpos($position, 'above') === 0 ? -10 : 50,
535 );
536 }
537 break;
538 default:
539 // We'll do nothing.
540 break;
541 }
542 }
543 break;
544 }
545 }
546
547
548 /**
549 * Implementation of hook_link().
550 *
551 * Add a "rate" link to node teaser.
552 */
553 function fivestar_link($type, $node = NULL, $teaser = FALSE) {
554 $links = array();
555 if ($type == "node" && $teaser) {
556 if (variable_get('fivestar_position_teaser_'. $node->type, 'above') == "link") {
557 $links['rate'] = array(
558 'title' => t('Rate'),
559 'href' => 'node/'. $node->nid,
560 'fragment' => 'fivestar-form-node-'. $node->nid,
561 'attributes' => array('title' => t('Rate this @type', array('@type' => node_get_types('name', $node->type)))),
562 );
563 }
564 }
565 return $links;
566 }
567
568
569 function fivestar_block($op = 'list', $delta = 0, $edit = array()) {
570 switch ($op) {
571 case 'list':
572 $blocks[0]['info'] = t('Fivestar: Rate this node');
573 return $blocks;
574
575 case 'view':
576 if (user_access('access content') && user_access('rate content')) {
577 if (arg(0) == 'node' && is_numeric(arg(1)) && (arg(2) == '' || arg(2) == 'view')) {
578 $node = node_load(arg(1));
579 $block = array();
580 foreach (fivestar_get_tags() as $tag) {
581 if (fivestar_validate_target('node', $node->nid, $tag)) {
582 if (!$block) {
583 $block['subject'] = t('Rate This');
584 $block['content'] = '';
585 }
586 $block['content'] .= fivestar_widget_form($node, $tag);
587 }
588 }
589 return $block;
590 }
591 }
592 break;
593 }
594 }
595
596 function fivestar_widget_form($node, $tag = 'vote') {
597 return drupal_get_form('fivestar_form_node_'. $node->nid .'_'. $tag, 'node', $node->nid, $tag);
598 }
599
600 /**
601 * Get a private token used to protect links from CSRF attacks.
602 */
603 function fivestar_get_token($value) {
604 global $user;
605
606 // Anonymous users don't get a session ID, which breaks page caching.
607 $session_id = $user->uid ? session_id() : '';
608 $private_key = drupal_get_private_key();
609 return md5($session_id . $value . $private_key);
610 }
611
612 /**
613 * Check to see if a token value matches the specified node.
614 */
615 function fivestar_check_token($token, $value) {
616 return fivestar_get_token($value) == $token;
617 }
618
619 /**
620 * Implementation of hook_forms().
621 *
622 * This is necessary when multiple fivestar forms appear on the same page, each
623 * requiring a separate form_id, but all using the same underlying callbacks.
624 */
625 function fivestar_forms($form_id, $args) {
626 if (strpos($form_id, 'fivestar_form') !== FALSE) {
627 if ($form_id == 'fivestar_form_'. $args[0] .'_'. $args[1] .'_'. $args[2]) {
628 $forms[$form_id] = array('callback' => 'fivestar_form');
629 return $forms;
630 }
631 }
632 }
633
634 /**
635 * Create the fivestar form for the current item.
636 * Note that this is not an implementation of hook_form(). We should probably
637 * change the function to reflect that.
638 */
639 function fivestar_form(&$form_state, $content_type, $content_id, $tag) {
640 global $user;
641
642 if ($content_type == 'node') {
643 if (is_numeric($content_id)) {
644 $node = node_load($content_id);
645 }
646 else {
647 return array();
648 }
649 }
650
651 $suffix = fivestar_get_suffix($node->type, $tag);
652 $star_display = variable_get('fivestar_style' . $suffix, 'average');
653 $text_display = variable_get('fivestar_text' . $suffix, 'dual');
654
655 if ($star_display == 'average' && ($text_display == 'average' || $text_display == 'none')) {
656 // Save a query and don't retrieve the user vote unnecessarily.
657 $votes = fivestar_get_votes($content_type, $content_id, $tag, 0);
658 }
659 else {
660 $votes = fivestar_get_votes($content_type, $content_id, $tag);
661 }
662
663 $values = array(
664 'user' => isset($votes['user']['value']) ? $votes['user']['value'] : 0,
665 'average' => isset($votes['average']['value']) ? $votes['average']['value'] : 0,
666 'count' => isset($votes['count']['value']) ? $votes['count']['value'] : 0,
667 );
668
669 $settings = array(
670 'stars' => variable_get('fivestar_stars' . $suffix, 5),
671 'allow_clear' => variable_get('fivestar_unvote' . $suffix, FALSE),
672 'style' => $star_display,
673 'text' => $text_display,
674 'content_type' => $content_type,
675 'content_id' => $content_id,
676 'tag' => 'vote',
677 'autosubmit' => TRUE,
678 'title' => variable_get('fivestar_title' . $suffix, 1) ? NULL : FALSE,
679 'feedback_enable' => variable_get('fivestar_feedback' . $suffix, 1),
680 'labels_enable' => variable_get('fivestar_labels_enable' . $suffix, 1),
681 'labels' => variable_get('fivestar_labels' . $suffix, array()),
682 'tag' => $tag,
683 );
684
685 return fivestar_custom_widget($form_state, $values, $settings);
686 }
687
688 /**
689 * Retreive and print out a static display of stars for a piece of content.
690 *
691 * @param $content_type
692 * The type of content that will have its vote retreived. i.e. "node".
693 * @param $content_id
694 * The ID of the content that will have its vote retreived.
695 * @param $node_type
696 * Optional. If retreiving a node's rating, passing in the node type will
697 * prevent Fivestar from doing an additional query to find it.
698 * @param $tag
699 * Optional. The voting tag that will be retreived. Defaults to "vote" if none
700 * is specified.
701 */
702 function fivestar_static($content_type, $content_id, $node_type = NULL, $tag = 'vote') {
703 global $user;
704
705 $criteria = array(
706 'content_type' => $content_type,
707 'content_id' => $content_id,
708 'value_type' => 'percent',
709 'tag' => 'vote',
710 );
711
712 $votes = fivestar_get_votes($content_type, $content_id, $tag);
713
714 if ($content_type == 'node') {
715 // Content type should always be passed to avoid this node load.
716 if (!isset($node_type)) {
717 $node = node_load($content_id);
718 $node_type = $node->type;
719 }
720
721 $settings = fivestar_get_settings($node_type, $tag);
722 $stars = $settings['stars'];
723 switch ($settings['star_display']) {
724 case 'average':
725 case 'dual':
726 $star_value = $votes['average']['value'];
727 $title = $settings['title_display'] ? t('Average') : NULL;
728 break;
729 case 'user':
730 $star_value = $votes['user']['value'];
731 $title = $settings['title_display'] ? t('Your rating') : NULL;
732 break;
733 case 'smart':
734 $star_value = $votes['user']['value'] ? $votes['user']['value'] : $votes['average']['value'];
735 $title = $settings['title_display'] ? $votes['user']['value'] ? t('Your rating') : t('Average') : NULL;
736 break;
737 }
738
739 if ($tag != 'vote') {
740 $title .= ' (' . ucfirst($tag) . ')';
741 }
742
743 // Set all text values, then unset the unnecessary ones.
744 $user_value = $votes['user']['value'];
745 $average_value = $votes['average']['value'];
746 $count_value = $votes['count']['value'];
747 switch ($settings['text_display']) {
748 case 'average':
749 $user_value = NULL;
750 break;
751 case 'user':
752 $average_value = NULL;
753 break;
754 case 'smart':
755 if ($votes['user']['value']) {
756 $average_value = NULL;
757 }
758 else {
759 $user_value = NULL;
760 }
761 break;
762 }
763 }
764 // Possibly add other content types here (comment, user, etc).
765 else {
766 $stars = 5;
767 $star_value = $votes['average']['value'];
768 $user_value = $votes['user']['value'];
769 $average_value = $votes['average']['value'];
770 $count_value = $votes['count']['value'];
771 }
772
773 $star_display = theme('fivestar_static', $star_value, $stars);
774 $text_display = $settings['text_display'] == 'none' ? NULL : theme('fivestar_summary', $user_value, $average_value, $count_value, $stars, FALSE);
775
776 return theme('fivestar_static_element', $star_display, $title, $text_display);
777 }
778
779 /**
780 * Form builder; Build a custom Fivestar rating widget with arbitrary settings.
781 *
782 * This function is usually not called directly, instead call
783 * drupal_get_form('fivestar_custom_widget', $values, $settings) when wanting
784 * to display a widget.
785 *
786 * @param $form_state
787 * The form state provided by Form API.
788 * @param $values
789 * An array of current vote values from 0 to 100, with the following array
790 * keys:
791 * - user: The user's current vote.
792 * - average: The average vote value.
793 * - count: The total number of votes so far on this content.
794 * @param $settings
795 * An array of settings that configure the properties of the rating widget.
796 * Available keys for the settings include:
797 * - content_type: The type of content which will be voted upon.
798 * - content_id: The content ID which will be voted upon.
799 * - stars: The number of stars to display in this widget, from 2 to 10.
800 * Defaults to 5.
801 * - autosubmit: Whether the form should be submitted upon star selection.
802 * Defaults to TRUE.
803 * - allow_clear: Whether or not to show the "Clear current vote" icon when
804 * showing the widget. Defaults to FALSE.
805 * - required: Whether this field is required before the form can be
806 * submitted. Defaults to FALSE.
807 * - feedback_enable: Toggles the option to show the "Vote is being saved"
808 * text while a vote is being registered through AJAX. Defaults to TRUE.
809 * - labels_enable: Toggles the option to show the "Give it 2/5 stars" text
810 * while hovering over the stars with the mouse.
811 * - labels: An array of labels to be used. The number of labels should match
812 * the number of stars.
813 * - tag: The VotingAPI tag that will be registered by this widget. Defaults
814 * to "vote".
815 */
816 function fivestar_custom_widget(&$form_state, $values, $settings) {
817 $form = array(
818 '#attributes' => array('class' => 'fivestar-widget'),
819 '#redirect' => FALSE,
820 '#theme' => 'fivestar_widget',
821 );
822 $form['#submit'][] = 'fivestar_form_submit';
823
824 if (isset($settings['content_type'])) {
825 $form['content_type'] = array(
826 '#type' => 'hidden',
827 '#value' => $settings['content_type'],
828 );
829 }
830
831 if (isset($settings['content_id'])) {
832 $form['content_id'] = array(
833 '#type' => 'hidden',
834 '#value' => $settings['content_id'],
835 );
836 }
837
838 if (isset($settings['tag'])) {
839 $form['tag'] = array(
840 '#type' => 'hidden',
841 '#value' => $settings['tag'],
842 );
843 }
844
845 $form['vote'] = array(
846 '#type' => 'fivestar',
847 '#stars' => $settings['stars'],
848 '#vote_count' => $values['count'],
849 '#vote_average' => $values['average'],
850 '#auto_submit' => isset($settings['autosubmit']) ? $settings['autosubmit'] : TRUE,
851 '#auto_submit_path' => (!isset($settings['autosubmit']) || $settings['autosubmit']) ? 'fivestar/vote/'. $settings['content_type'] .'/'. $settings['content_id'] .'/' . $settings['tag'] : NULL,
852 '#allow_clear' => $settings['allow_clear'],
853 '#content_id' => isset($settings['content_id']) ? $settings['content_id'] : NULL,
854 '#required' => isset($settings['required']) ? $settings['required'] : FALSE,
855 '#feedback_enable' => isset($settings['feedback_enable']) ? $settings['feedback_enable'] : TRUE,
856 '#labels_enable' => isset($settings['labels_enable']) ? $settings['labels_enable'] : TRUE,
857 '#labels' => isset($settings['labels']) ? $settings['labels'] : NULL,
858 '#tag' => isset($settings['tag']) ? $settings['tag'] : 'vote',
859 );
860
861 $form['destination'] = array(
862 '#type' => 'hidden',
863 '#value' => $_GET['q'],
864 );
865
866 $form['fivestar_submit'] = array(
867 '#type' => 'submit',
868 '#value' => t('Rate'),
869 '#attributes' => array('class' => 'fivestar-submit'),
870 );
871
872 $form['vote']['#attributes']['class'] = isset($form['vote']['#attributes']['class']) ? $form['vote']['#attributes']['class'] : '';
873 $settings['feedback_enable'] = isset($settings['feedback_enable']) ? $settings['feedback_enable'] : TRUE;
874 switch ($settings['text']) {
875 case 'user':
876 $form['vote']['#description'] = theme('fivestar_summary', $values['user'], NULL, $settings['style'] == 'dual' ? NULL : $values['count'], $settings['stars'], $settings['feedback_enable']);
877 $form['vote']['#attributes']['class'] .= ' fivestar-user-text';
878 break;
879 case 'average':
880 $form['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', NULL, $values['average'], $values['count'], $settings['stars'], $settings['feedback_enable']);
881 $form['vote']['#attributes']['class'] .= ' fivestar-average-text';
882 break;
883 case 'smart':
884 $form['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', $values['user'], $values['user'] ? NULL : $values['average'], $settings['style'] == 'dual' ? NULL : $values['count'], $settings['stars'], $settings['feedback_enable']);
885 $form['vote']['#attributes']['class'] .= ' fivestar-smart-text '. ($values['user'] ? 'fivestar-user-text' : 'fivestar-average-text');
886 break;
887 case 'dual':
888 $form['vote']['#description'] = theme('fivestar_summary', $values['user'], $settings['style'] == 'dual' ? NULL : $values['average'], $settings['style'] == 'dual' ? NULL : $values['count'], $settings['stars'], $settings['feedback_enable']);
889 $form['vote']['#attributes']['class'] .= ' fivestar-combo-text';
890 break;
891 }
892
893 switch ($settings['style']) {
894 case 'average':
895 $form['vote']['#title'] = t('Average');
896 $form['vote']['#default_value'] = $values['average'];
897 $form['vote']['#attributes']['class'] .= ' fivestar-average-stars';
898 break;
899 case 'user':
900 $form['vote']['#title'] = t('Your rating');
901 $form['vote']['#default_value'] = $values['user'];
902 $form['vote']['#attributes']['class'] .= ' fivestar-user-stars';
903 break;
904 case 'smart':
905 $form['vote']['#title'] = $values['user'] ? t('Your rating') : t('Average');
906 $form['vote']['#default_value'] = $values['user'] ? $values['user'] : $values['average'];
907 $form['vote']['#attributes']['class'] .= ' fivestar-smart-stars '. ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
908 break;
909 case 'dual':
910 $form['vote']['#title'] = t('Your rating');
911 $form['vote']['#default_value'] = $values['user'];
912 $form['vote']['#attributes']['class'] .= ' fivestar-combo-stars';
913 $form['#attributes']['class'] .= ' fivestar-combo-stars';
914 $static_average = theme('fivestar_static', $values['average'], $settings['stars'], $settings['tag']);
915 if ($settings['text'] == 'none' && !$settings['labels_enable'] && !$settings['feedback_enable']) {
916 $static_description = NULL;
917 }
918 elseif ($settings['text'] != 'none') {
919 $static_description = theme('fivestar_summary', NULL, $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0), isset($values['count']) ? $values['count'] : 0, $settings['stars'], FALSE);
920 }
921 else {
922 $static_description = '&nbsp;';
923 }
924 $form['average'] = array(
925 '#type' => 'markup',
926 '#value' => theme('fivestar_static_element', $static_average, $settings['title'] !== FALSE ? t('Average') : NULL, $static_description),
927 '#weight' => -1,
928 );
929 break;
930 }
931
932 // Set an over-ridding title if passed in.
933 // An empty title won't change the default, a string will set a new title,
934 // and title === FALSE will unset the title entirely.
935 if (isset($settings['title'])) {
936 if ($settings['title'] !== FALSE) {
937 $form['vote']['#title'] = $settings['title'];
938 }
939 else {
940 unset($form['vote']['#title']);
941 unset($form['average']['#title']);
942 }
943 }
944 elseif ($settings['tag'] && $settings['tag'] != 'vote') {
945 $form['vote']['#title'] .= ' (' . ucfirst($settings['tag']) . ')';
946 }
947
948 return $form;
949 }
950
951 /**
952 * Submit handler for the above form (non-javascript version).
953 */
954 function fivestar_form_submit($form, &$form_state) {
955 if ($form_state['values']['form_id'] == 'fivestar_form_'. $form_state['values']['content_type'] .'_'. $form_state['values']['content_id'] . '_' . $form_state['values']['tag']) {
956 // Cast the vote.
957 _fivestar_cast_vote($form_state['values']['content_type'], $form_state['values']['content_id'], $form_state['values']['vote'], $form_state['values']['tag']);
958 votingapi_recalculate_results($form_state['values']['content_type'], $form_state['values']['content_id']);
959
960 // Set a message that the vote was received.
961 if ($form_state['values']['vote'] === '0') {
962 drupal_set_message(t('Your vote has been cleared.'));
963 }
964 elseif (is_numeric($form_state['values']['vote'])) {
965 drupal_set_message(t('Thank you for your vote.'));
966 }
967 // Regenerate the page with a drupal_goto() to update the current values.
968 drupal_goto();
969 }
970 }
971
972 /**
973 * Implementation of hook_elements().
974 *
975 * Defines 'fivestar' form element type
976 */
977 function fivestar_elements() {
978 $type['fivestar'] = array(
979 '#input' => TRUE,
980 '#stars' => 5,
981 '#widget' => 'stars',
982 '#allow_clear' => FALSE,
983 '#auto_submit' => FALSE,
984 '#auto_submit_path' => '',
985 '#labels_enable' => TRUE,
986 '#feedback_enable' => TRUE,
987 '#process' => array('fivestar_expand'),
988 );
989 return $type;
990 }
991
992 /**
993 * Theme the fivestar form element by adding necessary css and javascript.
994 */
995 function theme_fivestar($element) {
996 if (empty($element['#description'])) {
997 if ($element['#feedback_enable']) {
998 $element['#description'] = '<div class="fivestar-summary fivestar-feedback-enabled">&nbsp;</div>';
999 }
1000 elseif ($element['#labels_enable']) {
1001 $element['#description'] = '<div class="fivestar-summary">&nbsp;</div>';
1002 }
1003 }
1004
1005 return theme('form_element', $element, $element['#children']);
1006 }
1007
1008 /**
1009 * Theme the straight HTML version of the fivestar select list. This is used
1010 * to remove the wrapping 'form-item' div from the select list.
1011 */
1012 function theme_fivestar_select($element) {
1013 $select = '';
1014 $size = $element['#size'] ? ' size="'. $element['#size'] .'"' : '';
1015 _form_set_class($element, array('form-select'));
1016 $multiple = isset($element['#multiple']) && $element['#multiple'];
1017 return '<select name="'. $element['#name'] .''. ($multiple ? '[]' : '') .'"'. ($multiple ? ' multiple="multiple" ' : '') . drupal_attributes($element['#attributes']) .' id="'. $element['#id'] .'" '. $size .'>'. form_select_options($element) .'</select>';
1018 }
1019
1020 /**
1021 * Theme an entire fivestar widget, including the submit button and the normal
1022 * fivestar widget themed in the theme_fivestar() function.
1023 */
1024 function theme_fivestar_widget($form) {
1025 // Only print out the summary if text is being displayed or using rollover text.
1026 if (empty($form['vote']['#description']) && strpos($form['vote']['#prefix'], 'fivestar-labels-hover') === FALSE) {
1027 unset($form['vote']['#description']);
1028 }
1029
1030 $class = 'fivestar-form';
1031 $class .= '-'. (isset($form['vote']['#tag']) ? $form['vote']['#tag'] : 'vote');
1032 $class .= '-'. (isset($form['content_id']['#value']) ? $form['content_id']['#value'] : 0);
1033
1034 $output = '';
1035 $output .= '<div class="'. $class .' clear-block">';
1036 $output .= drupal_render($form);
1037 $output .= '</div>';
1038 return $output;
1039 }
1040
1041 /**
1042 * Display a plain HTML view-only version of the widget with a specified rating.
1043 *
1044 * @param $rating
1045 * The desired rating to display out of 100 (i.e. 80 is 4 out of 5 stars).
1046 * @param $stars
1047 * The total number of stars this rating is out of.
1048 * @param $tag
1049 * Allows multiple ratings per node.
1050 * @return
1051 * A themed HTML string representing the star widget.
1052 */
1053 function theme_fivestar_static($rating, $stars = 5, $tag = 'vote') {
1054 $output = '';
1055 $output .= '<div class="fivestar-widget-static fivestar-widget-static-'. $tag .' fivestar-widget-static-'. $stars .' clear-block">';
1056 if (empty($stars)) {
1057 $stars = 5;
1058 }
1059 $numeric_rating = $rating/(100/$stars);
1060 for ($n=1; $n <= $stars; $n++) {
1061 $star_value = ceil((100/$stars) * $n);
1062 $prev_star_value = ceil((100/$stars) * ($n-1));
1063 $zebra = ($n % 2 == 0) ? 'even' : 'odd';
1064 $first = $n