mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-01-28 23:21:04 -05:00
Before <img width="1034" height="543" alt="image" src="https://github.com/user-attachments/assets/09408f81-42f1-4613-948b-b2c7e3ff2409" /> After <img width="1031" height="536" alt="image" src="https://github.com/user-attachments/assets/d4dd3702-c8a5-4f8d-9014-b419a982d9ec" /> The current day is now included within statistics. The `-1` thing started happening after #7752 but I'm not sure whether the correct solution is to display the current day or just fix it in the JS For reference here's what the chart used to looked like: <img width="1027" height="536" alt="image" src="https://github.com/user-attachments/assets/47a27bad-6593-4e84-a04f-e4385f601c6a" />
250 lines
8.1 KiB
PHP
250 lines
8.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Controller to handle application statistics.
|
|
*/
|
|
class FreshRSS_stats_Controller extends FreshRSS_ActionController {
|
|
|
|
/**
|
|
* @var FreshRSS_ViewStats
|
|
* @phpstan-ignore property.phpDocType
|
|
*/
|
|
protected $view;
|
|
|
|
public function __construct() {
|
|
parent::__construct(FreshRSS_ViewStats::class);
|
|
}
|
|
|
|
/**
|
|
* This action is called before every other action in that class. It is
|
|
* the common boilerplate for every action. It is triggered by the
|
|
* underlying framework.
|
|
*/
|
|
#[\Override]
|
|
public function firstAction(): void {
|
|
if (!FreshRSS_Auth::hasAccess()) {
|
|
Minz_Error::error(403);
|
|
}
|
|
|
|
$this->_csp([
|
|
'default-src' => "'self'",
|
|
'frame-ancestors' => "'none'",
|
|
'img-src' => '* data: blob:',
|
|
]);
|
|
|
|
$catDAO = FreshRSS_Factory::createCategoryDao();
|
|
$catDAO->checkDefault();
|
|
$this->view->categories = $catDAO->listSortedCategories(prePopulateFeeds: false);
|
|
|
|
FreshRSS_View::prependTitle(_t('admin.stats.title') . ' · ');
|
|
}
|
|
|
|
/**
|
|
* This action handles the statistic main page.
|
|
*
|
|
* It displays the statistic main page.
|
|
* The values computed to display the page are:
|
|
* - repartition of read/unread/favorite/not favorite (repartition)
|
|
* - number of article per day (entryCount)
|
|
* - number of feed by category (feedByCategory)
|
|
* - number of article by category (entryByCategory)
|
|
* - list of most prolific feed (topFeed)
|
|
*/
|
|
public function indexAction(): void {
|
|
$statsDAO = FreshRSS_Factory::createStatsDAO();
|
|
FreshRSS_View::appendScript(Minz_Url::display('/scripts/vendor/chart.umd.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/chart.umd.min.js')));
|
|
|
|
$this->view->repartitions = $statsDAO->calculateEntryRepartition();
|
|
|
|
$entryCount = $statsDAO->calculateEntryCount();
|
|
if (count($entryCount) > 0) {
|
|
$this->view->entryCount = $entryCount;
|
|
$this->view->average = round(array_sum(array_values($entryCount)) / count($entryCount), 2);
|
|
} else {
|
|
$this->view->entryCount = [];
|
|
$this->view->average = -1.0;
|
|
}
|
|
|
|
$feedByCategory = [];
|
|
$feedByCategory_calculated = $statsDAO->calculateFeedByCategory();
|
|
for ($i = 0; $i < count($feedByCategory_calculated); $i++) {
|
|
$feedByCategory['label'][$i] = $feedByCategory_calculated[$i]['label'];
|
|
$feedByCategory['data'][$i] = $feedByCategory_calculated[$i]['data'];
|
|
}
|
|
$this->view->feedByCategory = $feedByCategory;
|
|
|
|
$entryByCategory = [];
|
|
$entryByCategory_calculated = $statsDAO->calculateEntryByCategory();
|
|
for ($i = 0; $i < count($entryByCategory_calculated); $i++) {
|
|
$entryByCategory['label'][$i] = $entryByCategory_calculated[$i]['label'];
|
|
$entryByCategory['data'][$i] = $entryByCategory_calculated[$i]['data'];
|
|
}
|
|
$this->view->entryByCategory = $entryByCategory;
|
|
|
|
$this->view->topFeed = $statsDAO->calculateTopFeed();
|
|
|
|
$last30DaysLabels = [];
|
|
for ($i = 0; $i < 31; $i++) {
|
|
$last30DaysLabels[$i] = date('d.m.Y', strtotime((-30 + $i) . ' days') ?: null);
|
|
}
|
|
|
|
$this->view->last30DaysLabels = $last30DaysLabels;
|
|
}
|
|
|
|
/**
|
|
* This action handles the feed action on the idle statistic page.
|
|
* set the 'from' parameter to remember that it had a redirection coming from stats controller,
|
|
* to use the subscription controller to save it,
|
|
* but shows the stats idle page
|
|
*/
|
|
public function feedAction(): void {
|
|
$id = Minz_Request::paramInt('id');
|
|
$ajax = Minz_Request::paramBoolean('ajax');
|
|
if ($ajax) {
|
|
$url_redirect = ['c' => 'subscription', 'a' => 'feed', 'params' => ['id' => (string)$id, 'from' => 'stats', 'ajax' => (string)$ajax]];
|
|
} else {
|
|
$url_redirect = ['c' => 'subscription', 'a' => 'feed', 'params' => ['id' => (string)$id, 'from' => 'stats']];
|
|
}
|
|
Minz_Request::forward($url_redirect, true);
|
|
}
|
|
|
|
/**
|
|
* This action handles the idle feed statistic page.
|
|
*
|
|
* It displays the list of idle feed for different period. The supported
|
|
* periods are:
|
|
* - last 5 years
|
|
* - last 3 years
|
|
* - last 2 years
|
|
* - last year
|
|
* - last 6 months
|
|
* - last 3 months
|
|
* - last month
|
|
* - last week
|
|
*/
|
|
public function idleAction(): void {
|
|
FreshRSS_View::appendScript(Minz_Url::display('/scripts/feed.js?' . @filemtime(PUBLIC_PATH . '/scripts/feed.js')));
|
|
$feed_dao = FreshRSS_Factory::createFeedDao();
|
|
$statsDAO = FreshRSS_Factory::createStatsDAO();
|
|
$feeds = $statsDAO->calculateFeedLastDate();
|
|
$idleFeeds = [
|
|
'last_5_year' => [],
|
|
'last_3_year' => [],
|
|
'last_2_year' => [],
|
|
'last_year' => [],
|
|
'last_6_month' => [],
|
|
'last_3_month' => [],
|
|
'last_month' => [],
|
|
'last_week' => [],
|
|
];
|
|
$now = new \DateTime();
|
|
$feedDate = clone $now;
|
|
$lastWeek = clone $now;
|
|
$lastWeek->modify('-1 week');
|
|
$lastMonth = clone $now;
|
|
$lastMonth->modify('-1 month');
|
|
$last3Month = clone $now;
|
|
$last3Month->modify('-3 month');
|
|
$last6Month = clone $now;
|
|
$last6Month->modify('-6 month');
|
|
$lastYear = clone $now;
|
|
$lastYear->modify('-1 year');
|
|
$last2Year = clone $now;
|
|
$last2Year->modify('-2 year');
|
|
$last3Year = clone $now;
|
|
$last3Year->modify('-3 year');
|
|
$last5Year = clone $now;
|
|
$last5Year->modify('-5 year');
|
|
|
|
foreach ($feeds as $feed) {
|
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
|
$feedObject = $feedDAO->searchById($feed['id']);
|
|
if ($feedObject !== null) {
|
|
$feed['favicon'] = $feedObject->favicon();
|
|
}
|
|
|
|
$feedDate->setTimestamp($feed['last_date']);
|
|
if ($feedDate >= $lastWeek) {
|
|
continue;
|
|
}
|
|
if ($feedDate < $last5Year) {
|
|
$idleFeeds['last_5_year'][] = $feed;
|
|
} elseif ($feedDate < $last3Year) {
|
|
$idleFeeds['last_3_year'][] = $feed;
|
|
} elseif ($feedDate < $last2Year) {
|
|
$idleFeeds['last_2_year'][] = $feed;
|
|
} elseif ($feedDate < $lastYear) {
|
|
$idleFeeds['last_year'][] = $feed;
|
|
} elseif ($feedDate < $last6Month) {
|
|
$idleFeeds['last_6_month'][] = $feed;
|
|
} elseif ($feedDate < $last3Month) {
|
|
$idleFeeds['last_3_month'][] = $feed;
|
|
} elseif ($feedDate < $lastMonth) {
|
|
$idleFeeds['last_month'][] = $feed;
|
|
} elseif ($feedDate < $lastWeek) {
|
|
$idleFeeds['last_week'][] = $feed;
|
|
}
|
|
}
|
|
|
|
$this->view->idleFeeds = $idleFeeds;
|
|
$this->view->feeds = $feed_dao->listFeeds();
|
|
|
|
$id = Minz_Request::paramInt('id');
|
|
$this->view->displaySlider = false;
|
|
if ($id !== 0) {
|
|
$this->view->displaySlider = true;
|
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
|
$this->view->feed = $feedDAO->searchById($id) ?? FreshRSS_Feed::default();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This action handles the article repartition statistic page.
|
|
*
|
|
* It displays the number of article and the average of article for the
|
|
* following periods:
|
|
* - hour of the day
|
|
* - day of the week
|
|
* - month
|
|
*
|
|
* @todo verify that the metrics used here make some sense. Especially
|
|
* for the average.
|
|
*/
|
|
public function repartitionAction(): void {
|
|
$statsDAO = FreshRSS_Factory::createStatsDAO();
|
|
$categoryDAO = FreshRSS_Factory::createCategoryDao();
|
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
|
|
|
FreshRSS_View::appendScript(Minz_Url::display('/scripts/vendor/chart.umd.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/chart.umd.min.js')));
|
|
|
|
$id = Minz_Request::paramInt('id');
|
|
if ($id === 0) {
|
|
$id = null;
|
|
}
|
|
|
|
$this->view->categories = $categoryDAO->listCategories(prePopulateFeeds: true);
|
|
$this->view->feed = $id === null ? FreshRSS_Feed::default() : ($feedDAO->searchById($id) ?? FreshRSS_Feed::default());
|
|
$this->view->days = $statsDAO->getDays();
|
|
$this->view->months = $statsDAO->getMonths();
|
|
|
|
$this->view->repartition = $statsDAO->calculateEntryRepartitionPerFeed($id);
|
|
|
|
$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
|
|
$this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id);
|
|
|
|
$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
|
|
$this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
|
|
|
|
$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
|
|
$this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
|
|
|
|
$hours24Labels = [];
|
|
for ($i = 0; $i < 24; $i++) {
|
|
$hours24Labels[$i] = $i . ':xx';
|
|
}
|
|
|
|
$this->view->hours24Labels = $hours24Labels;
|
|
}
|
|
}
|