Project

General

Profile

Paste
Download (32.3 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / fivestar / fivestar.module @ d756b39a

1
<?php
2

    
3
/**
4
 * @file
5
 * A simple n-star voting widget, usable in other forms.
6
 */
7

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

    
10
/**
11
 * Implementation of hook_help().
12
 */
13
function fivestar_help($path, $arg) {
14
  $output = '';
15
  switch ($path) {
16
    case 'admin/config/content/fivestar':
17
      $output = t('This page is used to configure site-wide features of the Fivestar module.');
18
    break;
19
  }
20
  return $output;
21
}
22

    
23
/**
24
 * Implementation of hook_menu().
25
 */
26
function fivestar_menu() {
27
  $items = array();
28
  $items['admin/config/content/fivestar'] = array(
29
    'title'             => 'Fivestar',
30
    'description'       => 'Configure site-wide widgets used for Fivestar rating.',
31
    'page callback'     => 'drupal_get_form',
32
    'page arguments'    => array('fivestar_settings'),
33
    'access callback'   => 'user_access',
34
    'access arguments'  => array('administer site configuration'),
35
    'type'              => MENU_NORMAL_ITEM,
36
    'file'              => 'includes/fivestar.admin.inc',
37
  );
38

    
39
  return $items;
40
}
41

    
42
/**
43
 * Implements hook_microdata_suggestions().
44
 */
45
function fivestar_microdata_suggestions() {
46
  $mappings = array();
47

    
48
  // Add the review mapping for Schema.org
49
  $mappings['fields']['fivestar']['schema.org'] = array(
50
    '#itemprop' => array('aggregateRating'),
51
    '#is_item' => TRUE,
52
    '#itemtype' => array('http://schema.org/AggregateRating'),
53
    'average_rating' => array(
54
      '#itemprop' => array('ratingValue'),
55
    ),
56
    'rating_count' => array(
57
      '#itemprop' => array('ratingCount'),
58
    ),
59
  );
60

    
61
  return $mappings;
62
}
63

    
64
/**
65
 * Implementation of hook_permission().
66
 *
67
 * Exposes permissions for rating content.
68
 */
69
function fivestar_permission() {
70
  return array(
71
    'rate content' => array(
72
      'title' => t('Use Fivestar to rate content'),
73
    ),
74
  );
75
}
76

    
77
/**
78
 * Implementation of hook_theme().
79
 */
80
function fivestar_theme() {
81
  return array(
82
    // Fivestar theme functions.
83
    'fivestar' => array(
84
      'render element' => 'element',
85
      'file' => 'includes/fivestar.theme.inc',
86
    ),
87
    'fivestar_select' => array(
88
      'render element' => 'element',
89
      'file' => 'includes/fivestar.theme.inc',
90
    ),
91
    'fivestar_static' => array(
92
      'variables' => array('rating' => NULL, 'stars' => 5, 'tag' => 'vote', 'widget' => array('name' => 'default', 'css' => '')),
93
      'file' => 'includes/fivestar.theme.inc',
94
    ),
95
    'fivestar_static_element' => array(
96
      'variables' => array('star_display' => NULL, 'title' => NULL, 'description' => NULL),
97
      'file' => 'includes/fivestar.theme.inc',
98
    ),
99
    'fivestar_summary' => array(
100
      'variables' => array('user_rating' => NULL, 'average_rating' => NULL, 'votes' => 0, 'stars' => 5, 'microdata' => array()),
101
      'file' => 'includes/fivestar.theme.inc',
102
    ),
103
    // fivestar.admin.inc.
104
    'fivestar_preview' => array(
105
      'variables' => array('style' => NULL, 'text' => NULL, 'stars' => NULL, 'unvote' => NULL, 'title' => NULL),
106
      'file' => 'includes/fivestar.theme.inc',
107
    ),
108
    'fivestar_preview_widget' => array(
109
      'variables' => array('css' => NULL, 'name' => NULL),
110
      'file' => 'includes/fivestar.theme.inc',
111
    ),
112
    'fivestar_preview_wrapper' => array(
113
      'variables' => array('content' => NULL, 'type' => 'direct'),
114
      'file' => 'includes/fivestar.theme.inc',
115
    ),
116
    'fivestar_settings' => array(
117
      'render element' => 'form',
118
      'file' => 'includes/fivestar.theme.inc',
119
    ),
120
    'fivestar_color_form' => array(
121
      'render element' => 'form',
122
      'file' => 'includes/fivestar.theme.inc',
123
    ),
124
    'fivestar_formatter_default' => array(
125
      'render element' => 'element',
126
      'file' => 'includes/fivestar.theme.inc',
127
    ),
128
    'fivestar_formatter_rating' => array(
129
      'render element' => 'element',
130
      'file' => 'includes/fivestar.theme.inc',
131
    ),
132
    'fivestar_formatter_percentage' => array(
133
      'render element' => 'element',
134
      'file' => 'includes/fivestar.theme.inc',
135
    ),
136
  );
137
}
138

    
139
function _fivestar_variables() {
140
  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');
141
}
142

    
143
/**
144
 * Internal function to handle vote casting, flood control, XSS, IP based
145
 * voting, etc...
146
 */
147

    
148
function _fivestar_cast_vote($entity_type, $id, $value, $tag = NULL, $uid = NULL, $skip_validation = FALSE, $vote_source = NULL) {
149
  global $user;
150
  $tag = empty($tag) ? 'vote' : $tag;
151
  $uid = isset($uid) ? $uid : $user->uid;
152
  // Bail out if the user's trying to vote on an invalid object.
153
  if (!$skip_validation && !fivestar_validate_target($entity_type, $id, $tag, $uid)) {
154
    return array();
155
  }
156
  // Sanity-check the incoming values.
157
  if (is_numeric($id) && is_numeric($value)) {
158
    if ($value > 100) {
159
      $value = 100;
160
    }
161

    
162
    // Get the user's current vote.
163
    $criteria = array('entity_type' => $entity_type, 'entity_id' => $id, 'tag' => $tag, 'uid' => $uid);
164
    // Get the unique identifier for the user (IP Address if anonymous).
165

    
166
    if ($vote_source != NULL) {
167
      $user_criteria = array('vote_source' => $vote_source);
168
      $limit = 1;
169
    }
170
    else {
171
      $user_criteria = votingapi_current_user_identifier();
172
      $limit = 0;
173
    }
174
    $user_votes = votingapi_select_votes($criteria + $user_criteria, $limit);
175

    
176
    if ($value == 0) {
177
      votingapi_delete_votes($user_votes);
178
    }
179
    else {
180
      $votes = $criteria += array('value' => $value);
181
      votingapi_set_votes($votes);
182
    }
183

    
184
    // Moving the calculationg after saving/deleting the vote but before getting the votes.
185
    votingapi_recalculate_results($entity_type, $id);
186
    return fivestar_get_votes($entity_type, $id, $tag, $uid);
187
  }
188
}
189

    
190
/**
191
 * Utility function to retrieve VotingAPI votes.
192
 *
193
 * Note that this should not be used for general vote retrieval, instead the
194
 * VotingAPI function votingapi_select_results() should be used, which is more
195
 * efficient when retrieving multiple votes.
196
 *
197
 * @param $entity_type
198
 *   The Entity type for which to retrieve votes.
199
 * @param $id
200
 *   The ID for which to retrieve votes.
201
 * @param $tag
202
 *   The VotingAPI tag for which to retrieve votes.
203
 * @param $uid
204
 *   Optional. A user ID for which to retrieve votes.
205
 * @return
206
 *   An array of the following keys:
207
 *   - average: An array of VotingAPI results, including the average 'value'.
208
 *   - count: An array of VotingAPI results, including the count 'value'.
209
 *   - user: An array of VotingAPI results, including the user's vote 'value'.
210
 */
211
function fivestar_get_votes_multiple($entity_type, $ids, $tag = 'vote', $uid = NULL) {
212
  global $user;
213

    
214
  if (!isset($uid)) {
215
    $uid = $user->uid;
216
  }
217

    
218
  $criteria = array(
219
    'entity_type' => $entity_type,
220
    'entity_id' => $ids,
221
    'value_type' => 'percent',
222
    'tag' => $tag,
223
  );
224

    
225
  $votes = array();
226

    
227
  // Initialize defaults.
228
  foreach($ids as $id) {
229
    $votes[$entity_type][$id] = array(
230
      'average' => array(),
231
      'count' => array(),
232
      'user' => array(),
233
    );
234
  }
235

    
236
  $results = votingapi_select_results($criteria);
237
  $user_votes = array();
238
  if(!empty($results)) {
239
    foreach(votingapi_select_votes($criteria += array('uid' => $uid)) as $uv) {
240
      $user_votes[$uv['entity_type']][$uv['entity_id']] = $uv;
241
    }
242
  }
243

    
244
  foreach ($results as $result) {
245
    if ($result['function'] == 'average') {
246
      $votes[$result['entity_type']][$result['entity_id']]['average'] = $result;
247
    }
248
    if ($result['function'] == 'count') {
249
      $votes[$result['entity_type']][$result['entity_id']]['count'] = $result;
250
    }
251
	  if ($uid) {
252
	    if (!empty($user_votes[$result['entity_type']][$result['entity_id']])) {
253
        $votes[$result['entity_type']][$result['entity_id']]['user'] = $user_votes[$result['entity_type']][$result['entity_id']];
254
        $votes[$result['entity_type']][$result['entity_id']]['user']['function'] = 'user';
255
	    }
256
	  }
257
	  else {
258
	    // If the user is anonymous, we never bother loading their existing votes.
259
	    // Not only would it be hit-or-miss, it would break page caching. Safer to always
260
	    // show the 'fresh' version to anon users.
261
      $votes[$result['entity_type']][$result['entity_id']]['user'] = array('value' => 0);
262
	  }
263
  }
264

    
265
  return $votes;
266
}
267

    
268
function fivestar_get_votes($entity_type, $id, $tag = 'vote', $uid = NULL) {
269
  $multiple = fivestar_get_votes_multiple($entity_type, array($id), $tag, $uid);
270
  return $multiple[$entity_type][$id];
271
}
272

    
273
/**
274
 * Check that an item being voted upon is a valid vote.
275
 *
276
 * @param $entity_type
277
 *   Type of target.
278
 * @param $id
279
 *   Identifier within the type.
280
 * @param $tag
281
 *   The VotingAPI tag string.
282
 * @param $uid
283
 *   The user trying to cast the vote.
284
 *
285
 * @return boolean
286
 */
287
function fivestar_validate_target($entity_type, $id, $tag, $uid = NULL) {
288
  if (!isset($uid)) {
289
    $uid = $GLOBALS['user']->uid;
290
  }
291

    
292
  $access = module_invoke_all('fivestar_access', $entity_type, $id, $tag, $uid);
293
  foreach ($access as $result) {
294
    if ($result == TRUE) {
295
      return TRUE;
296
    }
297
    if ($result === FALSE) {
298
      return FALSE;
299
    }
300
  }
301
}
302

    
303
/**
304
 * Implementation of hook_fivestar_access().
305
 *
306
 * This hook is called before every vote is cast through Fivestar. It allows
307
 * modules to allow voting on any type of entity, such as nodes, users, or
308
 * comments.
309
 *
310
 * @param $entity_type
311
 *   Type entity.
312
 * @param $id
313
 *   Identifier within the type.
314
 * @param $tag
315
 *   The VotingAPI tag string.
316
 * @param $uid
317
 *   The user ID trying to cast the vote.
318
 *
319
 * @return boolean or NULL
320
 *   Returns TRUE if voting is supported on this object.
321
 *   Returns NULL if voting is not supported on this object by this module.
322
 *   If needing to absolutely deny all voting on this object, regardless
323
 *   of permissions defined in other modules, return FALSE. Note if all
324
 *   modules return NULL, stating no preference, then access will be denied.
325
 */
326
function fivestar_fivestar_access($entity_type, $id, $tag, $uid) {
327
  // Check to see if there is a field instance on this entity.
328
  $fields = field_read_fields(array('module' => 'fivestar'));
329
  foreach ($fields as $field) {
330
    if ($field['settings']['axis'] == $tag) {
331
      $params = array(
332
        'entity_type' => $entity_type,
333
        'field_name' => $field['field_name'],
334
      );
335
      $instance = field_read_instances($params);
336
      if (!empty($instance)) {
337
        return TRUE;
338
      }
339
    }
340
  }
341
}
342

    
343
/**
344
 * Implementation of hook_form_comment_form_alter().
345
 *
346
 * This hook removes the parent node, together with the fivestar field, from
347
 * the comment preview page. If this is left in, when the user presses the
348
 * "Save" button after the preview page has been displayed, the fivestar widget
349
 * gets the input rather than the comment; the user's input is lost. Based on a
350
 * suggestion by ChristianAdamski in issue 1289832-3.
351
 */
352
function fivestar_form_comment_form_alter(&$form, &$form_state, $form_id) {
353
  $fivestar_field_keys = array();
354
  if (isset($form['comment_output_below'])) {
355
    foreach($form['comment_output_below'] as $key => $value) {
356
      if (is_array($value) && !empty($value['#field_type']) && $value['#field_type'] == 'fivestar') {
357
        $fivestar_field_keys[] = $key;
358
      }
359
    }
360
  }
361
  if ($fivestar_field_keys) {
362
    foreach ($fivestar_field_keys as $key) {
363
      unset($form['comment_output_below'][$key]);
364
    }
365
  }
366
}
367

    
368
/**
369
 * Implementation of hook_fivestar_widgets().
370
 *
371
 * This hook allows other modules to create additional custom widgets for
372
 * the fivestar module.
373
 *
374
 * @return array
375
 *   An array of key => value pairs suitable for inclusion as the #options in a
376
 *   select or radios form element. Each key must be the location of a css
377
 *   file for a fivestar widget. Each value should be the name of the widget.
378
 */
379
function fivestar_fivestar_widgets() {
380
  $widgets = &drupal_static(__FUNCTION__);
381
  if (!isset($widgets)) {
382
    $cache = cache_get(__FUNCTION__);
383
    if ($cache) {
384
      $widgets = $cache->data;
385
    }
386
  }
387

    
388
  if (!isset($widgets)) {
389
    $widgets_directory = drupal_get_path('module', 'fivestar') . '/widgets';
390
    $files = file_scan_directory($widgets_directory, '/\.css$/');
391
    foreach ($files as $file) {
392
      if (strpos($file->filename, '-rtl.css') === FALSE) {
393
        $widgets[$file->uri] = drupal_ucfirst(str_replace('-color', '', $file->name));
394
      }
395
    }
396
    cache_set(__FUNCTION__, $widgets);
397
  }
398
  return $widgets;
399
}
400

    
401
/**
402
 * Form builder; Build a custom Fivestar rating widget with arbitrary settings.
403
 *
404
 * This function is usually not called directly, instead call
405
 * drupal_get_form('fivestar_custom_widget', $values, $settings) when wanting
406
 * to display a widget.
407
 *
408
 * @param $form_state
409
 *   The form state provided by Form API.
410
 * @param $values
411
 *   An array of current vote values from 0 to 100, with the following array
412
 *   keys:
413
 *   - user: The user's current vote.
414
 *   - average: The average vote value.
415
 *   - count: The total number of votes so far on this content.
416
 * @param $settings
417
 *   An array of settings that configure the properties of the rating widget.
418
 *   Available keys for the settings include:
419
 *   - content_type: The type of content which will be voted upon.
420
 *   - content_id: The content ID which will be voted upon.
421
 *   - stars: The number of stars to display in this widget, from 2 to 10.
422
 *     Defaults to 5.
423
 *   - autosubmit: Whether the form should be submitted upon star selection.
424
 *     Defaults to TRUE.
425
 *   - allow_clear: Whether or not to show the "Clear current vote" icon when
426
 *     showing the widget. Defaults to FALSE.
427
 *   - required: Whether this field is required before the form can be
428
 *     submitted. Defaults to FALSE.
429
 *   - tag: The VotingAPI tag that will be registered by this widget. Defaults
430
 *     to "vote".
431
 */
432
function fivestar_custom_widget($form, &$form_state, $values, $settings) {
433
  $form = array(
434
    '#attributes' => array(
435
      'class' => array('fivestar-widget')
436
    ),
437
  );
438
  $form['#submit'][] = 'fivestar_form_submit';
439

    
440
  $form_state['settings'] = $settings;
441

    
442
  // Define default settings.
443
  $default_settings = array(
444
    'allow_clear' => FALSE,
445
    // Taken from installation file.
446
    'allow_ownvote' => TRUE,
447
    'allow_revote' => TRUE,
448
    'autosubmit' => TRUE,
449
    'description' => '',
450
    'required' => FALSE,
451
    'stars' => 5,
452
    'tag' => 'vote',
453
    'widget' => array(
454
      'name' => 'default',
455
      'css' => 'default',
456
    ),
457
  );
458

    
459
  // Merge default settings.
460
  $settings = $settings + $default_settings;
461

    
462
  $form['vote'] = array(
463
    '#type' => 'fivestar',
464
    '#stars' => $settings['stars'],
465
    '#auto_submit' => $settings['autosubmit'],
466
    '#allow_clear' => $settings['allow_clear'],
467
    '#allow_revote' => $settings['allow_revote'],
468
    '#allow_ownvote' => $settings['allow_ownvote'],
469
    '#required' => $settings['required'],
470
    '#widget' => $settings['widget'],
471
    '#values' => $values,
472
    '#settings' => $settings,
473
    '#description' => $settings['description'],
474
  );
475

    
476
  $form['fivestar_submit'] = array(
477
    '#type' => 'submit',
478
    '#value' => t('Rate'),
479
    '#attributes' => array('class' => array('fivestar-submit')),
480
  );
481

    
482
  return $form;
483
}
484

    
485
/**
486
 * Submit handler for the above form (non-javascript version).
487
 */
488
function fivestar_form_submit($form, &$form_state) {
489

    
490
  // Cast the vote.
491
  _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
492

    
493
  // Set a message that the vote was received.
494
  if ($form_state['values']['vote'] === '0') {
495
    drupal_set_message(t('Your vote has been cleared.'));
496
  }
497
  elseif (is_numeric($form_state['values']['vote'])) {
498
    drupal_set_message(t('Thank you for your vote.'));
499
  }
500
}
501

    
502
/**
503
 * AJAX submit handler for fivestar_custom_widget
504
 */
505
function fivestar_ajax_submit($form, $form_state) {
506
  if (!empty($form_state['settings']['content_id'])) {
507
    $entity = entity_load($form_state['settings']['entity_type'], array($form_state['settings']['entity_id']));
508
    $entity = reset($entity);
509
    _fivestar_update_field_value($form_state['settings']['content_type'], $entity, $form_state['settings']['field_name'], $form_state['settings']['langcode'], $form_state['values']['vote']);
510
    $votes = _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
511
  }
512

    
513
  $values = array();
514
  $values['user'] = isset($votes['user']['value']) ? $votes['user']['value'] : 0;
515
  $values['average'] = isset($votes['average']['value']) ? $votes['average']['value'] : 0;
516
  $values['count'] = isset($votes['count']['value']) ? $votes['count']['value'] : 0;
517

    
518
  // We need to process the 'fivestar' element with the new values.
519
  $form['vote']['#values'] = $values;
520
  // Also need to pass on form_state and the complete form, just like form_builder() does.
521
  $new_element = fivestar_expand($form['vote'], $form_state, $form_state['complete form']);
522
  // Update the description. Since it doesn't account of our cast vote above.
523
  // TODO: Look into all this, I honestly don't like it and it's a bit weird.
524
  $form['vote']['vote']['#description'] = isset($new_element['vote']['#description']) ? $new_element['vote']['#description'] : '';
525

    
526
  return array(
527
    '#type' => 'ajax',
528
    '#commands' => array(
529
      array(
530
        'command' => 'fivestarUpdate',
531
        'data' => drupal_render($form['vote']),
532
      ),
533
    ),
534
  );
535
}
536

    
537
/**
538
 * Implementation of hook_elements().
539
 *
540
 * Defines 'fivestar' form element type
541
 */
542
function fivestar_element_info() {
543
  $type['fivestar'] = array(
544
    '#input' => TRUE,
545
    '#stars' => 5,
546
    '#allow_clear' => FALSE,
547
    '#allow_revote' => FALSE,
548
    '#allow_ownvote' => FALSE,
549
    '#auto_submit' => FALSE,
550
    '#process' => array('fivestar_expand'),
551
    '#theme_wrappers' => array('form_element'),
552
    '#widget' => array(
553
      'name' => 'default',
554
      'css' => 'default',
555
    ),
556
    '#values' => array(
557
      'user' => 0,
558
      'average' => 0,
559
      'count' => 0,
560
    ),
561
    '#settings' => array(
562
      'style' => 'user',
563
      'text' => 'none',
564
    ),
565
  );
566
  return $type;
567
}
568

    
569
/**
570
 * Process callback for fivestar_element -- see fivestar_element()
571
 */
572
function fivestar_expand($element, $form_state, $complete_form) {
573
  if (!_fivestar_allow_vote($element)){
574
    $element['#input'] = FALSE;
575
  }
576

    
577
  // Add CSS and JS
578
  $path = drupal_get_path('module', 'fivestar');
579
  $element['#attached']['js'][] = $path . '/js/fivestar.js';
580
  $element['#attached']['css'][] = $path . '/css/fivestar.css';
581
  $settings = $element['#settings'];
582
  $values = $element['#values'];
583
  $class[] = 'clearfix';
584

    
585
  $title = 'it';
586
  if (isset($element['#settings']['entity_id']) && isset($element['#settings']['entity_type'])) {
587
    $entity_id = $element['#settings']['entity_id'];
588
    $entity = entity_load($element['#settings']['entity_type'], array($entity_id));
589
    $entity = $entity[$entity_id];
590
    $title = $entity->title;
591
  } elseif (isset($complete_form['#node'])) {
592
    $title = $complete_form['#node']->title;
593
  }
594
  $options = array('-' => t('Select rating'));
595
  for ($i = 1; $i <= $element['#stars']; $i++) {
596
    $this_value = ceil($i * 100/$element['#stars']);
597
    $options[$this_value] = t('Give @title @star/@count', array('@title' => $title, '@star' => $i, '@count' => $element['#stars']));
598
  }
599
  // Display clear button only if enabled.
600
  if ($element['#allow_clear'] == TRUE) {
601
    $options[0] = t('Cancel rating');
602
  }
603

    
604
  $element['vote'] = array(
605
    '#type' => 'select',
606
    '#options' => $options,
607
    '#required' => $element['#required'],
608
    '#attributes' => $element['#attributes'],
609
    '#theme' => _fivestar_allow_vote($element) ? 'fivestar_select' : 'fivestar_static',
610
    '#default_value' => _fivestar_get_element_default_value($element),
611
    '#weight' => -2,
612
  );
613

    
614
  if (isset($element['#parents'])) {
615
    $element['vote']['#parents'] = $element['#parents'];
616
  }
617

    
618
  switch ($settings['text']) {
619
    case 'user':
620
      $element['vote']['#description'] = theme('fivestar_summary', array(
621
        'user_rating' => $values['user'],
622
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
623
        'stars' => $settings['stars'],
624
        'microdata' => $settings['microdata'],
625
      ));
626
      $class[] = 'fivestar-user-text';
627
      break;
628
    case 'average':
629
      $element['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', array(
630
        'average_rating' => $values['average'],
631
        'votes' => $values['count'],
632
        'stars' => $settings['stars'],
633
        'microdata' => $settings['microdata'],
634
      ));
635
      $class[] = 'fivestar-average-text';
636
      break;
637
    case 'smart':
638
      $element['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', array(
639
        'user_rating' => $values['user'],
640
        'average_rating' => $values['user'] ? NULL : $values['average'],
641
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
642
        'stars' => $settings['stars'],
643
        'microdata' => $settings['microdata'],
644
      ));
645
      $class[] = 'fivestar-smart-text';
646
      $class[] = $values['user'] ? 'fivestar-user-text' : 'fivestar-average-text';
647
      break;
648
    case 'dual':
649
      $element['vote']['#description'] = theme('fivestar_summary', array(
650
        'user_rating' => $values['user'],
651
        'average_rating' => $settings['style'] == 'dual' ? NULL : $values['average'],
652
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
653
        'stars' => $settings['stars'],
654
        'microdata' => $settings['microdata'],
655
      ));
656
      $class[] = ' fivestar-combo-text';
657
      break;
658
    case 'none':
659
      $element['vote']['#description'] = NULL;
660
      break;
661
  }
662

    
663
  switch ($settings['style']) {
664
    case 'average':
665
      $class[] = 'fivestar-average-stars';
666
      break;
667
    case 'user':
668
      $class[] = 'fivestar-user-stars';
669
      break;
670
    case 'smart':
671
      $class[] = 'fivestar-smart-stars ' . ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
672
      break;
673
    case 'dual':
674
      $class[] = 'fivestar-combo-stars';
675
      $static_average = theme('fivestar_static', array(
676
        'rating' => $values['average'],
677
        'stars' => $settings['stars'],
678
        'tag' => $settings['tag'],
679
        'widget' => $settings['widget'],
680
      ));
681
      if ($settings['text'] != 'none') {
682
        $static_description = theme('fivestar_summary', array(
683
          'average_rating' => $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0),
684
          'votes' => isset($values['count']) ? $values['count'] : 0,
685
          'stars' => $settings['stars'],
686
        ));
687
      }
688
      else {
689
        $static_description = '&nbsp;';
690
      }
691
      $element['average'] = array(
692
        '#type' => 'markup',
693
        '#markup' => theme('fivestar_static_element', array(
694
          'star_display' => $static_average,
695
          'title' => '',
696
          'description' => $static_description,
697
        )),
698
        '#weight' => -1,
699
      );
700
      break;
701
  }
702
  $class[] = 'fivestar-form-item';
703
  $class[] = 'fivestar-' . $element['#widget']['name'];
704
  if ($element['#widget']['name'] != 'default') {
705
    $element['#attached']['css'][] = $element['#widget']['css'];
706
  }
707
  $element['#prefix'] = '<div ' . drupal_attributes(array('class' => $class)) . '>';
708
  $element['#suffix'] = '</div>';
709

    
710
  // Add AJAX handling if necessary.
711
  if (!empty($element['#auto_submit'])) {
712
    $element['vote']['#ajax'] = array(
713
      'callback' => 'fivestar_ajax_submit',
714
    );
715
    $element['vote']['#attached']['js'][] = $path . '/js/fivestar.ajax.js';
716
  }
717

    
718
  if (empty($element['#input'])) {
719
    $static_stars = theme('fivestar_static', array(
720
      'rating' => $element['vote']['#default_value'],
721
      'stars' => $settings['stars'],
722
      'tag' => $settings['tag'],
723
      'widget' => $settings['widget'],
724
    ));
725

    
726
    $element['vote'] = array(
727
      '#type' => 'markup',
728
      '#markup' => theme('fivestar_static_element', array(
729
        'star_display' => $static_stars,
730
        'title' => '',
731
        'description' => $element['vote']['#description'],
732
      )),
733
    );
734
  }
735

    
736
  // Add validation function that considers a 0 value as empty.
737
  $element['#element_validate'] = array('fivestar_validate');
738

    
739
  return $element;
740
}
741

    
742
/**
743
 * Helper function to get the correct default value for a fivestar element.
744
 *
745
 * @param $element
746
 *   The fivestar element
747
 * @return
748
 *   The default value for the element.
749
 */
