diff options
| author | 2023-04-28 14:01:11 +0200 | |
|---|---|---|
| committer | 2023-04-28 14:01:11 +0200 | |
| commit | c72914bba2363e436574204b3d6093a6f3cfce89 (patch) | |
| tree | 377008a7393e4d80e4c8659f27dd42c0ccbab382 /app/Models | |
| parent | 26e2a703125ffe1d0d2746b0e5ea3491b627832c (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.php | 56 | ||||
| -rw-r--r-- | app/Models/DatabaseDAO.php | 59 | ||||
| -rw-r--r-- | app/Models/DatabaseDAOPGSQL.php | 54 | ||||
| -rw-r--r-- | app/Models/DatabaseDAOSQLite.php | 2 | ||||
| -rw-r--r-- | app/Models/Entry.php | 39 | ||||
| -rw-r--r-- | app/Models/EntryDAO.php | 209 | ||||
| -rw-r--r-- | app/Models/EntryDAOSQLite.php | 30 | ||||
| -rw-r--r-- | app/Models/FeedDAO.php | 58 | ||||
| -rw-r--r-- | app/Models/StatsDAO.php | 72 | ||||
| -rw-r--r-- | app/Models/StatsDAOPGSQL.php | 7 | ||||
| -rw-r--r-- | app/Models/StatsDAOSQLite.php | 7 | ||||
| -rw-r--r-- | app/Models/TagDAO.php | 275 | ||||
| -rw-r--r-- | app/Models/View.php | 6 |
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; } |
