Projet

Général

Profil

Paste
Télécharger (37,3 ko) Statistiques
| Branche: | Révision:

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

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
 * Implements hook_help().
12
 */
13
function fivestar_help($path, $arg) {
14
  switch ($path) {
15

    
16
    case 'admin/config/content/fivestar':
17
      $output = t('This page is used to configure site-wide features of the Fivestar module.');
18
      return $output;
19

    
20
    case 'admin/help#fivestar':
21
      $output = '';
22
      $output .= '<h3>' . t('About') . '</h3>';
23
      $output .= '<p>' . t('The <a href="@fivestar">Fivestar</a> voting module is a very simple rating module that provides the possibility to rate items with stars or similar items. This gives you the possibilities to rate various items or even to create online forms for evaluations and assessments with different questions to be answered.
24
For more information, see the online documentation for the <a href="@doco">Fivestar module</a>.',
25
      array(
26
        '@fivestar' => 'http://drupal.org/project/fivestar',
27
        '@doco' => 'https://drupal.org/handbook/modules/fivestar',
28
      )) . '</p>';
29
      $output .= '<h3>' . t('Uses') . '</h3>';
30
      $output .= '<dl>';
31
      $output .= '<dt>' . t('General') . '</dt>';
32
      $output .= '<dd>' . t('The Fivestar module can be used to easily rate various types of content on your website. These ratings can be used on the content itself or even from the comments of that piece of content.') . '</dd>';
33
      $output .= '<dt>' . t('Basic Concepts and Features') . '</dt>';
34
      $output .= '<dd>' . t('Fivestar is an excellent voting widget first made available for use on Drupal 5 websites. The D5 module included the ability to create a voting widget for nodes. With Drupal 6 came the ability to add comments. And with Drupal 7, the web developer was given the ability to create the voting widget with any entity.') . '</dd>';
35
      $output .= '<dt>' . t('Advanced Use Cases') . '</dt>';
36
      $output .= '<dd>' . t('There are many excellent resources online describing the many Fivestar features, such as the excellent article <a href="@features">"In-Depth features of Drupal Fivestar" </a>.',
37
        array(
38
          '@features' => 'https://www.greengeeks.com/kb/3233/drupal-fivestar/',
39
        )) . '</dd>';
40
      $output .= '</dl>';
41
      return $output;
42
  }
43
}
44

    
45
/**
46
 * Implements hook_menu().
47
 */
48
function fivestar_menu() {
49
  $items = array();
50
  $items['admin/config/content/fivestar'] = array(
51
    'title'             => 'Fivestar',
52
    'description'       => 'Configure site-wide widgets used for Fivestar rating.',
53
    'page callback'     => 'drupal_get_form',
54
    'page arguments'    => array('fivestar_settings'),
55
    'access callback'   => 'user_access',
56
    'access arguments'  => array('administer site configuration'),
57
    'type'              => MENU_NORMAL_ITEM,
58
    'file'              => 'includes/fivestar.admin.inc',
59
  );
60

    
61
  return $items;
62
}
63

    
64
/**
65
 * Implements hook_microdata_suggestions().
66
 */
67
function fivestar_microdata_suggestions() {
68
  $mappings = array();
69

    
70
  // Add the review mapping for Schema.org.
71
  $mappings['fields']['fivestar']['schema.org'] = array(
72
    '#itemprop' => array('aggregateRating'),
73
    '#is_item' => TRUE,
74
    '#itemtype' => array('http://schema.org/AggregateRating'),
75
    'average_rating' => array(
76
      '#itemprop' => array('ratingValue'),
77
    ),
78
    'rating_count' => array(
79
      '#itemprop' => array('ratingCount'),
80
    ),
81
  );
82

    
83
  return $mappings;
84
}
85

    
86
/**
87
 * Implements hook_permission().
88
 *
89
 * Exposes permissions for rating content.
90
 */
91
function fivestar_permission() {
92
  return array(
93
    'rate content' => array(
94
      'title' => t('Use Fivestar to rate content'),
95
    ),
96
  );
97
}
98

    
99
/**
100
 * Implements hook_theme().
101
 */
102
function fivestar_theme() {
103
  return array(
104
    // Fivestar theme functions.
105
    'fivestar' => array(
106
      'render element' => 'element',
107
      'file' => 'includes/fivestar.theme.inc',
108
    ),
109
    'fivestar_select' => array(
110
      'render element' => 'element',
111
      'file' => 'includes/fivestar.theme.inc',
112
    ),
113
    'fivestar_static' => array(
114
      'variables' => array('rating' => NULL, 'stars' => 5, 'tag' => 'vote', 'widget' => array('name' => 'default', 'css' => '')),
115
      'file' => 'includes/fivestar.theme.inc',
116
    ),
117
    'fivestar_static_element' => array(
118
      'variables' => array('star_display' => NULL, 'title' => NULL, 'description' => NULL),
119
      'file' => 'includes/fivestar.theme.inc',
120
    ),
121
    'fivestar_summary' => array(
122
      'variables' => array('user_rating' => NULL, 'average_rating' => NULL, 'votes' => 0, 'stars' => 5, 'microdata' => array()),
123
      'file' => 'includes/fivestar.theme.inc',
124
    ),
125
    // fivestar.admin.inc.
126
    'fivestar_preview' => array(
127
      'variables' => array('style' => NULL, 'text' => NULL, 'stars' => NULL, 'unvote' => NULL, 'title' => NULL),
128
      'file' => 'includes/fivestar.theme.inc',
129
    ),
130
    'fivestar_preview_widget' => array(
131
      'variables' => array('css' => NULL, 'name' => NULL),
132
      'file' => 'includes/fivestar.theme.inc',
133
    ),
134
    'fivestar_preview_wrapper' => array(
135
      'variables' => array('content' => NULL, 'type' => 'direct'),
136
      'file' => 'includes/fivestar.theme.inc',
137
    ),
138
    'fivestar_settings' => array(
139
      'render element' => 'form',
140
      'file' => 'includes/fivestar.theme.inc',
141
    ),
142
    'fivestar_color_form' => array(
143
      'render element' => 'form',
144
      'file' => 'includes/fivestar.theme.inc',
145
    ),
146
    'fivestar_formatter_default' => array(
147
      'render element' => 'element',
148
      'file' => 'includes/fivestar.theme.inc',
149
    ),
150
    'fivestar_formatter_rating' => array(
151
      'render element' => 'element',
152
      'file' => 'includes/fivestar.theme.inc',
153
    ),
154
    'fivestar_formatter_percentage' => array(
155
      'render element' => 'element',
156
      'file' => 'includes/fivestar.theme.inc',
157
    ),
158
  );
159
}
160

    
161
/**
162
 * Implements hook_views_api().
163
 */
