Projet

Général

Profil

Paste
Télécharger (25 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / votingapi / votingapi.module @ f419e8de

1
<?php
2

    
3
/**
4
 * @file
5
 * A generalized voting API for Drupal.
6
 *
7
 * Maintains and provides an interface for a shared bin of vote and rating
8
 * data. Modules can cast votes with arbitrary properties and VotingAPI will
9
 * total them automatically. Support for basic anonymous voting by IP address,
10
 * multi-criteria voting, arbitrary aggregation functions, etc.
11
 *
12
 * /**
13
 * Implementation of hook_help().
14
 */
15
function votingapi_help($path, $arg) {
16
  switch ($path) {
17
    case 'admin/help#votingapi':
18
      $output = '';
19
      $text = 'module helps developers who want to use a standardized API';
20
      $text .= ' and schema for storing, retrieving, and tabulating votes for';
21
      $text .= ' Drupal content. Note that this module does NOT directly';
22
      $text .= ' expose any voting mechanisms to end users. It is a framework';
23
      $text .= ' designed to make life easier for other developers, and to';
24
      $text .= ' standardize voting data for consumption by other modules';
25
      $text .= ' (like Views). For more information, see the online';
26
      $text .= ' documentation for the ';
27
      $output .= '<h3>' . t('About') . '</h3>';
28
      $output .= '<p>' . t('The <a href="@votingapi">VotingAPI</a> ' . $text . ' <a href="@doco">VotingAPI module</a>.',
29
          array(
30
            '@votingapi' => 'http://drupal.org/project/votingapi',
31
            '@doco' => 'https://www.drupal.org/node/68851',
32
          )) . '</p>';
33
      $output .= '<h3>' . t('Uses') . '</h3>';
34
      $output .= '<dl>';
35
      $output .= '<dt>' . t('General') . '</dt>';
36
      $output .= '<dd>' . t('VotingAPI is used to allow module developers to focus on their ideas (say, providing a "rate this thread" widget at the bottom of each forum post) without worrying about the grunt work of storing votes, preventing ballot-stuffing, calculating the results, and so on.') . '</dd>';
37
      $output .= '<dt>' . t('Basic Concepts and Features ') . '</dt>';
38
      $output .= '<dd>' . t('VotingAPI does four key jobs for module developers: ') . '</dd>';
39
      $output .= '<dd>' . t('1. CRUD: Create/Retrieve/Update/Delete operations for voting data. ') . '</dd>';
40
      $output .= '<dd>' . t('2. Calculation: Every time a user casts a vote, VotingAPI calculates the results and caches them for you.  ') . '</dd>';
41
      $output .= '<dd>' . t('3. Workflow: By integrating with Actions.module, VotingAPI can trigger workflow steps (like promoting a node to the front page, hiding a comment thathas been flagged as spam, or emailing an administrator) when votes are cast and results are tallied.  ') . '</dd>';
42
      $output .= '<dd>' . t('4. Display: VotingAPI integrates with Views.module, allowing you to slice and dice the content of your site based on user consensus.  ') . '</dd>';
43
      return $output;
44
  }
45
}
46

    
47
/**
48
 * Implements of hook_menu().
49
 */
50
function votingapi_menu() {
51
  $items = array();
52
  $items['admin/config/search/votingapi'] = array(
53
    'title' => 'Voting API',
54
    'description' => 'Configure sitewide settings for user-generated ratings and votes.',
55
    'page callback' => 'drupal_get_form',
56
    'page arguments' => array('votingapi_settings_form'),
57
    'access callback' => 'user_access',
58
    'access arguments' => array('administer voting api'),
59
    'file' => 'votingapi.admin.inc',
60
    'type' => MENU_NORMAL_ITEM,
61
  );
62

    
63
  if (module_exists('devel_generate')) {
64
    $items['admin/config/development/generate/votingapi'] = array(
65
      'title' => 'Generate votes',
66
      'description' => 'Generate a given number of votes on site content. Optionally delete existing votes.',
67
      'page callback' => 'drupal_get_form',
68
      'page arguments' => array('votingapi_generate_votes_form'),
69
      'access arguments' => array('administer voting api'),
70
      'file' => 'votingapi.admin.inc',
71
    );
72
  }
73

    
74
  $items['admin/config/search/votingapi/general'] = array(
75
    'title' => 'General Settings',
76
    'type' => MENU_DEFAULT_LOCAL_TASK,
77
    'weight' => -10,
78
  );
79
  $items['admin/config/search/votingapi/rebuild'] = array(
80
    'title' => t('Rebuild voting results'),
81
    'description' => t('Allows rebuilding of voting results cache'),
82
    'page callback' => 'drupal_get_form',
83
    'page arguments' => array('votingapi_rebuild_form'),
84
    'access callback' => 'user_access',
85
    'access arguments' => array('administer voting api'),
86
    'file' => 'votingapi.admin.inc',
87
    'type' => MENU_LOCAL_TASK,
88
  );
89

    
90
  return $items;
91
}
92

    
93
/**
94
 * Implements hook_permission().
95
 */
96
function votingapi_permission() {
97
  return array(
98
    'administer voting api' => array(
99
      'title' => t('Administer Voting API'),
100
    ),
101
  );
102
}
103

    
104
/**
105
 * Implements of hook_views_api().
106
 */
107
function votingapi_views_api() {
108
  return array(
109
    'api' => 3,
110
    'path' => drupal_get_path('module', 'votingapi') . '/views',
111
  );
112
}
113

    
114
/**
115
 * Implements of hook_cron().
116
 *
117
 * Allows db-intensive recalculations to be deferred until cron-time.
118
 */
119
function votingapi_cron() {
120
  if (variable_get('votingapi_calculation_schedule', 'immediate') == 'cron') {
121
    $time = REQUEST_TIME;
122
    $last_cron = variable_get('votingapi_last_cron', 0);
123
    $result = db_query('SELECT DISTINCT entity_type, entity_id FROM {votingapi_vote} WHERE timestamp > :timestamp', array(':timestamp' => $last_cron));
124
    foreach ($result as $content) {
125
      votingapi_recalculate_results($content->entity_type, $content->entity_id, TRUE);
126
    }
127

    
128
    variable_set('votingapi_last_cron', $time);
129
  }
130

    
131
  _votingapi_cron_delete_orphaned();
132
}
133

    
134
/**
135
 * Cast a vote on a particular piece of content.
136
 *
137
 * This function does most of the heavy lifting needed by third-party modules
138
 * based on VotingAPI. Handles clearing out old votes for a given piece of
139
 * content, saving the incoming votes, and re-tallying the results given the
140
 * new data.
141
 *
142
 * Modules that need more explicit control can call votingapi_add_votes() and
143
 * manage the deletion/recalculation tasks manually.
144
 *
145
 * @param array $votes
146
 *   An array of votes, each with the following structure:
147
 *   $vote['entity_type']  (Optional, defaults to 'node')
148
 *   $vote['entity_id']    (Required)
149
 *   $vote['value_type']    (Optional, defaults to 'percent')
150
 *   $vote['value']         (Required)
151
 *   $vote['tag']           (Optional, defaults to 'vote')
152
 *   $vote['uid']           (Optional, defaults to current user)
153
 *   $vote['vote_source']   (Optional, defaults to current IP)
154
 *   $vote['timestamp']     (Optional, defaults to REQUEST_TIME)
155
 * @param array $criteria
156
 *   A keyed array used to determine what votes will be deleted when the current
157
 *   vote is cast. If no value is specified, all votes for the current content
158
 *   by the current user will be reset. If an empty array is passed in, no votes
159
 *   will be reset and all incoming votes will be saved IN ADDITION to existing
160
 *   ones.
161
 *   $criteria['vote_id']     (If this is set, all other keys are skipped)
162
 *   $criteria['entity_type']
163
 *   $criteria['entity_type']
164
 *   $criteria['value_type']
165
 *   $criteria['tag']
166
 *   $criteria['uid']
167
 *   $criteria['vote_source']
168
 *   $criteria['timestamp']   (If this is set, records with timestamps
169
 *      GREATER THAN the set value will be selected.)
170
 *
171
 * @return array
172
 *   An array of vote result records affected by the vote. The values are
173
 *   contained in a nested array keyed thusly:
174
 *   $value = $results[$entity_type][$entity_id][$tag][$value_type][$function]
175
 *
176
 * @see votingapi_add_votes()
177
 * @see votingapi_recalculate_results()
178
 */
179
function votingapi_set_votes(&$votes, $criteria = NULL) {
180
  $touched = array();
181
  if (!empty($votes['entity_id'])) {
182
    $votes = array($votes);
183
  }
184

    
185
  // Allow other modules to modify or unset/remove votes.
186
  // module_invoke_all does not allow passing variables by reference
187
  // http://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke_all/7#comment-35778
188
  drupal_alter(array('votingapi_preset_votes'), $votes);
189
  // Handle clearing out old votes if they exist.
190
  if (!isset($criteria)) {
191
    // If the calling function didn't explicitly set criteria for vote deletion,
192
    // build up the delete queries here.
193
    foreach ($votes as $vote) {
194
      $tmp = $vote + votingapi_current_user_identifier();
195
      if (isset($tmp['value'])) {
196
        unset($tmp['value']);
197
      }
198
      votingapi_delete_votes(votingapi_select_votes($tmp));
199
    }
200
  }
201
  elseif (is_array($criteria)) {
202
    // The calling function passed in an explicit set of delete filters.
203
    if (!empty($criteria['entity_id'])) {
204
      $criteria = array($criteria);
205
    }
206
    foreach ($criteria as $c) {
207
      votingapi_delete_votes(votingapi_select_votes($c));
208
    }
209
  }
210

    
211
  foreach ($votes as $key => $vote) {
212
    _votingapi_prep_vote($vote);
213
    $votes[$key] = $vote; // Is this needed? Check to see how PHP4 handles refs.
214
  }
215

    
216
  // Cast the actual votes, inserting them into the table.
217
  votingapi_add_votes($votes);
218

    
219
  foreach ($votes as $vote) {
220
    $touched[$vote['entity_type']][$vote['entity_id']] = TRUE;
221
  }
222

    
223
  if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron') {
224
    foreach ($touched as $type => $ids) {
225
      foreach ($ids as $id => $bool) {
226
        $touched[$type][$id] = votingapi_recalculate_results($type, $id);
227
      }
228
    }
229
  }
230
  return $touched;
231
}
232

    
233
/**
234
 * Generate a proper identifier for the current user: if they have an account,
235
 * return their UID. Otherwise, return their IP address.
236
 */