750
function _fivestar_get_element_default_value($element) {
751
  if (isset($element['#default_value'])) {
752
    $default_value = $element['#default_value'];
753
  }
754
  else {
755
    switch ($element['#settings']['style']) {
756
      case 'average':
757
        $default_value = $element['#values']['average'];
758
        break;
759
      case 'user':
760
        $default_value = $element['#values']['user'];
761
        break;
762
      case 'smart':
763
        $default_value = (!empty($element['#values']['user']) ? $element['#values']['user'] : $element['#values']['average']);
764
        break;
765
      case 'dual':
766
        $default_value = $element['#values']['user'];
767
        break;
768
      default:
769
        $default_value = $element['#values']['average'];
770
    }
771
  }
772

    
773
  for ($i = 0; $i <= $element['#stars']; $i++) {
774
    $this_value = ceil($i * 100/$element['#stars']);
775
    $next_value = ceil(($i+1) * 100/$element['#stars']);
776

    
777
    // Round up the default value to the next exact star value if needed.
778
    if ($this_value < $default_value && $next_value > $default_value) {
779
      $default_value = $next_value;
780
    }
781
  }
782

    
783
  return $default_value;
784
}
785

    
786
/**
787
 * An #element_validate function for "fivestar" elements.
788
 */
789
function fivestar_validate($element, &$form_state) {
790
  if ($element['#required'] && (empty($element['#value']) || $element['#value'] == '-')) {
791
    form_error($element, t('@name field is required.', array('@name' => $element['#title'])));
792
  }
793
}
794

    
795
/**
796
 * Implementation of hook_votingapi_metadata_alter().
797
 */
