Projet

Général

Profil

Paste
Télécharger (28,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / fivestar / fivestar.module @ 4853591f

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
  );
57

    
58
  return $mappings;
59
}
60

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

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

    
136
function _fivestar_variables() {
137
  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');
138
}
139

    
140
/**
141
 * Internal function to handle vote casting, flood control, XSS, IP based
142
 * voting, etc...
143
 */
144

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

    
159
    // Get the user's current vote.
160
    $criteria = array('entity_type' => $entity_type, 'entity_id' => $id, 'tag' => $tag, 'uid' => $uid);
161
    // Get the unique identifier for the user (IP Address if anonymous).
162
    $user_criteria = votingapi_current_user_identifier();
163
    $user_votes = votingapi_select_votes($criteria + $user_criteria);
164

    
165
    if ($value == 0) {
166
      votingapi_delete_votes($user_votes);
167
    }
168
    else {
169
      $votes = $criteria += array('value' => $value);
170
      votingapi_set_votes($votes);
171
    }
172

    
173
    // Moving the calculationg after saving/deleting the vote but before getting the votes.
174
    votingapi_recalculate_results($entity_type, $id);
175
    return fivestar_get_votes($entity_type, $id, $tag, $uid);
176
  }
177
}
178

    
179
/**
180
 * Utility function to retrieve VotingAPI votes.
181
 *
182
 * Note that this should not be used for general vote retrieval, instead the
183
 * VotingAPI function votingapi_select_results() should be used, which is more
184
 * efficient when retrieving multiple votes.
185
 *
186
 * @param $entity_type
187
 *   The Entity type for which to retrieve votes.
188
 * @param $id
189
 *   The ID for which to retrieve votes.
190
 * @param $tag
191
 *   The VotingAPI tag for which to retrieve votes.
192
 * @param $uid
193
 *   Optional. A user ID for which to retrieve votes.
194
 * @return
195
 *   An array of the following keys:
196
 *   - average: An array of VotingAPI results, including the average 'value'.
197
 *   - count: An array of VotingAPI results, including the count 'value'.
198
 *   - user: An array of VotingAPI results, including the user's vote 'value'.
199
 */
200
function fivestar_get_votes($entity_type, $id, $tag = 'vote', $uid = NULL) {
201
  global $user;
202

    
203
  if (!isset($uid)) {
204
    $uid = $user->uid;
205
  }
206

    
207
  $criteria = array(
208
    'entity_type' => $entity_type,
209
    'entity_id' => $id,
210
    'value_type' => 'percent',
211
    'tag' => $tag,
212
  );
213

    
214
  $votes = array(
215
    'average' => array(),
216
    'count' => array(),
217
    'user' => array(),
218
  );
219

    
220
  $results = votingapi_select_results($criteria);
221
  foreach ($results as $result) {
222
    if ($result['function'] == 'average') {
223
      $votes['average'] = $result;
224
    }
225
    if ($result['function'] == 'count') {
226
      $votes['count'] = $result;
227
    }
228
  }
229
  if ($uid) {
230
    $user_vote = votingapi_select_votes($criteria += array('uid' => $uid));
231
    if ($user_vote) {
232
      $votes['user'] = $user_vote[0];
233
      $votes['user']['function'] = 'user';
234
    }
235
  }
236
  else {
237
    // If the user is anonymous, we never bother loading their existing votes.
238
    // Not only would it be hit-or-miss, it would break page caching. Safer to always
239
    // show the 'fresh' version to anon users.
240
    $votes['user'] = array('value' => 0);
241
  }
242

    
243
  return $votes;
244
}
245

    
246
/**
247
 * Check that an item being voted upon is a valid vote.
248
 *
249
 * @param $entity_type
250
 *   Type of target.
251
 * @param $id
252
 *   Identifier within the type.
253
 * @param $tag
254
 *   The VotingAPI tag string.
255
 * @param $uid
256
 *   The user trying to cast the vote.
257
 *
258
 * @return boolean
259
 */
