Jelajahi Sumber

Add article repartition in stats

Add article repartition per hour, per day of week, per month for all feeds but also for individual feeds.
Alexis Degrugillier 11 tahun lalu
induk
melakukan
d049c1bc80

+ 14 - 1
app/Controllers/statsController.php

@@ -55,7 +55,20 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
 
 		$this->view->idleFeeds = $idleFeeds;
 	}
-	
+
+	public function repartitionAction() {
+		$statsDAO = FreshRSS_Factory::createStatsDAO();
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		$id = Minz_Request::param ('id', null);
+		$this->view->feed = $feedDAO->searchById($id);
+		$this->view->days = $statsDAO->getDays();
+		$this->view->months = $statsDAO->getMonths();
+		$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+		$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+		$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+	}
+
 	public function firstAction() {
 		if (!$this->view->loginOk) {
 			Minz_Error::error(

+ 128 - 1
app/Models/StatsDAO.php

@@ -85,9 +85,83 @@ SQL;
 	 * @return array
 	 */
 	protected function initEntryCountArray() {
+		return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
+	}
+
+	/**
+	 * Calculates the number of article per hour of the day per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%k', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per day of week per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per month per feed
+	 *
+	 * @param integer $feed
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per period per feed
+	 *
+	 * @param string $period format string to use for grouping
+	 * @param integer $feed id
+	 * @return string
+	 */
+	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+		if ($feed) {
+			$restrict = "WHERE e.id_feed = {$feed}";
+		} else {
+			$restrict = '';
+		}
+		$sql = <<<SQL
+SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+		foreach ($res as $value) {
+			$repartition[(int) $value['period']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($repartition);
+	}
+
+	/**
+	 * Initialize an array for statistics depending on a range
+	 *
+	 * @param integer $min
+	 * @param integer $max
+	 * @return array
+	 */
+	protected function initStatsArray($min, $max) {
 		return array_map(function () {
 			return 0;
-		}, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
+		}, array_flip(range($min, $max)));
 	}
 
 	/**
@@ -205,4 +279,57 @@ SQL;
 		return json_encode($serie);
 	}
 
+	/**
+	 * Gets days ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getDays() {
+		return $this->convertToTranslatedJson(array(
+			'sun',
+			'mon',
+			'tue',
+			'wed',
+			'thu',
+			'fri',
+			'sat',
+		));
+	}
+
+	/**
+	 * Gets months ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getMonths() {
+		return $this->convertToTranslatedJson(array(
+			'jan',
+			'feb',
+			'mar',
+			'apr',
+			'may',
+			'jun',
+			'jul',
+			'aug',
+			'sep',
+			'oct',
+			'nov',
+			'dec',
+		));
+	}
+
+	/**
+	 * Translates array content and encode it as JSON
+	 *
+	 * @param array $data
+	 * @return string
+	 */
+	private function convertToTranslatedJson($data = array()) {
+		$translated = array_map(function ($a) {
+			return Minz_Translate::t($a);
+		}, $data);
+
+		return json_encode($translated);
+	}
+
 }

+ 35 - 12
app/i18n/en.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistics',
 	'stats_idle'			=> 'Idle feeds',
 	'stats_main'			=> 'Main statistics',
+	'stats_repartition'		=> 'Articles repartition',
+	'stats_entry_per_hour'		=> 'Per hour',
+	'stats_entry_per_day_of_week'	=> 'Per day of week',
+	'stats_entry_per_month'		=> 'Per month',
     
 	'last_week'			=> 'Last week',
 	'last_month'			=> 'Last month',
@@ -341,18 +345,37 @@ return array (
 	'confirm_action'		=> 'Are you sure you want to perform this action? It cannot be cancelled!',
 
 	// DATE
-	'january'			=> 'january',
-	'february'			=> 'february',
-	'march'				=> 'march',
-	'april'				=> 'april',
-	'may'				=> 'may',
-	'june'				=> 'june',
-	'july'				=> 'july',
-	'august'			=> 'august',
-	'september'			=> 'september',
-	'october'			=> 'october',
-	'november'			=> 'november',
-	'december'			=> 'december',
+	'january'			=> 'January',
+	'february'			=> 'February',
+	'march'				=> 'March',
+	'april'				=> 'April',
+	'may'				=> 'May',
+	'june'				=> 'June',
+	'july'				=> 'July',
+	'august'			=> 'August',
+	'september'			=> 'September',
+	'october'			=> 'October',
+	'november'			=> 'November',
+	'december'			=> 'December',
+	'january'			=> 'Jan',
+	'february'			=> 'Feb',
+	'march'				=> 'Mar',
+	'april'				=> 'Apr',
+	'may'				=> 'May',
+	'june'				=> 'Jun',
+	'july'				=> 'Jul',
+	'august'			=> 'Aug',
+	'september'			=> 'Sep',
+	'october'			=> 'Oct',
+	'november'			=> 'Nov',
+	'december'			=> 'Dec',
+	'sun'				=> 'Sun',
+	'mon'				=> 'Mon',
+	'tue'				=> 'Tue',
+	'wed'				=> 'Wed',
+	'thu'				=> 'Thu',
+	'fri'				=> 'Fri',
+	'sat'				=> 'Sat',
 	// special format for date() function
 	'Jan'				=> '\J\a\n\u\a\r\y',
 	'Feb'				=> '\F\e\b\r\u\a\r\y',

+ 23 - 0
app/i18n/fr.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistiques',
 	'stats_idle'			=> 'Flux inactifs',
 	'stats_main'			=> 'Statistiques principales',
+	'stats_repartition'		=> 'Répartition des articles',
+	'stats_entry_per_hour'		=> 'Par heure',
+	'stats_entry_per_day_of_week'	=> 'Par jour de la semaine',
+	'stats_entry_per_month'		=> 'Par mois',
 
 	'last_week'			=> 'La dernière semaine',
 	'last_month'			=> 'Le dernier mois',
@@ -353,6 +357,25 @@ return array (
 	'october'			=> 'octobre',
 	'november'			=> 'novembre',
 	'december'			=> 'décembre',
+	'jan'				=> 'jan.',
+	'feb'				=> 'fév.',
+	'mar'				=> 'mar.',
+	'apr'				=> 'avr.',
+	'may'				=> 'mai.',
+	'jun'				=> 'juin',
+	'jul'				=> 'jui.',
+	'aug'				=> 'août',
+	'sep'				=> 'sep.',
+	'oct'				=> 'oct.',
+	'nov'				=> 'nov.',
+	'dec'				=> 'déc.',
+	'sun'				=> 'dim.',
+	'mon'				=> 'lun.',
+	'tue'				=> 'mar.',
+	'wed'				=> 'mer.',
+	'thu'				=> 'jeu.',
+	'fri'				=> 'ven.',
+	'sat'				=> 'sam.',
 	// format spécial pour la fonction date()
 	'Jan'				=> '\j\a\n\v\i\e\r',
 	'Feb'				=> '\f\é\v\r\i\e\r',

+ 1 - 0
app/layout/aside_flux.phtml

@@ -83,6 +83,7 @@
 		<li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('administration'); ?></a></li>
 		<li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('actualize'); ?></a></li>
 		<li class="item"><a href="<?php echo _url ('entry', 'read', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a></li>
+		<li class="item"><a href="<?php echo _url ('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
 		<?php } ?>
 	</ul>
 </script>

+ 3 - 0
app/layout/aside_stats.phtml

@@ -6,4 +6,7 @@
 	<li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>">
 		<a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a>
 	</li>
+	<li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a>
+	</li>
 </ul>

+ 0 - 127
app/views/stats/main.phtml

@@ -1,127 +0,0 @@
-<?php $this->partial('aside_stats'); ?>
-
-<div class="post content">
-        <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
-        
-        <h1><?php echo Minz_Translate::t ('stats_main'); ?></h1>
-
-        <div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_entry_repartition'); ?></h2>
-		<table>
-			<thead>
-				<tr>
-					<th> </th>
-					<th><?php echo Minz_Translate::t ('main_stream'); ?></th>
-					<th><?php echo Minz_Translate::t ('all_feeds'); ?></th>
-				</tr>
-			</thead>
-			<tbody>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_total'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_read'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_unread'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_favorites'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td>
-				</tr>
-			</tbody>
-		</table>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_entry_per_day'); ?></h2>
-		<div id="statsEntryPerDay" style="height: 300px"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_feed_per_category'); ?></h2>
-		<div id="statsFeedPerCategory" style="height: 300px"></div>
-		<div id="statsFeedPerCategoryLegend"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_entry_per_category'); ?></h2>
-		<div id="statsEntryPerCategory" style="height: 300px"></div>
-		<div id="statsEntryPerCategoryLegend"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_top_feed'); ?></h2>
-		<table>
-			<thead>
-				<tr>
-					<th><?php echo Minz_Translate::t ('feed'); ?></th>
-					<th><?php echo Minz_Translate::t ('category'); ?></th>
-					<th><?php echo Minz_Translate::t ('stats_entry_count'); ?></th>
-				</tr>
-			</thead>
-			<tbody>
-				<?php foreach ($this->topFeed as $feed): ?>
-					<tr>
-						<td><?php echo $feed['name']; ?></td>
-						<td><?php echo $feed['category']; ?></td>
-						<td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
-					</tr>
-				<?php endforeach;?>
-			</tbody>
-		</table>
-	</div>
-</div>
-
-<script>
-"use strict";
-function initStats() {
-	if (!window.Flotr) {
-		if (window.console) {
-			console.log('FreshRSS waiting for Flotr…');
-		}
-		window.setTimeout(initStats, 50);
-		return;
-	}
-	// Entry per day
-	Flotr.draw(document.getElementById('statsEntryPerDay'),
-		[<?php echo $this->count ?>],
-		{
-			grid: {verticalLines: false},
-			bars: {horizontal: false, show: true},
-			xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
-		});
-	// Feed per category
-	Flotr.draw(document.getElementById('statsFeedPerCategory'),
-		<?php echo $this->feedByCategory ?>,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
-			legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
-		});
-	// Entry per category
-	Flotr.draw(document.getElementById('statsEntryPerCategory'),
-		<?php echo $this->entryByCategory ?>,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
-			legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
-		});
-}
-initStats();
-</script>

+ 100 - 0
app/views/stats/repartition.phtml

@@ -0,0 +1,100 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
+	
+	<?php if ($this->feed) {?>
+		<h1>
+			<?php echo _t ('stats_repartition'), " - "; ?>
+			<a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>"
+			   title="<?php echo date('Y-m-d', $feed['last_date']); ?>">
+				<?php echo $this->feed->name(); ?>
+			</a>
+		</h1>
+	<?php } else {?>
+		<h1><?php echo _t ('stats_repartition'); ?></h1>
+	<?php }?>
+	
+	<div class="stat">
+		<h2><?php echo _t ('stats_entry_per_hour'); ?></h2>
+		<div id="statsEntryPerHour" style="height: 300px"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo _t ('stats_entry_per_day_of_week'); ?></h2>
+		<div id="statsEntryPerDayOfWeek" style="height: 300px"></div>
+	</div>
+
+	<div class="stat">
+		<h2><?php echo _t ('stats_entry_per_month'); ?></h2>
+		<div id="statsEntryPerMonth" style="height: 300px"></div>
+	</div>
+</div>
+
+<script>
+"use strict";
+function initStats() {
+	if (!window.Flotr) {
+		if (window.console) {
+			console.log('FreshRSS waiting for Flotr…');
+		}
+		window.setTimeout(initStats, 50);
+		return;
+	}
+	// Entry per hour
+	Flotr.draw(document.getElementById('statsEntryPerHour'),
+		[<?php echo $this->repartitionHour ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 23,
+				tickFormatter: function(x) {
+					var x = parseInt(x);
+					return x + 1;
+				},
+				min: -0.9,
+				max: 23.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per day of week
+	Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
+		[<?php echo $this->repartitionDayOfWeek ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 6,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    days = <?php echo $this->days?>;
+					return days[x];
+				},
+				min: -0.9,
+				max: 6.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per month
+	Flotr.draw(document.getElementById('statsEntryPerMonth'),
+		[<?php echo $this->repartitionMonth ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 12,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    months = <?php echo $this->months?>;
+					return months[(x - 1)];
+				},
+				min: 0.1,
+				max: 12.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	
+}
+initStats();
+</script>