votingapi.module 25 KB

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