260
function fivestar_validate_target($entity_type, $id, $tag, $uid = NULL) {
261
  if (!isset($uid)) {
262
    $uid = $GLOBALS['user']->uid;
263
  }
264

    
265
  $access = module_invoke_all('fivestar_access', $entity_type, $id, $tag, $uid);
266
  foreach ($access as $result) {
267
    if ($result == TRUE) {
268
      return TRUE;
269
    }
270
    if ($result === FALSE) {
271
      return FALSE;
272
    }
273
  }
274
}
275

    
276
/**
277
 * Implementation of hook_fivestar_access().
278
 *
279
 * This hook is called before every vote is cast through Fivestar. It allows
280
 * modules to allow voting on any type of entity, such as nodes, users, or
281
 * comments.
282
 *
283
 * @param $entity_type
284
 *   Type entity.
285
 * @param $id
286
 *   Identifier within the type.
287
 * @param $tag
288
 *   The VotingAPI tag string.
289
 * @param $uid
290
 *   The user ID trying to cast the vote.
291
 *
292
 * @return boolean or NULL
293
 *   Returns TRUE if voting is supported on this object.
294
 *   Returns NULL if voting is not supported on this object by this module.
295
 *   If needing to absolutely deny all voting on this object, regardless
296
 *   of permissions defined in other modules, return FALSE. Note if all
297
 *   modules return NULL, stating no preference, then access will be denied.
298
 */
299
function fivestar_fivestar_access($entity_type, $id, $tag, $uid) {
300
  // Check to see if there is a field instance on this entity.
301
  $fields = field_read_fields(array('module' => 'fivestar'));
302
  foreach ($fields as $field) {
303
    if ($field['settings']['axis'] == $tag) {
304
      $params = array(
305
        'entity_type' => $entity_type,
306
        'field_name' => $field['field_name'],
307
      );
308
      $instance = field_read_instances($params);
309
      if (!empty($instance)) {
310
        return TRUE;
311
      }
312
    }
313
  }
314
}
315

    
316
/**
317
 * Implementation of hook_form_comment_form_alter().
318
 *
319
 * This hook removes the parent node, together with the fivestar field, from
320
 * the comment preview page. If this is left in, when the user presses the
321
 * "Save" button after the preview page has been displayed, the fivestar widget
322
 * gets the input rather than the comment; the user's input is lost. Based on a
323
 * suggestion by ChristianAdamski in issue 1289832-3.
324
 */
325
function fivestar_form_comment_form_alter(&$form, &$form_state, $form_id) {
326
  $is_fivestar = FALSE;
327
  if (isset($form['comment_output_below'])) {
328
    foreach($form['comment_output_below'] as $key => $value) {
329
      if (is_array($value) && !empty($value['#field_type'])) {
330
        if ($value['#field_type'] =='fivestar') {
331
          $is_fivestar = TRUE;
332
          $fivestar_key = $key;
333
        }
334
      }
335
    }
336
  }
337
  if ($is_fivestar) {
338
    unset($form['comment_output_below'][$fivestar_key]);
339
  }
340
}
341

    
342
/**
343
 * Implementation of hook_fivestar_widgets().
344
 *
345
 * This hook allows other modules to create additional custom widgets for
346
 * the fivestar module.
347
 *
348
 * @return array
349
 *   An array of key => value pairs suitable for inclusion as the #options in a
350
 *   select or radios form element. Each key must be the location of a css
351
 *   file for a fivestar widget. Each value should be the name of the widget.
352
 */