164
function fivestar_views_api() {
165
  return array(
166
    'api' => 3,
167
    'path' => drupal_get_path('module', 'fivestar') . '/includes',
168
  );
169
}
170

    
171
/**
172
 *
173
 */
174
function _fivestar_variables() {
175
  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');
176
}
177

    
178
/**
179
 * Internal function to handle vote casting, flood control, XSS, IP based
180
 * voting, etc...
181
 */
182
function _fivestar_cast_vote($entity_type, $id, $value, $tag = NULL, $uid = NULL, $skip_validation = FALSE, $vote_source = NULL) {
183
  global $user;
184
  $tag = empty($tag) ? 'vote' : $tag;
185
  $uid = isset($uid) ? $uid : $user->uid;
186
  // Bail out if the user's trying to vote on an invalid object.
187
  if (!$skip_validation && !fivestar_validate_target($entity_type, $id, $tag, $uid)) {
188
    return array();
189
  }
190
  // Sanity-check the incoming values.
191
  if (is_numeric($id) && is_numeric($value)) {
192
    if ($value > 100) {
193
      $value = 100;
194
    }
195

    
196
    // Get the user's current vote.
197
    $criteria = array('entity_type' => $entity_type, 'entity_id' => $id, 'tag' => $tag, 'uid' => $uid);
198
    // Get the unique identifier for the user (IP Address if anonymous).
199
    if ($vote_source != NULL) {
200
      $user_criteria = array('vote_source' => $vote_source);
201
      $limit = 1;
202
    }
203
    else {
204
      $user_criteria = votingapi_current_user_identifier();
205
      $limit = 0;
206
    }
207
    $user_votes = votingapi_select_votes($criteria + $user_criteria, $limit);
208

    
209
    if ($value == 0) {
210
      votingapi_delete_votes($user_votes);
211
    }
212
    else {
213
      $votes = $criteria += array('value' => $value);
214
      votingapi_set_votes($votes);
215
    }
216

    
217
    // Moving the calculation after saving/deleting the vote but before
218
    // getting the votes.
219
    votingapi_recalculate_results($entity_type, $id);
220
    return fivestar_get_votes($entity_type, $id, $tag, $uid);
221
  }
222
}
223

    
224
/**
225
 * Utility function to retrieve VotingAPI votes.
226
 *
227
 * Note that this should not be used for general vote retrieval, instead the
228
 * VotingAPI function votingapi_select_results() should be used, which is more
229
 * efficient when retrieving multiple votes.
230
 *
231
 * @param $entity_type
232
 *   The Entity type for which to retrieve votes.
233
 * @param $id
234
 *   The ID for which to retrieve votes.
235
 * @param $tag
236
 *   The VotingAPI tag for which to retrieve votes.
237
 * @param $uid
238
 *   Optional. A user ID for which to retrieve votes.
239
 *
240
 * @return
241
 *   An array of the following keys:
242
 *   - average: An array of VotingAPI results, including the average 'value'.
243
 *   - count: An array of VotingAPI results, including the count 'value'.
244
 *   - user: An array of VotingAPI results, including the user's vote 'value'.
245
 */
246
function fivestar_get_votes_multiple($entity_type, $ids, $tag = 'vote', $uid = NULL) {
247
  global $user;
248

    
249
  if (!isset($uid)) {
250
    $uid = $user->uid;
251
  }
252

    
253
  $criteria = array(
254
    'entity_type' => $entity_type,
255
    'entity_id' => $ids,
256
    'value_type' => 'percent',
257
    'tag' => $tag,
258
  );
259

    
260
  $votes = array();
261

    
262
  // Initialize defaults.
263
  foreach ($ids as $id) {
264
    $votes[$entity_type][$id] = array(
265
      'average' => array(),
266
      'count' => array(),
267
      'user' => array(),
268
    );
269
  }
270

    
271
  $results = votingapi_select_results($criteria);
272
  $user_votes = array();
273
  if (!empty($results)) {
274
    foreach (votingapi_select_votes($criteria += array('uid' => $uid)) as $uv) {
275
      $user_votes[$uv['entity_type']][$uv['entity_id']] = $uv;
276
    }
277
  }
278

    
279
  foreach ($results as $result) {
280
    if ($result['function'] == 'average') {
281
      $votes[$result['entity_type']][$result['entity_id']]['average'] = $result;
282
    }
283
    if ($result['function'] == 'count') {
284
      $votes[$result['entity_type']][$result['entity_id']]['count'] = $result;
285
    }
286
    if ($uid) {
287
      if (!empty($user_votes[$result['entity_type']][$result['entity_id']])) {
288
        $votes[$result['entity_type']][$result['entity_id']]['user'] = $user_votes[$result['entity_type']][$result['entity_id']];
289
        $votes[$result['entity_type']][$result['entity_id']]['user']['function'] = 'user';
290
      }
291
    }
292
    else {
293
      // If the user is anonymous, we never bother loading their existing votes.
294
      // Not only would it be hit-or-miss, it would break page caching. Safer to
295
      // always show the 'fresh' version to anon users.
296
      $votes[$result['entity_type']][$result['entity_id']]['user'] = array('value' => 0);
297
    }
298
  }
299

    
300
  return $votes;
301
}
302

    
303
/**
304
 *
305
 */
