Projet

Général

Profil

Paste
Télécharger (31,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / fivestar / fivestar.module @ 87dbc3bf

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('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
  $is_fivestar = FALSE;
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'])) {
357
        if ($value['#field_type'] =='fivestar') {
358
          $is_fivestar = TRUE;
359
          $fivestar_key = $key;
360
        }
361
      }
362
    }
363
  }
364
  if ($is_fivestar) {
365
    unset($form['comment_output_below'][$fivestar_key]);
366
  }
367
}
368

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

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

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

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

    
443
  $form['vote'] = array(
444
    '#type' => 'fivestar',
445
    '#stars' => $settings['stars'],
446
    '#auto_submit' => isset($settings['autosubmit']) ? $settings['autosubmit'] : TRUE,
447
    '#allow_clear' => $settings['allow_clear'],
448
    '#allow_revote' => $settings['allow_revote'],
449
    '#allow_ownvote' => $settings['allow_ownvote'],
450
    '#required' => isset($settings['required']) ? $settings['required'] : FALSE,
451
    '#widget' => isset($settings['widget']) ? $settings['widget'] : array('name' => 'default', 'css' => 'default'),
452
    '#values' => $values,
453
    '#settings' => $settings,
454
    '#description' => $settings['description'],
455
  );
456

    
457
  $form['fivestar_submit'] = array(
458
    '#type' => 'submit',
459
    '#value' => t('Rate'),
460
    '#attributes' => array('class' => array('fivestar-submit')),
461
  );
462

    
463
  return $form;
464
}
465

    
466
/**
467
 * Submit handler for the above form (non-javascript version).
468
 */
469
function fivestar_form_submit($form, &$form_state) {
470

    
471
  // Cast the vote.
472
  _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
473

    
474
  // Set a message that the vote was received.
475
  if ($form_state['values']['vote'] === '0') {
476
    drupal_set_message(t('Your vote has been cleared.'));
477
  }
478
  elseif (is_numeric($form_state['values']['vote'])) {
479
    drupal_set_message(t('Thank you for your vote.'));
480
  }
481
}
482

    
483
/**
484
 * AJAX submit handler for fivestar_custom_widget
485
 */
486
function fivestar_ajax_submit($form, $form_state) {
487
  if (!empty($form_state['settings']['content_id'])) {
488
    $entity = entity_load($form_state['settings']['entity_type'], array($form_state['settings']['entity_id']));
489
    $entity = reset($entity);
490
    _fivestar_update_field_value($form_state['settings']['content_type'], $entity, $form_state['settings']['field_name'], $form_state['settings']['langcode'], $form_state['values']['vote']);
491
    $votes = _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
492
  }
493

    
494
  $values = array();
495
  $values['user'] = isset($votes['user']['value']) ? $votes['user']['value'] : 0;
496
  $values['average'] = isset($votes['average']['value']) ? $votes['average']['value'] : 0;
497
  $values['count'] = isset($votes['count']['value']) ? $votes['count']['value'] : 0;
498

    
499
  // We need to process the 'fivestar' element with the new values.
500
  $form['vote']['#values'] = $values;
501
  $new_element = fivestar_expand($form['vote']);
502
  // Update the description. Since it doesn't account of our cast vote above.
503
  // TODO: Look into all this, I honestly don't like it and it's a bit weird.
504
  $form['vote']['vote']['#description'] = isset($new_element['vote']['#description']) ? $new_element['vote']['#description'] : '';
505

    
506
  return array(
507
    '#type' => 'ajax',
508
    '#commands' => array(
509
      array(
510
        'command' => 'fivestarUpdate',
511
        'data' => drupal_render($form['vote']),
512
      ),
513
    ),
514
  );
515
}
516

    
517
/**
518
 * Implementation of hook_elements().
519
 *
520
 * Defines 'fivestar' form element type
521
 */