353
function fivestar_fivestar_widgets() {
354
  $files = &drupal_static(__FUNCTION__);
355
  if (!isset($files)) {
356
    $widgets_directory = drupal_get_path('module', 'fivestar') . '/widgets';
357
    $files = file_scan_directory($widgets_directory, '/\.css$/');
358
  }
359
  $widgets = array();
360
  foreach ($files as $file) {
361
    if (strpos($file->filename, '-rtl.css') === FALSE) {
362
      $widgets[$file->uri] = drupal_ucfirst(str_replace('-color', '', $file->name));
363
    }
364
  }
365
  return $widgets;
366
}
367

    
368
/**
369
 * Form builder; Build a custom Fivestar rating widget with arbitrary settings.
370
 *
371
 * This function is usually not called directly, instead call
372
 * drupal_get_form('fivestar_custom_widget', $values, $settings) when wanting
373
 * to display a widget.
374
 *
375
 * @param $form_state
376
 *   The form state provided by Form API.
377
 * @param $values
378
 *   An array of current vote values from 0 to 100, with the following array
379
 *   keys:
380
 *   - user: The user's current vote.
381
 *   - average: The average vote value.
382
 *   - count: The total number of votes so far on this content.
383
 * @param $settings
384
 *   An array of settings that configure the properties of the rating widget.
385
 *   Available keys for the settings include:
386
 *   - content_type: The type of content which will be voted upon.
387
 *   - content_id: The content ID which will be voted upon.
388
 *   - stars: The number of stars to display in this widget, from 2 to 10.
389
 *     Defaults to 5.
390
 *   - autosubmit: Whether the form should be submitted upon star selection.
391
 *     Defaults to TRUE.
392
 *   - allow_clear: Whether or not to show the "Clear current vote" icon when
393
 *     showing the widget. Defaults to FALSE.
394
 *   - required: Whether this field is required before the form can be
395
 *     submitted. Defaults to FALSE.
396
 *   - tag: The VotingAPI tag that will be registered by this widget. Defaults
397
 *     to "vote".
398
 */
399
function fivestar_custom_widget($form, &$form_state, $values, $settings) {
400
  $form = array(
401
    '#attributes' => array(
402
      'class' => array('fivestar-widget')
403
    ),
404
  );
405
  $form['#submit'][] = 'fivestar_form_submit';
406

    
407
  $form_state['settings'] = $settings;
408

    
409
  $form['vote'] = array(
410
    '#type' => 'fivestar',
411
    '#stars' => $settings['stars'],
412
    '#auto_submit' => isset($settings['autosubmit']) ? $settings['autosubmit'] : TRUE,
413
    '#allow_clear' => $settings['allow_clear'],
414
    '#required' => isset($settings['required']) ? $settings['required'] : FALSE,
415
    '#widget' => isset($settings['widget']) ? $settings['widget'] : array('name' => 'default', 'css' => 'default'),
416
    '#values' => $values,
417
    '#settings' => $settings,
418
    '#description' => $settings['description'],
419
  );
420

    
421
  $form['fivestar_submit'] = array(
422
    '#type' => 'submit',
423
    '#value' => t('Rate'),
424
    '#attributes' => array('class' => array('fivestar-submit')),
425
  );
426

    
427
  return $form;
428
}
429

    
430
/**
431
 * Submit handler for the above form (non-javascript version).
432
 */
433
function fivestar_form_submit($form, &$form_state) {
434
  // Cast the vote.
435
  _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
436

    
437
  // Set a message that the vote was received.
438
  if ($form_state['values']['vote'] === '0') {
439
    drupal_set_message(t('Your vote has been cleared.'));
440
  }
441
  elseif (is_numeric($form_state['values']['vote'])) {
442
    drupal_set_message(t('Thank you for your vote.'));
443
  }
444
}
445

    
446
/**
447
 * AJAX submit handler for fivestar_custom_widget
448
 */