306
function fivestar_get_votes($entity_type, $id, $tag = 'vote', $uid = NULL) {
307
  $multiple = fivestar_get_votes_multiple($entity_type, array($id), $tag, $uid);
308
  return $multiple[$entity_type][$id];
309
}
310

    
311
/**
312
 * Check that an item being voted upon is a valid vote.
313
 *
314
 * @param $entity_type
315
 *   Type of target.
316
 * @param $id
317
 *   Identifier within the type.
318
 * @param $tag
319
 *   The VotingAPI tag string.
320
 * @param $uid
321
 *   The user trying to cast the vote.
322
 *
323
 * @return bool
324
 */
325
function fivestar_validate_target($entity_type, $id, $tag, $uid = NULL) {
326
  if (!isset($uid)) {
327
    $uid = $GLOBALS['user']->uid;
328
  }
329

    
330
  $access = module_invoke_all('fivestar_access', $entity_type, $id, $tag, $uid);
331
  foreach ($access as $result) {
332
    if ($result == TRUE) {
333
      return TRUE;
334
    }
335
    if ($result === FALSE) {
336
      return FALSE;
337
    }
338
  }
339
}
340

    
341
/**
342
 * Implements hook_fivestar_access().
343
 *
344
 * This hook is called before every vote is cast through Fivestar. It allows
345
 * modules to allow voting on any type of entity, such as nodes, users, or
346
 * comments.
347
 *
348
 * @param $entity_type
349
 *   Type entity.
350
 * @param $id
351
 *   Identifier within the type.
352
 * @param $tag
353
 *   The VotingAPI tag string.
354
 * @param $uid
355
 *   The user ID trying to cast the vote.
356
 *
357
 * @return bool|NULL
358
 *   Returns TRUE if voting is supported on this object.
359
 *   Returns NULL if voting is not supported on this object by this module.
360
 *   If needing to absolutely deny all voting on this object, regardless
361
 *   of permissions defined in other modules, return FALSE. Note if all
362
 *   modules return NULL, stating no preference, then access will be denied.
363
 */
364
function fivestar_fivestar_access($entity_type, $id, $tag, $uid) {
365
  // Check to see if there is a field instance on this entity.
366
  $fields = field_read_fields(array('module' => 'fivestar'));
367
  foreach ($fields as $field) {
368
    if ($field['settings']['axis'] == $tag) {
369
      $params = array(
370
        'entity_type' => $entity_type,
371
        'field_name' => $field['field_name'],
372
      );
373
      $instance = field_read_instances($params);
374
      if (!empty($instance)) {
375
        return TRUE;
376
      }
377
    }
378
  }
379
}
380

    
381
/**
382
 * Implements hook_form_comment_form_alter().
383
 *
384
 * This hook removes the parent node, together with the fivestar field, from
385
 * the comment preview page. If this is left in, when the user presses the
386
 * "Save" button after the preview page has been displayed, the fivestar widget
387
 * gets the input rather than the comment; the user's input is lost. Based on a
388
 * suggestion by ChristianAdamski in issue 1289832-3.
389
 */
390
function fivestar_form_comment_form_alter(&$form, &$form_state, $form_id) {
391
  $fivestar_field_keys = array();
392
  if (isset($form['comment_output_below'])) {
393
    foreach ($form['comment_output_below'] as $key => $value) {
394
      if (is_array($value) && !empty($value['#field_type']) && $value['#field_type'] == 'fivestar') {
395
        $fivestar_field_keys[] = $key;
396
      }
397
    }
398
  }
399
  if ($fivestar_field_keys) {
400
    foreach ($fivestar_field_keys as $key) {
401
      unset($form['comment_output_below'][$key]);
402
    }
403
  }
404
}
405

    
406
/**
407
 * Implements hook_fivestar_widgets().
408
 *
409
 * This hook allows other modules to create additional custom widgets for
410
 * the fivestar module.
411
 *
412
 * @return array
413
 *   An array of key => value pairs suitable for inclusion as the #options in a
414
 *   select or radios form element. Each key must be the location of a css
415
 *   file for a fivestar widget. Each value should be the name of the widget.
416
 */
