StatsDAO.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <?php
  2. class FreshRSS_StatsDAO extends Minz_ModelPdo {
  3. const ENTRY_COUNT_PERIOD = 30;
  4. protected function sqlFloor($s) {
  5. return "FLOOR($s)";
  6. }
  7. /**
  8. * Calculates entry repartition for all feeds and for main stream.
  9. *
  10. * @return array
  11. */
  12. public function calculateEntryRepartition() {
  13. return array(
  14. 'main_stream' => $this->calculateEntryRepartitionPerFeed(null, true),
  15. 'all_feeds' => $this->calculateEntryRepartitionPerFeed(null, false),
  16. );
  17. }
  18. /**
  19. * Calculates entry repartition for the selection.
  20. * The repartition includes:
  21. * - total entries
  22. * - read entries
  23. * - unread entries
  24. * - favorite entries
  25. *
  26. * @param null|integer $feed feed id
  27. * @param boolean $only_main
  28. * @return array
  29. */
  30. public function calculateEntryRepartitionPerFeed($feed = null, $only_main = false) {
  31. $filter = '';
  32. if ($only_main) {
  33. $filter .= 'AND f.priority = 10';
  34. }
  35. if (!is_null($feed)) {
  36. $filter .= "AND e.id_feed = {$feed}";
  37. }
  38. $sql = <<<SQL
  39. SELECT COUNT(1) AS total,
  40. COUNT(1) - SUM(e.is_read) AS count_unreads,
  41. SUM(e.is_read) AS count_reads,
  42. SUM(e.is_favorite) AS count_favorites
  43. FROM `_entry` AS e, `_feed` AS f
  44. WHERE e.id_feed = f.id
  45. {$filter}
  46. SQL;
  47. $stm = $this->pdo->query($sql);
  48. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  49. return $res[0];
  50. }
  51. /**
  52. * Calculates entry count per day on a 30 days period.
  53. *
  54. * @return array
  55. */
  56. public function calculateEntryCount() {
  57. $count = $this->initEntryCountArray();
  58. $midnight = mktime(0, 0, 0);
  59. $oldest = $midnight - (self::ENTRY_COUNT_PERIOD * 86400);
  60. // Get stats per day for the last 30 days
  61. $sqlDay = $this->sqlFloor("(date - $midnight) / 86400");
  62. $sql = <<<SQL
  63. SELECT {$sqlDay} AS day,
  64. COUNT(*) as count
  65. FROM `_entry`
  66. WHERE date >= {$oldest} AND date < {$midnight}
  67. GROUP BY day
  68. ORDER BY day ASC
  69. SQL;
  70. $stm = $this->pdo->query($sql);
  71. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  72. foreach ($res as $value) {
  73. $count[$value['day']] = (int) $value['count'];
  74. }
  75. return $count;
  76. }
  77. /**
  78. * Initialize an array for the entry count.
  79. *
  80. * @return array
  81. */
  82. protected function initEntryCountArray() {
  83. return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
  84. }
  85. /**
  86. * Calculates the number of article per hour of the day per feed
  87. *
  88. * @param integer $feed id
  89. * @return array
  90. */
  91. public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
  92. return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
  93. }
  94. /**
  95. * Calculates the number of article per day of week per feed
  96. *
  97. * @param integer $feed id
  98. * @return array
  99. */
  100. public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
  101. return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
  102. }
  103. /**
  104. * Calculates the number of article per month per feed
  105. *
  106. * @param integer $feed
  107. * @return array
  108. */
  109. public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
  110. $monthRepartition = $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
  111. // cut out the 0th month (Jan=1, Dec=12)
  112. \array_splice($monthRepartition, 0, 1);
  113. return $monthRepartition;
  114. }
  115. /**
  116. * Calculates the number of article per period per feed
  117. *
  118. * @param string $period format string to use for grouping
  119. * @param integer $feed id
  120. * @return array<int,int>
  121. */
  122. protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
  123. $restrict = '';
  124. if ($feed) {
  125. $restrict = "WHERE e.id_feed = {$feed}";
  126. }
  127. $sql = <<<SQL
  128. SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
  129. , COUNT(1) AS count
  130. FROM `_entry` AS e
  131. {$restrict}
  132. GROUP BY period
  133. ORDER BY period ASC
  134. SQL;
  135. $stm = $this->pdo->query($sql);
  136. $res = $stm->fetchAll(PDO::FETCH_NAMED);
  137. switch ($period) {
  138. case '%H':
  139. $periodMax = 24;
  140. break;
  141. case '%w':
  142. $periodMax = 7;
  143. break;
  144. case '%m':
  145. $periodMax = 12;
  146. break;
  147. default:
  148. $periodMax = 30;
  149. }
  150. $repartition = array_fill(0, $periodMax, 0);
  151. foreach ($res as $value) {
  152. $repartition[(int) $value['period']] = (int) $value['count'];
  153. }
  154. return $repartition;
  155. }
  156. /**
  157. * Calculates the average number of article per hour per feed
  158. *
  159. * @param integer $feed id
  160. * @return float
  161. */
  162. public function calculateEntryAveragePerFeedPerHour($feed = null) {
  163. return $this->calculateEntryAveragePerFeedPerPeriod(1 / 24, $feed);
  164. }
  165. /**
  166. * Calculates the average number of article per day of week per feed
  167. *
  168. * @param integer $feed id
  169. * @return float
  170. */
  171. public function calculateEntryAveragePerFeedPerDayOfWeek($feed = null) {
  172. return $this->calculateEntryAveragePerFeedPerPeriod(7, $feed);
  173. }
  174. /**
  175. * Calculates the average number of article per month per feed
  176. *
  177. * @param integer $feed id
  178. * @return float
  179. */
  180. public function calculateEntryAveragePerFeedPerMonth($feed = null) {
  181. return $this->calculateEntryAveragePerFeedPerPeriod(30, $feed);
  182. }
  183. /**
  184. * Calculates the average number of article per feed
  185. *
  186. * @param float $period number used to divide the number of day in the period
  187. * @param integer $feed id
  188. * @return float
  189. */
  190. protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) {
  191. $restrict = '';
  192. if ($feed) {
  193. $restrict = "WHERE e.id_feed = {$feed}";
  194. }
  195. $sql = <<<SQL
  196. SELECT COUNT(1) AS count
  197. , MIN(date) AS date_min
  198. , MAX(date) AS date_max
  199. FROM `_entry` AS e
  200. {$restrict}
  201. SQL;
  202. $stm = $this->pdo->query($sql);
  203. $res = $stm->fetch(PDO::FETCH_NAMED);
  204. $date_min = new \DateTime();
  205. $date_min->setTimestamp($res['date_min']);
  206. $date_max = new \DateTime();
  207. $date_max->setTimestamp($res['date_max']);
  208. $interval = $date_max->diff($date_min, true);
  209. $interval_in_days = $interval->format('%a');
  210. if ($interval_in_days <= 0) {
  211. // Surely only one article.
  212. // We will return count / (period/period) == count.
  213. $interval_in_days = $period;
  214. }
  215. return $res['count'] / ($interval_in_days / $period);
  216. }
  217. /**
  218. * Initialize an array for statistics depending on a range
  219. *
  220. * @param integer $min
  221. * @param integer $max
  222. * @return array
  223. */
  224. protected function initStatsArray($min, $max) {
  225. return array_map(function () {
  226. return 0;
  227. }, array_flip(range($min, $max)));
  228. }
  229. /**
  230. * Calculates feed count per category.
  231. * @return array
  232. */
  233. public function calculateFeedByCategory() {
  234. $sql = <<<SQL
  235. SELECT c.name AS label
  236. , COUNT(f.id) AS data
  237. FROM `_category` AS c, `_feed` AS f
  238. WHERE c.id = f.category
  239. GROUP BY label
  240. ORDER BY data DESC
  241. SQL;
  242. $stm = $this->pdo->query($sql);
  243. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  244. return $res;
  245. }
  246. /**
  247. * Calculates entry count per category.
  248. * @return array
  249. */
  250. public function calculateEntryByCategory() {
  251. $sql = <<<SQL
  252. SELECT c.name AS label
  253. , COUNT(e.id) AS data
  254. FROM `_category` AS c, `_feed` AS f, `_entry` AS e
  255. WHERE c.id = f.category
  256. AND f.id = e.id_feed
  257. GROUP BY label
  258. ORDER BY data DESC
  259. SQL;
  260. $stm = $this->pdo->query($sql);
  261. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  262. return $res;
  263. }
  264. /**
  265. * Calculates the 10 top feeds based on their number of entries
  266. *
  267. * @return array
  268. */
  269. public function calculateTopFeed() {
  270. $sql = <<<SQL
  271. SELECT f.id AS id
  272. , MAX(f.name) AS name
  273. , MAX(c.name) AS category
  274. , COUNT(e.id) AS count
  275. FROM `_category` AS c, `_feed` AS f, `_entry` AS e
  276. WHERE c.id = f.category
  277. AND f.id = e.id_feed
  278. GROUP BY f.id
  279. ORDER BY count DESC
  280. LIMIT 10
  281. SQL;
  282. $stm = $this->pdo->query($sql);
  283. return $stm->fetchAll(PDO::FETCH_ASSOC);
  284. }
  285. /**
  286. * Calculates the last publication date for each feed
  287. *
  288. * @return array
  289. */
  290. public function calculateFeedLastDate() {
  291. $sql = <<<SQL
  292. SELECT MAX(f.id) as id
  293. , MAX(f.name) AS name
  294. , MAX(date) AS last_date
  295. , COUNT(*) AS nb_articles
  296. FROM `_feed` AS f, `_entry` AS e
  297. WHERE f.id = e.id_feed
  298. GROUP BY f.id
  299. ORDER BY name
  300. SQL;
  301. $stm = $this->pdo->query($sql);
  302. return $stm->fetchAll(PDO::FETCH_ASSOC);
  303. }
  304. /**
  305. * Gets days ready for graphs
  306. *
  307. * @return array<string>
  308. */
  309. public function getDays() {
  310. return $this->convertToTranslatedJson(array(
  311. 'sun',
  312. 'mon',
  313. 'tue',
  314. 'wed',
  315. 'thu',
  316. 'fri',
  317. 'sat',
  318. ));
  319. }
  320. /**
  321. * Gets months ready for graphs
  322. *
  323. * @return array<string>
  324. */
  325. public function getMonths() {
  326. return $this->convertToTranslatedJson(array(
  327. 'jan',
  328. 'feb',
  329. 'mar',
  330. 'apr',
  331. 'may_',
  332. 'jun',
  333. 'jul',
  334. 'aug',
  335. 'sep',
  336. 'oct',
  337. 'nov',
  338. 'dec',
  339. ));
  340. }
  341. /**
  342. * Translates array content
  343. *
  344. * @param array $data
  345. * @return array<string>
  346. */
  347. private function convertToTranslatedJson($data = array()) {
  348. $translated = array_map(function($a) {
  349. return _t('gen.date.' . $a);
  350. }, $data);
  351. return $translated;
  352. }
  353. }