522
function fivestar_element_info() {
523
  $type['fivestar'] = array(
524
    '#input' => TRUE,
525
    '#stars' => 5,
526
    '#allow_clear' => FALSE,
527
    '#allow_revote' => FALSE,
528
    '#allow_ownvote' => FALSE,
529
    '#auto_submit' => FALSE,
530
    '#process' => array('fivestar_expand'),
531
    '#theme_wrappers' => array('form_element'),
532
    '#widget' => array(
533
      'name' => 'default',
534
      'css' => 'default',
535
    ),
536
    '#values' => array(
537
      'user' => 0,
538
      'average' => 0,
539
      'count' => 0,
540
    ),
541
    '#settings' => array(
542
      'style' => 'user',
543
      'text' => 'none',
544
    ),
545
  );
546
  return $type;
547
}
548

    
549
/**
550
 * Process callback for fivestar_element -- see fivestar_element()
551
 */
552
function fivestar_expand($element) {
553
  if (!_fivestar_allow_vote($element)){
554
    $element['#input'] = FALSE;
555
  }
556

    
557
  // Add CSS and JS
558
  $path = drupal_get_path('module', 'fivestar');
559
  $element['#attached']['js'][] = $path . '/js/fivestar.js';
560
  $element['#attached']['css'][] = $path . '/css/fivestar.css';
561
  $settings = $element['#settings'];
562
  $values = $element['#values'];
563
  $class[] = 'clearfix';
564

    
565
  $options = array('-' => t('Select rating'));
566
  for ($i = 1; $i <= $element['#stars']; $i++) {
567
    $this_value = ceil($i * 100/$element['#stars']);
568
    $options[$this_value] = t('Give it @star/@count', array('@star' => $i, '@count' => $element['#stars']));
569
  }
570
  // Display clear button only if enabled.
571
  if ($element['#allow_clear'] == TRUE) {
572
    $options[0] = t('Cancel rating');
573
  }
574

    
575
  $element['vote'] = array(
576
    '#type' => 'select',
577
    '#options' => $options,
578
    '#required' => $element['#required'],
579
    '#attributes' => $element['#attributes'],
580
    '#theme' => _fivestar_allow_vote($element) ? 'fivestar_select' : 'fivestar_static',
581
    '#default_value' => _fivestar_get_element_default_value($element),
582
    '#weight' => -2,
583
  );
584

    
585
  if (isset($element['#parents'])) {
586
    $element['vote']['#parents'] = $element['#parents'];
587
  }
588

    
589
  switch ($settings['text']) {
590
    case 'user':
591
      $element['vote']['#description'] = theme('fivestar_summary', array(
592
        'user_rating' => $values['user'],
593
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
594
        'stars' => $settings['stars'],
595
        'microdata' => $settings['microdata'],
596
      ));
597
      $class[] = 'fivestar-user-text';
598
      break;
599
    case 'average':
600
      $element['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', array(
601
        'average_rating' => $values['average'],
602
        'votes' => $values['count'],
603
        'stars' => $settings['stars'],
604
        'microdata' => $settings['microdata'],
605
      ));
606
      $class[] = 'fivestar-average-text';
607
      break;
608
    case 'smart':
609
      $element['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', array(
610
        'user_rating' => $values['user'],
611
        'average_rating' => $values['user'] ? NULL : $values['average'],
612
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
613
        'stars' => $settings['stars'],
614
        'microdata' => $settings['microdata'],
615
      ));
616
      $class[] = 'fivestar-smart-text';
617
      $class[] = $values['user'] ? 'fivestar-user-text' : 'fivestar-average-text';
618
      break;
619
    case 'dual':
620
      $element['vote']['#description'] = theme('fivestar_summary', array(
621
        'user_rating' => $values['user'],
622
        'average_rating' => $settings['style'] == 'dual' ? NULL : $values['average'],
623
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
624
        'stars' => $settings['stars'],
625
        'microdata' => $settings['microdata'],
626
      ));
627
      $class[] = ' fivestar-combo-text';
628
      break;
629
    case 'none':
630
      $element['vote']['#description'] = NULL;
631
      break;
632
  }
633

    
634
  switch ($settings['style']) {
635
    case 'average':
636
      $class[] = 'fivestar-average-stars';
637
      break;
638
    case 'user':
639
      $class[] = 'fivestar-user-stars';
640
      break;
641
    case 'smart':
642
      $class[] = 'fivestar-smart-stars ' . ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
643
      break;
644
    case 'dual':
645
      $class[] = 'fivestar-combo-stars';
646
      $static_average = theme('fivestar_static', array(
647
        'rating' => $values['average'],
648
        'stars' => $settings['stars'],
649
        'tag' => $settings['tag'],
650
        'widget' => $settings['widget'],
651
      ));
652
      if ($settings['text'] != 'none') {
653
        $static_description = theme('fivestar_summary', array(
654
          'average_rating' => $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0),
655
          'votes' => isset($values['count']) ? $values['count'] : 0,
656
          'stars' => $settings['stars'],
657
        ));
658
      }
659
      else {
660
        $static_description = '&nbsp;';
661
      }
662
      $element['average'] = array(
663
        '#type' => 'markup',
664
        '#markup' => theme('fivestar_static_element', array(
665
          'star_display' => $static_average,
666
          'title' => '',
667
          'description' => $static_description,
668
        )),
669
        '#weight' => -1,
670
      );
671
      break;
672
  }