417
function fivestar_fivestar_widgets() {
418
  $widgets = &drupal_static(__FUNCTION__);
419
  if (!isset($widgets)) {
420
    $cache = cache_get(__FUNCTION__);
421
    if ($cache) {
422
      $widgets = $cache->data;
423
    }
424
  }
425

    
426
  if (!isset($widgets)) {
427
    $widgets_directory = drupal_get_path('module', 'fivestar') . '/widgets';
428
    $files = file_scan_directory($widgets_directory, '/\.css$/');
429
    foreach ($files as $file) {
430
      if (strpos($file->filename, '-rtl.css') === FALSE) {
431
        $widgets[$file->uri] = drupal_ucfirst(str_replace('-color', '', $file->name));
432
      }
433
    }
434
    cache_set(__FUNCTION__, $widgets);
435
  }
436
  return $widgets;
437
}
438

    
439
/**
440
 * Form builder; Build a custom Fivestar rating widget with arbitrary settings.
441
 *
442
 * This function is usually not called directly, instead call
443
 * drupal_get_form('fivestar_custom_widget', $values, $settings) when wanting
444
 * to display a widget.
445
 *
446
 * @param $form_state
447
 *   The form state provided by Form API.
448
 * @param $values
449
 *   An array of current vote values from 0 to 100, with the following array
450
 *   keys:
451
 *   - user: The user's current vote.
452
 *   - average: The average vote value.
453
 *   - count: The total number of votes so far on this content.
454
 * @param $settings
455
 *   An array of settings that configure the properties of the rating widget.
456
 *   Available keys for the settings include:
457
 *   - content_type: The type of content which will be voted upon.
458
 *   - content_id: The content ID which will be voted upon.
459
 *   - stars: The number of stars to display in this widget, from 2 to 10.
460
 *     Defaults to 5.
461
 *   - autosubmit: Whether the form should be submitted upon star selection.
462
 *     Defaults to TRUE.
463
 *   - allow_clear: Whether or not to show the "Clear current vote" icon when
464
 *     showing the widget. Defaults to FALSE.
465
 *   - required: Whether this field is required before the form can be
466
 *     submitted. Defaults to FALSE.
467
 *   - tag: The VotingAPI tag that will be registered by this widget. Defaults
468
 *     to "vote".
469
 */
470
function fivestar_custom_widget($form, &$form_state, $values, $settings) {
471
  $form = array(
472
    '#attributes' => array(
473
      'class' => array('fivestar-widget'),
474
    ),
475
  );
476
  $form['#submit'][] = 'fivestar_form_submit';
477

    
478
  $form_state['settings'] = $settings;
479

    
480
  // Define default settings.
481
  $default_settings = array(
482
    'allow_clear' => FALSE,
483
    // Taken from installation file.
484
    'allow_ownvote' => TRUE,
485
    'allow_revote' => TRUE,
486
    'autosubmit' => TRUE,
487
    'description' => '',
488
    'required' => FALSE,
489
    'stars' => 5,
490
    'tag' => 'vote',
491
    'widget' => array(
492
      'name' => 'default',
493
      'css' => 'default',
494
    ),
495
  );
496

    
497
  // Merge default settings.
498
  $settings = $settings + $default_settings;
499

    
500
  $form['vote'] = array(
501
    '#type' => 'fivestar',
502
    '#stars' => $settings['stars'],
503
    '#auto_submit' => $settings['autosubmit'],
504
    '#allow_clear' => $settings['allow_clear'],
505
    '#allow_revote' => $settings['allow_revote'],
506
    '#allow_ownvote' => $settings['allow_ownvote'],
507
    '#required' => $settings['required'],
508
    '#widget' => $settings['widget'],
509
    '#values' => $values,
510
    '#settings' => $settings,
511
    '#description' => $settings['description'],
512
  );
513

    
514
  $form['fivestar_submit'] = array(
515
    '#type' => 'submit',
516
    '#value' => t('Rate'),
517
    '#attributes' => array('class' => array('fivestar-submit')),
518
  );
519

    
520
  return $form;
521
}
522

    
523
/**
524
 * Submit handler for the above form (non-javascript version).
525
 */
