StatsDAO.php 8.6 KB

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