237
function votingapi_current_user_identifier() {
238
  global $user;
239
  $criteria = array('uid' => $user->uid);
240
  if (!$user->uid) {
241
    $criteria['vote_source'] = ip_address();
242
  }
243
  return $criteria;
244
}
245

    
246
/**
247
 * Implements of hook_votingapi_storage_add_vote().
248
 */
249
function votingapi_votingapi_storage_add_vote(&$vote) {
250
  drupal_write_record('votingapi_vote', $vote);
251
}
252

    
253
/**
254
 * Implements of hook_votingapi_storage_delete_votes().
255
 */
256
function votingapi_votingapi_storage_delete_votes($votes, $vids) {
257
  db_delete('votingapi_vote')->condition('vote_id', $vids, 'IN')->execute();
258
}
259

    
260
/**
261
 * Implements of hook_votingapi_storage_select_votes().
262
 */
263
function votingapi_votingapi_storage_select_votes($criteria, $limit) {
264
  $query = db_select('votingapi_vote')->fields('votingapi_vote');
265
  foreach ($criteria as $key => $value) {
266
    if ($key == 'timestamp') {
267
      $query->condition($key, $value, '>');
268
    }
269
    else {
270
      $query->condition($key, $value, is_array($value) ? 'IN' : '=');
271
    }
272
  }
273
  if (!empty($limit)) {
274
    $query->range(0, $limit);
275
  }
276
  return $query->execute()->fetchAll(PDO::FETCH_ASSOC);
277
}
278

    
279
/**
280
 * Save a collection of votes to the database.
281
 *
282
 * This function does most of the heavy lifting for VotingAPI the main
283
 * votingapi_set_votes() function, but does NOT automatically triger
284
 * re-tallying
285
 * of results. As such, it's useful for modules that must insert their votes in
286
 * separate batches without triggering unecessary recalculation.
287
 *
288
 * Remember that any module calling this function implicitly assumes
289
 * responsibility for calling votingapi_recalculate_results() when all votes
290
 * have been inserted.
291
 *
292
 * @param array $votes
293
 *   A vote or array of votes, each with the following structure:
294
 *   $vote['entity_type']  (Optional, defaults to 'node')
295
 *   $vote['entity_id']    (Required)
296
 *   $vote['value_type']    (Optional, defaults to 'percent')
297
 *   $vote['value']         (Required)
298
 *   $vote['tag']           (Optional, defaults to 'vote')
299
 *   $vote['uid']           (Optional, defaults to current user)
300
 *   $vote['vote_source']   (Optional, defaults to current IP)
301
 *   $vote['timestamp']     (Optional, defaults to REQUEST_TIME)
302
 *
303
 * @return array
304
 *   The same votes, with vote_id keys populated.
305
 *
306
 * @see votingapi_set_votes()
307
 * @see votingapi_recalculate_results()
308
 */
