PageRenderTime 58ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/Cohort.php

https://github.com/whale2/users
PHP | 362 lines | 283 code | 40 blank | 39 comment | 12 complexity | 4b34715f3231e036ab7b93603b061f2d MD5 | raw file
  1. <?php
  2. class Cohort {
  3. private $id;
  4. private $title;
  5. private $total;
  6. public function __construct($id, $title, $total) {
  7. $this->id = $id;
  8. $this->title = $title;
  9. $this->total = $total;
  10. }
  11. public function getID() {
  12. return $this->id;
  13. }
  14. public function getTitle() {
  15. return $this->title;
  16. }
  17. public function getTotal() {
  18. return $this->total;
  19. }
  20. }
  21. abstract class CohortProvider {
  22. private $title;
  23. private $id;
  24. // most of cohort lists don't create cohorts that are sequential, no reason to compare them
  25. protected $compare_to_previous_cohort = false;
  26. /**
  27. * @param string $id ID of the provider
  28. * @param string $title Display name of the provider
  29. */
  30. public function __construct($id, $title) {
  31. $this->id = $id;
  32. $this->title = $title;
  33. }
  34. public function getID() {
  35. return $this->id;
  36. }
  37. public function getTitle() {
  38. return $this->title;
  39. }
  40. public function canCompareToPreviousCohort() {
  41. return $this->compare_to_previous_cohort;
  42. }
  43. /**
  44. * Returns a list of cohorts tracked by specific CohortProvider
  45. *
  46. * @return array $cohorts an array of Cohort objects
  47. */
  48. public abstract function getCohorts();
  49. /**
  50. * Returns the name of dimension specific provider separates users by
  51. *
  52. * @return string name of dimension
  53. */
  54. public abstract function getDimensionTitle();
  55. /*
  56. * @param int $activityid Activity ID
  57. * @param int $actnum Number of days in activity period
  58. */
  59. public function getActivityRate($activityid, $actnum)
  60. {
  61. $db = UserConfig::getDB();
  62. $siteadminsstring = null;
  63. if (count(UserConfig::$admins) > 0) {
  64. $siteadminsstring = implode(", ", UserConfig::$admins);
  65. }
  66. $aggregates = array();
  67. $query = 'SELECT u.cohort_id AS cohort_id,
  68. FLOOR(DATEDIFF(a.time, u.regtime) / ?) AS actperiod,
  69. COUNT(DISTINCT u.id) AS total
  70. FROM `'.UserConfig::$mysql_prefix.'activity` AS a
  71. INNER JOIN ('.$this->getCohortSQL().') AS u
  72. ON a.user_id = u.id
  73. WHERE `activity_id` = ?';
  74. if (!is_null($siteadminsstring)) {
  75. $query .= "\nAND u.id NOT IN ($siteadminsstring)";
  76. }
  77. $query .= '
  78. GROUP BY cohort_id, actperiod
  79. ORDER BY actperiod ASC';
  80. if ($stmt = $db->prepare($query))
  81. {
  82. if (!$stmt->bind_param('ii', $actnum, $activityid))
  83. {
  84. throw new Exception("Can't bind parameter".$stmt->error);
  85. }
  86. if (!$stmt->execute())
  87. {
  88. throw new Exception("Can't execute statement: ".$stmt->error);
  89. }
  90. if (!$stmt->bind_result($cohort_id, $actperiod, $activeusers))
  91. {
  92. throw new Exception("Can't bind result: ".$stmt->error);
  93. }
  94. while($stmt->fetch() === TRUE)
  95. {
  96. $aggregates[$cohort_id][$actperiod] = $activeusers;
  97. }
  98. $stmt->close();
  99. }
  100. else
  101. {
  102. throw new Exception("Can't prepare statement: ".$db->error);
  103. }
  104. return $aggregates;
  105. }
  106. }
  107. /**
  108. * This class allows dropping users into cohorts by registration date
  109. */
  110. class GenerationCohorts extends CohortProvider {
  111. const YEAR = 0;
  112. const MONTH = 1;
  113. const WEEK = 2;
  114. /**
  115. * A period of time between generations
  116. * Must be one of GenerationCohorts::MONTH or GenerationCohorts::WEEK
  117. */
  118. private $period;
  119. public function __construct($period = self::MONTH) {
  120. $title = 'User genrations by ';
  121. $id = 'gen';
  122. switch ($period) {
  123. case self::MONTH:
  124. $title .= "month";
  125. $id .= "month";
  126. break;
  127. case self::YEAR:
  128. $title .= "year";
  129. $id .= "year";
  130. break;
  131. case self::WEEK:
  132. $title .= "week";
  133. $id .= "week";
  134. break;
  135. default:
  136. throw new Exception('Wrong generation period');
  137. }
  138. parent::__construct($id, $title);
  139. // cohorts are chronological so it makes sense to compare to previous
  140. $this->compare_to_previous_cohort = true;
  141. $this->period = $period;
  142. }
  143. public function getDimensionTitle() {
  144. return 'Reg. date';
  145. }
  146. /**
  147. * Returns a list of generation cohorts
  148. *
  149. * @return array $cohorts an array of Cohort objects
  150. */
  151. public function getCohorts() {
  152. $db = UserConfig::getDB();
  153. $siteadminsstring = null;
  154. if (count(UserConfig::$admins) > 0) {
  155. $siteadminsstring = implode(", ", UserConfig::$admins);
  156. }
  157. // an array of cohorts to return
  158. $cohorts = array();
  159. /**
  160. * The query must return a unique cohort_id, title and total members
  161. */
  162. switch ($this->period) {
  163. case self::MONTH:
  164. $query = "SELECT EXTRACT(YEAR_MONTH FROM regtime) AS cohort_id,
  165. DATE_FORMAT(regtime, '%b %Y') AS title,
  166. COUNT(*) AS totals
  167. FROM ".UserConfig::$mysql_prefix.'users';
  168. break;
  169. case self::YEAR:
  170. $query = "SELECT YEAR(regtime) AS cohort_id,
  171. YEAR(regtime) AS title,
  172. COUNT(*) AS totals
  173. FROM ".UserConfig::$mysql_prefix.'users';
  174. break;
  175. case self::WEEK:
  176. $query = "SELECT YEARWEEK(regtime) AS cohort_id,
  177. CONCAT(
  178. DATE_FORMAT(
  179. DATE(DATE_SUB(regtime, INTERVAL WEEKDAY(regtime) DAY)),
  180. '%b %e, %Y'
  181. ), ' - ',
  182. DATE_FORMAT(
  183. DATE(DATE_ADD(regtime, INTERVAL 6-WEEKDAY(regtime) DAY)),
  184. '%b %e, %Y'
  185. )
  186. ) AS title,
  187. COUNT(*) AS totals
  188. FROM ".UserConfig::$mysql_prefix.'users';
  189. break;
  190. default:
  191. throw new Exception('Wrong generation period');
  192. }
  193. if (!is_null($siteadminsstring)) {
  194. $query .= "\nWHERE id NOT IN ($siteadminsstring)";
  195. }
  196. $query .= ' GROUP BY cohort_id ORDER BY regtime DESC';
  197. if ($stmt = $db->prepare($query))
  198. {
  199. if (!$stmt->execute())
  200. {
  201. throw new Exception("Can't execute statement: ".$stmt->error);
  202. }
  203. if (!$stmt->bind_result($cohort_id, $title, $total))
  204. {
  205. throw new Exception("Can't bind result: ".$stmt->error);
  206. }
  207. while($stmt->fetch() === TRUE)
  208. {
  209. $cohorts[] = new Cohort($cohort_id, $title, $total);
  210. }
  211. $stmt->close();
  212. }
  213. else
  214. {
  215. throw new Exception("Can't prepare statement: ".$db->error);
  216. }
  217. return $cohorts;
  218. }
  219. /**
  220. * @return string SQL statement for generating a resultset with id, regtime and cohort_id
  221. */
  222. public function getCohortSQL() {
  223. switch ($this->period) {
  224. case self::MONTH:
  225. $query = 'SELECT id, regtime, EXTRACT(YEAR_MONTH FROM regtime) AS cohort_id
  226. FROM '.UserConfig::$mysql_prefix.'users';
  227. break;
  228. case self::YEAR:
  229. $query = 'SELECT id, regtime, YEAR(regtime) AS cohort_id
  230. FROM '.UserConfig::$mysql_prefix.'users';
  231. break;
  232. case self::WEEK:
  233. $query = 'SELECT id, regtime, YEARWEEK(regtime) AS cohort_id
  234. FROM '.UserConfig::$mysql_prefix.'users';
  235. break;
  236. default:
  237. throw new Exception('Wrong generation period');
  238. }
  239. return $query;
  240. }
  241. }
  242. /**
  243. * This class allows dropping users into cohorts by registration method / module
  244. */
  245. class RegMethodCohorts extends CohortProvider {
  246. public function __construct() {
  247. parent::__construct('byregmethod', 'Users registration method');
  248. }
  249. public function getDimensionTitle() {
  250. return 'Reg. module';
  251. }
  252. /**
  253. * Returns a list of generation cohorts
  254. *
  255. * @return array $cohorts an array of Cohort objects
  256. */
  257. public function getCohorts() {
  258. $db = UserConfig::getDB();
  259. $cohort_titles = array();
  260. foreach (UserConfig::$authentication_modules as $module) {
  261. $cohort_titles[$module->getID()] = $module->getTitle();
  262. }
  263. /**
  264. * The query must return a unique cohort_id, title and total members
  265. */
  266. $query = "SELECT regmodule AS cohort_id, COUNT(*) AS totals
  267. FROM ".UserConfig::$mysql_prefix.'users';
  268. $siteadminsstring = null;
  269. if (count(UserConfig::$admins) > 0) {
  270. $siteadminsstring = implode(", ", UserConfig::$admins);
  271. }
  272. if (!is_null($siteadminsstring)) {
  273. $query .= "\nWHERE id NOT IN ($siteadminsstring)";
  274. }
  275. $cohorts = array();
  276. $query .= ' GROUP BY cohort_id ORDER BY regtime DESC';
  277. if ($stmt = $db->prepare($query))
  278. {
  279. if (!$stmt->execute())
  280. {
  281. throw new Exception("Can't execute statement: ".$stmt->error);
  282. }
  283. if (!$stmt->bind_result($cohort_id, $total))
  284. {
  285. throw new Exception("Can't bind result: ".$stmt->error);
  286. }
  287. while($stmt->fetch() === TRUE)
  288. {
  289. $cohorts[] = new Cohort($cohort_id, $cohort_titles[$cohort_id], $total);
  290. }
  291. $stmt->close();
  292. }
  293. else
  294. {
  295. throw new Exception("Can't prepare statement: ".$db->error);
  296. }
  297. return $cohorts;
  298. }
  299. /**
  300. * @return string SQL statement for generating a resultset with id, regtime and cohort_id
  301. */
  302. public function getCohortSQL() {
  303. return 'SELECT id, regtime, regmodule AS cohort_id FROM '.UserConfig::$mysql_prefix.'users';
  304. }
  305. }