526
function fivestar_form_submit($form, &$form_state) {
527

    
528
  // Cast the vote.
529
  _fivestar_cast_vote($form_state['settings']['content_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
530

    
531
  // Set a message that the vote was received.
532
  if ($form_state['values']['vote'] === '0') {
533
    drupal_set_message(t('Your vote has been cleared.'));
534
  }
535
  elseif (is_numeric($form_state['values']['vote'])) {
536
    drupal_set_message(t('Thank you for your vote.'));
537
  }
538
}
539

    
540
/**
541
 * AJAX submit handler for fivestar_custom_widget.
542
 */
543
function fivestar_ajax_submit($form, $form_state) {
544
  if (!empty($form_state['settings']['content_id'])) {
545
    $entity = entity_load($form_state['settings']['entity_type'], array($form_state['settings']['entity_id']));
546
    $entity = reset($entity);
547
    _fivestar_update_field_value($form_state['settings']['content_type'], $entity, $form_state['settings']['field_name'], $form_state['settings']['langcode'], $form_state['values']['vote']);
548
    $votes = _fivestar_cast_vote($form_state['settings']['entity_type'], $form_state['settings']['content_id'], $form_state['values']['vote'], $form_state['settings']['tag']);
549
  }
550

    
551
  $values = array();
552
  $values['user'] = isset($votes['user']['value']) ? $votes['user']['value'] : 0;
553
  $values['average'] = isset($votes['average']['value']) ? $votes['average']['value'] : 0;
554
  $values['count'] = isset($votes['count']['value']) ? $votes['count']['value'] : 0;
555

    
556
  // We need to process the 'fivestar' element with the new values.
557
  $form['vote']['#values'] = $values;
558
  // Also need to pass on form_state and the complete form,
559
  // just like form_builder() does.
560
  $new_element = fivestar_expand($form['vote'], $form_state, $form_state['complete form']);
561
  // Update the description. Since it doesn't account of our cast vote above.
562
  // @todo Look into all this, I honestly don't like it and it's a bit weird.
563
  $form['vote']['vote']['#description'] = isset($new_element['vote']['#description']) ? $new_element['vote']['#description'] : '';
564
  if (!$form['vote']['#allow_revote'] && !$form['vote']['#allow_clear']) {
565
    $form['vote'] = $new_element;
566
  }
567
  return array(
568
    '#type' => 'ajax',
569
    '#commands' => array(
570
      array(
571
        'command' => 'fivestarUpdate',
572
        'data' => drupal_render($form['vote']),
573
      ),
574
    ),
575
  );
576
}
577

    
578
/**
579
 * Implements hook_elements().
580
 *
581
 * Defines 'fivestar' form element type.
582
 */
583
function fivestar_element_info() {
584
  $type['fivestar'] = array(
585
    '#input' => TRUE,
586
    '#stars' => 5,
587
    '#allow_clear' => FALSE,
588
    '#allow_revote' => FALSE,
589
    '#allow_ownvote' => FALSE,
590
    '#auto_submit' => FALSE,
591
    '#process' => array('fivestar_expand'),
592
    '#theme_wrappers' => array('form_element'),
593
    '#widget' => array(
594
      'name' => 'default',
595
      'css' => 'default',
596
    ),
597
    '#values' => array(
598
      'user' => 0,
599
      'average' => 0,
600
      'count' => 0,
601
    ),
602
    '#settings' => array(
603
      'style' => 'user',
604
      'text' => 'none',
605
    ),
606
  );
607
  return $type;
608
}
609

    
610
/**
611
 * Process callback for fivestar_element -- see fivestar_element()
612
 */
613
function fivestar_expand($element, $form_state, $complete_form) {
614
  if (!_fivestar_allow_vote($element)) {
615
    $element['#input'] = FALSE;
616
  }
617

    
618
  // Add CSS and JS.
619
  $path = drupal_get_path('module', 'fivestar');
620
  $element['#attached']['js'][] = $path . '/js/fivestar.js';
621
  $element['#attached']['css'][] = $path . '/css/fivestar.css';
622
  $settings = $element['#settings'];
623
  $values = $element['#values'];
624
  $class[] = 'clearfix';
625

    
626
  $title = t('it');
627
  if (isset($element['#settings']['entity_id']) && isset($element['#settings']['entity_type'])) {
628
    $entity_id = $element['#settings']['entity_id'];
629
    $entity = entity_load($element['#settings']['entity_type'], array($entity_id));
630
    $entity = $entity[$entity_id];
631
    if (isset($entity->title)) {
632
      $title = $entity->title;
633
    }
634
  }
635
  elseif (isset($complete_form['#node'])) {
636
    $title = $complete_form['#node']->title;
637
  }
638
  $options = array('-' => t('Select rating'));
639
  for ($i = 1; $i <= $element['#stars']; $i++) {
640
    $this_value = ceil($i * 100 / $element['#stars']);
641
    $options[$this_value] = t('Give @title @star/@count', array('@title' => $title, '@star' => $i, '@count' => $element['#stars']));
642
  }
643
  // Display clear button only if enabled.
644
  if ($element['#allow_clear'] == TRUE) {
645
    $options[0] = t('Cancel rating');
646
  }
647

    
648
  $element['vote'] = array(
649
    '#type' => 'select',
650
    '#options' => $options,
651
    '#required' => $element['#required'],
652
    '#attributes' => $element['#attributes'],
653
    '#theme' => _fivestar_allow_vote($element) ? 'fivestar_select' : 'fivestar_static',
654
    '#default_value' => _fivestar_get_element_default_value($element),
655
    '#weight' => -2,
656
  );
657

    
658
  if (isset($element['#parents'])) {
659
    $element['vote']['#parents'] = $element['#parents'];
660
  }
661

    
662
  switch ($settings['text']) {
663
    case 'user':
664
      $element['vote']['#description'] = theme('fivestar_summary', array(
665
        'user_rating' => $values['user'],
666
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
667
        'stars' => $settings['stars'],
668
        'microdata' => $settings['microdata'],
669
      ));
670
      $class[] = 'fivestar-user-text';
671
      break;
672

    
673
    case 'average':
674
      $element['vote']['#description'] = $settings['style'] == 'dual' ? NULL : theme('fivestar_summary', array(
675
        'average_rating' => $values['average'],
676
        'votes' => $values['count'],
677
        'stars' => $settings['stars'],
678
        'microdata' => $settings['microdata'],
679
      ));
680
      $class[] = 'fivestar-average-text';
681
      break;
682

    
683
    case 'smart':
684
      $element['vote']['#description'] = ($settings['style'] == 'dual' && !$values['user']) ? NULL : theme('fivestar_summary', array(
685
        'user_rating' => $values['user'],
686
        'average_rating' => $values['user'] ? NULL : $values['average'],
687
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
688
        'stars' => $settings['stars'],
689
        'microdata' => $settings['microdata'],
690
      ));
691
      $class[] = 'fivestar-smart-text';
692
      $class[] = $values['user'] ? 'fivestar-user-text' : 'fivestar-average-text';
693
      break;
694

    
695
    case 'dual':
696
      $element['vote']['#description'] = theme('fivestar_summary', array(
697
        'user_rating' => $values['user'],
698
        'average_rating' => $settings['style'] == 'dual' ? NULL : $values['average'],
699
        'votes' => $settings['style'] == 'dual' ? NULL : $values['count'],
700
        'stars' => $settings['stars'],
701
        'microdata' => $settings['microdata'],
702
      ));
703
      $class[] = ' fivestar-combo-text';
704
      break;
705

    
706
    case 'none':
707
      $element['vote']['#description'] = NULL;
708
      break;
709
  }
710

    
711
  switch ($settings['style']) {
712
    case 'average':
713
      $class[] = 'fivestar-average-stars';
714
      break;
715

    
716
    case 'user':
717
      $class[] = 'fivestar-user-stars';
718
      break;
719

    
720
    case 'smart':
721
      $class[] = 'fivestar-smart-stars ' . ($values['user'] ? 'fivestar-user-stars' : 'fivestar-average-stars');
722
      break;
723

    
724
    case 'dual':
725
      $class[] = 'fivestar-combo-stars';
726
      $static_average = theme('fivestar_static', array(
727
        'rating' => $values['average'],
728
        'stars' => $settings['stars'],
729
        'tag' => $settings['tag'],
730
        'widget' => $settings['widget'],
731
      ));
732
      if ($settings['text'] != 'none') {
733
        $static_description = theme('fivestar_summary', array(
734
          'average_rating' => $settings['text'] == 'user' ? NULL : (isset($values['average']) ? $values['average'] : 0),
735
          'votes' => isset($values['count']) ? $values['count'] : 0,
736
          'stars' => $settings['stars'],
737
        ));
738
      }
739
      else {
740
        $static_description = '&nbsp;';
741
      }
742
      $element['average'] = array(
743
        '#type' => 'markup',
744
        '#markup' => theme('fivestar_static_element', array(
745
          'star_display' => $static_average,
746
          'title' => '',
747
          'description' => $static_description,
748
        )),
749
        '#weight' => -1,
750
      );
751
      break;
752
  }
753
  $class[] = 'fivestar-form-item';
754
  $class[] = 'fivestar-' . $element['#widget']['name'];
755
  if ($element['#widget']['name'] != 'default') {
756
    $element['#attached']['css'][] = $element['#widget']['css'];
757
  }
758
  $element['#prefix'] = '<div ' . drupal_attributes(array('class' => $class)) . '>';
759
  $element['#suffix'] = '</div>';
760

    
761
  // Add AJAX handling if necessary.
762
  if (!empty($element['#auto_submit'])) {
763
    $element['vote']['#ajax'] = array(
764
      'callback' => 'fivestar_ajax_submit',
765
    );
766
    $element['vote']['#attached']['js'][] = $path . '/js/fivestar.ajax.js';
767
  }
768

    
769
  if (empty($element['#input'])) {
770
    $static_stars = theme('fivestar_static', array(
771
      'rating' => $element['vote']['#default_value'],
772
      'stars' => $settings['stars'],
773
      'tag' => $settings['tag'],
774
      'widget' => $settings['widget'],
775
    ));
776

    
777
    $element['vote'] = array(
778
      '#type' => 'markup',
779
      '#markup' => theme('fivestar_static_element', array(
780
        'star_display' => $static_stars,
781
        'title' => '',
782
        'description' => $element['vote']['#description'],
783
      )),
784
    );
785
  }
786

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

    
790
  return $element;
791
}
792

    
793
/**
794
 * Helper function to get the correct default value for a fivestar element.
795
 *
796
 * @param $element
797
 *   The fivestar element
798
 *
799
 * @return
800
 *   The default value for the element.
801
 */
802
function _fivestar_get_element_default_value($element) {
803
  if (isset($element['#default_value'])) {
804
    $default_value = $element['#default_value'];
805
  }
806
  else {
807
    switch ($element['#settings']['style']) {
808
      case 'average':
809
        $default_value = $element['#values']['average'];
810
        break;
811

    
812
      case 'user':
813
        $default_value = $element['#values']['user'];
814
        break;
815

    
816
      case 'smart':
817
        $default_value = (!empty($element['#values']['user']) ? $element['#values']['user'] : $element['#values']['average']);
818
        break;
819

    
820
      case 'dual':
821
        $default_value = $element['#values']['user'];
822
        break;
823

    
824
      default:
825
        $default_value = $element['#values']['average'];
826
    }
827
  }
828

    
829
  for ($i = 0; $i <= $element['#stars']; $i++) {
830
    $this_value = ceil($i * 100 / $element['#stars']);
831
    $next_value = ceil(($i + 1) * 100 / $element['#stars']);
832

    
833
    // Round up the default value to the next exact star value if needed.
834
    if ($this_value < $default_value && $next_value > $default_value) {
835
      $default_value = $next_value;
836
    }
837
  }
838

    
839
  return $default_value;
840
}
841

    
842
/**
843
 * An #element_validate function for "fivestar" elements.
844
 */
845
function fivestar_validate($element, &$form_state) {
846
  if ($element['#required'] && (empty($element['#value']) || $element['#value'] == '-')) {
847
    form_error($element, t('@name field is required.', array('@name' => $element['#title'])));
848
  }
849
}
850

    
851
/**
852
 * Implements hook_votingapi_metadata_alter().
853
 */
854
function fivestar_votingapi_metadata_alter(&$data) {
855
  foreach (fivestar_get_tags() as $tag) {
856
    // Add several custom tags that are being used by fivestar.
857
    $data['tags'][$tag] = array(
858
      'name' => t($tag),
859
      'description' => t('@tag used by fivestar.', array('@tag' => $tag)),
860
      'module' => 'fivestar',
861
    );
862
  }
863
}
864

    
865
/**
866
 *
867
 */
868
function fivestar_get_tags() {
869
  $tags_txt = variable_get('fivestar_tags', 'vote');
870
  $tags_exploded = explode(',', $tags_txt);
871

    
872
  $tags = array();
873
  $got_vote = FALSE;
874
  foreach ($tags_exploded as $tag) {
875
    $tag_trimmed = trim($tag);
876
    if ($tag_trimmed) {
877
      $tags[$tag_trimmed] = $tag_trimmed;
878
      if ($tag_trimmed == 'vote') {
879
        $got_vote = TRUE;
880
      }
881
    }
882
  }
883

    
884
  if (!$got_vote) {
885
    $tags['vote'] = 'vote';
886
  }
887
  return $tags;
888
}
889

    
890
/**
891
 *
892
 */
893
function fivestar_get_targets($field, $instance, $key = FALSE, $entity = FALSE, $langcode = LANGUAGE_NONE) {
894
  $options = array();
895
  $targets = module_invoke_all('fivestar_target_info', $field, $instance);
896
  if ($key == FALSE) {
897
    foreach ($targets as $target => $info) {
898
      $options[$target] = $info['title'];
899
    }
900
    return $options;
901
  }
902
  else {
903
    if (isset($targets[$key]) && !empty($targets[$key]['callback']) && function_exists($targets[$key]['callback'])) {
904
      return call_user_func($targets[$key]['callback'], $entity, $field, $instance, $langcode);
905
    }
906
  }
907
}
908

    
909
/**
910
 * Implements hook_fivestar_target_info().
911
 */
912
function fivestar_fivestar_target_info($field, $instance) {
913
  $entity_type = $instance['entity_type'];
914
  $bundle = $instance['bundle'];
915
  $options = array(
916
    'none' => array(
917
      'title' => t('None'),
918
    ),
919
  );
920
  // Add node_referrence support.
921
  if (module_exists('node_reference')) {
922
    $field_names = array_keys(field_read_fields(array('module' => 'node_reference')));
923
    if (!empty($field_names)) {
924
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
925
      if (!empty($field_instances)) {
926
        foreach ($field_instances as $field_instance) {
927
          $options[$field_instance['field_name']] = array(
928
            'title' => t('Node reference: @field', array('@field' => $field_instance['field_name'])),
929
            'callback' => '_fivestar_target_node_reference',
930
          );
931
        }
932
      }
933
    }
934
  }
935

    
936
  // Add entityreference support.
937
  if (module_exists('entityreference')) {
938
    $field_names = array_keys(field_read_fields(array('module' => 'entityreference')));
939
    if (!empty($field_names)) {
940
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
941
      if (!empty($field_instances)) {
942
        foreach ($field_instances as $field_instance) {
943
          $options[$field_instance['field_name']] = array(
944
            'title' => t('Entity reference: @field', array('@field' => $field_instance['field_name'])),
945
            'callback' => '_fivestar_target_entityreference',
946
          );
947
        }
948
      }
949
    }
950
  }
951

    
952
  // Add user reference support.
953
  if (module_exists('user_reference')) {
954
    $field_names = array_keys(field_read_fields(array('module' => 'user_reference')));
955
    if (!empty($field_names)) {
956
      $field_instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle, 'field_name' => $field_names));
957
      if (!empty($field_instances)) {
958
        foreach ($field_instances as $field_instance) {
959
          $options[$field_instance['field_name']] = array(
960
            'title' => t('User reference: @field', array('@field' => $field_instance['field_name'])),
961
            'callback' => '_fivestar_target_user_reference',
962
          );
963
        }
964
      }
965
    }
966
  }
967

    
968
  // Add comment module support.
969
  if ($instance['entity_type'] == 'comment') {
970
    $options['parent_node'] = array(
971
      'title' => t('Parent Node'),
972
      'callback' => '_fivestar_target_comment_parent_node',
973
    );
974
  }
975

    
976
  return $options;
977
}
978

    
979
/**
980
 *
981
 * @return array
982
 *   array('entity_type', 'entity_id').
983
 */