309
function votingapi_add_votes(&$votes) {
310
  if (!empty($votes['entity_id'])) {
311
    $votes = array($votes);
312
  }
313
  $function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_add_vote';
314
  foreach ($votes as $key => $vote) {
315
    _votingapi_prep_vote($vote);
316
    $function($vote);
317
    $votes[$key] = $vote;
318
  }
319
  module_invoke_all('votingapi_insert', $votes);
320
  return $votes;
321
}
322

    
323
/**
324
 * Save a bundle of vote results to the database.
325
 *
326
 * This function is called by votingapi_recalculate_results() after tallying
327
 * the values of all cast votes on a piece of content. This function will be of
328
 * little use for most third-party modules, unless they manually insert their
329
 * own result data.
330
 *
331
 * @param array vote_results
332
 *   An array of vote results, each with the following properties:
333
 *   $vote_result['entity_type']
334
 *   $vote_result['entity_id']
335
 *   $vote_result['value_type']
336
 *   $vote_result['value']
337
 *   $vote_result['tag']
338
 *   $vote_result['function']
339
 *   $vote_result['timestamp']   (Optional, defaults to REQUEST_TIME)
340
 */
341
function votingapi_add_results($vote_results = array()) {
342
  if (!empty($vote_results['entity_id'])) {
343
    $vote_results = array($vote_results);
344
  }
345

    
346
  foreach ($vote_results as $vote_result) {
347
    $vote_result['timestamp'] = REQUEST_TIME;
348
    drupal_write_record('votingapi_cache', $vote_result);
349
  }
350
}
351

    
352
/**
353
 * Delete votes from the database.
354
 *
355
 * @param $votes
356
 *   An array of votes to delete. Minimally, each vote must have the 'vote_id'
357
 *   key set.
358
 */