673
  $class[] = 'fivestar-form-item';
674
  $class[] = 'fivestar-' . $element['#widget']['name'];
675
  if ($element['#widget']['name'] != 'default') {
676
    $element['#attached']['css'][] = $element['#widget']['css'];
677
  }
678
  $element['#prefix'] = '<div ' . drupal_attributes(array('class' => $class)) . '>';
679
  $element['#suffix'] = '</div>';
680

    
681
  // Add AJAX handling if necessary.
682
  if (!empty($element['#auto_submit'])) {
683
    $element['vote']['#ajax'] = array(
684
      'callback' => 'fivestar_ajax_submit',
685
    );
686
    $element['vote']['#attached']['js'][] = $path . '/js/fivestar.ajax.js';
687
  }
688

    
689
  if (empty($element['#input'])) {
690
    $static_stars = theme('fivestar_static', array(
691
      'rating' => $element['vote']['#default_value'],
692
      'stars' => $settings['stars'],
693
      'tag' => $settings['tag'],
694
      'widget' => $settings['widget'],
695
    ));
696

    
697
    $element['vote'] = array(
698
      '#type' => 'markup',
699
      '#markup' => theme('fivestar_static_element', array(
700
        'star_display' => $static_stars,
701
        'title' => '',
702
        'description' => $element['vote']['#description'],
703
      )),
704
    );
705
  }
706

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

    
710
  return $element;
711
}
712

    
713
/**
714
 * Helper function to get the correct default value for a fivestar element.
715
 *
716
 * @param $element
717
 *   The fivestar element
718
 * @return
719
 *   The default value for the element.
720
 */
721
function _fivestar_get_element_default_value($element) {
722
  if (isset($element['#default_value'])) {
723
    $default_value = $element['#default_value'];
724
  }
725
  else {
726
    switch ($element['#settings']['style']) {
727
      case 'average':
728
        $default_value = $element['#values']['average'];
729
        break;
730
      case 'user':
731
        $default_value = $element['#values']['user'];
732
        break;
733
      case 'smart':
734
        $default_value = (!empty($element['#values']['user']) ? $element['#values']['user'] : $element['#values']['average']);
735
        break;
736
      case 'dual':
737
        $default_value = $element['#values']['user'];
738
        break;
739
      default:
740
        $default_value = $element['#values']['average'];
741
    }
742
  }
743

    
744
  for ($i = 0; $i <= $element['#stars']; $i++) {
745
    $this_value = ceil($i * 100/$element['#stars']);
746
    $next_value = ceil(($i+1) * 100/$element['#stars']);
747

    
748
    // Round up the default value to the next exact star value if needed.
749
    if ($this_value < $default_value && $next_value > $default_value) {
750
      $default_value = $next_value;
751
    }
752
  }
753

    
754
  return $default_value;
755
}
756

    
757
/**
758
 * An #element_validate function for "fivestar" elements.
759
 */
760
function fivestar_validate($element, &$form_state) {
761
  if ($element['#required'] && (empty($element['#value']) || $element['#value'] == '-')) {
762
    form_error($element, t('@name field is required.', array('@name' => $element['#title'])));
763
  }
764
}
765

    
766
/**
767
 * Implementation of hook_votingapi_metadata_alter().
768
 */