798
function fivestar_votingapi_metadata_alter(&$data) {
799
  foreach (fivestar_get_tags() as $tag) {
800
    // Add several custom tags that are being used by fivestar.
801
    $data['tags'][$tag] = array(
802
      'name' => t($tag),
803
      'description' => t('@tag used by fivestar.', array('@tag' => $tag)),
804
      'module' => 'fivestar',
805
    );
806
  }
807
}
808

    
809
function fivestar_get_tags() {
810
  $tags_txt = variable_get('fivestar_tags', 'vote');
811
  $tags_exploded = explode(',', $tags_txt);
812

    
813
  $tags = array();
814
  $got_vote = FALSE;
815
  foreach ($tags_exploded as $tag) {
816
    $tag_trimmed = trim($tag);
817
    if ($tag_trimmed) {
818
      $tags[$tag_trimmed] = $tag_trimmed;
819
      if ($tag_trimmed == 'vote') {
820
        $got_vote = TRUE;
821
      }
822
    }
823
  }
824

    
825
  if (!$got_vote) {
826
    $tags['vote'] = 'vote';
827
  }
828
  return $tags;
829
}
830

    
831
function fivestar_get_targets($field, $instance, $key = FALSE, $entity = FALSE, $langcode = LANGUAGE_NONE) {
832
  $options = array();
833
  $targets = module_invoke_all('fivestar_target_info', $field, $instance);
834
  if ($key == FALSE) {
835
    foreach ($targets as $target => $info) {
836
      $options[$target] = $info['title'];
837
    }
838
    return $options;
839
  }
840
  else {
841
    if (isset($targets[$key]) && !empty($targets[$key]['callback']) && function_exists($targets[$key]['callback'])) {
842
      return call_user_func($targets[$key]['callback'], $entity, $field, $instance, $langcode);
843
    }
844
  }
845
}
846

    
847
/**
848
 * Implements hook_fivestar_target_info().
849
 */
