4
0

StatsDAO.php 8.2 KB

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