769
function fivestar_votingapi_metadata_alter(&$data) {
770
  foreach (fivestar_get_tags() as $tag) {
771
    // Add several custom tags that are being used by fivestar.
772
    $data['tags'][$tag] = array(
773
      'name' => t($tag),
774
      'description' => t('@tag used by fivestar.', array('@tag' => $tag)),
775
      'module' => 'fivestar',
776
    );
777
  }
778
}
779

    
780
function fivestar_get_tags() {
781
  $tags_txt = variable_get('fivestar_tags', 'vote');
782
  $tags_exploded = explode(',', $tags_txt);
783

    
784
  $tags = array();
785
  $got_vote = FALSE;
786
  foreach ($tags_exploded as $tag) {
787
    $tag_trimmed = trim($tag);
788
    if ($tag_trimmed) {
789
      $tags[$tag_trimmed] = $tag_trimmed;
790
      if ($tag_trimmed == 'vote') {
791
        $got_vote = TRUE;
792
      }
793
    }
794
  }
795

    
796
  if (!$got_vote) {
797
    $tags['vote'] = 'vote';
798
  }
799
  return $tags;
800
}
801

    
802
function fivestar_get_targets($field, $instance, $key = FALSE, $entity = FALSE, $langcode = LANGUAGE_NONE) {
803
  $options = array();
804
  $targets = module_invoke_all('fivestar_target_info', $field, $instance);
805
  if ($key == FALSE) {
806
    foreach ($targets as $target => $info) {
807
      $options[$target] = $info['title'];
808
    }
809
    return $options;
810
  }
811
  else {
812
    if (isset($targets[$key]) && !empty($targets[$key]['callback']) && function_exists($targets[$key]['callback'])) {
813
      return call_user_func($targets[$key]['callback'], $entity, $field, $instance, $langcode);
814
    }
815
  }
816
}
817

    
818
/**
819
 * Implements hook_fivestar_target_info().
820
 */
821
function fivestar_fivestar_target_info($field, $instance) {
822
  $entity_type = $instance['entity_type'];
823
  $bundle = $instance['bundle'];
824
  $options = array(
825
    'none' => array(
826
      'title' => t('None'),
827
    ),
828
  );
829
  // Add node_referrence support.
830
  if (module_exists('node_reference')) {
831
    $field_names = array_keys(field_read_fields(array('module' => 'node_reference')));
832
    if (!empty($field_names)) {
833
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
834
      if (!empty($field_instances)) {
835
        foreach ($field_instances as $field_instance) {
836
          $options[$field_instance['field_name']] = array(
837
            'title' => t('Node reference: @field', array('@field' => $field_instance['field_name'])),
838
            'callback' => '_fivestar_target_node_reference'
839
          );
840
        }
841
      }
842
    }
843
  }
844

    
845
  // Add entityreference support.
846
  if (module_exists('entityreference')) {
847
    $field_names = array_keys(field_read_fields(array('module' => 'entityreference')));
848
    if (!empty($field_names)) {
849
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
850
      if (!empty($field_instances)) {
851
        foreach ($field_instances as $field_instance) {
852
          $options[$field_instance['field_name']] = array(
853
            'title' => t('Entity reference: @field', array('@field' => $field_instance['field_name'])),
854
            'callback' => '_fivestar_target_entityreference'
855
          );
856
        }
857
      }
858
    }
859
  }
860

    
861
  // Add user reference support.
862
  if (module_exists('user_reference')) {
863
    $field_names = array_keys(field_read_fields(array('module' => 'user_reference')));
864
    if (!empty($field_names)) {
865
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
866
      if (!empty($field_instances)) {
867
        foreach ($field_instances as $field_instance) {
868
          $options[$field_instance['field_name']] = array(
869
            'title' => t('User reference: @field', array('@field' => $field_instance['field_name'])),
870
            'callback' => '_fivestar_target_user_reference'
871
          );
872
        }
873
      }
874
    }
875
  }
876

    
877
  // Add comment module support.
878
  if ($instance['entity_type'] == 'comment') {
879
    $options['parent_node'] = array(
880
      'title' => t('Parent Node'),
881
      'callback' => '_fivestar_target_comment_parent_node'
882
    );
883
  }
884

    
885
  return $options;
886
}
887

    
888
/**
889
 *
890
 * @return array
891
 *   array('entity_type', 'entity_id').
892
 */