850
function fivestar_fivestar_target_info($field, $instance) {
851
  $entity_type = $instance['entity_type'];
852
  $bundle = $instance['bundle'];
853
  $options = array(
854
    'none' => array(
855
      'title' => t('None'),
856
    ),
857
  );
858
  // Add node_referrence support.
859
  if (module_exists('node_reference')) {
860
    $field_names = array_keys(field_read_fields(array('module' => 'node_reference')));
861
    if (!empty($field_names)) {
862
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
863
      if (!empty($field_instances)) {
864
        foreach ($field_instances as $field_instance) {
865
          $options[$field_instance['field_name']] = array(
866
            'title' => t('Node reference: @field', array('@field' => $field_instance['field_name'])),
867
            'callback' => '_fivestar_target_node_reference'
868
          );
869
        }
870
      }
871
    }
872
  }
873

    
874
  // Add entityreference support.
875
  if (module_exists('entityreference')) {
876
    $field_names = array_keys(field_read_fields(array('module' => 'entityreference')));
877
    if (!empty($field_names)) {
878
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
879
      if (!empty($field_instances)) {
880
        foreach ($field_instances as $field_instance) {
881
          $options[$field_instance['field_name']] = array(
882
            'title' => t('Entity reference: @field', array('@field' => $field_instance['field_name'])),
883
            'callback' => '_fivestar_target_entityreference'
884
          );
885
        }
886
      }
887
    }
888
  }
889

    
890
  // Add user reference support.
891
  if (module_exists('user_reference')) {
892
    $field_names = array_keys(field_read_fields(array('module' => 'user_reference')));
893
    if (!empty($field_names)) {
894
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
895
      if (!empty($field_instances)) {
896
        foreach ($field_instances as $field_instance) {
897
          $options[$field_instance['field_name']] = array(
898
            'title' => t('User reference: @field', array('@field' => $field_instance['field_name'])),
899
            'callback' => '_fivestar_target_user_reference'
900
          );
901
        }
902
      }
903
    }
904
  }
905

    
906
  // Add comment module support.
907
  if ($instance['entity_type'] == 'comment') {
908
    $options['parent_node'] = array(
909
      'title' => t('Parent Node'),
910
      'callback' => '_fivestar_target_comment_parent_node'
911
    );
912
  }
913

    
914
  return $options;
915
}
916

    
917
/**
918
 *
919
 * @return array
920
 *   array('entity_type', 'entity_id').
921
 */