359
function votingapi_delete_votes($votes = array()) {
360
  if (!empty($votes)) {
361
    module_invoke_all('votingapi_delete', $votes);
362
    $vids = array();
363
    foreach ($votes as $vote) {
364
      $vids[] = $vote['vote_id'];
365
    }
366
    $function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_delete_votes';
367
    $function($votes, $vids);
368
  }
369
}
370

    
371
/**
372
 * Delete cached vote results from the database.
373
 *
374
 * @param $vote_results
375
 *   An array of vote results to delete. Minimally, each vote result must have
376
 *   the 'vote_cache_id' key set.
377
 */
378
function votingapi_delete_results($vote_results = array()) {
379
  if (!empty($vote_results)) {
380
    $vids = array();
381
    foreach ($vote_results as $vote) {
382
      $vids[] = $vote['vote_cache_id'];
383
    }
384
    db_delete('votingapi_cache')
385
      ->condition('vote_cache_id', $vids, 'IN')
386
      ->execute();
387
  }
388
}
389

    
390
/**
391
 * Select individual votes from the database.
392
 *
393
 * @param $criteria
394
 *   A keyed array used to build the select query. Keys can contain
395
 *   a single value or an array of values to be matched.
396
 *   $criteria['vote_id']       (If this is set, all other keys are skipped)
397
 *   $criteria['entity_id']
398
 *   $criteria['entity_type']
399
 *   $criteria['value_type']
400
 *   $criteria['tag']
401
 *   $criteria['uid']
402
 *   $criteria['vote_source']
403
 *   $criteria['timestamp']   (If this is set, records with timestamps
404
 *      GREATER THAN the set value will be selected.)
405
 * @param $limit
406
 *   An optional integer specifying the maximum number of votes to return.
407
 *
408
 * @return
409
 *   An array of votes matching the criteria.
410
 */