893
function _fivestar_target_node_reference($entity, $field, $instance, $langcode) {
894
  $target = array();
895

    
896
  $node_reference = $instance['settings']['target'];
897
  if (isset($entity->{$node_reference}[$langcode][0]) && is_numeric($entity->{$node_reference}[$langcode][0]['nid'])) {
898
    $target['entity_id'] = $entity->{$node_reference}[$langcode][0]['nid'];
899
    $target['entity_type'] = 'node';
900
  }
901

    
902
  return $target;
903
}
904

    
905
/**
906
 * @return (array) array('entity_type', 'entity_id')
907
 */
908
function _fivestar_target_entityreference($entity, $field, $instance, $langcode) {
909
  $target = array();
910

    
911
  $entityreference = $instance['settings']['target'];
912

    
913
  // Retrieve entity settings for the referenced field.
914
  $field_info = field_info_field($entityreference);
915

    
916
  if (isset($entity->{$entityreference}[$langcode][0]) && isset($entity->{$entityreference}[$langcode][0]['target_id']) && is_numeric($entity->{$entityreference}[$langcode][0]['target_id'])) {
917
    $target['entity_id'] = $entity->{$entityreference}[$langcode][0]['target_id'];
918
    $target['entity_type'] = $field_info['settings']['target_type'];
919
  }
920

    
921
  return $target;
922
}
923

    
924
/**
925
 *
926
 * @return array
927
 *   array('entity_type', 'entity_id').
928
 */
929
function _fivestar_target_user_reference($entity, $field, $instance, $langcode) {
930
  $target = array();
931

    
932
  $user_reference = $instance['settings']['target'];
933
  if (isset($entity->{$user_reference}[$langcode][0]) && is_numeric($entity->{$user_reference}[$langcode][0]['uid'])) {
934
    $target['entity_id'] = $entity->{$user_reference}[$langcode][0]['uid'];
935
    $target['entity_type'] = 'user';
936
  }
937

    
938
  return $target;
939
}
940

    
941
function _fivestar_target_comment_parent_node($entity, $field, $instance, $langcode) {
942
  return array(
943
    'entity_id' => $entity->nid,
944
    'entity_type' => 'node',
945
  );
946
}
947

    
948
/**
949
 * Helper function to determine if a user can vote on content.
950
 *
951
 * @return Boolean
952
 */
953
function _fivestar_allow_vote($element) {
954
  global $user;
955

    
956
  // Check allowed to re-vote.
957
  $can_revote = FALSE;
958
  if ($element['#allow_revote']) {
959
    $can_revote = TRUE;
960
  }
961
  else {
962
    $criteria = array(
963
      'entity_id' => isset($element['#settings']['content_id']) ? $element['#settings']['content_id'] : NULL,
964
      'entity_type' => isset($element['#settings']['content_type']) ? $element['#settings']['content_type'] : NULL,
965
      'uid' => $user->uid,
966
    );
967

    
968
  $can_revote = !votingapi_select_votes($criteria);
969
  }
970
  if (!$can_revote) {
971
    return FALSE;
972
  }
973
  // Check allowed own vote.
974
  if ($element['#allow_ownvote']) {
975
    return TRUE;
976
  }
977
  // Check that we have entity details, allow if not.
978
  if (!isset($element['#settings']['entity_id']) || !isset($element['#settings']['entity_type'])) {
979
    return TRUE;
980
  }
981
  $entity_id = $element['#settings']['entity_id'];
982
  $entity = entity_load($element['#settings']['entity_type'], array($entity_id));
983
  $entity = $entity[$entity_id];
984
  $uid1 = $entity->uid;
985
  $uid2 = $user->uid;
986
  return $entity->uid != $user->uid;
987
}