449
function fivestar_ajax_submit($form, $form_state) {
450
  if (!empty($form_state['settings']['content_id'])) {
451
    _fivestar_update_field_value($form_state['settings']['content_type'], $form_state['settings']['entity'], $form_state['settings']['field_name'], $form_state['settings']['langcode'], $form_state['values']['vote']);
452
    $votes = _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
453
  }
454

    
455

    
456
  $values = array();
457
  $values['user'] = isset($votes['user']['value']) ? $votes['user']['value'] : 0;
458
  $values['average'] = isset($votes['average']['value']) ? $votes['average']['value'] : 0;
459
  $values['count'] = isset($votes['count']['value']) ? $votes['count']['value'] : 0;
460

    
461
  // We need to process the 'fivestar' element with the new values.
462
  $form['vote']['#values'] = $values;
463
  $new_element = fivestar_expand($form['vote']);
464
  // Update the description. Since it doesn't account of our cast vote above.
465
  // TODO: Look into all this, I honestly don't like it and it's a bit weird.
466
  $form['vote']['vote']['#description'] = $new_element['vote']['#description'];
467

    
468
  return array(
469
    '#type' => 'ajax',
470
    '#commands' => array(
471
      array(
472
        'command' => 'fivestarUpdate',
473
        'data' => drupal_render($form['vote']),
474
      ),
475
    ),
476
  );
477
}
478

    
479
/**
480
 * Implementation of hook_elements().
481
 *
482
 * Defines 'fivestar' form element type
483
 */
484
function fivestar_element_info() {
485
  $type['fivestar'] = array(
486
    '#input' => TRUE,
487
    '#stars' => 5,
488
    '#allow_clear' => FALSE,
489
    '#auto_submit' => FALSE,
490
    '#process' => array('fivestar_expand'),
491
    '#theme_wrappers' => array('form_element'),
492
    '#widget' => array(
493
      'name' => 'default',
494
      'css' => 'default',
495
    ),
496
    '#values' => array(
497
      'user' => 0,
498
      'average' => 0,
499
      'count' => 0,
500
    ),
501
    '#settings' => array(
502
      'style' => 'user',
503
      'text' => 'none',
504
    ),
505
  );
506
  return $type;
507
}
508

    
509
/**
510
 * Process callback for fivestar_element -- see fivestar_element()
511
 */
512
function fivestar_expand($element) {
513
  // Add CSS and JS
514
  $path = drupal_get_path('module', 'fivestar');
515
  $element['#attached']['js'][] = $path . '/js/fivestar.js';
516
  $element['#attached']['css'][] = $path . '/css/fivestar.css';
517
  $settings = $element['#settings'];
518
  $values = $element['#values'];
519
  $class[] = 'clearfix';
520

    
521
  $options = array('-' => t('Select rating'));
522
  for ($i = 1; $i <= $element['#stars']; $i++) {
523
    $this_value = ceil($i * 100/$element['#stars']);
524
    $options[$this_value] = t('Give it @star/@count', array('@star' => $i, '@count' => $element['#stars']));
525
  }
526
  // Display clear button only if enabled.
527
  if ($element['#allow_clear'] == TRUE) {
528
    $options[0] = t('Cancel rating');
529
  }
530

    
531
  $element['vote'] = array(
532
    '#type' => 'select',
533
    '#options' => $options,
534
    '#required' => $element['#required'],
535
    '#theme' => 'fivestar_select',
536
    '#attributes' => $element['#attributes'],
537
    '#default_value' => _fivestar_get_element_default_value($element),
538
    '#weight' => -2,
539
  );
540

    
541
  if (isset($element['#parents'])) {
542
    $element['vote']['#parents'] = $element['#parents'];
543
  }
544

    
545
  switch ($settings['text']) {
546
    case 'user':
547
      $element['vote']['#description'] = theme('fivestar_summary', array(
548
        'user_rating' => $values['user'],
549
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
550
        'stars' => $settings['stars'],
551
        'microdata' => $settings['microdata'],
552
      ));
553
      $class[] = 'fivestar-user-text';
554
      break;
555
    case 'average':
556
      $element['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', array(
557
        'average_rating' => $values['average'],
558
        'votes' => $values['count'],
559
        'stars' => $settings['stars'],
560
        'microdata' => $settings['microdata'],
561
      ));
562
      $class[] = 'fivestar-average-text';
563
      break;
564
    case 'smart':
565
      $element['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', array(
566
        'user_rating' => $values['user'],
567
        'average_rating' => $values['user'] ? NULL : $values['average'],
568
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
569
        'stars' => $settings['stars'],
570
        'microdata' => $settings['microdata'],
571
      ));
572
      $class[] = 'fivestar-smart-text';
573
      $class[] = $values['user'] ? 'fivestar-user-text' : 'fivestar-average-text';
574
      break;
575
    case 'dual':
576
      $element['vote']['#description'] = theme('fivestar_summary', array(
577
        'user_rating' => $values['user'],
578
        'average_rating' => $settings['style'] == 'dual' ? NULL : $values['average'],
579
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
580
        'stars' => $settings['stars'],
581
        'microdata' => $settings['microdata'],
582
      ));
583
      $class[] = ' fivestar-combo-text';
584
      break;
585
  }
586

    
587
  switch ($settings['style']) {
588
    case 'average':
589
      $class[] = 'fivestar-average-stars';
590
      break;
591
    case 'user':
592
      $class[] = 'fivestar-user-stars';
593
      break;
594
    case 'smart':
595
      $class[] = 'fivestar-smart-stars ' . ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
596
      break;
597
    case 'dual':
598
      $class[] = 'fivestar-combo-stars';
599
      $static_average = theme('fivestar_static', array(
600
        'rating' => $values['average'],
601
        'stars' => $settings['stars'],
602
        'tag' => $settings['tag'],
603
        'widget' => $settings['widget'],
604
      ));
605
      if ($settings['text'] != 'none') {
606
        $static_description = theme('fivestar_summary', array(
607
          'average_rating' => $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0),
608
          'votes' => isset($values['count']) ? $values['count'] : 0,
609
          'stars' => $settings['stars'],
610
        ));
611
      }
612
      else {
613
        $static_description = '&nbsp;';
614
      }
615
      $element['average'] = array(
616
        '#type' => 'markup',
617
        '#markup' => theme('fivestar_static_element', array(
618
          'star_display' => $static_average,
619
          'title' => '',
620
          'description' => $static_description,
621
        )),
622
        '#weight' => -1,
623
      );
624
      break;
625
  }