411
function votingapi_select_votes($criteria = array(), $limit = 0) {
412
  $window = -1;
413
  if (empty($criteria['uid']) || $criteria['uid'] == 0) {
414
    if (!empty($criteria['vote_source'])) {
415
      $window = variable_get('votingapi_anonymous_window', 86400);
416
    }
417
  }
418
  else {
419
    $window = variable_get('votingapi_user_window', -1);
420
  }
421
  if ($window >= 0) {
422
    $criteria['timestamp'] = REQUEST_TIME - $window;
423
  }
424
  $function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_select_votes';
425
  return $function($criteria, $limit);
426
}
427

    
428
/**
429
 * Select cached vote results from the database.
430
 *
431
 * @param $criteria
432
 *   A keyed array used to build the select query. Keys can contain
433
 *   a single value or an array of values to be matched.
434
 *   $criteria['vote_cache_id']     (If this is set, all other keys are skipped)
435
 *   $criteria['entity_id']
436
 *   $criteria['entity_type']
437
 *   $criteria['value_type']
438
 *   $criteria['tag']
439
 *   $criteria['function']
440
 *   $criteria['timestamp']   (If this is set, records with timestamps
441
 *      GREATER THAN the set value will be selected.)
442
 * @param $limit
443
 *   An optional integer specifying the maximum number of votes to return.
444
 *
445
 * @return
446
 *   An array of vote results matching the criteria.
447
 */
448
function votingapi_select_results($criteria = array(), $limit = 0) {
449
  $query = db_select('votingapi_cache')->fields('votingapi_cache');
450
  foreach ($criteria as $key => $value) {
451
    $query->condition($key, $value, is_array($value) ? 'IN' : '=');
452
  }
453
  if (!empty($limit)) {
454
    $query->range(0, $limit);
455
  }
456
  return $query->execute()->fetchAll(PDO::FETCH_ASSOC);
457
}
458

    
459
/**
460
 * Recalculates the aggregate results of all votes for a piece of content.
461
 *
462
 * Loads all votes for a given piece of content, then calculates and caches the
463
 * aggregate vote results. This is only intended for modules that have assumed
464
 * responsibility for the full voting cycle: the votingapi_set_vote() function
465
 * recalculates automatically.
466
 *
467
 * @param string $entity_type
468
 *   A string identifying the type of content being rated. Node, comment,
469
 *   aggregator item, etc.
470
 * @param string $entity_id
471
 *   The key ID of the content being rated.
472
 * @param bool $force_calculation
473
 *
474
 * @return array
475
 *   An array of the resulting votingapi_cache records, structured thusly:
476
 *   $value = $results[$ag][$value_type][$function]
477
 *
478
 * @see votingapi_set_votes()
479
 */
480
function votingapi_recalculate_results($entity_type, $entity_id, $force_calculation = FALSE) {
481
  // if we're operating in cron mode, and the 'force recalculation' flag is NOT set,
482
  // bail out. The cron run will pick up the results.
483

    
484
  if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron' || $force_calculation == TRUE) {
485
    $query = db_delete('votingapi_cache')
486
      ->condition('entity_type', $entity_type)
487
      ->condition('entity_id', $entity_id)
488
      ->execute();
489

    
490
    $function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_standard_results';
491
    // Bulk query to pull the majority of the results we care about.
492
    $cache = $function($entity_type, $entity_id);
493

    
494
    // Give other modules a chance to alter the collection of votes.
495
    drupal_alter('votingapi_results', $cache, $entity_type, $entity_id);
496

    
497
    // Now, do the caching. Woo.
498
    $cached = array();
499
    foreach ($cache as $tag => $types) {
500
      foreach ($types as $type => $functions) {
501
        foreach ($functions as $function => $value) {
502
          $cached[] = array(
503
            'entity_type' => $entity_type,
504
            'entity_id' => $entity_id,
505
            'value_type' => $type,
506
            'value' => $value,
507
            'tag' => $tag,
508
            'function' => $function,
509
          );
510
        }
511
      }
512
    }
513
    votingapi_add_results($cached);
514

    
515
    // Give other modules a chance to act on the results of the vote totaling.
516
    module_invoke_all('votingapi_results', $cached, $entity_type, $entity_id);
517

    
518
    return $cached;
519
  }
