You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Social_analytics.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. /**
  3. * Data class for Social Analytics stats
  4. *
  5. * PHP version 5
  6. *
  7. * @category Data
  8. * @package StatusNet
  9. * @author Stéphane Bérubé <chimo@chromic.org>
  10. * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  11. * @link http://github.com/chimo/social-analytics
  12. *
  13. * StatusNet - the distributed open-source microblogging tool
  14. * Copyright (C) 2009, StatusNet, Inc.
  15. *
  16. * This program is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU Affero General Public License as published by
  18. * the Free Software Foundation, either version 3 of the License, or
  19. * (at your option) any later version.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU Affero General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Affero General Public License
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  28. */
  29. if (!defined('STATUSNET')) {
  30. exit(1);
  31. }
  32. /**
  33. * Data class for Social Analytics stats
  34. *
  35. * We use the DB_DataObject framework for data classes in StatusNet. Each
  36. * table maps to a particular data class, making it easier to manipulate
  37. * data.
  38. *
  39. * Data classes should extend Memcached_DataObject, the (slightly misnamed)
  40. * extension of DB_DataObject that provides caching, internationalization,
  41. * and other bits of good functionality to StatusNet-specific data classes.
  42. *
  43. * @category Data
  44. * @package StatusNet
  45. * @author Stéphane Bérubé <chimo@chromic.org>
  46. * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  47. * @link http://github.com/chimo/SocialAnalytics
  48. *
  49. * @see DB_DataObject
  50. */
  51. class Social_analytics extends Managed_DataObject
  52. {
  53. /**
  54. * Satisfy Managed_DataObject
  55. */
  56. public static function schemaDef()
  57. {
  58. return array();
  59. }
  60. /**
  61. * TODO: Document
  62. */
  63. static function init($user_id, $sdate, $edate)
  64. {
  65. $sa = new Social_analytics();
  66. $sa->user_id = $user_id;
  67. $sa->sdate = $sdate;
  68. $sa->edate = $edate;
  69. // TODO: Handle cases where 'new DateTime()' fails (due to bad param)
  70. // Print notice and use default (first/last day of this month
  71. // Make sure edate > sdate
  72. if($sa->edate < $sa->sdate) {
  73. $tmp = $sa->edate;
  74. $sa->edate = $sa->sdate;
  75. $sa->sdate = $tmp;
  76. }
  77. $sa->ttl_notices = 0;
  78. $sa->ttl_replies = 0;
  79. $sa->ttl_bookmarks = 0;
  80. // The list of graphs we'll be generating
  81. $sa->graphs = array(
  82. 'trends' => array(),
  83. 'hosts_you_started_to_follow' => array(),
  84. 'hosts_who_started_to_follow_you' => array(),
  85. 'clients' => array(),
  86. 'people_you_replied_to' => array(),
  87. 'people_who_mentioned_you' => array(),
  88. 'people_you_repeated' => array(),
  89. 'notices_per_group' => array(),
  90. 'notices_per_hashtag' => array()
  91. );
  92. $sa->map = array();
  93. // Initialize 'trends' table. We do this now since we know which rows we need in advance (all non-future days of month)
  94. $i_date = clone($sa->sdate);
  95. $today = new DateTime();
  96. while($i_date <= $sa->edate) {
  97. $sa->graphs['trends'][$i_date->format('Y-m-d')] = array(
  98. 'notices' => array(),
  99. 'following' => array(),
  100. 'followers' => array(),
  101. 'faves' => array(),
  102. 'o_faved' => array(),
  103. 'bookmarks' => array(),
  104. 'repeats' => array()
  105. );
  106. // Do not process dates from the future
  107. if($i_date->format('Y-m-d') == $today->format('Y-m-d')) {
  108. break;
  109. }
  110. $i_date->modify('+1 day');
  111. }
  112. // Gather "Notice" information from db and place into appropriate arrays
  113. $notices = self::cachedQuery('Notice', sprintf("SELECT * FROM notice
  114. WHERE profile_id = %d AND created >= '%s' AND created <= '%s'",
  115. $user_id,
  116. $sa->sdate->format('Y-m-d'),
  117. $sa->edate->format('Y-m-d')));
  118. $date_created = new DateTime();
  119. foreach($notices->_items as $notice) {
  120. $date_created->modify($notice->created); // String to Date
  121. // Extract group info
  122. $groups = $notice->getGroups();
  123. foreach ($groups as $group) {
  124. $sa->graphs['notices_per_group'][$group->nickname]['notices'][] = $notice;
  125. }
  126. // Extract hashtag info
  127. $hashtags = $notice->getTags();
  128. foreach($hashtags as $hashtag) {
  129. $sa->graphs['notices_per_hashtag'][$hashtag]['notices'][] = $notice;
  130. }
  131. // Repeats
  132. if($notice->repeat_of) {
  133. $repeat = Notice::getKV('id', $notice->repeat_of);
  134. $u_repeat = Profile::getKV('id', $repeat->profile_id);
  135. if(!isset($sa->graphs['people_you_repeated'][$u_repeat->nickname])) {
  136. $sa->graphs['people_you_repeated'][$u_repeat->nickname] = array('notices' => array());
  137. }
  138. $sa->graphs['people_you_repeated'][$u_repeat->nickname]['notices'][] = $notice;
  139. $sa->graphs['trends'][$date_created->format('Y-m-d')]['repeats'][] = $notice;
  140. }
  141. // Clients
  142. if(!isset($sa->graphs['clients'][$notice->source])) {
  143. $sa->graphs['clients'][$notice->source] = array('clients' => array());
  144. }
  145. $sa->graphs['clients'][$notice->source]['clients'][] = $notice;
  146. // Replies
  147. if($notice->reply_to) {
  148. $reply_to = Notice::getKV('id', $notice->reply_to);
  149. $repliee = Profile::getKV('id', $reply_to->profile_id);
  150. if(!isset($sa->graphs['people_you_replied_to'][$repliee->nickname])) {
  151. $sa->graphs['people_you_replied_to'][$repliee->nickname] = array('notices' => array());
  152. }
  153. $sa->graphs['people_you_replied_to'][$repliee->nickname]['notices'][] = $notice;
  154. $sa->ttl_replies++;
  155. }
  156. // Bookmarks
  157. if(preg_match('/\/bookmark$/', $notice->object_type)) {
  158. $sa->graphs['trends'][$date_created->format('Y-m-d')]['bookmarks'][] = $notice;
  159. $sa->ttl_bookmarks++;
  160. }
  161. // Notices
  162. $sa->graphs['trends'][$date_created->format('Y-m-d')]['notices'][] = $notice;
  163. $sa->ttl_notices++; // FIXME: Do we want to include bookmarks with notices now that we have a 'bookmarks' trend?
  164. }
  165. // Favored notices (both by 'you' and 'others')
  166. $sa->ttl_faves = 0;
  167. $sa->ttl_o_faved = 0;
  168. $faved = self::cachedQuery('Fave', sprintf("SELECT * FROM fave
  169. WHERE modified >= '%s' AND modified <= '%s'",
  170. $sa->sdate->format('Y-m-d'),
  171. $sa->edate->format('Y-m-d')));
  172. foreach($faved->_items as $fave) {
  173. $date_created->modify($fave->modified); // String to Date
  174. $notice = Notice::getKV('id', $fave->notice_id);
  175. // User's faves
  176. if($fave->user_id == $user_id) {
  177. $sa->graphs['trends'][$date_created->format('Y-m-d')]['faves'][] = $notice;
  178. $sa->ttl_faves++;
  179. }
  180. else { // User's notices favored by others
  181. $sa->graphs['trends'][$date_created->format('Y-m-d')]['o_faved'][] = $notice;
  182. $sa->ttl_o_faved++;
  183. }
  184. }
  185. // People who mentioned you
  186. $sa->ttl_mentions = 0;
  187. $mentions = self::listGetClass('Reply', 'profile_id', array($user_id));
  188. foreach($mentions[$user_id] as $mention) {
  189. $date_created->modify($mention->modified);
  190. if($date_created >= $sa->sdate && $date_created <= $sa->edate) {
  191. $notice = Notice::getKV('id', $mention->notice_id);
  192. $profile = Profile::getKV('id', $notice->profile_id);
  193. if(!isset($sa->graphs['people_who_mentioned_you'][$profile->nickname])) {
  194. $sa->graphs['people_who_mentioned_you'][$profile->nickname] = array('notices' => array());
  195. }
  196. $sa->graphs['people_who_mentioned_you'][$profile->nickname]['notices'][] = $notice;
  197. $sa->ttl_mentions++;
  198. }
  199. }
  200. // Hosts you are following
  201. $sa->ttl_following = 0;
  202. $arr_following = self::listGetClass('Subscription', 'subscriber', array($user_id));
  203. foreach($arr_following[$user_id] as $following) {
  204. // This is in my DB, but doesn't show up in my 'Following' total (???)
  205. if($following->subscriber == $following->subscribed) {
  206. continue;
  207. }
  208. $date_created->modify($following->created); // Convert string to DateTime
  209. if($date_created >= $sa->sdate && $date_created <= $sa->edate) {
  210. $profile = Profile::getKV('id', $following->subscribed);
  211. $sa->graphs['trends'][$date_created->format('Y-m-d')]['following'][] = $profile;
  212. if(isset($profile->lat) && isset($profile->lon)) {
  213. $sa->map['following'][$profile->nickname] = '{ lon: ' . $profile->lon . ', lat: ' . $profile->lat . ', profileUrl: "' . $profile->profileurl . '"}';
  214. }
  215. $hst = parse_url($profile->profileurl, PHP_URL_HOST);
  216. if(!isset($sa->graphs['hosts_you_started_to_follow'][$hst])) {
  217. $sa->graphs['hosts_you_started_to_follow'][$hst] = array('host' => array());
  218. }
  219. $sa->graphs['hosts_you_started_to_follow'][$hst]['host'][] = $profile;
  220. $sa->ttl_following++;
  221. }
  222. }
  223. // Hosts who follow you
  224. $sa->ttl_followers = 0;
  225. $followers = self::listGetClass('Subscription', 'subscribed', array($user_id));
  226. foreach($followers[$user_id] as $follower) {
  227. // This is in my DB, but doesn't show up in my 'Following' total (???)
  228. if($follower->subscriber == $follower->subscribed) {
  229. continue;
  230. }
  231. $date_created->modify($follower->created); // Convert string to DateTime
  232. if($date_created >= $sa->sdate && $date_created <= $sa->edate) {
  233. $profile = Profile::getKV('id', $follower->subscriber);
  234. $sa->graphs['trends'][$date_created->format('Y-m-d')]['followers'][] = $profile;
  235. if(isset($profile->lat) && isset($profile->lon)) {
  236. $sa->map['followers'][$profile->nickname] = '{ lon: ' . $profile->lon . ', lat: ' . $profile->lat . ', profileUrl: "' . $profile->profileurl . '"}';
  237. }
  238. $hst = parse_url($profile->profileurl, PHP_URL_HOST);
  239. if(!isset($sa->graphs['hosts_who_started_to_follow_you'][$hst])) {
  240. $sa->graphs['hosts_who_started_to_follow_you'][$hst] = array('host' => array());
  241. }
  242. $sa->graphs['hosts_who_started_to_follow_you'][$hst]['host'][] = $profile;
  243. $sa->ttl_followers++;
  244. }
  245. }
  246. return $sa;
  247. }
  248. }