626
  $class[] = 'fivestar-form-item';
627
  $class[] = 'fivestar-' . $element['#widget']['name'];
628
  if ($element['#widget']['name'] != 'default') {
629
    $element['#attached']['css'][] = $element['#widget']['css'];
630
  }
631
  $element['#prefix'] = '<div ' . drupal_attributes(array('class' => $class)) . '>';
632
  $element['#suffix'] = '</div>';
633

    
634
  // Add AJAX handling if necessary.
635
  if (!empty($element['#auto_submit'])) {
636
    $element['vote']['#ajax'] = array(
637
      'callback' => 'fivestar_ajax_submit',
638
    );
639
    $element['vote']['#attached']['js'][] = $path . '/js/fivestar.ajax.js';
640
  }
641

    
642
  if (empty($element['#input'])) {
643
    $static_stars = theme('fivestar_static', array(
644
      'rating' => $element['vote']['#default_value'],
645
      'stars' => $settings['stars'],
646
      'tag' => $settings['tag'],
647
      'widget' => $settings['widget'],
648
    ));
649

    
650
    $element['vote'] = array(
651
      '#type' => 'markup',
652
      '#markup' => theme('fivestar_static_element', array(
653
        'star_display' => $static_stars,
654
        'title' => '',
655
        'description' => $element['vote']['#description'],
656
      )),
657
    );
658
  }
659

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

    
663
  return $element;
664
}
665

    
666
/**
667
 * Helper function to get the correct default value for a fivestar element.
668
 *
669
 * @param $element
670
 *   The fivestar element
671
 * @return
672
 *   The default value for the element.
673
 */