520
}
521

    
522

    
523
/**
524
 * Returns metadata about tags, value_types, and results defined by vote
525
 * modules.
526
 *
527
 * If your module needs to determine what existing tags, value_types, etc., are
528
 * being supplied by other modules, call this function. Querying the votingapi
529
 * tables for this information directly is discouraged, as values may not
530
 * appear
531
 * consistently. (For example, 'average' does not appear in the cache table
532
 * until votes have actually been cast in the cache table.)
533
 *
534
 * Three major bins of data are stored: tags, value_types, and functions. Each
535
 * entry in these bins is keyed by the value stored in the actual VotingAPI
536
 * tables, and contains an array with (minimally) 'name' and 'description'
537
 * keys.
538
 * Modules can add extra keys to their entries if desired.
539
 *
540
 * This metadata can be modified or expanded using
541
 * hook_votingapi_metadata_alter().
542
 *
543
 * @param bool $reset
544
 *   A reset status.
545
 *
546
 * @return array
547
 *   An array of metadata defined by VotingAPI and altered by vote modules.
548
 *
549
 * @see hook_votingapi_metadata_alter()
550
 */
551
function votingapi_metadata($reset = FALSE) {
552
  static $data;
553
  if ($reset || !isset($data)) {
554
    $data = array(
555
      'tags' => array(
556
        'vote' => array(
557
          'name' => t('Normal vote'),
558
          'description' => t('The default tag for votes on content. If multiple votes with different tags are being cast on a piece of content, consider casting a "summary" vote with this tag as well.'),
559
        ),
560
      ),
561
      'value_types' => array(
562
        'percent' => array(
563
          'name' => t('Percent'),
564
          'description' => t('Votes in a specific range. Values are stored in a 1-100 range, but can be represented as any scale when shown to the user.'),
565
        ),
566
        'points' => array(
567
          'name' => t('Points'),
568
          'description' => t('Votes that contribute points/tokens/karma towards a total. May be positive or negative.'),
569
        ),
570
      ),
571
      'functions' => array(
572
        'count' => array(
573
          'name' => t('Number of votes'),
574
          'description' => t('The number of votes cast for a given piece of content.'),
575
        ),
576
        'average' => array(
577
          'name' => t('Average vote'),
578
          'description' => t('The average vote cast on a given piece of content.'),
579
        ),
580
        'sum' => array(
581
          'name' => t('Total score'),
582
          'description' => t('The sum of all votes for a given piece of content.'),
583
          'value_types' => array('points'),
584
        ),
585
      ),
586
    );
587

    
588
    drupal_alter('votingapi_metadata', $data);
589
  }
590

    
591
  return $data;
592
}
593

    
594
/**
595
 * Builds the default VotingAPI results for the three supported voting styles.
596
 */
597
function votingapi_votingapi_storage_standard_results($entity_type, $entity_id) {
598
  $cache = array();
599

    
600
  $sql = "SELECT v.value_type, v.tag, ";
601
  $sql .= "COUNT(v.value) as value_count, SUM(v.value) as value_sum  ";
602
  $sql .= "FROM {votingapi_vote} v ";
603
  $sql .= "WHERE v.entity_type = :type AND v.entity_id = :id AND v.value_type IN ('points', 'percent') ";
604
  $sql .= "GROUP BY v.value_type, v.tag";
605
  $results = db_query($sql, array(
606
    ':type' => $entity_type,
607
    ':id' => $entity_id,
608
  ));
609

    
610
  foreach ($results as $result) {
611
    $cache[$result->tag][$result->value_type]['count'] = $result->value_count;
612
    $cache[$result->tag][$result->value_type]['average'] = $result->value_sum / $result->value_count;
613
    if ($result->value_type == 'points') {
614
      $cache[$result->tag][$result->value_type]['sum'] = $result->value_sum;
615
    }
616
  }
617

    
618
  $sql = "SELECT v.tag, v.value, v.value_type, COUNT(1) AS score ";
619
  $sql .= "FROM {votingapi_vote} v ";
620
  $sql .= "WHERE v.entity_type = :type AND v.entity_id = :id AND v.value_type = 'option' ";
621
  $sql .= "GROUP BY v.value, v.tag, v.value_type";
622
  $results = db_query($sql, array(
623
    ':type' => $entity_type,
624
    ':id' => $entity_id,
625
  ));
626

    
627
  foreach ($results as $result) {
628
    $cache[$result->tag][$result->value_type]['option-' . $result->value] = $result->score;
629
  }
630

    
631
  return $cache;
632
}
633

    
634
/**
635
 * Retrieve the value of the first vote matching the criteria passed in.
636
 */