984
function _fivestar_target_node_reference($entity, $field, $instance, $langcode) {
985
  $target = array();
986

    
987
  $node_reference = $instance['settings']['target'];
988
  if (isset($entity->{$node_reference}[$langcode][0]) && is_numeric($entity->{$node_reference}[$langcode][0]['nid'])) {
989
    $target['entity_id'] = $entity->{$node_reference}[$langcode][0]['nid'];
990
    $target['entity_type'] = 'node';
991
  }
992

    
993
  return $target;
994
}
995

    
996
/**
997
 * @return (array) array('entity_type', 'entity_id')
998
 */
999
function _fivestar_target_entityreference($entity, $field, $instance, $langcode) {
1000
  $target = array();
1001

    
1002
  $entityreference = $instance['settings']['target'];
1003

    
1004
  // Retrieve entity settings for the referenced field.
1005
  $field_info = field_info_field($entityreference);
1006

    
1007
  if (isset($entity->{$entityreference}[$langcode][0]) && isset($entity->{$entityreference}[$langcode][0]['target_id']) && is_numeric($entity->{$entityreference}[$langcode][0]['target_id'])) {
1008
    $target['entity_id'] = $entity->{$entityreference}[$langcode][0]['target_id'];
1009
    $target['entity_type'] = $field_info['settings']['target_type'];
1010
  }
1011

    
1012
  return $target;
1013
}
1014

    
1015
/**
1016
 *
1017
 * @return array
1018
 *   array('entity_type', 'entity_id').
1019
 */