674
function _fivestar_get_element_default_value($element) {
675
  if (isset($element['#default_value'])) {
676
    $default_value = $element['#default_value'];
677
  }
678
  else {
679
    switch ($element['#settings']['style']) {
680
      case 'average':
681
        $default_value = $element['#values']['average'];
682
        break;
683
      case 'user':
684
        $default_value = $element['#values']['user'];
685
        break;
686
      case 'smart':
687
        $default_value = (!empty($element['#values']['user']) ? $element['#values']['user'] : $element['#values']['average']);
688
        break;
689
      case 'dual':
690
        $default_value = $element['#values']['user'];
691
        break;
692
      default:
693
        $default_value = $element['#values']['average'];
694
    }
695
  }
696

    
697
  for ($i = 0; $i <= $element['#stars']; $i++) {
698
    $this_value = ceil($i * 100/$element['#stars']);
699
    $next_value = ceil(($i+1) * 100/$element['#stars']);
700

    
701
    // Round up the default value to the next exact star value if needed.
702
    if ($this_value < $default_value && $next_value > $default_value) {
703
      $default_value = $next_value;
704
    }
705
  }
706

    
707
  return $default_value;
708
}
709

    
710
/**
711
 * An #element_validate function for "fivestar" elements.
712
 */
713
function fivestar_validate($element, &$form_state) {
714
  if ($element['#required'] && (empty($element['#value']) || $element['#value'] == '-')) {
715
    form_error($element, t('@name field is required.', array('@name' => $element['#title'])));
716
  }
717
}
718

    
719
/**
720
 * Implementation of hook_votingapi_metadata_alter().
721
 */
722
function fivestar_votingapi_metadata_alter(&$data) {
723
  foreach (fivestar_get_tags() as $tag) {
724
    // Add several custom tags that are being used by fivestar.
725
    $data['tags'][$tag] = array(
726
      'name' => t($tag),
727
      'description' => t('@tag used by fivestar.', array('@tag' => $tag)),
728
      'module' => 'fivestar',
729
    );
730
  }
731
}
732

    
733
function fivestar_get_tags() {
734
  $tags_txt = variable_get('fivestar_tags', 'vote');
735
  $tags_exploded = explode(',', $tags_txt);
736

    
737
  $tags = array();
738
  $got_vote = FALSE;
739
  foreach ($tags_exploded as $tag) {
740
    $tag_trimmed = trim($tag);
741
    if ($tag_trimmed) {
742
      $tags[$tag_trimmed] = $tag_trimmed;
743
      if ($tag_trimmed == 'vote') {
744
        $got_vote = TRUE;
745
      }
746
    }
747
  }
748

    
749
  if (!$got_vote) {
750
    $tags['vote'] = 'vote';
751
  }
752
  return $tags;
753
}
754

    
755
function fivestar_get_targets($field, $instance, $key = FALSE, $entity = FALSE, $langcode = LANGUAGE_NONE) {
756
  $options = array();
757
  $targets = module_invoke_all('fivestar_target_info', $field, $instance);
758
  if ($key == FALSE) {
759
    foreach ($targets as $target => $info) {
760
      $options[$target] = $info['title'];
761
    }
762
    return $options;
763
  }
764
  else {
765
    if (isset($targets[$key]) && !empty($targets[$key]['callback']) && function_exists($targets[$key]['callback'])) {
766
      return call_user_func($targets[$key]['callback'], $entity, $field, $instance, $langcode);
767
    }
768
  }
769
}
770

    
771
/**
772
 * Implements hook_fivestar_target_info().
773
 */