637
function votingapi_select_single_vote_value($criteria = array()) {
638
  if ($results = votingapi_select_votes($criteria, 1)) {
639
    return $results[0]['value'];
640
  }
641
}
642

    
643
/**
644
 * Retrieve the value of the first result matching the criteria passed in.
645
 */
646
function votingapi_select_single_result_value($criteria = array()) {
647
  if ($results = votingapi_select_results($criteria, 1)) {
648
    return $results[0]['value'];
649
  }
650
}
651

    
652
/**
653
 * Populate the value of any unset vote properties.
654
 *
655
 * @param $vote
656
 *   A single vote.
657
 */
658
function _votingapi_prep_vote(&$vote) {
659
  global $user;
660
  if (empty($vote['prepped'])) {
661
    $vote += array(
662
      'entity_type' => 'node',
663
      'value_type' => 'percent',
664
      'tag' => 'vote',
665
      'uid' => $user->uid,
666
      'timestamp' => REQUEST_TIME,
667
      'vote_source' => ip_address(),
668
      'prepped' => TRUE,
669
    );
670
  }
671
}
672

    
673
/**
674
 * Implements hook_entity_delete().
675
 *
676
 * Delete all votes and cache entries for the deleted entities
677
 */
678
function votingapi_entity_delete($entity, $type) {
679
  $ids = entity_extract_ids($type, $entity);
680
  $id = array($ids[0]);
681
  _votingapi_delete_cache_by_entity($id, $type);
682
  _votingapi_delete_votes_by_entity($id, $type);
683
}
684

    
685
/**
686
 * Helper function to delete all cache entries on given entities.
687
 *
688
 * @param array entity ids
689
 * @param string entity type
690
 */
691
function _votingapi_delete_cache_by_entity($entity_ids, $type) {
692
  $result = db_select('votingapi_cache', 'v')
693
    ->fields('v', array('vote_cache_id'))
694
    ->condition('entity_type', $type)
695
    ->condition('entity_id', $entity_ids)
696
    ->execute();
697
  $votes = array();
698
  foreach ($result as $row) {
699
    $votes[]['vote_cache_id'] = $row->vote_cache_id;
700
  }
701
  votingapi_delete_results($votes);
702
}
703

    
704
/**
705
 * Helper function to delete all votes on given entities.
706
 *
707
 * @param array entity ids
708
 * @param string entity type
709
 */
710
function _votingapi_delete_votes_by_entity($entity_ids, $type) {
711
  $result = db_select('votingapi_vote', 'v')
712
    ->fields('v', array('vote_id', 'entity_type', 'entity_id'))
713
    ->condition('entity_type', $type)
714
    ->condition('entity_id', $entity_ids)
715
    ->execute();
716
  $votes = array();
717
  foreach ($result as $row) {
718
    $votes[] = (array) $row;
719
  }
720
  votingapi_delete_votes($votes);
721
}
722

    
723
/**
724
 * Delete votes and cache entries for a number of entities in the queue.
725
 */
726
function _votingapi_cron_delete_orphaned() {
727
  $queue = DrupalQueue::get('VotingAPIOrphaned');
728
  $limit = variable_get('votingapi_cron_orphaned_max', 50);
729
  $done = 0;
730
  while (($item = $queue->claimItem()) && $done++ < $limit) {
731
    _votingapi_delete_cache_by_entity(array($item->data['entity_id']), $item->data['entity_type']);
732
    _votingapi_delete_votes_by_entity(array($item->data['entity_id']), $item->data['entity_type']);
733
    $queue->deleteItem($item);
734
  }
735
}