1020
function _fivestar_target_user_reference($entity, $field, $instance, $langcode) {
1021
  $target = array();
1022

    
1023
  $user_reference = $instance['settings']['target'];
1024
  if (isset($entity->{$user_reference}[$langcode][0]) && is_numeric($entity->{$user_reference}[$langcode][0]['uid'])) {
1025
    $target['entity_id'] = $entity->{$user_reference}[$langcode][0]['uid'];
1026
    $target['entity_type'] = 'user';
1027
  }
1028

    
1029
  return $target;
1030
}
1031

    
1032
/**
1033
 *
1034
 */
1035
function _fivestar_target_comment_parent_node($entity, $field, $instance, $langcode) {
1036
  return array(
1037
    'entity_id' => $entity->nid,
1038
    'entity_type' => 'node',
1039
  );
1040
}
1041

    
1042
/**
1043
 * Helper function to determine if a user can vote on content.
1044
 *
1045
 * @return bool
1046
 */
1047
function _fivestar_allow_vote($element) {
1048
  global $user;
1049

    
1050
  // Check allowed to re-vote.
1051
  $can_revote = FALSE;
1052
  if ($element['#allow_revote']) {
1053
    $can_revote = TRUE;
1054
  }
1055
  else {
1056
    $criteria = array(
1057
      'entity_id' => isset($element['#settings']['content_id']) ? $element['#settings']['content_id'] : NULL,
1058
      'entity_type' => isset($element['#settings']['content_type']) ? $element['#settings']['content_type'] : NULL,
1059
      'tag' => isset($element['#settings']['tag']) ? $element['#settings']['tag'] : NULL,
1060
      'uid' => $user->uid,
1061
    );
1062

    
1063
    $can_revote = !votingapi_select_votes($criteria);
1064
  }
1065
  if (!$can_revote) {
1066
    return FALSE;
1067
  }
1068
  // Check allowed own vote.
1069
  if ($element['#allow_ownvote']) {
1070
    return TRUE;
1071
  }
1072
  // Check that we have entity details, allow if not.
1073
  if (!isset($element['#settings']['entity_id']) || !isset($element['#settings']['entity_type'])) {
1074
    return TRUE;
1075
  }
1076
  $entity_id = $element['#settings']['entity_id'];
1077
  $entity = entity_load($element['#settings']['entity_type'], array($entity_id));
1078
  $entity = $entity[$entity_id];
1079
  $uid1 = $entity->uid;
1080
  $uid2 = $user->uid;
1081
  return $entity->uid != $user->uid;
1082
}
1083

    
1084
/**
1085
 * Implements hook_ctools_content_subtype_alter()
1086
 */
