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
|
return $items;
|
75
|
}
|
76
|
|
77
|
/**
|
78
|
* Implements hook_permission().
|
79
|
*/
|
80
|
function votingapi_permission() {
|
81
|
return array(
|
82
|
'administer voting api' => array(
|
83
|
'title' => t('Administer Voting API'),
|
84
|
),
|
85
|
);
|
86
|
}
|
87
|
|
88
|
/**
|
89
|
* Implements of hook_views_api().
|
90
|
*/
|
91
|
function votingapi_views_api() {
|
92
|
return array(
|
93
|
'api' => 3,
|
94
|
'path' => drupal_get_path('module', 'votingapi') . '/views',
|
95
|
);
|
96
|
}
|
97
|
|
98
|
/**
|
99
|
* Implements of hook_cron().
|
100
|
*
|
101
|
* Allows db-intensive recalculations to be deferred until cron-time.
|
102
|
*/
|
103
|
function votingapi_cron() {
|
104
|
if (variable_get('votingapi_calculation_schedule', 'immediate') == 'cron') {
|
105
|
$time = REQUEST_TIME;
|
106
|
$last_cron = variable_get('votingapi_last_cron', 0);
|
107
|
$result = db_query('SELECT DISTINCT entity_type, entity_id FROM {votingapi_vote} WHERE timestamp > :timestamp', array(':timestamp' => $last_cron));
|
108
|
foreach ($result as $content) {
|
109
|
votingapi_recalculate_results($content->entity_type, $content->entity_id, TRUE);
|
110
|
}
|
111
|
|
112
|
variable_set('votingapi_last_cron', $time);
|
113
|
}
|
114
|
|
115
|
_votingapi_cron_delete_orphaned();
|
116
|
}
|
117
|
|
118
|
/**
|
119
|
* Cast a vote on a particular piece of content.
|
120
|
*
|
121
|
* This function does most of the heavy lifting needed by third-party modules
|
122
|
* based on VotingAPI. Handles clearing out old votes for a given piece of
|
123
|
* content, saving the incoming votes, and re-tallying the results given the
|
124
|
* new data.
|
125
|
*
|
126
|
* Modules that need more explicit control can call votingapi_add_votes() and
|
127
|
* manage the deletion/recalculation tasks manually.
|
128
|
*
|
129
|
* @param array $votes
|
130
|
* An array of votes, each with the following structure:
|
131
|
* $vote['entity_type'] (Optional, defaults to 'node')
|
132
|
* $vote['entity_id'] (Required)
|
133
|
* $vote['value_type'] (Optional, defaults to 'percent')
|
134
|
* $vote['value'] (Required)
|
135
|
* $vote['tag'] (Optional, defaults to 'vote')
|
136
|
* $vote['uid'] (Optional, defaults to current user)
|
137
|
* $vote['vote_source'] (Optional, defaults to current IP)
|
138
|
* $vote['timestamp'] (Optional, defaults to REQUEST_TIME)
|
139
|
* @param array $criteria
|
140
|
* A keyed array used to determine what votes will be deleted when the current
|
141
|
* vote is cast. If no value is specified, all votes for the current content
|
142
|
* by the current user will be reset. If an empty array is passed in, no votes
|
143
|
* will be reset and all incoming votes will be saved IN ADDITION to existing
|
144
|
* ones.
|
145
|
* $criteria['vote_id'] (If this is set, all other keys are skipped)
|
146
|
* $criteria['entity_type']
|
147
|
* $criteria['entity_type']
|
148
|
* $criteria['value_type']
|
149
|
* $criteria['tag']
|
150
|
* $criteria['uid']
|
151
|
* $criteria['vote_source']
|
152
|
* $criteria['timestamp'] (If this is set, records with timestamps
|
153
|
* GREATER THAN the set value will be selected.)
|
154
|
*
|
155
|
* @return array
|
156
|
* An array of vote result records affected by the vote. The values are
|
157
|
* contained in a nested array keyed thusly:
|
158
|
* $value = $results[$entity_type][$entity_id][$tag][$value_type][$function]
|
159
|
*
|
160
|
* @see votingapi_add_votes()
|
161
|
* @see votingapi_recalculate_results()
|
162
|
*/
|
163
|
function votingapi_set_votes(&$votes, $criteria = NULL) {
|
164
|
$touched = array();
|
165
|
if (!empty($votes['entity_id'])) {
|
166
|
$votes = array($votes);
|
167
|
}
|
168
|
|
169
|
// Allow other modules to modify or unset/remove votes.
|
170
|
// module_invoke_all does not allow passing variables by reference
|
171
|
// http://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke_all/7#comment-35778
|
172
|
drupal_alter(array('votingapi_preset_votes'), $votes);
|
173
|
// Handle clearing out old votes if they exist.
|
174
|
if (!isset($criteria)) {
|
175
|
// If the calling function didn't explicitly set criteria for vote deletion,
|
176
|
// build up the delete queries here.
|
177
|
foreach ($votes as $vote) {
|
178
|
$tmp = $vote + votingapi_current_user_identifier();
|
179
|
if (isset($tmp['value'])) {
|
180
|
unset($tmp['value']);
|
181
|
}
|
182
|
votingapi_delete_votes(votingapi_select_votes($tmp));
|
183
|
}
|
184
|
}
|
185
|
elseif (is_array($criteria)) {
|
186
|
// The calling function passed in an explicit set of delete filters.
|
187
|
if (!empty($criteria['entity_id'])) {
|
188
|
$criteria = array($criteria);
|
189
|
}
|
190
|
foreach ($criteria as $c) {
|
191
|
votingapi_delete_votes(votingapi_select_votes($c));
|
192
|
}
|
193
|
}
|
194
|
|
195
|
foreach ($votes as $key => $vote) {
|
196
|
_votingapi_prep_vote($vote);
|
197
|
$votes[$key] = $vote; // Is this needed? Check to see how PHP4 handles refs.
|
198
|
}
|
199
|
|
200
|
// Cast the actual votes, inserting them into the table.
|
201
|
votingapi_add_votes($votes);
|
202
|
|
203
|
foreach ($votes as $vote) {
|
204
|
$touched[$vote['entity_type']][$vote['entity_id']] = TRUE;
|
205
|
}
|
206
|
|
207
|
if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron') {
|
208
|
foreach ($touched as $type => $ids) {
|
209
|
foreach ($ids as $id => $bool) {
|
210
|
$touched[$type][$id] = votingapi_recalculate_results($type, $id);
|
211
|
}
|
212
|
}
|
213
|
}
|
214
|
return $touched;
|
215
|
}
|
216
|
|
217
|
/**
|
218
|
* Generate a proper identifier for the current user: if they have an account,
|
219
|
* return their UID. Otherwise, return their IP address.
|
220
|
*/
|
221
|
function votingapi_current_user_identifier() {
|
222
|
global $user;
|
223
|
$criteria = array('uid' => $user->uid);
|
224
|
if (!$user->uid) {
|
225
|
$criteria['vote_source'] = ip_address();
|
226
|
}
|
227
|
return $criteria;
|
228
|
}
|
229
|
|
230
|
/**
|
231
|
* Implements of hook_votingapi_storage_add_vote().
|
232
|
*/
|
233
|
function votingapi_votingapi_storage_add_vote(&$vote) {
|
234
|
drupal_write_record('votingapi_vote', $vote);
|
235
|
}
|
236
|
|
237
|
/**
|
238
|
* Implements of hook_votingapi_storage_delete_votes().
|
239
|
*/
|
240
|
function votingapi_votingapi_storage_delete_votes($votes, $vids) {
|
241
|
db_delete('votingapi_vote')->condition('vote_id', $vids, 'IN')->execute();
|
242
|
}
|
243
|
|
244
|
/**
|
245
|
* Implements of hook_votingapi_storage_select_votes().
|
246
|
*/
|
247
|
function votingapi_votingapi_storage_select_votes($criteria, $limit) {
|
248
|
$query = db_select('votingapi_vote')->fields('votingapi_vote');
|
249
|
foreach ($criteria as $key => $value) {
|
250
|
if ($key == 'timestamp') {
|
251
|
$query->condition($key, $value, '>');
|
252
|
}
|
253
|
else {
|
254
|
$query->condition($key, $value, is_array($value) ? 'IN' : '=');
|
255
|
}
|
256
|
}
|
257
|
if (!empty($limit)) {
|
258
|
$query->range(0, $limit);
|
259
|
}
|
260
|
return $query->execute()->fetchAll(PDO::FETCH_ASSOC);
|
261
|
}
|
262
|
|
263
|
/**
|
264
|
* Save a collection of votes to the database.
|
265
|
*
|
266
|
* This function does most of the heavy lifting for VotingAPI the main
|
267
|
* votingapi_set_votes() function, but does NOT automatically triger
|
268
|
* re-tallying
|
269
|
* of results. As such, it's useful for modules that must insert their votes in
|
270
|
* separate batches without triggering unecessary recalculation.
|
271
|
*
|
272
|
* Remember that any module calling this function implicitly assumes
|
273
|
* responsibility for calling votingapi_recalculate_results() when all votes
|
274
|
* have been inserted.
|
275
|
*
|
276
|
* @param array $votes
|
277
|
* A vote or array of votes, each with the following structure:
|
278
|
* $vote['entity_type'] (Optional, defaults to 'node')
|
279
|
* $vote['entity_id'] (Required)
|
280
|
* $vote['value_type'] (Optional, defaults to 'percent')
|
281
|
* $vote['value'] (Required)
|
282
|
* $vote['tag'] (Optional, defaults to 'vote')
|
283
|
* $vote['uid'] (Optional, defaults to current user)
|
284
|
* $vote['vote_source'] (Optional, defaults to current IP)
|
285
|
* $vote['timestamp'] (Optional, defaults to REQUEST_TIME)
|
286
|
*
|
287
|
* @return array
|
288
|
* The same votes, with vote_id keys populated.
|
289
|
*
|
290
|
* @see votingapi_set_votes()
|
291
|
* @see votingapi_recalculate_results()
|
292
|
*/
|
293
|
function votingapi_add_votes(&$votes) {
|
294
|
if (!empty($votes['entity_id'])) {
|
295
|
$votes = array($votes);
|
296
|
}
|
297
|
$function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_add_vote';
|
298
|
foreach ($votes as $key => $vote) {
|
299
|
_votingapi_prep_vote($vote);
|
300
|
$function($vote);
|
301
|
$votes[$key] = $vote;
|
302
|
}
|
303
|
module_invoke_all('votingapi_insert', $votes);
|
304
|
return $votes;
|
305
|
}
|
306
|
|
307
|
/**
|
308
|
* Save a bundle of vote results to the database.
|
309
|
*
|
310
|
* This function is called by votingapi_recalculate_results() after tallying
|
311
|
* the values of all cast votes on a piece of content. This function will be of
|
312
|
* little use for most third-party modules, unless they manually insert their
|
313
|
* own result data.
|
314
|
*
|
315
|
* @param array vote_results
|
316
|
* An array of vote results, each with the following properties:
|
317
|
* $vote_result['entity_type']
|
318
|
* $vote_result['entity_id']
|
319
|
* $vote_result['value_type']
|
320
|
* $vote_result['value']
|
321
|
* $vote_result['tag']
|
322
|
* $vote_result['function']
|
323
|
* $vote_result['timestamp'] (Optional, defaults to REQUEST_TIME)
|
324
|
*/
|
325
|
function votingapi_add_results($vote_results = array()) {
|
326
|
if (!empty($vote_results['entity_id'])) {
|
327
|
$vote_results = array($vote_results);
|
328
|
}
|
329
|
|
330
|
foreach ($vote_results as $vote_result) {
|
331
|
$vote_result['timestamp'] = REQUEST_TIME;
|
332
|
drupal_write_record('votingapi_cache', $vote_result);
|
333
|
}
|
334
|
}
|
335
|
|
336
|
/**
|
337
|
* Delete votes from the database.
|
338
|
*
|
339
|
* @param $votes
|
340
|
* An array of votes to delete. Minimally, each vote must have the 'vote_id'
|
341
|
* key set.
|
342
|
*/
|
343
|
function votingapi_delete_votes($votes = array()) {
|
344
|
if (!empty($votes)) {
|
345
|
module_invoke_all('votingapi_delete', $votes);
|
346
|
$vids = array();
|
347
|
foreach ($votes as $vote) {
|
348
|
$vids[] = $vote['vote_id'];
|
349
|
}
|
350
|
$function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_delete_votes';
|
351
|
$function($votes, $vids);
|
352
|
}
|
353
|
}
|
354
|
|
355
|
/**
|
356
|
* Delete cached vote results from the database.
|
357
|
*
|
358
|
* @param $vote_results
|
359
|
* An array of vote results to delete. Minimally, each vote result must have
|
360
|
* the 'vote_cache_id' key set.
|
361
|
*/
|
362
|
function votingapi_delete_results($vote_results = array()) {
|
363
|
if (!empty($vote_results)) {
|
364
|
$vids = array();
|
365
|
foreach ($vote_results as $vote) {
|
366
|
$vids[] = $vote['vote_cache_id'];
|
367
|
}
|
368
|
db_delete('votingapi_cache')
|
369
|
->condition('vote_cache_id', $vids, 'IN')
|
370
|
->execute();
|
371
|
}
|
372
|
}
|
373
|
|
374
|
/**
|
375
|
* Select individual votes from the database.
|
376
|
*
|
377
|
* @param $criteria
|
378
|
* A keyed array used to build the select query. Keys can contain
|
379
|
* a single value or an array of values to be matched.
|
380
|
* $criteria['vote_id'] (If this is set, all other keys are skipped)
|
381
|
* $criteria['entity_id']
|
382
|
* $criteria['entity_type']
|
383
|
* $criteria['value_type']
|
384
|
* $criteria['tag']
|
385
|
* $criteria['uid']
|
386
|
* $criteria['vote_source']
|
387
|
* $criteria['timestamp'] (If this is set, records with timestamps
|
388
|
* GREATER THAN the set value will be selected.)
|
389
|
* @param $limit
|
390
|
* An optional integer specifying the maximum number of votes to return.
|
391
|
*
|
392
|
* @return
|
393
|
* An array of votes matching the criteria.
|
394
|
*/
|
395
|
function votingapi_select_votes($criteria = array(), $limit = 0) {
|
396
|
$window = -1;
|
397
|
if (empty($criteria['uid']) || $criteria['uid'] == 0) {
|
398
|
if (!empty($criteria['vote_source'])) {
|
399
|
$window = variable_get('votingapi_anonymous_window', 86400);
|
400
|
}
|
401
|
}
|
402
|
else {
|
403
|
$window = variable_get('votingapi_user_window', -1);
|
404
|
}
|
405
|
if ($window >= 0) {
|
406
|
$criteria['timestamp'] = REQUEST_TIME - $window;
|
407
|
}
|
408
|
$function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_select_votes';
|
409
|
return $function($criteria, $limit);
|
410
|
}
|
411
|
|
412
|
/**
|
413
|
* Select cached vote results from the database.
|
414
|
*
|
415
|
* @param $criteria
|
416
|
* A keyed array used to build the select query. Keys can contain
|
417
|
* a single value or an array of values to be matched.
|
418
|
* $criteria['vote_cache_id'] (If this is set, all other keys are skipped)
|
419
|
* $criteria['entity_id']
|
420
|
* $criteria['entity_type']
|
421
|
* $criteria['value_type']
|
422
|
* $criteria['tag']
|
423
|
* $criteria['function']
|
424
|
* $criteria['timestamp'] (If this is set, records with timestamps
|
425
|
* GREATER THAN the set value will be selected.)
|
426
|
* @param $limit
|
427
|
* An optional integer specifying the maximum number of votes to return.
|
428
|
*
|
429
|
* @return
|
430
|
* An array of vote results matching the criteria.
|
431
|
*/
|
432
|
function votingapi_select_results($criteria = array(), $limit = 0) {
|
433
|
$query = db_select('votingapi_cache')->fields('votingapi_cache');
|
434
|
foreach ($criteria as $key => $value) {
|
435
|
$query->condition($key, $value, is_array($value) ? 'IN' : '=');
|
436
|
}
|
437
|
if (!empty($limit)) {
|
438
|
$query->range(0, $limit);
|
439
|
}
|
440
|
return $query->execute()->fetchAll(PDO::FETCH_ASSOC);
|
441
|
}
|
442
|
|
443
|
/**
|
444
|
* Recalculates the aggregate results of all votes for a piece of content.
|
445
|
*
|
446
|
* Loads all votes for a given piece of content, then calculates and caches the
|
447
|
* aggregate vote results. This is only intended for modules that have assumed
|
448
|
* responsibility for the full voting cycle: the votingapi_set_vote() function
|
449
|
* recalculates automatically.
|
450
|
*
|
451
|
* @param string $entity_type
|
452
|
* A string identifying the type of content being rated. Node, comment,
|
453
|
* aggregator item, etc.
|
454
|
* @param string $entity_id
|
455
|
* The key ID of the content being rated.
|
456
|
* @param bool $force_calculation
|
457
|
*
|
458
|
* @return array
|
459
|
* An array of the resulting votingapi_cache records, structured thusly:
|
460
|
* $value = $results[$ag][$value_type][$function]
|
461
|
*
|
462
|
* @see votingapi_set_votes()
|
463
|
*/
|
464
|
function votingapi_recalculate_results($entity_type, $entity_id, $force_calculation = FALSE) {
|
465
|
// if we're operating in cron mode, and the 'force recalculation' flag is NOT set,
|
466
|
// bail out. The cron run will pick up the results.
|
467
|
|
468
|
if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron' || $force_calculation == TRUE) {
|
469
|
$query = db_delete('votingapi_cache')
|
470
|
->condition('entity_type', $entity_type)
|
471
|
->condition('entity_id', $entity_id)
|
472
|
->execute();
|
473
|
|
474
|
$function = variable_get('votingapi_storage_module', 'votingapi') . '_votingapi_storage_standard_results';
|
475
|
// Bulk query to pull the majority of the results we care about.
|
476
|
$cache = $function($entity_type, $entity_id);
|
477
|
|
478
|
// Give other modules a chance to alter the collection of votes.
|
479
|
drupal_alter('votingapi_results', $cache, $entity_type, $entity_id);
|
480
|
|
481
|
// Now, do the caching. Woo.
|
482
|
$cached = array();
|
483
|
foreach ($cache as $tag => $types) {
|
484
|
foreach ($types as $type => $functions) {
|
485
|
foreach ($functions as $function => $value) {
|
486
|
$cached[] = array(
|
487
|
'entity_type' => $entity_type,
|
488
|
'entity_id' => $entity_id,
|
489
|
'value_type' => $type,
|
490
|
'value' => $value,
|
491
|
'tag' => $tag,
|
492
|
'function' => $function,
|
493
|
);
|
494
|
}
|
495
|
}
|
496
|
}
|
497
|
votingapi_add_results($cached);
|
498
|
|
499
|
// Give other modules a chance to act on the results of the vote totaling.
|
500
|
module_invoke_all('votingapi_results', $cached, $entity_type, $entity_id);
|
501
|
|
502
|
return $cached;
|
503
|
}
|
504
|
}
|
505
|
|
506
|
|
507
|
/**
|
508
|
* Returns metadata about tags, value_types, and results defined by vote
|
509
|
* modules.
|
510
|
*
|
511
|
* If your module needs to determine what existing tags, value_types, etc., are
|
512
|
* being supplied by other modules, call this function. Querying the votingapi
|
513
|
* tables for this information directly is discouraged, as values may not
|
514
|
* appear
|
515
|
* consistently. (For example, 'average' does not appear in the cache table
|
516
|
* until votes have actually been cast in the cache table.)
|
517
|
*
|
518
|
* Three major bins of data are stored: tags, value_types, and functions. Each
|
519
|
* entry in these bins is keyed by the value stored in the actual VotingAPI
|
520
|
* tables, and contains an array with (minimally) 'name' and 'description'
|
521
|
* keys.
|
522
|
* Modules can add extra keys to their entries if desired.
|
523
|
*
|
524
|
* This metadata can be modified or expanded using
|
525
|
* hook_votingapi_metadata_alter().
|
526
|
*
|
527
|
* @param bool $reset
|
528
|
* A reset status.
|
529
|
*
|
530
|
* @return array
|
531
|
* An array of metadata defined by VotingAPI and altered by vote modules.
|
532
|
*
|
533
|
* @see hook_votingapi_metadata_alter()
|
534
|
*/
|
535
|
function votingapi_metadata($reset = FALSE) {
|
536
|
static $data;
|
537
|
if ($reset || !isset($data)) {
|
538
|
$data = array(
|
539
|
'tags' => array(
|
540
|
'vote' => array(
|
541
|
'name' => t('Normal vote'),
|
542
|
'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.'),
|
543
|
),
|
544
|
),
|
545
|
'value_types' => array(
|
546
|
'percent' => array(
|
547
|
'name' => t('Percent'),
|
548
|
'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.'),
|
549
|
),
|
550
|
'points' => array(
|
551
|
'name' => t('Points'),
|
552
|
'description' => t('Votes that contribute points/tokens/karma towards a total. May be positive or negative.'),
|
553
|
),
|
554
|
),
|
555
|
'functions' => array(
|
556
|
'count' => array(
|
557
|
'name' => t('Number of votes'),
|
558
|
'description' => t('The number of votes cast for a given piece of content.'),
|
559
|
),
|
560
|
'average' => array(
|
561
|
'name' => t('Average vote'),
|
562
|
'description' => t('The average vote cast on a given piece of content.'),
|
563
|
),
|
564
|
'sum' => array(
|
565
|
'name' => t('Total score'),
|
566
|
'description' => t('The sum of all votes for a given piece of content.'),
|
567
|
'value_types' => array('points'),
|
568
|
),
|
569
|
),
|
570
|
);
|
571
|
|
572
|
drupal_alter('votingapi_metadata', $data);
|
573
|
}
|
574
|
|
575
|
return $data;
|
576
|
}
|
577
|
|
578
|
/**
|
579
|
* Builds the default VotingAPI results for the three supported voting styles.
|
580
|
*/
|
581
|
function votingapi_votingapi_storage_standard_results($entity_type, $entity_id) {
|
582
|
$cache = array();
|
583
|
|
584
|
$sql = "SELECT v.value_type, v.tag, ";
|
585
|
$sql .= "COUNT(v.value) as value_count, SUM(v.value) as value_sum ";
|
586
|
$sql .= "FROM {votingapi_vote} v ";
|
587
|
$sql .= "WHERE v.entity_type = :type AND v.entity_id = :id AND v.value_type IN ('points', 'percent') ";
|
588
|
$sql .= "GROUP BY v.value_type, v.tag";
|
589
|
$results = db_query($sql, array(
|
590
|
':type' => $entity_type,
|
591
|
':id' => $entity_id,
|
592
|
));
|
593
|
|
594
|
foreach ($results as $result) {
|
595
|
$cache[$result->tag][$result->value_type]['count'] = $result->value_count;
|
596
|
$cache[$result->tag][$result->value_type]['average'] = $result->value_sum / $result->value_count;
|
597
|
if ($result->value_type == 'points') {
|
598
|
$cache[$result->tag][$result->value_type]['sum'] = $result->value_sum;
|
599
|
}
|
600
|
}
|
601
|
|
602
|
$sql = "SELECT v.tag, v.value, v.value_type, COUNT(1) AS score ";
|
603
|
$sql .= "FROM {votingapi_vote} v ";
|
604
|
$sql .= "WHERE v.entity_type = :type AND v.entity_id = :id AND v.value_type = 'option' ";
|
605
|
$sql .= "GROUP BY v.value, v.tag, v.value_type";
|
606
|
$results = db_query($sql, array(
|
607
|
':type' => $entity_type,
|
608
|
':id' => $entity_id,
|
609
|
));
|
610
|
|
611
|
foreach ($results as $result) {
|
612
|
$cache[$result->tag][$result->value_type]['option-' . $result->value] = $result->score;
|
613
|
}
|
614
|
|
615
|
return $cache;
|
616
|
}
|
617
|
|
618
|
/**
|
619
|
* Retrieve the value of the first vote matching the criteria passed in.
|
620
|
*/
|
621
|
function votingapi_select_single_vote_value($criteria = array()) {
|
622
|
if ($results = votingapi_select_votes($criteria, 1)) {
|
623
|
return $results[0]['value'];
|
624
|
}
|
625
|
}
|
626
|
|
627
|
/**
|
628
|
* Retrieve the value of the first result matching the criteria passed in.
|
629
|
*/
|
630
|
function votingapi_select_single_result_value($criteria = array()) {
|
631
|
if ($results = votingapi_select_results($criteria, 1)) {
|
632
|
return $results[0]['value'];
|
633
|
}
|
634
|
}
|
635
|
|
636
|
/**
|
637
|
* Populate the value of any unset vote properties.
|
638
|
*
|
639
|
* @param $vote
|
640
|
* A single vote.
|
641
|
*/
|
642
|
function _votingapi_prep_vote(&$vote) {
|
643
|
global $user;
|
644
|
if (empty($vote['prepped'])) {
|
645
|
$vote += array(
|
646
|
'entity_type' => 'node',
|
647
|
'value_type' => 'percent',
|
648
|
'tag' => 'vote',
|
649
|
'uid' => $user->uid,
|
650
|
'timestamp' => REQUEST_TIME,
|
651
|
'vote_source' => ip_address(),
|
652
|
'prepped' => TRUE,
|
653
|
);
|
654
|
}
|
655
|
}
|
656
|
|
657
|
/**
|
658
|
* Implements hook_entity_delete().
|
659
|
*
|
660
|
* Delete all votes and cache entries for the deleted entities
|
661
|
*/
|
662
|
function votingapi_entity_delete($entity, $type) {
|
663
|
$ids = entity_extract_ids($type, $entity);
|
664
|
$id = array($ids[0]);
|
665
|
_votingapi_delete_cache_by_entity($id, $type);
|
666
|
_votingapi_delete_votes_by_entity($id, $type);
|
667
|
}
|
668
|
|
669
|
/**
|
670
|
* Helper function to delete all cache entries on given entities.
|
671
|
*
|
672
|
* @param array entity ids
|
673
|
* @param string entity type
|
674
|
*/
|
675
|
function _votingapi_delete_cache_by_entity($entity_ids, $type) {
|
676
|
$result = db_select('votingapi_cache', 'v')
|
677
|
->fields('v', array('vote_cache_id'))
|
678
|
->condition('entity_type', $type)
|
679
|
->condition('entity_id', $entity_ids)
|
680
|
->execute();
|
681
|
$votes = array();
|
682
|
foreach ($result as $row) {
|
683
|
$votes[]['vote_cache_id'] = $row->vote_cache_id;
|
684
|
}
|
685
|
votingapi_delete_results($votes);
|
686
|
}
|
687
|
|
688
|
/**
|
689
|
* Helper function to delete all votes on given entities.
|
690
|
*
|
691
|
* @param array entity ids
|
692
|
* @param string entity type
|
693
|
*/
|
694
|
function _votingapi_delete_votes_by_entity($entity_ids, $type) {
|
695
|
$result = db_select('votingapi_vote', 'v')
|
696
|
->fields('v', array('vote_id', 'entity_type', 'entity_id'))
|
697
|
->condition('entity_type', $type)
|
698
|
->condition('entity_id', $entity_ids)
|
699
|
->execute();
|
700
|
$votes = array();
|
701
|
foreach ($result as $row) {
|
702
|
$votes[] = (array) $row;
|
703
|
}
|
704
|
votingapi_delete_votes($votes);
|
705
|
}
|
706
|
|
707
|
/**
|
708
|
* Delete votes and cache entries for a number of entities in the queue.
|
709
|
*/
|
710
|
function _votingapi_cron_delete_orphaned() {
|
711
|
$queue = DrupalQueue::get('VotingAPIOrphaned');
|
712
|
$limit = variable_get('votingapi_cron_orphaned_max', 50);
|
713
|
$done = 0;
|
714
|
while (($item = $queue->claimItem()) && $done++ < $limit) {
|
715
|
_votingapi_delete_cache_by_entity(array($item->data['entity_id']), $item->data['entity_type']);
|
716
|
_votingapi_delete_votes_by_entity(array($item->data['entity_id']), $item->data['entity_type']);
|
717
|
$queue->deleteItem($item);
|
718
|
}
|
719
|
}
|