aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2023-04-28 14:01:11 +0200
committerGravatar GitHub <noreply@github.com> 2023-04-28 14:01:11 +0200
commitc72914bba2363e436574204b3d6093a6f3cfce89 (patch)
tree377008a7393e4d80e4c8659f27dd42c0ccbab382 /app/Models
parent26e2a703125ffe1d0d2746b0e5ea3491b627832c (diff)
PHPStan Level 7 for more DAO PDO (#5328)
* PHPStan Level 7 for more DAO PDO With new function to address common type and check problems * A bit more * PHPStan Level 7 for FreshRSS_Entry
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/CategoryDAO.php56
-rw-r--r--app/Models/DatabaseDAO.php59
-rw-r--r--app/Models/DatabaseDAOPGSQL.php54
-rw-r--r--app/Models/DatabaseDAOSQLite.php2
-rw-r--r--app/Models/Entry.php39
-rw-r--r--app/Models/EntryDAO.php209
-rw-r--r--app/Models/EntryDAOSQLite.php30
-rw-r--r--app/Models/FeedDAO.php58
-rw-r--r--app/Models/StatsDAO.php72
-rw-r--r--app/Models/StatsDAOPGSQL.php7
-rw-r--r--app/Models/StatsDAOSQLite.php7
-rw-r--r--app/Models/TagDAO.php275
-rw-r--r--app/Models/View.php6
13 files changed, 443 insertions, 431 deletions
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index e5301cb2c..ec7bb45cb 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -7,7 +7,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
public function resetDefaultCategoryName(): bool {
//FreshRSS 1.15.1
$stm = $this->pdo->prepare('UPDATE `_category` SET name = :name WHERE id = :id');
- if ($stm) {
+ if ($stm !== false) {
$stm->bindValue(':id', self::DEFAULTCATEGORYID, PDO::PARAM_INT);
$stm->bindValue(':name', 'Uncategorized');
}
@@ -115,14 +115,14 @@ SQL;
$valuesTmp['name'],
);
- if ($stm && $stm->execute($values) && $stm->rowCount() > 0) {
+ if ($stm !== false && $stm->execute($values) && $stm->rowCount() > 0) {
return $this->pdo->lastInsertId('`_category_id_seq`');
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->addCategory($valuesTmp);
}
- Minz_Log::error('SQL error addCategory: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -143,7 +143,7 @@ SQL;
}
/**
- * @param array<string,mixed> $valuesTmp
+ * @param array{'name':string,'kind':int,'attributes':array<string,mixed>} $valuesTmp
* @return int|false
*/
public function updateCategory(int $id, array $valuesTmp) {
@@ -155,7 +155,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
$valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8');
- if (!isset($valuesTmp['attributes'])) {
+ if (empty($valuesTmp['attributes'])) {
$valuesTmp['attributes'] = [];
}
$values = array(
@@ -166,14 +166,14 @@ SQL;
$valuesTmp['name'],
);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->updateCategory($id, $valuesTmp);
}
- Minz_Log::error('SQL error updateCategory: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -188,11 +188,11 @@ SQL;
];
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::warning(__METHOD__ . ' error: ' . $sql . ' : ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -205,20 +205,20 @@ SQL;
$sql = 'DELETE FROM `_category` WHERE id=:id';
$stm = $this->pdo->prepare($sql);
$stm->bindParam(':id', $id, PDO::PARAM_INT);
- if ($stm && $stm->execute()) {
+ if ($stm !== false && $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error deleteCategory: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
- /** @return Traversable<array<string,string|int>> */
+ /** @return Traversable<array{'id':int,'name':string,'kind':int,'lastUpdate':int,'error':int,'attributes'?:array<string,mixed>}> */
public function selectAll(): Traversable {
$sql = 'SELECT id, name, kind, `lastUpdate`, error, attributes FROM `_category`';
$stm = $this->pdo->query($sql);
- if ($stm != false) {
+ if ($stm !== false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
yield $row;
}
@@ -235,7 +235,7 @@ SQL;
public function searchById(int $id): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE id=:id';
$stm = $this->pdo->prepare($sql);
- if ($stm &&
+ if ($stm !== false &&
$stm->bindParam(':id', $id, PDO::PARAM_INT) &&
$stm->execute()) {
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
@@ -250,19 +250,12 @@ SQL;
/** @return FreshRSS_Category|null|false */
public function searchByName(string $name) {
$sql = 'SELECT * FROM `_category` WHERE name=:name';
- $stm = $this->pdo->prepare($sql);
- if ($stm == false) {
+ $res = $this->fetchAssoc($sql, ['name' => $name]);
+ if ($res == null) {
return false;
}
- $stm->bindParam(':name', $name);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
$cat = self::daoToCategory($res);
- if (isset($cat[0])) {
- return $cat[0];
- } else {
- return null;
- }
+ return $cat[0] ?? null;
}
/** @return array<FreshRSS_Category>|false */
@@ -300,20 +293,19 @@ SQL;
. 'ORDER BY c.name, f.name';
$stm = $this->pdo->prepare($sql);
$values = [ ':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL ];
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC));
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->listCategories($prePopulateFeeds, $details);
}
- Minz_Log::error('SQL error listCategories: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
} else {
- $sql = 'SELECT * FROM `_category` ORDER BY name';
- $stm = $this->pdo->query($sql);
- return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC));
+ $res = $this->fetchAssoc('SELECT * FROM `_category` ORDER BY name');
+ return $res == null ? false : self::daoToCategory($res);
}
}
@@ -322,7 +314,7 @@ SQL;
$sql = 'SELECT * FROM `_category` WHERE kind = :kind AND `lastUpdate` < :lu ORDER BY `lastUpdate`'
. ($limit < 1 ? '' : ' LIMIT ' . $limit);
$stm = $this->pdo->prepare($sql);
- if ($stm &&
+ if ($stm !== false &&
$stm->bindValue(':kind', FreshRSS_Category::KIND_DYNAMIC_OPML, PDO::PARAM_INT) &&
$stm->bindValue(':lu', time() - $defaultCacheDuration, PDO::PARAM_INT) &&
$stm->execute()) {
@@ -376,11 +368,11 @@ SQL;
$cat->name(),
);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $this->pdo->lastInsertId('`_category_id_seq`');
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error check default category: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php
index aeac6f435..384e59b16 100644
--- a/app/Models/DatabaseDAO.php
+++ b/app/Models/DatabaseDAO.php
@@ -36,8 +36,11 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
try {
$sql = 'SELECT 1';
$stm = $this->pdo->query($sql);
+ if ($stm === false) {
+ return 'Error during SQL connection test!';
+ }
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return $res == false ? 'Error during SQL connection test!' : '';
+ return $res == false ? 'Error during SQL connection fetch test!' : '';
} catch (Exception $e) {
syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
return $e->getMessage();
@@ -45,8 +48,10 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
}
public function tablesAreCorrect(): bool {
- $stm = $this->pdo->query('SHOW TABLES');
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $res = $this->fetchAssoc('SHOW TABLES');
+ if ($res == null) {
+ return false;
+ }
$tables = array(
$this->pdo->prefix() . 'category' => false,
@@ -60,21 +65,23 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$tables[array_pop($value)] = true;
}
- return count(array_keys($tables, true, true)) == count($tables);
+ return count(array_keys($tables, true, true)) === count($tables);
}
- /** @return array<array<string,string|bool>> */
+ /** @return array<array<string,string|int|bool|null>> */
public function getSchema(string $table): array {
- $sql = 'DESC `_' . $table . '`';
- $stm = $this->pdo->query($sql);
- return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
+ $res = $this->fetchAssoc('DESC `_' . $table . '`');
+ return $res == null ? [] : $this->listDaoToSchema($res);
}
/** @param array<string> $schema */
public function checkTable(string $table, array $schema): bool {
$columns = $this->getSchema($table);
+ if (count($columns) === 0 || count($schema) === 0) {
+ return false;
+ }
- $ok = (count($columns) == count($schema));
+ $ok = count($columns) === count($schema);
foreach ($columns as $c) {
$ok &= in_array($c['name'], $schema);
}
@@ -123,21 +130,21 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
}
/**
- * @param array<string,string> $dao
- * @return array<string,string|bool>
+ * @param array<string,string|int|bool|null> $dao
+ * @return array<string,string|int|bool|null>
*/
public function daoToSchema(array $dao): array {
- return array(
- 'name' => $dao['Field'],
- 'type' => strtolower($dao['Type']),
+ return [
+ 'name' => (string)($dao['Field']),
+ 'type' => strtolower((string)($dao['Type'])),
'notnull' => (bool)$dao['Null'],
'default' => $dao['Default'],
- );
+ ];
}
/**
- * @param array<array<string,string>> $listDAO
- * @return array<array<string,string|bool>>
+ * @param array<array<string,string|int|bool|null>> $listDAO
+ * @return array<array<string,string|int|bool|null>>
*/
public function listDaoToSchema(array $listDAO): array {
$list = array();
@@ -151,16 +158,18 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function size(bool $all = false): int {
$db = FreshRSS_Context::$system_conf->db;
- $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?'; //MySQL
- $values = array($db['base']);
+ //MySQL:
+ $sql = <<<'SQL'
+SELECT SUM(data_length + index_length)
+FROM information_schema.TABLES WHERE table_schema=:table_schema
+SQL;
+ $values = [':table_schema' => $db['base']];
if (!$all) {
- $sql .= ' AND table_name LIKE ?';
- $values[] = $this->pdo->prefix() . '%';
+ $sql .= ' AND table_name LIKE :table_name';
+ $values[':table_name'] = $this->pdo->prefix() . '%';
}
- $stm = $this->pdo->prepare($sql);
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return intval($res[0]);
+ $res = $this->fetchColumn($sql, 0, $values);
+ return isset($res[0]) ? (int)($res[0]) : -1;
}
public function optimize(): bool {
diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
index b1db0f347..29b16e3dc 100644
--- a/app/Models/DatabaseDAOPGSQL.php
+++ b/app/Models/DatabaseDAOPGSQL.php
@@ -11,56 +11,54 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
public function tablesAreCorrect(): bool {
$db = FreshRSS_Context::$system_conf->db;
- $dbowner = $db['user'];
- $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=?';
- $stm = $this->pdo->prepare($sql);
- $values = array($dbowner);
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=:tableowner';
+ $res = $this->fetchAssoc($sql, [':tableowner' => $db['user']]);
+ if ($res == null) {
+ return false;
+ }
- $tables = array(
+ $tables = [
$this->pdo->prefix() . 'category' => false,
$this->pdo->prefix() . 'feed' => false,
$this->pdo->prefix() . 'entry' => false,
$this->pdo->prefix() . 'entrytmp' => false,
$this->pdo->prefix() . 'tag' => false,
$this->pdo->prefix() . 'entrytag' => false,
- );
+ ];
foreach ($res as $value) {
$tables[array_pop($value)] = true;
}
- return count(array_keys($tables, true, true)) == count($tables);
+ return count(array_keys($tables, true, true)) === count($tables);
}
- /** @return array<array<string,string|bool>> */
+ /** @return array<array<string,string|int|bool|null>> */
public function getSchema(string $table): array {
- $sql = 'select column_name as field, data_type as type, column_default as default, is_nullable as null from INFORMATION_SCHEMA.COLUMNS where table_name = ?';
- $stm = $this->pdo->prepare($sql);
- $stm->execute(array($this->pdo->prefix() . $table));
- return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
+ $sql = <<<'SQL'
+SELECT column_name AS field, data_type AS type, column_default AS default, is_nullable AS null
+FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = :table_name
+SQL;
+ $res = $this->fetchAssoc($sql, [':table_name' => $this->pdo->prefix() . $table]);
+ return $res == null ? [] : $this->listDaoToSchema($res);
}
/**
- * @param array<string,string> $dao
- * @return array<string,string|bool>
+ * @param array<string,string|int|bool|null> $dao
+ * @return array{'name':string,'type':string,'notnull':bool,'default':mixed}
*/
public function daoToSchema(array $dao): array {
- return array(
- 'name' => $dao['field'],
- 'type' => strtolower($dao['type']),
+ return [
+ 'name' => (string)($dao['field']),
+ 'type' => strtolower((string)($dao['type'])),
'notnull' => (bool)$dao['null'],
'default' => $dao['default'],
- );
+ ];
}
public function size(bool $all = false): int {
if ($all) {
$db = FreshRSS_Context::$system_conf->db;
- $sql = 'SELECT pg_database_size(:base)';
- $stm = $this->pdo->prepare($sql);
- $stm->bindParam(':base', $db['base']);
- $stm->execute();
+ $res = $this->fetchColumn('SELECT pg_database_size(:base)', 0, [':base' => $db['base']]);
} else {
$sql = <<<SQL
SELECT
@@ -71,13 +69,9 @@ pg_total_relation_size('`{$this->pdo->prefix()}entrytmp`') +
pg_total_relation_size('`{$this->pdo->prefix()}tag`') +
pg_total_relation_size('`{$this->pdo->prefix()}entrytag`')
SQL;
- $stm = $this->pdo->query($sql);
- }
- if ($stm == false) {
- return 0;
+ $res = $this->fetchColumn($sql, 0);
}
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return intval($res[0]);
+ return intval($res[0] ?? -1);
}
diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php
index 0909613a7..8bd8d60ed 100644
--- a/app/Models/DatabaseDAOSQLite.php
+++ b/app/Models/DatabaseDAOSQLite.php
@@ -28,7 +28,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
return count(array_keys($tables, true, true)) == count($tables);
}
- /** @return array<array<string,string|bool>> */
+ /** @return array<array<string,string|int|bool|null>> */
public function getSchema(string $table): array {
$sql = 'PRAGMA table_info(' . $table . ')';
$stm = $this->pdo->query($sql);
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 83ccf6552..c29f84172 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -40,9 +40,11 @@ class FreshRSS_Entry extends Minz_Model {
/**
* @param int|string $pubdate
+ * @param bool|int|null $is_read
+ * @param bool|int|null $is_favorite
*/
public function __construct(int $feedId = 0, string $guid = '', string $title = '', string $authors = '', string $content = '',
- string $link = '', $pubdate = 0, ?bool $is_read = false, ?bool $is_favorite = false, string $tags = '') {
+ string $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, string $tags = '') {
$this->_title($title);
$this->_authors($authors);
$this->_content($content);
@@ -55,11 +57,18 @@ class FreshRSS_Entry extends Minz_Model {
$this->_guid($guid);
}
- /** @param array<string,string|int> $dao */
+ /** @param array{'id'?:string,'id_feed'?:int,'guid'?:string,'title'?:string,'author'?:string,'content'?:string,'link'?:string,'date'?:int|string,
+ * 'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string,'attributes'?:string,'thumbnail'?:string,'timestamp'?:string,'categories'?:string} $dao */
public static function fromArray(array $dao): FreshRSS_Entry {
if (empty($dao['content'])) {
$dao['content'] = '';
}
+
+ $dao['attributes'] = empty($dao['attributes']) ? [] : json_decode($dao['attributes'], true);
+ if (!is_array($dao['attributes'])) {
+ $dao['attributes'] = [];
+ }
+
if (!empty($dao['thumbnail'])) {
$dao['attributes']['thumbnail'] = [
'url' => $dao['thumbnail'],
@@ -81,7 +90,7 @@ class FreshRSS_Entry extends Minz_Model {
$entry->_id($dao['id']);
}
if (!empty($dao['timestamp'])) {
- $entry->_date(strtotime($dao['timestamp']));
+ $entry->_date(strtotime($dao['timestamp']) ?: 0);
}
if (!empty($dao['categories'])) {
$entry->_tags($dao['categories']);
@@ -283,7 +292,7 @@ HTML;
}
/**
- * @return array<string,string>|null
+ * @return array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string,'height'?:int,'width'?:int,'thumbnails'?:array<string>}|null
*/
public function thumbnail(bool $searchEnclosures = true): ?array {
$thumbnail = $this->attributes('thumbnail');
@@ -317,7 +326,11 @@ HTML;
public function machineReadableDate(): string {
return @date (DATE_ATOM, $this->date);
}
- /** @return int|string */
+
+ /**
+ * @phpstan-return ($raw is false ? string : ($microsecond is true ? string : int))
+ * @return int|string
+ */
public function dateAdded(bool $raw = false, bool $microsecond = false) {
if ($raw) {
if ($microsecond) {
@@ -437,10 +450,10 @@ HTML;
if (!is_array($value)) {
if (strpos($value, ';') !== false) {
$value = htmlspecialchars_decode($value, ENT_QUOTES);
- $value = preg_split('/\s*[;]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY) ?: '';
+ $value = preg_split('/\s*[;]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];
$value = Minz_Helper::htmlspecialchars_utf8($value);
} else {
- $value = preg_split('/\s*[,]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+ $value = preg_split('/\s*[,]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];
}
}
$this->authors = $value;
@@ -462,15 +475,17 @@ HTML;
/** @param int|string $value */
public function _dateAdded($value, bool $microsecond = false): void {
if ($microsecond) {
- $this->date_added = $value;
+ $this->date_added = (string)($value);
} else {
$this->date_added = $value . '000000';
}
}
- public function _isRead(?bool $value): void {
+ /** @param bool|int|null $value */
+ public function _isRead($value): void {
$this->is_read = $value === null ? null : (bool)$value;
}
- public function _isFavorite(?bool $value): void {
+ /** @param bool|int|null $value */
+ public function _isFavorite($value): void {
$this->is_favorite = $value === null ? null : (bool)$value;
}
@@ -702,7 +717,7 @@ HTML;
if ($nodes != false) {
foreach ($nodes as $node) {
if (!empty($attributes['path_entries_filter'])) {
- $filterednodes = $xpath->query(new Gt\CssXPath\Translator($attributes['path_entries_filter']), $node);
+ $filterednodes = $xpath->query(new Gt\CssXPath\Translator($attributes['path_entries_filter']), $node) ?: [];
foreach ($filterednodes as $filterednode) {
$filterednode->parentNode->removeChild($filterednode);
}
@@ -790,7 +805,7 @@ HTML;
private static function dec2hex($dec): string {
return PHP_INT_SIZE < 8 ? // 32-bit ?
str_pad(gmp_strval(gmp_init($dec, 10), 16), 16, '0', STR_PAD_LEFT) :
- str_pad(dechex($dec), 16, '0', STR_PAD_LEFT);
+ str_pad(dechex((int)($dec)), 16, '0', STR_PAD_LEFT);
}
/**
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 19547ac31..4c6bf0641 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -185,7 +185,7 @@ SQL;
$this->addEntryPrepared = null;
return $this->addEntry($valuesTmp);
} elseif ((int)((int)$info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
- Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']);
}
return false;
@@ -295,7 +295,7 @@ SQL;
if ($this->autoUpdateDb($info)) {
return $this->updateEntry($valuesTmp);
}
- Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while updating entry with GUID ' . $valuesTmp['guid'] . ' in feed ' . $valuesTmp['id_feed']);
return false;
}
@@ -333,11 +333,11 @@ SQL;
$values = array($is_favorite ? 1 : 0);
$values = array_merge($values, $ids);
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markFavorite: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -350,15 +350,15 @@ SQL;
* @todo It can use the query builder refactoring to build that query
*/
protected function updateCacheUnreads(?int $catId = null, ?int $feedId = null): bool {
- $sql = 'UPDATE `_feed` f '
- . 'LEFT OUTER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(*) AS nbUnreads '
- . 'FROM `_entry` e '
- . 'WHERE e.is_read=0 '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'SET f.`cache_nbUnreads`=COALESCE(x.nbUnreads, 0)';
+ $sql = <<<'SQL'
+UPDATE `_feed` f LEFT OUTER JOIN (
+ SELECT e.id_feed, COUNT(*) AS nbUnreads
+ FROM `_entry` e
+ WHERE e.is_read = 0
+ GROUP BY e.id_feed
+) x ON x.id_feed = f.id
+SET f.`cache_nbUnreads` = COALESCE(x.nbUnreads, 0)
+SQL;
$hasWhere = false;
$values = array();
if ($feedId != null) {
@@ -374,11 +374,11 @@ SQL;
$values[] = $catId;
}
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return true;
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -423,7 +423,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markRead: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' A ' . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -438,11 +438,11 @@ SQL;
. 'WHERE e.id=? AND e.is_read=?';
$values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markRead: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' B ' . json_encode($info));
return false;
}
}
@@ -490,7 +490,7 @@ SQL;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadEntries: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -528,7 +528,7 @@ SQL;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadCat: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -567,7 +567,7 @@ SQL;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadFeed: ' . $info[2] . ' with SQL: ' . $sql . $search);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info) . ' with SQL: ' . $sql . $search);
$this->pdo->rollBack();
return false;
}
@@ -581,7 +581,7 @@ SQL;
$stm->bindParam(':id', $id_feed, PDO::PARAM_INT);
if (!($stm && $stm->execute())) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadFeed cache: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -622,7 +622,7 @@ SQL;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -684,7 +684,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($params)) {
+ if ($stm !== false && $stm->execute($params)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
@@ -718,39 +718,33 @@ SQL;
}
public function searchByGuid(int $id_feed, string $guid): ?FreshRSS_Entry {
- // un guid est unique pour un flux donné
- $sql = 'SELECT id, guid, title, author, '
- . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
- . 'FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid';
- $stm = $this->pdo->prepare($sql);
- $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT);
- $stm->bindParam(':guid', $guid);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
+ $sql = <<<SQL
+SELECT id, guid, title, author, link, date, is_read, is_favorite, id_feed, tags, attributes, {$content}
+FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid
+SQL;
+ $res = $this->fetchAssoc($sql, [':id_feed' => $id_feed, ':guid' => $guid]);
+ /** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
public function searchById(string $id): ?FreshRSS_Entry {
- $sql = 'SELECT id, guid, title, author, '
- . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
- . 'FROM `_entry` WHERE id=:id';
- $stm = $this->pdo->prepare($sql);
- $stm->bindParam(':id', $id, PDO::PARAM_INT);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
+ $sql = <<<SQL
+SELECT id, guid, title, author, link, date, is_read, is_favorite, id_feed, tags, attributes, {$content}
+FROM `_entry` WHERE id=:id
+SQL;
+ $res = $this->fetchAssoc($sql, [':id' => $id]);
+ /** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
public function searchIdByGuid(int $id_feed, string $guid): ?string {
$sql = 'SELECT id FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid';
- $stm = $this->pdo->prepare($sql);
- $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT);
- $stm->bindParam(':guid', $guid);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return isset($res[0]) ? $res[0] : null;
+ $res = $this->fetchColumn($sql, 0, [':id_feed' => $id_feed, ':guid' => $guid]);
+ return empty($res[0]) ? null : (string)($res[0]);
}
/** @return array{0:array<int|string>,1:string} */
@@ -1135,14 +1129,14 @@ SQL;
. 'ORDER BY e0.id ' . $order;
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm;
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
}
- Minz_Log::error('SQL error listWhereRaw: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -1157,6 +1151,8 @@ SQL;
$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
if ($stm) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
yield FreshRSS_Entry::fromArray($row);
}
}
@@ -1180,17 +1176,24 @@ SQL;
}
return;
}
+ if ($order !== 'DESC' && $order !== 'ASC') {
+ $order = 'DESC';
+ }
- $sql = 'SELECT id, guid, title, author, '
- . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
- . 'FROM `_entry` '
- . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) '
- . 'ORDER BY id ' . $order;
+ $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
+ $repeats = str_repeat('?,', count($ids) - 1) . '?';
+ $sql = <<<SQL
+SELECT id, guid, title, author, link, date, is_read, is_favorite, id_feed, tags, attributes, {$content}
+FROM `_entry`
+WHERE id IN ({$repeats})
+ORDER BY id {$order}
+SQL;
$stm = $this->pdo->prepare($sql);
$stm->execute($ids);
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
yield FreshRSS_Entry::fromArray($row);
}
}
@@ -1198,16 +1201,12 @@ SQL;
/**
* @phpstan-param 'a'|'A'|'s'|'S'|'c'|'f'|'t'|'T'|'ST' $type
* @param int $id category/feed/tag ID
- * @return array<numeric-string>|false
+ * @return array<numeric-string>
*/
public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
- string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null) {
+ string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null): ?array {
[$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters);
-
- $stm = $this->pdo->prepare($sql);
- $stm->execute($values);
-
- return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ return $this->fetchColumn($sql, 0, $values);
}
/**
@@ -1232,7 +1231,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
$values = array($id_feed);
$values = array_merge($values, $guids);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
$rows = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$result[$row['guid']] = $row['hex_hash'];
@@ -1243,7 +1242,7 @@ SQL;
if ($this->autoUpdateDb($info)) {
return $this->listHashForFeedGuids($id_feed, $guids);
}
- Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while querying feed ' . $id_feed);
return false;
}
@@ -1272,14 +1271,14 @@ SQL;
}
$values = array($mtime, $id_feed);
$values = array_merge($values, $guids);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->updateLastSeen($id_feed, $guids);
}
- Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while updating feed ' . $id_feed);
return false;
}
@@ -1287,32 +1286,33 @@ SQL;
/** @return array<string,int>|false */
public function countUnreadRead() {
- $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE f.priority > 0'
- . ' UNION SELECT COUNT(e.id) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE f.priority > 0 AND e.is_read=0';
- $stm = $this->pdo->query($sql);
- if ($stm === false) {
+ $sql = <<<'SQL'
+SELECT COUNT(e.id) AS count FROM `_entry` e
+ INNER JOIN `_feed` f ON e.id_feed=f.id
+ WHERE f.priority > 0
+UNION
+SELECT COUNT(e.id) AS count FROM `_entry` e
+ INNER JOIN `_feed` f ON e.id_feed=f.id
+ WHERE f.priority > 0 AND e.is_read=0
+SQL;
+ $res = $this->fetchColumn($sql, 0);
+ if ($res == null) {
return false;
}
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
rsort($res);
- $all = empty($res[0]) ? 0 : (int)$res[0];
- $unread = empty($res[1]) ? 0 : (int)$res[1];
- return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+ $all = (int)($res[0] ?? 0);
+ $unread = (int)($res[1] ?? 0);
+ return ['all' => $all, 'unread' => $unread, 'read' => $all - $unread];
}
- /** @return int|false */
- public function count(?int $minPriority = null) {
+ public function count(?int $minPriority = null): int {
$sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e';
if ($minPriority !== null) {
$sql .= ' INNER JOIN `_feed` f ON e.id_feed=f.id';
- $sql .= ' WHERE f.priority > ' . $minPriority;
- }
- $stm = $this->pdo->query($sql);
- if ($stm == false) {
- return false;
+ $sql .= ' WHERE f.priority > :priority';
}
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return isset($res[0]) ? intval($res[0]) : 0;
+ $res = $this->fetchColumn($sql, 0, [':priority' => $minPriority]);
+ return isset($res[0]) ? (int)($res[0]) : -1;
}
public function countNotRead(?int $minPriority = null): int {
@@ -1322,11 +1322,10 @@ SQL;
}
$sql .= ' WHERE e.is_read=0';
if ($minPriority !== null) {
- $sql .= ' AND f.priority > ' . $minPriority;
+ $sql .= ' AND f.priority > :priority';
}
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return isset($res[0]) ? intval($res[0]) : 0;
+ $res = $this->fetchColumn($sql, 0, [':priority' => $minPriority]);
+ return isset($res[0]) ? (int)($res[0]) : -1;
}
/** @return array<string,int>|false */
@@ -1334,33 +1333,31 @@ SQL;
$sql = <<<'SQL'
SELECT c FROM (
SELECT COUNT(e1.id) AS c, 1 AS o
- FROM `_entry` AS e1
- JOIN `_feed` AS f1 ON e1.id_feed = f1.id
+ FROM `_entry` AS e1
+ JOIN `_feed` AS f1 ON e1.id_feed = f1.id
WHERE e1.is_favorite = 1
- AND f1.priority >= :priority_normal1
+ AND f1.priority >= :priority_normal1
UNION
SELECT COUNT(e2.id) AS c, 2 AS o
- FROM `_entry` AS e2
- JOIN `_feed` AS f2 ON e2.id_feed = f2.id
+ FROM `_entry` AS e2
+ JOIN `_feed` AS f2 ON e2.id_feed = f2.id
WHERE e2.is_favorite = 1
- AND e2.is_read = 0
- AND f2.priority >= :priority_normal2
+ AND e2.is_read = 0 AND f2.priority >= :priority_normal2
) u
ORDER BY o
SQL;
- $stm = $this->pdo->prepare($sql);
- if (!$stm) {
- Minz_Log::error('SQL error in ' . __method__ . ' ' . json_encode($this->pdo->errorInfo()));
+ //Binding a value more than once is not standard and does not work with native prepared statements (e.g. MySQL) https://bugs.php.net/bug.php?id=40417
+ $res = $this->fetchColumn($sql, 0, [
+ ':priority_normal1' => FreshRSS_Feed::PRIORITY_NORMAL,
+ ':priority_normal2' => FreshRSS_Feed::PRIORITY_NORMAL,
+ ]);
+ if ($res == null) {
return false;
}
- //Binding a value more than once is not standard and does not work with native prepared statements (e.g. MySQL) https://bugs.php.net/bug.php?id=40417
- $stm->bindValue(':priority_normal1', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT);
- $stm->bindValue(':priority_normal2', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+
rsort($res);
- $all = empty($res[0]) ? 0 : intval($res[0]);
- $unread = empty($res[1]) ? 0 : intval($res[1]);
- return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+ $all = (int)($res[0] ?? 0);
+ $unread = (int)($res[1] ?? 0);
+ return ['all' => $all, 'unread' => $unread, 'read' => $all - $unread];
}
}
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index c86791372..bcd139f5f 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -49,7 +49,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
}
public function commitNewEntries(): bool {
- $sql = '
+ $sql = <<<'SQL'
DROP TABLE IF EXISTS `tmp`;
CREATE TEMP TABLE `tmp` AS
SELECT id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes
@@ -63,14 +63,14 @@ INSERT OR IGNORE INTO `_entry`
ORDER BY date, id;
DELETE FROM `_entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`);
DROP TABLE IF EXISTS `tmp`;
-';
+SQL;
$hadTransaction = $this->pdo->inTransaction();
if (!$hadTransaction) {
$this->pdo->beginTransaction();
}
$result = $this->pdo->exec($sql) !== false;
if (!$result) {
- Minz_Log::error('SQL error commitNewEntries: ' . json_encode($this->pdo->errorInfo()));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($this->pdo->errorInfo()));
}
if (!$hadTransaction) {
$this->pdo->commit();
@@ -79,10 +79,12 @@ DROP TABLE IF EXISTS `tmp`;
}
protected function updateCacheUnreads(?int $catId = null, ?int $feedId = null): bool {
- $sql = 'UPDATE `_feed` '
- . 'SET `cache_nbUnreads`=('
- . 'SELECT COUNT(*) AS nbUnreads FROM `_entry` e '
- . 'WHERE e.id_feed=`_feed`.id AND e.is_read=0)';
+ $sql = <<<'SQL'
+UPDATE `_feed`
+SET `cache_nbUnreads`=(
+ SELECT COUNT(*) AS nbUnreads FROM `_entry` e
+ WHERE e.id_feed=`_feed`.id AND e.is_read=0)
+SQL;
$hasWhere = false;
$values = array();
if ($feedId != null) {
@@ -98,11 +100,11 @@ DROP TABLE IF EXISTS `tmp`;
$values[] = $catId;
}
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return true;
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -137,7 +139,7 @@ DROP TABLE IF EXISTS `tmp`;
$stm = $this->pdo->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markRead 1: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' A ' . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -149,7 +151,7 @@ DROP TABLE IF EXISTS `tmp`;
$stm = $this->pdo->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markRead 2: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' B ' . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -201,7 +203,7 @@ DROP TABLE IF EXISTS `tmp`;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadEntries: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -240,7 +242,7 @@ DROP TABLE IF EXISTS `tmp`;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadCat: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
@@ -279,7 +281,7 @@ DROP TABLE IF EXISTS `tmp`;
$stm = $this->pdo->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markReadTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
$affected = $stm->rowCount();
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index a4d76fb62..98bcef379 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -68,14 +68,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES),
);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return (int)($this->pdo->lastInsertId('`_feed_id_seq`'));
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->addFeed($valuesTmp);
}
- Minz_Log::error('SQL error addFeed: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -171,14 +171,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
$values[] = $id;
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
return $this->updateFeed($id, $valuesTmp);
}
- Minz_Log::error('SQL error updateFeed: ' . $info[2] . ' for feed ' . $id);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info) . ' for feed ' . $id);
return false;
}
}
@@ -208,7 +208,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
);
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
@@ -239,11 +239,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
$idOldCat
);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error changeCategory: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -255,11 +255,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
$values = array($id);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error deleteFeed: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -277,11 +277,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
$values = array($id);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -301,10 +301,10 @@ SQL;
public function searchById(int $id): ?FreshRSS_Feed {
$sql = 'SELECT * FROM `_feed` WHERE id=:id';
- $stm = $this->pdo->prepare($sql);
- $stm->bindParam(':id', $id, PDO::PARAM_INT);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $res = $this->fetchAssoc($sql, [':id' => $id]);
+ if ($res == null) {
+ return null;
+ }
$feed = self::daoToFeed($res);
return $feed[$id] ?? null;
}
@@ -375,7 +375,7 @@ SQL;
if ($this->autoUpdateDb($info)) {
return $this->listFeedsOrderUpdate($defaultCacheDuration, $limit);
}
- Minz_Log::error('SQL error listFeedsOrderUpdate: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return array();
}
}
@@ -388,7 +388,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
$stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
- if ($stm && $stm->execute()) {
+ if ($stm !== false && $stm->execute()) {
return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
}
return false;
@@ -446,15 +446,15 @@ SQL;
. '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `_entry` e2 WHERE e2.id_feed=`_feed`.id AND e2.is_read=0)'
. ($id != 0 ? ' WHERE id=:id' : '');
$stm = $this->pdo->prepare($sql);
- if ($stm && $id != 0) {
+ if ($stm !== false && $id != 0) {
$stm->bindParam(':id', $id, PDO::PARAM_INT);
}
- if ($stm && $stm->execute()) {
+ if ($stm !== false && $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error updateCachedValue: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -483,7 +483,7 @@ SQL;
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error keepMaxUnread: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -514,7 +514,7 @@ SQL;
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error markAsReadUponGone: ' . json_encode($info));
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
@@ -529,7 +529,7 @@ SQL;
$this->pdo->beginTransaction();
if (!($stm && $stm->execute())) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error truncate: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -541,7 +541,7 @@ SQL;
$stm->bindParam(':id', $id, PDO::PARAM_INT);
if (!($stm && $stm->execute())) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error truncate: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -556,7 +556,7 @@ SQL;
$this->pdo->beginTransaction();
if (!($stm && $stm->execute())) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error truncate: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' A ' . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -565,7 +565,7 @@ SQL;
$stm = $this->pdo->prepare($sql);
if (!($stm && $stm->execute())) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error truncate: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' B ' . json_encode($info));
$this->pdo->rollBack();
return false;
}
@@ -575,7 +575,7 @@ SQL;
/**
* @param array<int,array<string,string|int>>|array<string,string|int> $listDAO
- * @return array<FreshRSS_Feed>
+ * @return array<int,FreshRSS_Feed>
*/
public static function daoToFeed(array $listDAO, ?int $catID = null): array {
$list = array();
@@ -626,13 +626,13 @@ SQL;
$stm = $this->pdo->prepare($sql);
if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' A ' . json_encode($info));
$sql2 = 'ALTER TABLE `_feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3
$stm = $this->pdo->query($sql2);
if ($stm === false) {
$info = $this->pdo->errorInfo();
- Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' B ' . json_encode($info));
}
} else {
$stm->execute(array(':new_value' => -3600, ':old_value' => -1));
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index a0a4ec498..fb21407a5 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -11,7 +11,7 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
/**
* Calculates entry repartition for all feeds and for main stream.
*
- * @return array{'main_stream':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int},'all_feeds':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}}
+ * @return array{'main_stream':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false,'all_feeds':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false}
*/
public function calculateEntryRepartition(): array {
return array(
@@ -28,9 +28,9 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
* - unread entries
* - favorite entries
*
- * @return array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}
+ * @return array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false
*/
- public function calculateEntryRepartitionPerFeed(?int $feed = null, bool $only_main = false): array {
+ public function calculateEntryRepartitionPerFeed(?int $feed = null, bool $only_main = false) {
$filter = '';
if ($only_main) {
$filter .= 'AND f.priority = 10';
@@ -47,10 +47,9 @@ FROM `_entry` AS e, `_feed` AS f
WHERE e.id_feed = f.id
{$filter}
SQL;
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
-
- return $res[0];
+ $res = $this->fetchAssoc($sql);
+ /** @var array<array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}>|null $res */
+ return $res[0] ?? false;
}
/**
@@ -72,14 +71,14 @@ WHERE date >= {$oldest} AND date < {$midnight}
GROUP BY day
ORDER BY day ASC
SQL;
- $stm = $this->pdo->query($sql);
- /** @var array<array{'day':int,'count':int}> */
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
-
+ $res = $this->fetchAssoc($sql);
+ if ($res == false) {
+ return [];
+ }
+ /** @var array<array{'day':int,'count':int}> $res */
foreach ($res as $value) {
- $count[(int)($value['day'])] = (int) $value['count'];
+ $count[(int)($value['day'])] = (int)($value['count']);
}
-
return $count;
}
@@ -138,9 +137,10 @@ GROUP BY period
ORDER BY period ASC
SQL;
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_NAMED);
-
+ $res = $this->fetchAssoc($sql);
+ if ($res == false) {
+ return [];
+ }
switch ($period) {
case '%H':
$periodMax = 24;
@@ -152,12 +152,12 @@ SQL;
$periodMax = 12;
break;
default:
- $periodMax = 30;
+ $periodMax = 30;
}
$repartition = array_fill(0, $periodMax, 0);
foreach ($res as $value) {
- $repartition[(int) $value['period']] = (int) $value['count'];
+ $repartition[(int)$value['period']] = (int)$value['count'];
}
return $repartition;
@@ -200,12 +200,14 @@ SELECT COUNT(1) AS count
FROM `_entry` AS e
{$restrict}
SQL;
- $stm = $this->pdo->query($sql);
- $res = $stm->fetch(PDO::FETCH_NAMED);
+ $res = $this->fetchAssoc($sql);
+ if ($res == null || empty($res[0])) {
+ return -1.0;
+ }
$date_min = new \DateTime();
- $date_min->setTimestamp($res['date_min']);
+ $date_min->setTimestamp((int)($res[0]['date_min']));
$date_max = new \DateTime();
- $date_max->setTimestamp($res['date_max']);
+ $date_max->setTimestamp((int)($res[0]['date_max']));
$interval = $date_max->diff($date_min, true);
$interval_in_days = (float)($interval->format('%a'));
if ($interval_in_days <= 0) {
@@ -214,7 +216,7 @@ SQL;
$interval_in_days = $period;
}
- return intval($res['count']) / ($interval_in_days / $period);
+ return intval($res[0]['count']) / ($interval_in_days / $period);
}
/**
@@ -240,10 +242,9 @@ WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
SQL;
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
-
- return $res;
+ $res = $this->fetchAssoc($sql);
+ /** @var array<array{'label':string,'data':int}>|null @res */
+ return $res == null ? [] : $res;
}
/**
@@ -260,10 +261,9 @@ AND f.id = e.id_feed
GROUP BY label
ORDER BY data DESC
SQL;
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
-
- return $res;
+ $res = $this->fetchAssoc($sql);
+ /** @var array<array{'label':string,'data':int}>|null $res */
+ return $res == null ? [] : $res;
}
/**
@@ -283,8 +283,9 @@ GROUP BY f.id
ORDER BY count DESC
LIMIT 10
SQL;
- $stm = $this->pdo->query($sql);
- return $stm->fetchAll(PDO::FETCH_ASSOC);
+ $res = $this->fetchAssoc($sql);
+ /** @var array<array{'id':int,'name':string,'category':string,'count':int}>|null $res */
+ return $res == null ? [] : $res;
}
/**
@@ -302,8 +303,9 @@ WHERE f.id = e.id_feed
GROUP BY f.id
ORDER BY name
SQL;
- $stm = $this->pdo->query($sql);
- return $stm->fetchAll(PDO::FETCH_ASSOC);
+ $res = $this->fetchAssoc($sql);
+ /** @var array<array{'id':int,'name':string,'last_date':int,'nb_articles':int}>|null $res */
+ return $res == null ? [] : $res;
}
/**
diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php
index 52a99d2f4..6e0e04c60 100644
--- a/app/Models/StatsDAOPGSQL.php
+++ b/app/Models/StatsDAOPGSQL.php
@@ -47,11 +47,10 @@ GROUP BY period
ORDER BY period ASC
SQL;
- $stm = $this->pdo->query($sql);
- if ($stm === false) {
+ $res = $this->fetchAssoc($sql);
+ if ($res == null) {
return [];
}
- $res = $stm->fetchAll(PDO::FETCH_NAMED);
switch ($period) {
case 'hour':
@@ -69,7 +68,7 @@ SQL;
$repartition = array_fill(0, $periodMax, 0);
foreach ($res as $value) {
- $repartition[(int) $value['period']] = (int) $value['count'];
+ $repartition[(int)$value['period']] = (int)$value['count'];
}
return $repartition;
diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php
index 9f292aae6..7d5be10c0 100644
--- a/app/Models/StatsDAOSQLite.php
+++ b/app/Models/StatsDAOSQLite.php
@@ -24,11 +24,10 @@ GROUP BY period
ORDER BY period ASC
SQL;
- $stm = $this->pdo->query($sql);
- if ($stm === false) {
+ $res = $this->fetchAssoc($sql);
+ if ($res == null) {
return [];
}
- $res = $stm->fetchAll(PDO::FETCH_NAMED);
switch ($period) {
case '%H':
@@ -46,7 +45,7 @@ SQL;
$repartition = array_fill(0, $periodMax, 0);
foreach ($res as $value) {
- $repartition[(int) $value['period']] = (int) $value['count'];
+ $repartition[(int)$value['period']] = (int)$value['count'];
}
return $repartition;
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index c0d6e3a36..de480f0ff 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -43,7 +43,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo {
}
/**
- * @param array<string,string|array<string,mixed>> $valuesTmp
+ * @param array{'id'?:int,'name':string,'attributes'?:array<string,mixed>} $valuesTmp
* @return int|false
*/
public function addTag(array $valuesTmp) {
@@ -66,16 +66,17 @@ SQL;
$valuesTmp['name'],
);
- if ($stm && $stm->execute($values) && $stm->rowCount() > 0) {
+ if ($stm !== false && $stm->execute($values) && $stm->rowCount() > 0) {
return (int)($this->pdo->lastInsertId('`_tag_id_seq`'));
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error addTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
- public function addTagObject(FreshRSS_Tag $tag): int {
+ /** @return int|false */
+ public function addTagObject(FreshRSS_Tag $tag) {
$tag0 = $this->searchByName($tag->name());
if (!$tag0) {
$values = array(
@@ -87,49 +88,53 @@ SQL;
return $tag->id();
}
- /**
- * @param array<string,string|int|array<string,mixed>> $valuesTmp
- * @return int|false
- */
- public function updateTag(int $id, array $valuesTmp) {
+ /** @return int|false */
+ public function updateTagName(int $id, string $name) {
// No category of the same name
$sql = <<<'SQL'
-UPDATE `_tag` SET name=?, attributes=? WHERE id=?
+UPDATE `_tag` SET name=? WHERE id=?
AND NOT EXISTS (SELECT 1 FROM `_category` WHERE name = ?)
SQL;
+ $name = mb_strcut(trim($name), 0, 63, 'UTF-8');
$stm = $this->pdo->prepare($sql);
-
- $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8');
- if (!isset($valuesTmp['attributes'])) {
- $valuesTmp['attributes'] = [];
- }
- $values = array(
- $valuesTmp['name'],
- is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES),
- $id,
- $valuesTmp['name'],
- );
-
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false &&
+ $stm->bindValue(':id', $id, PDO::PARAM_INT) &&
+ $stm->bindValue(':name', $name, PDO::PARAM_STR) &&
+ $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error updateTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
/**
+ * @param array<string,mixed> $attributes
+ * @return int|false
+ */
+ public function updateTagAttributes(int $id, array $attributes) {
+ $sql = 'UPDATE `_tag` SET attributes=:attributes WHERE id=:id';
+ $stm = $this->pdo->prepare($sql);
+ if ($stm !== false &&
+ $stm->bindValue(':id', $id, PDO::PARAM_INT) &&
+ $stm->bindValue(':attributes', json_encode($attributes, JSON_UNESCAPED_SLASHES), PDO::PARAM_STR) &&
+ $stm->execute()) {
+ return $stm->rowCount();
+ }
+ $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
+ }
+
+ /**
* @param mixed $value
* @return int|false
*/
public function updateTagAttribute(FreshRSS_Tag $tag, string $key, $value) {
$tag->_attributes($key, $value);
- return $this->updateTag(
- $tag->id(),
- [ 'attributes' => $tag->attributes() ]
- );
+ return $this->updateTagAttributes($tag->id(), $tag->attributes());
}
/**
@@ -144,19 +149,23 @@ SQL;
$values = array($id);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error deleteTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
- /** @return Traversable<array{'id':int,'name':string,'attributes':string}> */
+ /** @return Traversable<array{'id':int,'name':string,'attributes'?:array<string,mixed>}> */
public function selectAll(): Traversable {
$sql = 'SELECT id, name, attributes FROM `_tag`';
$stm = $this->pdo->query($sql);
+ if ($stm === false) {
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($this->pdo->errorInfo()));
+ return;
+ }
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
yield $row;
}
@@ -166,6 +175,10 @@ SQL;
public function selectEntryTag(): Traversable {
$sql = 'SELECT id_tag, id_entry FROM `_entrytag`';
$stm = $this->pdo->query($sql);
+ if ($stm === false) {
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($this->pdo->errorInfo()));
+ return;
+ }
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
yield $row;
}
@@ -180,53 +193,44 @@ DELETE FROM `_entrytag` WHERE EXISTS (
SQL;
$stm = $this->pdo->prepare($sql);
- if (!($stm && $stm->execute([$newTagId, $oldTagId]))) {
+ if ($stm === false || !$stm->execute([$newTagId, $oldTagId])) {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error moveTag: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . ' A ' . json_encode($info));
return false;
}
$sql = 'UPDATE `_entrytag` SET id_tag = ? WHERE id_tag = ?';
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute([$newTagId, $oldTagId])) {
+ if ($stm !== false && $stm->execute([$newTagId, $oldTagId])) {
return $stm->rowCount();
- } else {
- $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error moveTag: ' . $info[2]);
- return false;
}
+ $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . ' B ' . json_encode($info));
+ return false;
}
public function searchById(int $id): ?FreshRSS_Tag {
- $sql = 'SELECT * FROM `_tag` WHERE id=?';
- $stm = $this->pdo->prepare($sql);
- $values = array($id);
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- $tag = self::daoToTag($res);
- return isset($tag[0]) ? $tag[0] : null;
+ $res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE id=:id', [':id' => $id]);
+ return $res === null ? null : self::daoToTag($res)[0] ?? null;
}
public function searchByName(string $name): ?FreshRSS_Tag {
- $sql = 'SELECT * FROM `_tag` WHERE name=?';
- $stm = $this->pdo->prepare($sql);
- $values = array($name);
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- $tag = self::daoToTag($res);
- return isset($tag[0]) ? $tag[0] : null;
+ $res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE name=:name', [':name' => $name]);
+ return $res === null ? null : self::daoToTag($res)[0] ?? null;
}
/** @return array<FreshRSS_Tag>|false */
public function listTags(bool $precounts = false) {
if ($precounts) {
- $sql = 'SELECT t.id, t.name, count(e.id) AS unreads '
- . 'FROM `_tag` t '
- . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id '
- . 'LEFT OUTER JOIN `_entry` e ON et.id_entry = e.id AND e.is_read = 0 '
- . 'GROUP BY t.id '
- . 'ORDER BY t.name';
+ $sql = <<<'SQL'
+SELECT t.id, t.name, count(e.id) AS unreads
+FROM `_tag` t
+LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id
+LEFT OUTER JOIN `_entry` e ON et.id_entry = e.id AND e.is_read = 0
+GROUP BY t.id
+ORDER BY t.name
+SQL;
} else {
$sql = 'SELECT * FROM `_tag` ORDER BY name';
}
@@ -239,27 +243,31 @@ SQL;
if ($this->autoUpdateDb($info)) {
return $this->listTags($precounts);
}
- Minz_Log::error('SQL error listTags: ' . $info[2]);
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false;
}
}
/** @return array<string,string> */
public function listTagsNewestItemUsec(?int $id_tag = null): array {
- $sql = 'SELECT t.id AS id_tag, MAX(e.id) AS newest_item_us '
- . 'FROM `_tag` t '
- . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id '
- . 'LEFT OUTER JOIN `_entry` e ON et.id_entry = e.id ';
+ $sql = <<<'SQL'
+SELECT t.id AS id_tag, MAX(e.id) AS newest_item_us
+FROM `_tag` t
+LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id
+LEFT OUTER JOIN `_entry` e ON et.id_entry = e.id
+SQL;
if ($id_tag === null) {
- $sql .= 'GROUP BY t.id';
+ $sql .= ' GROUP BY t.id';
} else {
- $sql .= 'WHERE t.id=' . intval($id_tag);
+ $sql .= ' WHERE t.id=' . intval($id_tag);
+ }
+ $res = $this->fetchAssoc($sql);
+ if ($res == null) {
+ return [];
}
- $stm = $this->pdo->query($sql);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
$newestItemUsec = [];
foreach ($res as $line) {
- $newestItemUsec['t_' . $line['id_tag']] = $line['newest_item_us'];
+ $newestItemUsec['t_' . $line['id_tag']] = (string)($line['newest_item_us']);
}
return $newestItemUsec;
}
@@ -271,56 +279,47 @@ SQL;
if ($stm !== false) {
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return (int)$res[0]['count'];
- } else {
- $info = $this->pdo->errorInfo();
- if ($this->autoUpdateDb($info)) {
- return $this->count();
- }
- Minz_Log::error('SQL error TagDAO::count: ' . $info[2]);
- return false;
}
+ $info = $this->pdo->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->count();
+ }
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
}
/**
* @return int|false
*/
public function countEntries(int $id) {
- $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` WHERE id_tag=?';
- $values = array($id);
- if (($stm = $this->pdo->prepare($sql)) !== false &&
- $stm->execute($values) &&
- ($res = $stm->fetchAll(PDO::FETCH_ASSOC)) !== false) {
- return (int)$res[0]['count'];
- } else {
- $info = is_object($stm) ? $stm->errorInfo() : $this->pdo->errorInfo();
- Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` WHERE id_tag=:id_tag';
+ $res = $this->fetchAssoc($sql, [':id_tag' => $id]);
+ if ($res == null || !isset($res[0]['count'])) {
return false;
}
+ return (int)$res[0]['count'];
}
/**
* @return int|false
*/
public function countNotRead(?int $id = null) {
- $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` et '
- . 'INNER JOIN `_entry` e ON et.id_entry=e.id '
- . 'WHERE e.is_read=0';
- $values = null;
-
+ $sql = <<<'SQL'
+SELECT COUNT(*) AS count FROM `_entrytag` et
+INNER JOIN `_entry` e ON et.id_entry=e.id
+WHERE e.is_read=0
+SQL;
+ $values = [];
if (null !== $id) {
- $sql .= ' AND et.id_tag=?';
- $values = [$id];
+ $sql .= ' AND et.id_tag=:id_tag';
+ $values[':id_tag'] = $id;
}
- if (($stm = $this->pdo->prepare($sql)) !== false &&
- $stm->execute($values) &&
- ($res = $stm->fetchAll(PDO::FETCH_ASSOC)) !== false) {
- return (int)$res[0]['count'];
- } else {
- $info = is_object($stm) ? $stm->errorInfo() : $this->pdo->errorInfo();
- Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ $res = $this->fetchAssoc($sql, $values);
+ if ($res == null || !isset($res[0]['count'])) {
return false;
}
+ return (int)$res[0]['count'];
}
public function tagEntry(int $id_tag, string $id_entry, bool $checked = true): bool {
@@ -332,42 +331,42 @@ SQL;
$stm = $this->pdo->prepare($sql);
$values = array($id_tag, $id_entry);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return true;
- } else {
- $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- Minz_Log::error('SQL error tagEntry: ' . $info[2]);
- return false;
}
+ $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
}
/**
* @return array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}>|false
*/
public function getTagsForEntry(string $id_entry) {
- $sql = 'SELECT t.id, t.name, et.id_entry IS NOT NULL as checked '
- . 'FROM `_tag` t '
- . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id AND et.id_entry=? '
- . 'ORDER BY t.name';
+ $sql = <<<'SQL'
+SELECT t.id, t.name, et.id_entry IS NOT NULL as checked
+FROM `_tag` t
+LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id AND et.id_entry=?
+ORDER BY t.name
+SQL;
$stm = $this->pdo->prepare($sql);
$values = array($id_entry);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
$lines = $stm->fetchAll(PDO::FETCH_ASSOC);
for ($i = count($lines) - 1; $i >= 0; $i--) {
$lines[$i]['id'] = intval($lines[$i]['id']);
$lines[$i]['checked'] = !empty($lines[$i]['checked']);
}
return $lines;
- } else {
- $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- if ($this->autoUpdateDb($info)) {
- return $this->getTagsForEntry($id_entry);
- }
- Minz_Log::error('SQL error getTagsForEntry: ' . $info[2]);
- return false;
}
+ $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->getTagsForEntry($id_entry);
+ }
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
}
/**
@@ -375,9 +374,11 @@ SQL;
* @return array<array{'id_entry':string,'id_tag':int,'name':string}>|false
*/
public function getTagsForEntries(array $entries) {
- $sql = 'SELECT et.id_entry, et.id_tag, t.name '
- . 'FROM `_tag` t '
- . 'INNER JOIN `_entrytag` et ON et.id_tag = t.id';
+ $sql = <<<'SQL'
+SELECT et.id_entry, et.id_tag, t.name
+FROM `_tag` t
+INNER JOIN `_entrytag` et ON et.id_tag = t.id
+SQL;
$values = array();
if (is_array($entries) && count($entries) > 0) {
@@ -392,9 +393,12 @@ SQL;
$sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1). '?)';
if (is_array($entries[0])) {
foreach ($entries as $entry) {
- $values[] = $entry['id'];
+ if (!empty($entry['id'])) {
+ $values[] = $entry['id'];
+ }
}
} elseif (is_object($entries[0])) {
+ /** @var array<FreshRSS_Entry> $entries */
foreach ($entries as $entry) {
$values[] = $entry->id();
}
@@ -406,16 +410,15 @@ SQL;
}
$stm = $this->pdo->prepare($sql);
- if ($stm && $stm->execute($values)) {
+ if ($stm !== false && $stm->execute($values)) {
return $stm->fetchAll(PDO::FETCH_ASSOC);
- } else {
- $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
- if ($this->autoUpdateDb($info)) {
- return $this->getTagsForEntries($entries);
- }
- Minz_Log::error('SQL error getTagsForEntries: ' . $info[2]);
- return false;
}
+ $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->getTagsForEntries($entries);
+ }
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
}
/**
@@ -425,7 +428,7 @@ SQL;
*/
public function getEntryIdsTagNames(array $entries): array {
$result = array();
- foreach ($this->getTagsForEntries($entries) as $line) {
+ foreach ($this->getTagsForEntries($entries) ?: [] as $line) {
$entryId = 'e_' . $line['id_entry'];
$tagName = $line['name'];
if (empty($result[$entryId])) {
@@ -437,18 +440,16 @@ SQL;
}
/**
- * @param array<array<string,string|int>>|array<string,string|int> $listDAO
+ * @param iterable<array<string,int|string|null>> $listDAO
* @return array<FreshRSS_Tag>
*/
- private static function daoToTag(array $listDAO): array {
- $list = array();
- if (!is_array($listDAO)) {
- $listDAO = array($listDAO);
- }
+ private static function daoToTag(iterable $listDAO): array {
+ $list = [];
foreach ($listDAO as $dao) {
- $tag = new FreshRSS_Tag(
- $dao['name']
- );
+ if (empty($dao['id']) || !is_int($dao['id']) || empty($dao['name']) || !is_string($dao['name'])) {
+ continue;
+ }
+ $tag = new FreshRSS_Tag($dao['name']);
$tag->_id($dao['id']);
if (!empty($dao['attributes'])) {
$tag->_attributes('', $dao['attributes']);
diff --git a/app/Models/View.php b/app/Models/View.php
index c5415c58e..3c56afa0e 100644
--- a/app/Models/View.php
+++ b/app/Models/View.php
@@ -201,15 +201,17 @@ class FreshRSS_View extends Minz_View {
public $last30DaysLabels;
/** @var array<string,string> */
public $months;
- /** @var array<string,array<string,int>>|array<string,int> */
+ /** @var array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false */
public $repartition;
+ /** @var array{'main_stream':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false,'all_feeds':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false} */
+ public $repartitions;
/** @var array<int,int> */
public $repartitionDayOfWeek;
/** @var array<string,int>|array<int,int> */
public $repartitionHour;
/** @var array<int,int> */
public $repartitionMonth;
- /** @var array<array<string,int|string>> */
+ /** @var array<array{'id':int,'name':string,'category':string,'count':int}> */
public $topFeed;
}