922
function _fivestar_target_node_reference($entity, $field, $instance, $langcode) {
923
  $target = array();
924

    
925
  $node_reference = $instance['settings']['target'];
926
  if (isset($entity->{$node_reference}[$langcode][0]) && is_numeric($entity->{$node_reference}[$langcode][0]['nid'])) {
927
    $target['entity_id'] = $entity->{$node_reference}[$langcode][0]['nid'];
928
    $target['entity_type'] = 'node';
929
  }
930

    
931
  return $target;
932
}
933

    
934
/**
935
 * @return (array) array('entity_type', 'entity_id')
936
 */
937
function _fivestar_target_entityreference($entity, $field, $instance, $langcode) {
938
  $target = array();
939

    
940
  $entityreference = $instance['settings']['target'];
941

    
942
  // Retrieve entity settings for the referenced field.
943
  $field_info = field_info_field($entityreference);
944

    
945
  if (isset($entity->{$entityreference}[$langcode][0]) && isset($entity->{$entityreference}[$langcode][0]['target_id']) && is_numeric($entity->{$entityreference}[$langcode][0]['target_id'])) {
946
    $target['entity_id'] = $entity->{$entityreference}[$langcode][0]['target_id'];
947
    $target['entity_type'] = $field_info['settings']['target_type'];
948
  }
949

    
950
  return $target;
951
}
952

    
953
/**
954
 *
955
 * @return array
956
 *   array('entity_type', 'entity_id').
957
 */
