user_stats.module 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. <?php
  2. /**
  3. * @file
  4. * User Stats provides commonly requested user statistics for themers.
  5. * These are:
  6. * - days registered;
  7. * - join date;
  8. * - days since last login;
  9. * - days since last post;
  10. * - post count;
  11. * - login count;
  12. * - user online/offline;
  13. * - IP address;
  14. *
  15. * Note for hackers: function parameters should go in the order
  16. * $op/$type, $uid, $data (where applicable).
  17. */
  18. /**
  19. * Implements hook_permission().
  20. */
  21. function user_stats_permission() {
  22. return array(
  23. 'administer user stats' => array(
  24. 'title' => t('administer user stats'),
  25. 'description' => t('TODO Add a description for \'administer user stats\''),
  26. ),
  27. 'View statistics' => array(
  28. 'title' => t('View statistics'),
  29. 'description' => t('TODO Add a description for \'View statistics\''),
  30. ),
  31. 'View IP addresses' => array(
  32. 'title' => t('View IP addresses'),
  33. 'description' => t('TODO Add a description for \'View IP addresses\''),
  34. ),
  35. );
  36. }
  37. /**
  38. * Implements hook_menu().
  39. */
  40. function user_stats_menu() {
  41. $items = array();
  42. // Admin settings
  43. $items['admin/config/people/user_stats'] = array(
  44. 'title' => 'User Stats settings',
  45. 'description' => 'Configuration of user stats module options.',
  46. 'page callback' => 'drupal_get_form',
  47. 'page arguments' => array('user_stats_admin_settings'),
  48. 'access arguments' => array('administer user stats'),
  49. 'file' => 'user_stats.admin.inc',
  50. 'type' => MENU_NORMAL_ITEM,
  51. );
  52. $items['admin/config/people/user_stats/reset_post_count'] = array(
  53. 'title' => 'reset user post stats',
  54. 'page callback' => 'user_stats_reset_post_count',
  55. 'access arguments' => array('administer user stats'),
  56. 'file' => 'user_stats.admin.inc',
  57. 'type' => MENU_CALLBACK,
  58. );
  59. $items['admin/config/people/reset_login_count'] = array(
  60. 'title' => 'reset user login stats',
  61. 'page callback' => 'user_stats_reset_login_count',
  62. 'access arguments' => array('administer user stats'),
  63. 'file' => 'user_stats.admin.inc',
  64. 'type' => MENU_CALLBACK,
  65. );
  66. return $items;
  67. }
  68. /**
  69. * Returns user stats.
  70. *
  71. * @param $type
  72. * The statistic to return. Possible values are:
  73. * - "ip_address"
  74. * - "join_date"
  75. * - "login_count"
  76. * - "login_days"
  77. * - "post_count"
  78. * - "post_days"
  79. * - "reg_days"
  80. * - "online"
  81. * - "profile"
  82. * @param $uid
  83. * The user id who's stats should be retrieved.
  84. *
  85. * @return
  86. * The statistic requested. Every statistic except join_date, online and IP address is a numeric.
  87. * Join date is a string, whilst online is a boolean and IP Address a string.
  88. * Note: if $type = "post_days" and the user hasn't posted any content (of the
  89. * counted types) then 'n/a' is returned.
  90. */
  91. function user_stats_get_stats($type, $uid) {
  92. // Sometimes $uid can be NULL (comment previews for example).
  93. if (!is_numeric($uid)) {
  94. return;
  95. }
  96. // IP address is really a bit of feature creep.
  97. // At some point in the future, this could be split off into its own module.
  98. if ($type == 'ip_address') {
  99. if (!user_access('View IP addresses')) {
  100. return FALSE;
  101. }
  102. // Check cache.
  103. if (user_stats_cache_get($type, $uid) === FALSE) {
  104. $query = db_query("SELECT ip_address
  105. FROM {user_stats_ips} WHERE uid = :uid
  106. ORDER BY first_seen_timestamp LIMIT 1", array(':uid' => $uid));
  107. user_stats_cache_set($type, $uid, $query->fetchField());
  108. }
  109. return user_stats_cache_get($type, $uid);
  110. }
  111. // Everything else is under the 'View statistics' permission.
  112. if (!user_access('View statistics')) {
  113. return FALSE;
  114. }
  115. // Check cache first.
  116. if (user_stats_cache_get($type, $uid) !== FALSE) {
  117. return user_stats_cache_get($type, $uid);
  118. }
  119. switch ($type) {
  120. case 'join_date':
  121. $data = db_query("SELECT created FROM {users} WHERE uid = :uid", array(':uid' => $uid))->fetchField();
  122. break;
  123. case 'login_count':
  124. if (!variable_get('user_stats_count_logins', TRUE)) {
  125. $data = 'n/a';
  126. }
  127. else if (user_stats_isset($type, $uid)) {
  128. $data = db_query("SELECT value FROM {user_stats_values} WHERE name = :name AND uid = :uid", array(':name' => 'login_count', ':uid' => $uid))->fetchField();
  129. }
  130. else {
  131. return 0;
  132. }
  133. break;
  134. case 'login_days':
  135. $user_access = db_query("SELECT access FROM {users} WHERE uid = :uid", array(':uid' => $uid))->fetchField();
  136. $data = floor((REQUEST_TIME - $user_access) / 86400);
  137. break;
  138. case 'login_date':
  139. $data = db_query("SELECT access FROM {users} WHERE uid = :uid", array(':uid' => $uid))->fetchField();
  140. break;
  141. case 'post_count':
  142. if (!variable_get('user_stats_count_posts', TRUE) && !variable_get('user_stats_count_comments', TRUE)) {
  143. $data = 'n/a';
  144. }
  145. else if (!user_stats_isset('post_count', $uid)) {
  146. user_stats_post_count_update('reset', $uid);
  147. }
  148. $query = db_query("SELECT value FROM {user_stats_values}
  149. WHERE name = :name AND uid = :uid", array(':name' => 'post_count', ':uid' => $uid));
  150. $posts = $query->fetchField();
  151. //@TODO Figure out why adding comments here wasn't in the D6 version
  152. if(variable_get('user_stats_count_comments', TRUE)){
  153. }
  154. $data = $posts;
  155. break;
  156. case 'post_days':
  157. $last_post = _user_stats_last_post($uid);
  158. if ($last_post !== FALSE) {
  159. $data = floor((REQUEST_TIME - $last_post) / 86400);
  160. }
  161. else {
  162. $data = 'n/a';
  163. }
  164. break;
  165. case 'reg_days':
  166. $user_created = db_query("SELECT created FROM {users} WHERE uid = :uid", array(':uid' => $uid))->fetchField();
  167. $data = floor((REQUEST_TIME - $user_created) / 86400);
  168. break;
  169. case 'online':
  170. $user_access = db_query("SELECT timestamp FROM {sessions} WHERE uid = :uid", array(':uid' => $uid))->fetchField();
  171. $data = ((REQUEST_TIME - $user_access) < variable_get('user_block_seconds_online', 900) ? TRUE : FALSE);
  172. break;
  173. default:
  174. // Check for custom statistics
  175. $custom_stats = array();
  176. $module_list = module_implements('default_user_stats');
  177. foreach ($module_list as $module) {
  178. $custom_stats = array_merge($custom_stats, module_invoke($module, 'default_user_stats'));
  179. }
  180. if (array_key_exists($type, $custom_stats)) {
  181. module_load_include('module', 'qna');
  182. $data = call_user_func($custom_stats[$type], $uid);
  183. break;
  184. }
  185. else {
  186. // Raise an error if the statistic doesn't exist.
  187. $err_message = 'Statistic "' . check_plain($type) . '" does not exist.';
  188. trigger_error($err_message, E_USER_WARNING);
  189. return;
  190. }
  191. }
  192. user_stats_cache_set($type, $uid, $data);
  193. return user_stats_cache_get($type, $uid);
  194. }
  195. /**
  196. * Return data from the non-persistent User Stats cache. Single values
  197. * are returned according to type of statistic and unique user id.
  198. *
  199. * @param $type
  200. * The type of statistic to retrieve, this corresponds to the statistic
  201. * types used by user_stats_get_stats().
  202. * @param $uid
  203. * Unique ID of the user who's statistic is being retrieved.
  204. *
  205. * @return
  206. * A single value, representing the statistic $type where the unique user id
  207. * is $uid. Or FALSE if there is no value in the cache for this combination
  208. * of $type and $uid.
  209. *
  210. * @see user_stats_get_stats().
  211. * @see user_stats_cache_set().
  212. */
  213. function user_stats_cache_get($type, $uid) {
  214. $user_stats_cache = user_stats_cache_set();
  215. if (isset($user_stats_cache[$uid][$type])) {
  216. return $user_stats_cache[$uid][$type];
  217. }
  218. else {
  219. return FALSE;
  220. }
  221. }
  222. /**
  223. * Store a value in the non-persistent User Stats cache.
  224. *
  225. * If the function is called with no arguments, the entire cache is returned
  226. * without being cleared.
  227. *
  228. * The User Stats cache is a static array, which is why we call it
  229. * non-persistent. The array structure is:
  230. * $user_stats_cache[$uid][$type] = $value.
  231. *
  232. * @param $type
  233. * The type of statistic being stored, this corresponds to the statistic
  234. * types used by user_stats_get_stats(), and one extra used to reset the
  235. * cache: 'reset'.
  236. * @param $uid
  237. * Unique ID of the user who's statistic is being stored. If the type
  238. * is set to 'reset', this user id will have the cache values associated with
  239. * it reset. Alternatively, if $type is set to 'reset' and this is -1, the
  240. * entire cache will be reset.
  241. *
  242. * @return
  243. * Array of the entire cache, or NULL if the cache has been reset.
  244. *
  245. * @see user_stats_get_stats().
  246. * @see user_stats_cache_get().
  247. */
  248. function user_stats_cache_set($type = NULL, $uid = 0, $data = NULL) {
  249. static $user_stats_cache = array();
  250. // Flush entire cache.
  251. if ($uid == -1 && $type == 'reset') {
  252. unset($user_stats_cache);
  253. return;
  254. }
  255. else if ($uid > -1 && $type == 'reset') {
  256. unset($user_stats_cache[$uid]);
  257. return;
  258. }
  259. // Set cache data. Check against NULL since a zero (in $data at least)
  260. // is valid.
  261. if ($type !== NULL && $data !== NULL) {
  262. $user_stats_cache[$uid][$type] = $data;
  263. }
  264. return $user_stats_cache;
  265. }
  266. /**
  267. * Drupal hook implementations.
  268. */
  269. /**
  270. * Implements hook_node_insert().
  271. */
  272. function user_stats_node_insert($node) {
  273. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  274. if ((empty($post_count_content_types) ||
  275. in_array($node->type, $post_count_content_types)) &&
  276. variable_get('user_stats_count_posts', TRUE)) {
  277. if ($node->status) {
  278. user_stats_post_count_update('increment', $node->uid);
  279. }
  280. }
  281. // Do IP Address update.
  282. global $user;
  283. // User IP addresses are only interesting if they are posting the content.
  284. if ($node->uid == $user->uid) {
  285. user_stats_ip_address_update($user->uid, ip_address());
  286. }
  287. }
  288. /**
  289. * Implements hook_node_update().
  290. */
  291. function user_stats_node_update($node) {
  292. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  293. if ((empty($post_count_content_types) ||
  294. in_array($node->type, $post_count_content_types)) &&
  295. variable_get('user_stats_count_posts', TRUE)) {
  296. // Can't think of any other way of doing this than resetting the user.
  297. user_stats_post_count_update('reset', $node->uid);
  298. }
  299. // User IP addresses are only interesting if they are posting the content.
  300. global $user;
  301. if ($node->uid == $user->uid) {
  302. user_stats_ip_address_update($user->uid, ip_address());
  303. }
  304. }
  305. /**
  306. * Implements hook_node_delete().
  307. */
  308. function user_stats_node_delete($node) {
  309. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  310. if ((empty($post_count_content_types) ||
  311. in_array($node->type, $post_count_content_types)) &&
  312. variable_get('user_stats_count_posts', TRUE)) {
  313. // Node must be published as unpublished nodes would have already been
  314. // removed from user's post count.
  315. if ($node->status) {
  316. user_stats_post_count_update('decrement', $node->uid);
  317. }
  318. }
  319. }
  320. /**
  321. * Implements hook_comment_insert().
  322. */
  323. function user_stats_comment_insert($comment) {
  324. if (variable_get('user_stats_count_comments', TRUE)) {
  325. if ($comment->uid == NULL) {
  326. $comment->uid = 0;
  327. }
  328. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  329. $node = node_load($comment->nid);
  330. if (empty($post_count_content_types) || in_array($node->type, $post_count_content_types)) {
  331. if ($comment->status == COMMENT_PUBLISHED) {
  332. user_stats_post_count_update('increment', $comment->uid);
  333. }
  334. }
  335. }
  336. global $user;
  337. // User IP addresses are only interesting if they are posting the content.
  338. if (TRUE && $comment->uid == $user->uid) {
  339. // User IP addresses are only interesting if they are posting the content.
  340. user_stats_ip_address_update($user->uid, ip_address());
  341. }
  342. }
  343. /**
  344. * Implements hook_comment_update().
  345. */
  346. function user_stats_comment_update($comment) {
  347. if (variable_get('user_stats_count_comments', TRUE)) {
  348. if ($comment->uid == NULL) {
  349. $comment->uid = 0;
  350. }
  351. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  352. $node = node_load($comment->nid);
  353. if (empty($post_count_content_types) || in_array($node->type, $post_count_content_types)) {
  354. if ($comment->status == COMMENT_PUBLISHED) {
  355. user_stats_post_count_update('reset', $comment->uid);
  356. }
  357. }
  358. }
  359. }
  360. /**
  361. * Implements hook_comment_delete().
  362. */
  363. function user_stats_comment_delete($comment) {
  364. if (variable_get('user_stats_count_comments', TRUE)) {
  365. if ($comment->uid == NULL) {
  366. $comment->uid = 0;
  367. }
  368. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  369. $node = node_load($comment->nid);
  370. if (empty($post_count_content_types) || in_array($node->type, $post_count_content_types)) {
  371. if ($comment->status == COMMENT_PUBLISHED) {
  372. user_stats_post_count_update('decrement', $comment->uid);
  373. }
  374. }
  375. }
  376. }
  377. /**
  378. * Implements hook_cron().
  379. *
  380. * We slowly work through all users without a post count
  381. * updating them.
  382. */
  383. function user_stats_cron() {
  384. if (variable_get('user_stats_rebuild_stats', TRUE) &&
  385. (variable_get('user_stats_count_posts', TRUE) ||
  386. variable_get('user_stats_count_comments', TRUE))) {
  387. $sql = "SELECT uid FROM {users} WHERE uid NOT IN
  388. (SELECT uid FROM {user_stats_values} WHERE name = 'post_count')";
  389. // Update 25 users per cron run.
  390. $result = db_query_range($sql, 0, variable_get('user_stats_user_per_cron', '25'));
  391. $users_updated = FALSE;
  392. foreach ($result as $update_user) {
  393. user_stats_post_count_update('reset', $update_user->uid);
  394. $users_updated = TRUE;
  395. }
  396. // If all users have been updated we'll avoid running this expensive
  397. // query again by setting the following flag.
  398. if (!$users_updated) {
  399. variable_set('user_stats_rebuild_stats', FALSE);
  400. }
  401. }
  402. // Fire rules day_older event.
  403. // This may seem grossly inefficient, but testing showed that, even firing
  404. // the event for ~100 users, takes less than a second to run when there are
  405. // no rules using this event. With a rule (that adds a role if the user has
  406. // been a member for over 1,000 days) cron took an extra ~40 seconds to run.
  407. // Basically, this has no potential to harm a site's performance, unless a
  408. // rule is configured.
  409. // Having said this: if there's a better way, please raise a bug report!
  410. if (module_exists('rules')) {
  411. $sql = "SELECT uid FROM {users} u ";
  412. // ((last cron - created) - (time() - created)) > one day
  413. $sql .= "WHERE (FLOOR((:request_time-created)/(60*60*24))-FLOOR((:cron_last-created)/(60*60*24)))>0 AND uid>0";
  414. $result = db_query($sql, array(':request_time' => REQUEST_TIME, ':cron_last' => variable_get('cron_last', REQUEST_TIME)));
  415. $reset_user_count = 0;
  416. foreach ($result as $update_user) {
  417. rules_invoke_event('user_stats_day_older', $update_user->uid);
  418. }
  419. }
  420. if (variable_get('user_stats_track_ips', TRUE)) {
  421. // Delete items from the IP log that are past expiry.
  422. db_delete('user_stats_ips')
  423. ->condition('first_seen_timestamp', REQUEST_TIME - variable_get('user_stats_flush_ips_timer', 31536000), '<')
  424. ->execute();
  425. }
  426. }
  427. /**
  428. * Implements hook_user_login().
  429. */
  430. function user_stats_user_login(&$edit, $account) {
  431. if (variable_get('user_stats_count_logins', TRUE)) {
  432. user_stats_login_count_update('increment', $account->uid);
  433. }
  434. user_stats_ip_address_update($account->uid, ip_address());
  435. }
  436. /**
  437. * Implements hook_user_logout().
  438. */
  439. function user_stats_user_logout($account) {
  440. user_stats_ip_address_update($account->uid, ip_address());
  441. }
  442. /**
  443. * Helper function to get the last post created by the user.
  444. *
  445. * @param $account
  446. * User object.
  447. *
  448. * @return
  449. * Unix timestamp: date of the last post (node or comment).
  450. */
  451. function _user_stats_last_post($uid) {
  452. $sql = "SELECT MAX(created) FROM {node} WHERE status=:status AND uid=:uid";
  453. $all_content_types = node_type_get_types();
  454. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  455. $where = "";
  456. // If some, but not all, content types have been selected in the admin
  457. // interface add a WHERE clause to select only them.
  458. if (!empty($post_count_content_types) && array_keys($all_content_types) != array_keys($post_count_content_types)) {
  459. $content_types = "'" . implode("','", $post_count_content_types) . "'";
  460. $where = ' AND type IN (' . $content_types . ')';
  461. }
  462. $sql .= $where;
  463. // TODO Please convert this statement to the D7 database API syntax.
  464. $max_node = db_query($sql, array(':status' => COMMENT_PUBLISHED, ':uid' => $uid))->fetchField();
  465. $sql = "SELECT MAX(c.changed) FROM {comment} c ";
  466. $where = " WHERE c.status=:status AND c.uid=:uid ";
  467. $join = "";
  468. if (!empty($post_count_content_types) && array_keys($all_content_types) != array_keys($post_count_content_types)) {
  469. $join = " INNER JOIN {node} n ON c.nid=n.nid ";
  470. $where .= 'AND n.type IN (' . $content_types . ')';
  471. }
  472. $sql .= $join . $where;
  473. // TODO Please convert this statement to the D7 database API syntax.
  474. $max_comments = db_query($sql, array(':status' => COMMENT_PUBLISHED, ':uid' => $uid))->fetchField();
  475. if (is_null($max_node) && is_null($max_comments)) {
  476. return FALSE;
  477. }
  478. else if ($max_node > $max_comments) {
  479. return $max_node;
  480. }
  481. else if ($max_node <= $max_comments) {
  482. return $max_comments;
  483. }
  484. }
  485. /**
  486. * Implements hook_views_api().
  487. *
  488. * Other Views hooks in user_stats.views.inc.
  489. */
  490. function user_stats_views_api() {
  491. return array(
  492. 'api' => '2.0',
  493. 'path' => drupal_get_path('module', 'user_stats') . '/views',
  494. );
  495. }
  496. /**
  497. * Actions/Rules hooks and implementing functions.
  498. *
  499. * (we don't do Triggers as the API doesn't seem complete -- having to use
  500. * _trigger_get_hook_aids() for example). Patches welcome for this, as long
  501. * as they do not use private member functions!
  502. *
  503. * Most Rules hooks are in user_stats.rules.inc.
  504. */
  505. /**
  506. * Implements hook_action_info().
  507. */
  508. function user_stats_action_info() {
  509. return array(
  510. 'user_stats_post_count_reset_action' => array(
  511. 'label' => t('Reset user post count'),
  512. 'type' => 'user',
  513. 'configurable' => FALSE,
  514. 'triggers' => array(
  515. 'nodeapi_delete',
  516. 'nodeapi_insert',
  517. 'nodeapi_update',
  518. 'nodeapi_view',
  519. 'comment_view',
  520. 'comment_insert',
  521. 'comment_update',
  522. 'comment_delete',
  523. 'user_login',
  524. 'user_logout',
  525. ),
  526. ),
  527. 'user_stats_login_count_reset_action' => array(
  528. 'label' => t('Reset user login count'),
  529. 'type' => 'user',
  530. 'configurable' => FALSE,
  531. 'triggers' => array(
  532. 'nodeapi_delete',
  533. 'nodeapi_insert',
  534. 'nodeapi_update',
  535. 'nodeapi_view',
  536. 'comment_view',
  537. 'comment_insert',
  538. 'comment_update',
  539. 'comment_delete',
  540. 'user_login',
  541. 'user_logout',
  542. ),
  543. ),
  544. );
  545. }
  546. /**
  547. * Implementation of a Drupal action.
  548. *
  549. * Resets a user's post count.
  550. */
  551. function user_stats_post_count_reset_action(&$object, $context = array()) {
  552. if (isset($object->uid)) {
  553. $uid = $object->uid;
  554. }
  555. elseif (isset($context['uid'])) {
  556. $uid = $context['uid'];
  557. }
  558. else {
  559. global $user;
  560. $uid = $user->uid;
  561. }
  562. user_stats_post_count_update('reset', $uid);
  563. }
  564. /**
  565. * Implementation of a Drupal action.
  566. * Resets a user's login count.
  567. */
  568. function user_stats_login_count_reset_action(&$object, $context = array()) {
  569. if (isset($object->uid)) {
  570. $uid = $object->uid;
  571. }
  572. elseif (isset($context['uid'])) {
  573. $uid = $context['uid'];
  574. }
  575. else {
  576. global $user;
  577. $uid = $user->uid;
  578. }
  579. user_stats_login_count_update('reset', $uid);
  580. }
  581. /**
  582. * Implements hook_user_stats().
  583. *
  584. * Invoke the Rules module.
  585. */
  586. function user_stats_user_stats($type, $op, $uid, $value) {
  587. /*
  588. if (module_exists('rules')) {
  589. rules_invoke_event('user_stats_' . $type . '_' . $op, $uid, $value);
  590. }
  591. */
  592. }
  593. /**
  594. * Token hook implementations
  595. */
  596. /**
  597. * Implements hook_token_values().
  598. */
  599. function user_stats_token_values($type, $object = NULL) {
  600. switch ($type) {
  601. case 'user':
  602. case 'all':
  603. if (isset($object)) {
  604. // Think this is sometimes an array (please raise this as an issue if wrong).
  605. $object = (object) $object;
  606. $uid = $object->uid;
  607. }
  608. else {
  609. global $user;
  610. $uid = $user->uid;
  611. }
  612. // Check_plain added as per Greggles suggestion: http://drupal.org/node/166305#comment-665874
  613. $values['reg-days'] = check_plain(user_stats_get_stats('reg_days', $uid));
  614. $values['login-days'] = check_plain(user_stats_get_stats('login_days', $uid));
  615. $values['post-days'] = check_plain(user_stats_get_stats('post_days', $uid));
  616. $values['post-count'] = check_plain(user_stats_get_stats('post_count', $uid));
  617. $values['ip-address'] = check_plain(user_stats_get_stats('ip_address', $uid));
  618. $values['login-count'] = check_plain(user_stats_get_stats('login_count', $uid));
  619. return $values;
  620. }
  621. }
  622. /**
  623. * Implements hook_token_list().
  624. */
  625. function user_stats_token_list($type = 'all') {
  626. if ($type == 'user' || $type == 'all') {
  627. $tokens['user']['reg-days'] = t('Number of days since the user registered');
  628. $tokens['user']['login-days'] = t('Number of days since the user logged in');
  629. $tokens['user']['post-days'] = t('Number of days since the user posted');
  630. $tokens['user']['post-count'] = t("User's post count");
  631. $tokens['user']['ip-address'] = t("User's IP address");
  632. $tokens['user']['login-count'] = t("User's login count");
  633. return $tokens;
  634. }
  635. }
  636. /**
  637. * Checks whether a statistic is set for a given user.
  638. *
  639. * @param $uid
  640. * User ID of the user who's statistics should be checked.
  641. * @param $statistic
  642. * What statistic to check.
  643. */
  644. function user_stats_isset($statistic, $uid) {
  645. $result = db_query("SELECT COUNT(*)
  646. FROM {user_stats_values}
  647. WHERE uid = :uid AND name = :name",
  648. array(':uid' => $uid, ':name' => $statistic))->fetchField();
  649. if ($result > 0) {
  650. return TRUE;
  651. }
  652. return FALSE;
  653. }
  654. /**
  655. * Manage the login count of a given user.
  656. *
  657. * @param $uid
  658. * Unique id of the user who's record should be updated.
  659. * @param $op
  660. * Whether the user login count should be incremented, decremented, or reset.
  661. * Possible values are:
  662. * - 'increment'
  663. * - 'decrement'
  664. * - 'reset'
  665. */
  666. function user_stats_login_count_update($op, $uid) {
  667. if (!is_numeric($uid)) {
  668. return;
  669. }
  670. switch ($op) {
  671. case 'increment':
  672. if (user_stats_isset('login_count', $uid)) {
  673. // Update existing value.
  674. db_update('user_stats_values')
  675. ->expression('value', 'value + :value', array(':value' => 1))
  676. ->condition('name', 'login_count')
  677. ->condition('uid', $uid)
  678. ->execute();
  679. }
  680. else {
  681. // If there isn't a value insert it.
  682. $id = db_insert('user_stats_values')
  683. ->fields(array(
  684. 'name' => 'login_count',
  685. 'uid' => $uid,
  686. 'value' => 1,
  687. ))
  688. ->execute();
  689. }
  690. break;
  691. case 'decrement':
  692. if (user_stats_isset('login_count', $uid)) {
  693. // Update existing value.
  694. $count = (user_stats_cache_get('login_count', $uid) - 1);
  695. db_update('user_stats_values')
  696. ->expression('value', 'value + :value', array(':value' => 1))
  697. ->condition('name', 'login_count')
  698. ->condition('uid', $uid)
  699. ->execute();
  700. }
  701. else {
  702. // If there isn't a value insert it.
  703. $id = db_insert('user_stats_values')
  704. ->fields(array(
  705. 'name' => 'login_count',
  706. 'uid' => $uid,
  707. 'value' => 0,
  708. ))
  709. ->execute();
  710. }
  711. break;
  712. case 'reset':
  713. db_delete('user_stats_values')
  714. ->condition('name', 'login_count')
  715. ->condition('uid', $uid)
  716. ->execute();
  717. break;
  718. }
  719. // Flush token cache.
  720. //if (module_exists('token')) {
  721. //token_get_values('user', NULL, TRUE);
  722. //}
  723. // Flush internal cache.
  724. user_stats_cache_set('reset', $uid);
  725. // Allow modules to react to a statistic change.
  726. module_invoke_all('user_stats', 'login_count', $op, $uid, user_stats_get_stats('login_count', $uid));
  727. }
  728. /**
  729. * Manage the post count of a given user.
  730. *
  731. * @param $uid
  732. * Unique id of the user who's record should be updated.
  733. * @param $op
  734. * Whether the user post count should be incremented, decremented, or reset.
  735. * The default is to increment. Possible values are:
  736. * 'increment'
  737. * 'decrement'
  738. * 'reset'
  739. */
  740. function user_stats_post_count_update($op, $uid) {
  741. if (!is_numeric($uid)) {
  742. return;
  743. }
  744. switch ($op) {
  745. case 'increment':
  746. if (user_stats_isset('post_count', $uid)) {
  747. //@TODO: Previous query tried to update and add in one query,
  748. // that wasn't working.
  749. $count = (user_stats_get_stats('post_count', $uid) + 1);
  750. db_update('user_stats_values')
  751. ->fields(array(
  752. 'value' => $count,
  753. ))
  754. ->condition('name', 'post_count')
  755. ->condition('uid', $uid)
  756. ->execute();
  757. // Flush internal cache.
  758. user_stats_cache_set('reset', $uid);
  759. }
  760. else {
  761. user_stats_post_count_update('reset', $uid);
  762. }
  763. break;
  764. case 'decrement':
  765. if (user_stats_isset('post_count', $uid)) {
  766. //@TODO: Same issue as 'increment'. Previous query tried to update
  767. // and add in one query... that wasn't working
  768. $count = (user_stats_get_stats('post_count', $uid) - 1);
  769. db_update('user_stats_values')
  770. ->fields(array(
  771. 'value' => $count,
  772. ))
  773. ->condition('name', 'post_count')
  774. ->condition('uid', $uid)
  775. ->execute();
  776. // Flush internal cache.
  777. user_stats_cache_set('reset', $uid);
  778. }
  779. else {
  780. user_stats_post_count_update('reset', $uid);
  781. }
  782. break;
  783. case 'reset':
  784. $total_count = 0;
  785. if (variable_get('user_stats_count_posts', TRUE)) {
  786. $sql = "SELECT COUNT(*) FROM {node} WHERE uid = :uid AND status = 1";
  787. $post_count_content_types = variable_get('user_stats_included_content_types', array());
  788. if (!empty($post_count_content_types)) {
  789. $content_types = "'" . implode("','", $post_count_content_types) . "'";
  790. $where = ' AND type IN (' . $content_types . ')';
  791. $sql .= $where;
  792. }
  793. $node_count = db_query($sql, array(':uid' => $uid))->fetchField();
  794. $total_count += $node_count;
  795. }
  796. if (variable_get('user_stats_count_comments', TRUE)) {
  797. // COMMENT_PUBLISHED is now 1 in D7, and COMMENT_UNPUBLISHED is 0
  798. $sql = "SELECT COUNT(*) FROM {comment} c
  799. INNER JOIN {node} n ON c.nid = n.nid
  800. WHERE c.uid = :uid AND c.status = 1 AND n.status = 1";
  801. if (!empty($post_count_content_types)) {
  802. $where = ' AND n.type IN (' . $content_types . ')';
  803. $sql .= $where;
  804. }
  805. $comments_count = db_query($sql, array(':uid' => $uid))->fetchField();
  806. $total_count += $comments_count;
  807. }
  808. db_delete('user_stats_values')
  809. ->condition('name', 'post_count')
  810. ->condition('uid', $uid)
  811. ->execute();
  812. $id = db_insert('user_stats_values')
  813. ->fields(array(
  814. 'name' => 'post_count',
  815. 'uid' => $uid,
  816. 'value' => $total_count,
  817. ))
  818. ->execute();
  819. // Prime the cache, this will be used by module_invoke_all() below.
  820. user_stats_cache_set('post_count', $uid, $total_count);
  821. break;
  822. }
  823. // Flush token cache
  824. //if (module_exists('token')) {
  825. //token_get_values('user', NULL, TRUE);
  826. //}
  827. // Allow modules to react to a statistic change.
  828. module_invoke_all('user_stats', 'post_count', $op, $uid, user_stats_get_stats('post_count', $uid));
  829. }
  830. /**
  831. * Update the IP address of a given user.
  832. *
  833. * The IP address is not updated if it is the same as the last recorded IP,
  834. * however, if the user has IP address A, then switches to IP address B
  835. * and back to A again, A will be recorded twice. This is to keep an accurate
  836. * log of IP addresses used by users.
  837. *
  838. * @param $uid
  839. * User ID of user who's IP is being updated.
  840. * @param $ip_address
  841. * IP address to assign to user.
  842. */
  843. function user_stats_ip_address_update($uid, $ip_address) {
  844. if (!is_numeric($uid)) {
  845. return;
  846. }
  847. // Don't bother recording IPs of anonymous users, and don't record any
  848. // addresses if the config form tells us not to.
  849. if ($uid == 0 || !variable_get('user_stats_track_ips', TRUE)) {
  850. return;
  851. }
  852. $query = db_query_range("SELECT ip_address
  853. FROM {user_stats_ips}
  854. WHERE uid = :uid
  855. ORDER BY first_seen_timestamp DESC",
  856. 0, 1, array(':uid' => $uid));
  857. if ($ip_address != $query->fetchField()) {
  858. // Reset internal cache.
  859. user_stats_cache_set('reset', $uid);
  860. $id = db_insert('user_stats_ips')
  861. ->fields(array(
  862. 'uid' => $uid,
  863. 'ip_address' => $ip_address,
  864. 'first_seen_timestamp' => REQUEST_TIME,
  865. ))
  866. ->execute();
  867. // Allow modules to react to an IP address change.
  868. module_invoke_all('user_stats', 'ip_address', 'insert', $uid, $ip_address);
  869. }
  870. }
  871. /**
  872. * Resets statistics. Full stop.
  873. *
  874. * @param $statistic
  875. * The name of the statistic to be reset.
  876. * Corresponds with {user_stats_values}.name.
  877. */
  878. function user_stats_reset_counts($statistic) {
  879. db_delete('user_stats_values')
  880. ->condition('name', $statistic)
  881. ->execute();
  882. }
  883. /**
  884. * Implements hook_user_insert to record ip of new user on registration.
  885. */
  886. function user_stats_user_insert(&$edit, $account, $category) {
  887. global $user;
  888. if ($user->uid == 0) {
  889. $uid = $account->uid;
  890. $ip = ip_address();
  891. user_stats_ip_address_update($uid, $ip);
  892. }
  893. }