774
function fivestar_fivestar_target_info($field, $instance) {
775
  $entity_type = $instance['entity_type'];
776
  $bundle = $instance['bundle'];
777
  $options = array(
778
    'none' => array(
779
      'title' => t('None'),
780
    ),
781
  );
782
  // Add node_referrence support.
783
  if (module_exists('node_reference')) {
784
    $field_names = array_keys(field_read_fields(array('module' => 'node_reference')));
785
    if (!empty($field_names)) {
786
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
787
      if (!empty($field_instances)) {
788
        foreach ($field_instances as $field_instance) {
789
          $options[$field_instance['field_name']] = array(
790
            'title' => t('Node reference: @field', array('@field' => $field_instance['field_name'])),
791
            'callback' => '_fivestar_target_node_reference'
792
          );
793
        }
794
      }
795
    }
796
  }
797

    
798
  // Add entityreference support.
799
  if (module_exists('entityreference')) {
800
    $field_names = array_keys(field_read_fields(array('module' => 'entityreference')));
801
    if (!empty($field_names)) {
802
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
803
      if (!empty($field_instances)) {
804
        foreach ($field_instances as $field_instance) {
805
          $options[$field_instance['field_name']] = array(
806
            'title' => t('Entity reference: @field', array('@field' => $field_instance['field_name'])),
807
            'callback' => '_fivestar_target_entityreference'
808
          );
809
        }
810
      }
811
    }
812
  }
813

    
814
  // Add user reference support.
815
  if (module_exists('user_reference')) {
816
    $field_names = array_keys(field_read_fields(array('module' => 'user_reference')));
817
    if (!empty($field_names)) {
818
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
819
      if (!empty($field_instances)) {
820
        foreach ($field_instances as $field_instance) {
821
          $options[$field_instance['field_name']] = array(
822
            'title' => t('User reference: @field', array('@field' => $field_instance['field_name'])),
823
            'callback' => '_fivestar_target_user_reference'
824
          );
825
        }
826
      }
827
    }
828
  }
829

    
830
  // Add comment module support.
831
  if ($instance['entity_type'] == 'comment') {
832
    $options['parent_node'] = array(
833
      'title' => t('Parent Node'),
834
      'callback' => '_fivestar_target_comment_parent_node'
835
    );
836
  }
837

    
838
  return $options;
839
}
840

    
841
/**
842
 *
843
 * @return array
844
 *   array('entity_type', 'entity_id').
845
 */
846
function _fivestar_target_node_reference($entity, $field, $instance, $langcode) {
847
  $target = array();
848

    
849
  $node_reference = $instance['settings']['target'];
850
  if (isset($entity->{$node_reference}[$langcode][0]) && is_numeric($entity->{$node_reference}[$langcode][0]['nid'])) {
851
    $target['entity_id'] = $entity->{$node_reference}[$langcode][0]['nid'];
852
    $target['entity_type'] = 'node';
853
  }
854

    
855
  return $target;
856
}
857

    
858
/**
859
 * @return (array) array('entity_type', 'entity_id')
860
 */
861
function _fivestar_target_entityreference($entity, $field, $instance, $langcode) {
862
  $target = array();
863

    
864
  $entityreference = $instance['settings']['target'];
865

    
866
  // Retrieve entity settings for the referenced field.
867
  $field_info = field_info_field($entityreference);
868

    
869
  if (isset($entity->{$entityreference}[$langcode][0]) && isset($entity->{$entityreference}[$langcode][0]['target_id']) && is_numeric($entity->{$entityreference}[$langcode][0]['target_id'])) {
870
    $target['entity_id'] = $entity->{$entityreference}[$langcode][0]['target_id'];
871
    $target['entity_type'] = $field_info['settings']['target_type'];
872
  }
873

    
874
  return $target;
875
}
876

    
877
/**
878
 *
879
 * @return array
880
 *   array('entity_type', 'entity_id').
881
 */
882
function _fivestar_target_user_reference($entity, $field, $instance, $langcode) {
883
  $target = array();
884

    
885
  $user_reference = $instance['settings']['target'];
886
  if (isset($entity->{$user_reference}[$langcode][0]) && is_numeric($entity->{$user_reference}[$langcode][0]['uid'])) {
887
    $target['entity_id'] = $entity->{$user_reference}[$langcode][0]['uid'];
888
    $target['entity_type'] = 'user';
889
  }
890

    
891
  return $target;
892
}
893

    
894
function _fivestar_target_comment_parent_node($entity, $field, $instance, $langcode) {
895
  return array(
896
    'entity_id' => $entity->nid,
897
    'entity_type' => 'node',
898
  );
899
}