1087
function fivestar_ctools_content_subtype_alter(&$subtype, &$plugin) {
1088
  if ($plugin['name'] == 'entity_field' && isset($subtype['subtype_id'])) {
1089
    list($entity_type, $field_name) = explode(':', $subtype['subtype_id'], 2);
1090
    $field = field_info_field($field_name);
1091
    if ($field['type'] == 'fivestar') {
1092
      $subtype['render callback'] = 'fivestar_fivestar_field_content_type_render';
1093
    }
1094
  }
1095
}
1096

    
1097
/**
1098
 * Render the custom content type.
1099
 */
1100
function fivestar_fivestar_field_content_type_render($subtype, $conf, $panel_args, $context) {
1101
  if (empty($context) || empty($context->data)) {
1102
    return;
1103
  }
1104

    
1105
  // Get a shortcut to the entity.
1106
  $entity = $context->data;
1107
  list($entity_type, $field_name) = explode(':', $subtype, 2);
1108

    
1109
  // Load the entity type's information for this field.
1110
  $ids = entity_extract_ids($entity_type, $entity);
1111
  $field = field_info_instance($entity_type, $field_name, $ids[2]);
1112

    
1113
  // Do not render if the entity type does not have this field.
1114
  if (empty($field)) {
1115
    return;
1116
  }
1117
  $language = field_language($entity_type, $entity, $field_name);
1118

    
1119
  if (empty($conf['label']) || $conf['label'] == 'title') {
1120
    $label = 'hidden';
1121
    $conf['label'] = 'title';
1122
  }
1123
  else {
1124
    $label = $conf['label'];
1125
  }
1126

    
1127
  $field_settings = array(
1128
    'label' => $label,
1129
    'type' => $conf['formatter'],
1130
    // Pass all entity field panes settings to field display settings.
1131
    'pane_settings' => $conf,
1132
  );
1133

    
1134
  // Get the field output, and the title.
1135
  if (!empty($conf['formatter_settings'])) {
1136
    $field_settings['settings'] = $conf['formatter_settings'];
1137
  }
1138

    
1139
  $all_values = field_get_items($entity_type, $entity, $field_name, $language);
1140

    
1141
  if (!$all_values) {
1142
    $all_values = array();
1143
  }
1144

    
1145
  // Reverse values.
1146
  if (isset($conf['delta_reversed']) && $conf['delta_reversed']) {
1147
    $all_values = array_reverse($all_values, TRUE);
1148
  }
1149

    
1150
  if (isset($conf['delta_limit'])) {
1151
    $offset = intval($conf['delta_offset']);
1152
    $limit = !empty($conf['delta_limit']) ? $conf['delta_limit'] : NULL;
1153
    $all_values = array_slice($all_values, $offset, $limit, TRUE);
1154
  }
1155

    
1156
  $clone = clone $entity;
1157
  $clone->{$field_name}[$language] = $all_values;
1158
  $field_output = field_view_field($entity_type, $clone, $field_name, $field_settings, $language);
1159

    
1160
  if (!empty($field_output) && !empty($conf['override_title'])) {
1161
    $field_output['#title'] = filter_xss_admin($conf['override_title_text']);
1162
  }
1163

    
1164
  // Build the content type block.
1165
  $block = new stdClass();
1166
  $block->module = 'entity_field';
1167
  if ($conf['label'] == 'title' && isset($field_output['#title'])) {
1168
    $block->title = $field_output['#title'];
1169
  }
1170

    
1171
  $block->content = $field_output;
1172
  $block->delta   = $ids[0];
1173

    
1174
  return $block;
1175
}