958
function _fivestar_target_user_reference($entity, $field, $instance, $langcode) {
959
  $target = array();
960

    
961
  $user_reference = $instance['settings']['target'];
962
  if (isset($entity->{$user_reference}[$langcode][0]) && is_numeric($entity->{$user_reference}[$langcode][0]['uid'])) {
963
    $target['entity_id'] = $entity->{$user_reference}[$langcode][0]['uid'];
964
    $target['entity_type'] = 'user';
965
  }
966

    
967
  return $target;
968
}
969

    
970
function _fivestar_target_comment_parent_node($entity, $field, $instance, $langcode) {
971
  return array(
972
    'entity_id' => $entity->nid,
973
    'entity_type' => 'node',
974
  );
975
}
976

    
977
/**
978
 * Helper function to determine if a user can vote on content.
979
 *
980
 * @return Boolean
981
 */
982
function _fivestar_allow_vote($element) {
983
  global $user;
984

    
985
  // Check allowed to re-vote.
986
  $can_revote = FALSE;
987
  if ($element['#allow_revote']) {
988
    $can_revote = TRUE;
989
  }
990
  else {
991
    $criteria = array(
992
      'entity_id' => isset($element['#settings']['content_id']) ? $element['#settings']['content_id'] : NULL,
993
      'entity_type' => isset($element['#settings']['content_type']) ? $element['#settings']['content_type'] : NULL,
994
      'uid' => $user->uid,
995
    );
996

    
997
  $can_revote = !votingapi_select_votes($criteria);
998
  }
999
  if (!$can_revote) {
1000
    return FALSE;
1001
  }
1002
  // Check allowed own vote.
1003
  if ($element['#allow_ownvote']) {
1004
    return TRUE;
1005
  }
1006
  // Check that we have entity details, allow if not.
1007
  if (!isset($element['#settings']['entity_id']) || !isset($element['#settings']['entity_type'])) {
1008
    return TRUE;
1009
  }
1010
  $entity_id = $element['#settings']['entity_id'];
1011
  $entity = entity_load($element['#settings']['entity_type'], array($entity_id));
1012
  $entity = $entity[$entity_id];
1013
  $uid1 = $entity->uid;
1014
  $uid2 = $user->uid;
1015
  return $entity->uid != $user->uid;
1016
}