Просмотр исходного кода

Add feed visibility filter to unread dates view (#8489)

* Add feed visibility filter to unread dates view

* Date field sanitize
Alexandre Alapetite 1 месяц назад
Родитель
Сommit
f17ed2f7c8

+ 2 - 1
app/Controllers/statsController.php

@@ -257,7 +257,8 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
 		if (!in_array($granularity, ['day', 'month', 'year'], true)) {
 			$granularity = 'day';
 		}
-		$dates = $statsDAO->getMaxUnreadDates($field, $granularity, Minz_Request::paramInt('max') ?: 100);
+		$dates = $statsDAO->getMaxUnreadDates($field, $granularity, Minz_Request::paramInt('max') ?: 100,
+			Minz_Request::paramIntNull('min_priority') ?? FreshRSS_Feed::PRIORITY_HIDDEN);
 		$this->view->unreadDates = $dates;
 	}
 }

+ 23 - 14
app/Models/StatsDAO.php

@@ -17,7 +17,7 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
 	 * @param 'day'|'month'|'year' $granularity of the date intervals
 	 */
 	protected function sqlDateToIsoGranularity(string $field, int $precision, string $granularity): string {
-		if (!preg_match('/^[a-zA-Z0-9_]+$/', $field)) {
+		if (!preg_match('/^[a-zA-Z0-9_.]+$/', $field)) {
 			throw new InvalidArgumentException('Invalid date field!');
 		}
 		$offset = $this->getTimezoneOffset();
@@ -388,19 +388,28 @@ SQL;
 	 * @param 'day'|'month'|'year' $granularity of the date intervals
 	 * @return list<array{'granularity':string,'unread_count':int}>
 	 */
-	public function getMaxUnreadDates(string $field, string $granularity, int $max = 100): array {
+	public function getMaxUnreadDates(string $field, string $granularity, int $max = 100, int $minPriority = FreshRSS_Feed::PRIORITY_HIDDEN): array {
 		$sql = <<<SQL
-SELECT
-	{$this->sqlDateToIsoGranularity($field, precision: $field === 'id' ? 1000000 : 1, granularity: $granularity)} AS granularity,
-	COUNT(*) AS unread_count
-FROM `_entry`
-WHERE is_read = 0
-GROUP BY granularity
-ORDER BY unread_count DESC, granularity DESC
-LIMIT $max;
-SQL;
-		$res = $this->fetchAssoc($sql);
-		/** @var list<array{granularity:string,unread_count:int}>|null $res */
-		return is_array($res) ? $res : [];
+		SELECT
+			{$this->sqlDateToIsoGranularity('e.' . $field, precision: $field === 'id' ? 1000000 : 1, granularity: $granularity)} AS granularity,
+			COUNT(*) AS unread_count
+		FROM `_entry` e
+		INNER JOIN `_feed` f ON e.id_feed = f.id
+		WHERE e.is_read = 0 AND f.priority >= :min_priority
+		GROUP BY granularity
+		ORDER BY unread_count DESC, granularity DESC
+		LIMIT :max
+		SQL;
+		if (($stm = $this->pdo->prepare($sql)) !== false &&
+			$stm->bindValue(':min_priority', $minPriority, PDO::PARAM_INT) &&
+			$stm->bindValue(':max', $max, PDO::PARAM_INT) &&
+			$stm->execute() && is_array($res = $stm->fetchAll(PDO::FETCH_ASSOC))) {
+			/** @var list<array{granularity:string,unread_count:int}> $res */
+			return $res;
+		} else {
+			$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+			Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+			return [];
+		}
 	}
 }

+ 3 - 0
app/Models/StatsDAOPGSQL.php

@@ -5,6 +5,9 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
 
 	#[\Override]
 	protected function sqlDateToIsoGranularity(string $field, int $precision, string $granularity): string {
+		if (!preg_match('/^[a-zA-Z0-9_.]+$/', $field)) {
+			throw new InvalidArgumentException('Invalid date field!');
+		}
 		$offset = $this->getTimezoneOffset();
 		return match ($granularity) {
 			'day' => "to_char(to_timestamp(($field / $precision) + $offset), 'YYYY-MM-DD')",

+ 3 - 0
app/Models/StatsDAOSQLite.php

@@ -5,6 +5,9 @@ class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
 
 	#[\Override]
 	protected function sqlDateToIsoGranularity(string $field, int $precision, string $granularity): string {
+		if (!preg_match('/^[a-zA-Z0-9_.]+$/', $field)) {
+			throw new InvalidArgumentException('Invalid date field!');
+		}
 		$offset = $this->getTimezoneOffset();
 		return match ($granularity) {
 			'day' => "strftime('%Y-%m-%d', ($field / $precision) + $offset, 'unixepoch')",

+ 8 - 0
app/views/stats/unreadDates.phtml

@@ -18,6 +18,14 @@
 			<option value="month" <?= Minz_Request::paramString('granularity') === 'month' ? 'selected="selected"' : '' ?>><?= _t('gen.period.months') ?></option>
 			<option value="year" <?= Minz_Request::paramString('granularity') === 'year' ? 'selected="selected"' : '' ?>><?= _t('gen.period.years') ?></option>
 		</select>
+		<select name="min_priority" id="min_priority">
+			<?php $currentPriority = Minz_Request::paramIntNull('min_priority'); ?>
+			<option value=""><?= _t('sub.feed.priority._') ?></option>
+			<option value="<?= FreshRSS_Feed::PRIORITY_IMPORTANT ?>" <?= $currentPriority === FreshRSS_Feed::PRIORITY_IMPORTANT ? 'selected="selected"' : '' ?>><?= _t('sub.feed.priority.important') ?></option>
+			<option value="<?= FreshRSS_Feed::PRIORITY_MAIN_STREAM ?>" <?= $currentPriority === FreshRSS_Feed::PRIORITY_MAIN_STREAM ? 'selected="selected"' : '' ?>><?= _t('sub.feed.priority.main_stream') ?></option>
+			<option value="<?= FreshRSS_Feed::PRIORITY_CATEGORY ?>" <?= $currentPriority === FreshRSS_Feed::PRIORITY_CATEGORY ? 'selected="selected"' : '' ?>><?= _t('sub.feed.priority.category') ?></option>
+			<option value="<?= FreshRSS_Feed::PRIORITY_FEED ?>" <?= $currentPriority === FreshRSS_Feed::PRIORITY_FEED ? 'selected="selected"' : '' ?>><?= _t('sub.feed.priority.feed') ?></option>
+		</select>
 		<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
 	</form>
 	<table>