From 7b4cd3abae88bf1b8a04642489b68a741e4d2c6a Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 11 Sep 2019 18:40:17 +0200 Subject: add: Allow admins to configure default feeds (#2515) * Homogenize SQL_INSERT_FEEDS across database types * Extract default feeds into system configuration * Adapt default feeds init during installation --- app/Models/UserDAO.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'app/Models/UserDAO.php') diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index e9d3a7329..0cf163bae 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -30,19 +30,17 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { } } if ($ok && $insertDefaultFeeds) { - if (defined('SQL_INSERT_FEEDS')) { //E.g. MySQL - $sql = sprintf(SQL_INSERT_FEEDS, $bd_prefix_user); + $default_feeds = FreshRSS_Context::$system_conf->default_feeds; + foreach ($default_feeds as $feed) { + $sql = sprintf(SQL_INSERT_FEED, $bd_prefix_user); $stm = $userPDO->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } else { //E.g. SQLite - global $SQL_INSERT_FEEDS; - if (is_array($SQL_INSERT_FEEDS)) { - foreach ($SQL_INSERT_FEEDS as $instruction) { - $sql = sprintf($instruction, $bd_prefix_user); - $stm = $userPDO->bd->prepare($sql); - $ok &= ($stm && $stm->execute()); - } - } + $parameters = array( + ':url' => $feed['url'], + ':name' => $feed['name'], + ':website' => $feed['website'], + ':description' => $feed['description'], + ); + $ok &= ($stm && $stm->execute($parameters)); } } } catch (Exception $e) { -- cgit v1.2.3 From c76a318193cda63064625b2d92c719b7150d7d64 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Sep 2019 21:36:53 +0200 Subject: CLI to export/import any database to/from SQLite (#2496) * CLI to export/import any database to/from SQLite Require PHP 5.5+ https://github.com/FreshRSS/FreshRSS/pull/2495 * Travis * Execution rights * Fix wrong static fields * Fix MySQL bad default buffering https://stackoverflow.com/questions/6895098/pdo-mysql-memory-consumption-with-large-result-set/6935271#6935271 https://php.net/manual/ref.pdo-mysql * Fix count on progression * Avoid static DB information To ease working with two DBs at the same time * Less static, simplify Needs some testing * Small corrections * Special case for SQLite to SQLite * Modify special case for SQLite * Remove special case for SQLite More uniform logic for the 3 databases. Fix wrong DROP TABLE for SQLite. * Drop indexes * Revert "Drop indexes" This reverts commit f28d2bae0935745c1c74ea38f2ee083f3fd4bf9d. * Fix deletion * Fix classic export * Update cli/README.md Co-Authored-By: Marien Fressinaud * Addressing part of review * Remove goto :cry: * Travis * Comment for SQLite case * Fix missing fields when inserting --- app/Controllers/importExportController.php | 2 - app/Controllers/userController.php | 11 +- app/Models/CategoryDAO.php | 14 ++- app/Models/DatabaseDAO.php | 170 ++++++++++++++++++++++++++++- app/Models/EntryDAO.php | 32 ++++-- app/Models/EntryDAOPGSQL.php | 4 + app/Models/EntryDAOSQLite.php | 8 ++ app/Models/Factory.php | 4 + app/Models/FeedDAO.php | 22 +++- app/Models/TagDAO.php | 21 +++- app/Models/UserDAO.php | 71 +++++++----- app/SQL/install.sql.sqlite.php | 12 +- cli/README.md | 32 ++++-- cli/export-sqlite-for-user.php | 28 +++++ cli/import-sqlite-for-user.php | 34 ++++++ lib/Minz/ModelPdo.php | 39 ++++--- 16 files changed, 421 insertions(+), 83 deletions(-) create mode 100755 cli/export-sqlite-for-user.php create mode 100755 cli/import-sqlite-for-user.php (limited to 'app/Models/UserDAO.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 51fec75eb..93897dde5 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -709,8 +709,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->entryDAO = FreshRSS_Factory::createEntryDao($username); $this->feedDAO = FreshRSS_Factory::createFeedDao($username); - $this->entryDAO->disableBuffering(); - if ($export_feeds === true) { //All feeds $export_feeds = $this->feedDAO->listFeedsIds(); diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index a1d649c0a..96e4fec8c 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -237,8 +237,8 @@ class FreshRSS_user_Controller extends Minz_ActionController { $ok &= (file_put_contents($configPath, "createUser($new_user_name, $userConfig['language'], $insertDefaultFeeds); + $newUserDAO = FreshRSS_Factory::createUserDao($new_user_name); + $ok &= $newUserDAO->createUser($userConfig['language'], $insertDefaultFeeds); $ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain); } return $ok; @@ -316,9 +316,6 @@ class FreshRSS_user_Controller extends Minz_ActionController { } public static function deleteUser($username) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - $ok = self::checkUsername($username); if ($ok) { $default_user = FreshRSS_Context::$system_conf->default_user; @@ -328,8 +325,8 @@ class FreshRSS_user_Controller extends Minz_ActionController { $ok &= is_dir($user_data); if ($ok) { self::deleteFeverKey($username); - $userDAO = new FreshRSS_UserDAO(); - $ok &= $userDAO->deleteUser($username); + $oldUserDAO = FreshRSS_Factory::createUserDao($username); + $ok &= $oldUserDAO->deleteUser(); $ok &= recursive_unlink($user_data); array_map('unlink', glob(PSHB_PATH . '/feeds/*/' . $username . '.txt')); } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 6535adae7..b0fcb5033 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -77,6 +77,15 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } } + public function selectAll() { + $sql = 'SELECT id, name FROM `' . $this->prefix . 'category`'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchById($id) { $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; $stm = $this->bd->prepare($sql); @@ -96,6 +105,9 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable public function searchByName($name) { $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; $stm = $this->bd->prepare($sql); + if ($stm == false) { + return false; + } $values = array($name); @@ -156,7 +168,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $cat->_id(self::DEFAULTCATEGORYID); $sql = 'INSERT INTO `' . $this->prefix . 'category`(id, name) VALUES(?, ?)'; - if (parent::$sharedDbType === 'pgsql') { + if ($this->bd->dbType() === 'pgsql') { //Force call to nextval() $sql .= ' RETURNING nextval(\'"' . $this->prefix . 'category_id_seq"\');'; } diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php index b331eccc3..ec84da664 100644 --- a/app/Models/DatabaseDAO.php +++ b/app/Models/DatabaseDAO.php @@ -144,8 +144,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function ensureCaseInsensitiveGuids() { $ok = true; - $db = FreshRSS_Context::$system_conf->db; - if ($db['type'] === 'mysql') { + if ($this->bd->dbType() === 'mysql') { include_once(APP_PATH . '/SQL/install.sql.mysql.php'); if (defined('SQL_UPDATE_GUID_LATIN1_BIN')) { //FreshRSS 1.12 try { @@ -154,7 +153,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $ok = $stm->execute(); } catch (Exception $e) { $ok = false; - Minz_Log::error('FreshRSS_DatabaseDAO::ensureCaseInsensitiveGuids error: ' . $e->getMessage()); + Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage()); } } } @@ -164,4 +163,169 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function minorDbMaintenance() { $this->ensureCaseInsensitiveGuids(); } + + private static function stdError($error) { + if (defined('STDERR')) { + fwrite(STDERR, $error . "\n"); + } + Minz_Log::error($error); + return false; + } + + const SQLITE_EXPORT = 1; + const SQLITE_IMPORT = 2; + + public function dbCopy($filename, $mode, $clearFirst = false) { + $error = ''; + + $userDAO = FreshRSS_Factory::createUserDao(); + $catDAO = FreshRSS_Factory::createCategoryDao(); + $feedDAO = FreshRSS_Factory::createFeedDao(); + $entryDAO = FreshRSS_Factory::createEntryDao(); + $tagDAO = FreshRSS_Factory::createTagDao(); + + switch ($mode) { + case self::SQLITE_EXPORT: + if (@filesize($filename) > 0) { + $error = 'Error: SQLite export file already exists: ' . $filename; + } + break; + case self::SQLITE_IMPORT: + if (!is_readable($filename)) { + $error = 'Error: SQLite import file is not readable: ' . $filename; + } elseif ($clearFirst) { + $userDAO->deleteUser(); + if ($this->bd->dbType() === 'sqlite') { + //We cannot just delete the .sqlite file otherwise PDO gets buggy. + //SQLite is the only one with database-level optimization, instead of at table level. + $this->optimize(); + } + } else { + $nbEntries = $entryDAO->countUnreadRead(); + if (!empty($nbEntries['all'])) { + $error = 'Error: Destination database already contains some entries!'; + } + } + break; + default: + $error = 'Invalid copy mode!'; + break; + } + if ($error != '') { + return self::stdError($error); + } + + $sqlite = null; + + try { + $sqlite = new MinzPDOSQLite('sqlite:' . $filename); + $sqlite->exec('PRAGMA foreign_keys = ON;'); + } catch (Exception $e) { + $error = 'Error while initialising SQLite copy: ' . $e->getMessage(); + return self::stdError($error); + } + + Minz_ModelPdo::clean(); + $userDAOSQLite = new FreshRSS_UserDAO('', '', $sqlite); + $categoryDAOSQLite = new FreshRSS_CategoryDAO('', '', $sqlite); + $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', '', $sqlite); + $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', '', $sqlite); + $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', '', $sqlite); + + switch ($mode) { + case self::SQLITE_EXPORT: + $userFrom = $userDAO; $userTo = $userDAOSQLite; + $catFrom = $catDAO; $catTo = $categoryDAOSQLite; + $feedFrom = $feedDAO; $feedTo = $feedDAOSQLite; + $entryFrom = $entryDAO; $entryTo = $entryDAOSQLite; + $tagFrom = $tagDAO; $tagTo = $tagDAOSQLite; + break; + case self::SQLITE_IMPORT: + $userFrom = $userDAOSQLite; $userTo = $userDAO; + $catFrom = $categoryDAOSQLite; $catTo = $catDAO; + $feedFrom = $feedDAOSQLite; $feedTo = $feedDAO; + $entryFrom = $entryDAOSQLite; $entryTo = $entryDAO; + $tagFrom = $tagDAOSQLite; $tagTo = $tagDAO; + break; + } + + $idMaps = []; + + if (defined('STDERR')) { + fwrite(STDERR, "Start SQL copy…\n"); + } + + $userTo->createUser(); + + $catTo->beginTransaction(); + foreach ($catFrom->selectAll() as $category) { + $cat = $catTo->searchByName($category['name']); //Useful for the default category + if ($cat != null) { + $catId = $cat->id(); + } else { + $catId = $catTo->addCategory($category); + if ($catId == false) { + $error = 'Error during SQLite copy of categories!'; + return self::stdError($error); + } + } + $idMaps['c' . $category['id']] = $catId; + } + foreach ($feedFrom->selectAll() as $feed) { + $feed['category'] = empty($idMaps['c' . $feed['category']]) ? FreshRSS_CategoryDAO::DEFAULTCATEGORYID : $idMaps['c' . $feed['category']]; + $feedId = $feedTo->addFeed($feed); + if ($feedId == false) { + $error = 'Error during SQLite copy of feeds!'; + return self::stdError($error); + } + $idMaps['f' . $feed['id']] = $feedId; + } + $catTo->commit(); + + $nbEntries = $entryFrom->count(); + $n = 0; + $entryTo->beginTransaction(); + foreach ($entryFrom->selectAll() as $entry) { + $n++; + if (!empty($idMaps['f' . $entry['id_feed']])) { + $entry['id_feed'] = $idMaps['f' . $entry['id_feed']]; + if (!$entryTo->addEntry($entry, false)) { + $error = 'Error during SQLite copy of entries!'; + return self::stdError($error); + } + } + if ($n % 100 === 1 && defined('STDERR')) { //Display progression + fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries); + } + } + if (defined('STDERR')) { + fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries . "\n"); + } + $entryTo->commit(); + $feedTo->updateCachedValues(); + + $idMaps = []; + + $tagTo->beginTransaction(); + foreach ($tagFrom->selectAll() as $tag) { + $tagId = $tagTo->addTag($tag); + if ($tagId == false) { + $error = 'Error during SQLite copy of tags!'; + return self::stdError($error); + } + $idMaps['t' . $tag['id']] = $tagId; + } + foreach ($tagFrom->selectEntryTag() as $entryTag) { + if (!empty($idMaps['t' . $entryTag['id_tag']])) { + $entryTag['id_tag'] = $idMaps['t' . $entryTag['id_tag']]; + if (!$tagTo->tagEntry($entryTag['id_tag'], $entryTag['id_entry'])) { + $error = 'Error during SQLite copy of entry-tags!'; + return self::stdError($error); + } + } + } + $tagTo->commit(); + + return true; + } } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 1b2786a6a..b13c83d67 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -3,11 +3,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function isCompressed() { - return parent::$sharedDbType === 'mysql'; + return true; } public function hasNativeHex() { - return parent::$sharedDbType !== 'sqlite'; + return true; } public function sqlHexDecode($x) { @@ -64,7 +64,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } $this->triedUpdateToUtf8mb4 = true; $db = FreshRSS_Context::$system_conf->db; - if ($db['type'] === 'mysql') { + if ($this->bd->dbType() === 'mysql') { include_once(APP_PATH . '/SQL/install.sql.mysql.php'); if (defined('SQL_UPDATE_UTF8MB4')) { Minz_Log::warning('Updating MySQL to UTF8MB4...'); //v1.5.0 @@ -98,8 +98,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $this->bd->commit(); } try { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); Minz_Log::warning('SQL CREATE TABLE entrytmp...'); if (defined('SQL_CREATE_TABLE_ENTRYTMP')) { $sql = sprintf(SQL_CREATE_TABLE_ENTRYTMP, $this->prefix); @@ -152,9 +151,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { private $addEntryPrepared = null; - public function addEntry($valuesTmp) { + public function addEntry($valuesTmp, $useTmpTable = true) { if ($this->addEntryPrepared == null) { - $sql = 'INSERT INTO `' . $this->prefix . 'entrytmp` (id, guid, title, author, ' + $sql = 'INSERT INTO `' . $this->prefix . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' . 'VALUES(:id, :guid, :title, :author, ' @@ -178,7 +177,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $valuesTmp['link'] = safe_ascii($valuesTmp['link']); $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']); $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT); - $valuesTmp['lastSeen'] = time(); + if (empty($valuesTmp['lastSeen'])) { + $valuesTmp['lastSeen'] = time(); + } $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT); $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0; $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT); @@ -637,6 +638,18 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } + public function selectAll() { + $sql = 'SELECT id, guid, title, author, ' + . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . ', link, date, `lastSeen`, ' . $this->sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags ' + . 'FROM `' . $this->prefix . 'entry`'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchByGuid($id_feed, $guid) { // un guid est unique pour un flux donné $sql = 'SELECT id, guid, title, author, ' @@ -991,6 +1004,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0' . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0 AND e.is_read=0'; $stm = $this->bd->prepare($sql); + if ($stm == false) { + return false; + } $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); rsort($res); diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php index e571e457f..e90aa8332 100644 --- a/app/Models/EntryDAOPGSQL.php +++ b/app/Models/EntryDAOPGSQL.php @@ -2,6 +2,10 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite { + public function hasNativeHex() { + return true; + } + public function sqlHexDecode($x) { return 'decode(' . $x . ", 'hex')"; } diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index f8cd14fe6..f53685e35 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,6 +2,14 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + public function isCompressed() { + return false; + } + + public function hasNativeHex() { + return false; + } + public function sqlHexDecode($x) { return $x; } diff --git a/app/Models/Factory.php b/app/Models/Factory.php index 1accb491c..6f2ca2217 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -2,6 +2,10 @@ class FreshRSS_Factory { + public static function createUserDao($username = null) { + return new FreshRSS_UserDAO($username); + } + public static function createCategoryDao($username = null) { return new FreshRSS_CategoryDAO($username); } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index c9c9f6301..1dad4a834 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -39,6 +39,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { description, `lastUpdate`, priority, + `pathEntries`, `httpAuth`, error, keep_history, @@ -46,11 +47,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { attributes ) VALUES - (?, ?, ?, ?, ?, ?, 10, ?, 0, ?, ?, ?)'; + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; $stm = $this->bd->prepare($sql); $valuesTmp['url'] = safe_ascii($valuesTmp['url']); $valuesTmp['website'] = safe_ascii($valuesTmp['website']); + if (!isset($valuesTmp['pathEntries'])) { + $valuesTmp['pathEntries'] = ''; + } $values = array( substr($valuesTmp['url'], 0, 511), @@ -59,8 +63,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { substr($valuesTmp['website'], 0, 255), mb_strcut($valuesTmp['description'], 0, 1023, 'UTF-8'), $valuesTmp['lastUpdate'], + isset($valuesTmp['priority']) ? intval($valuesTmp['priority']) : FreshRSS_Feed::PRIORITY_MAIN_STREAM, + mb_strcut($valuesTmp['pathEntries'], 0, 511, 'UTF-8'), base64_encode($valuesTmp['httpAuth']), - FreshRSS_Feed::KEEP_HISTORY_DEFAULT, + isset($valuesTmp['error']) ? intval($valuesTmp['error']) : 0, + isset($valuesTmp['keep_history']) ? intval($valuesTmp['keep_history']) : FreshRSS_Feed::KEEP_HISTORY_DEFAULT, isset($valuesTmp['ttl']) ? intval($valuesTmp['ttl']) : FreshRSS_Feed::TTL_DEFAULT, isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', ); @@ -238,6 +245,17 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } + public function selectAll() { + $sql = 'SELECT id, url, category, name, website, description, `lastUpdate`, priority, ' + . '`pathEntries`, `httpAuth`, error, keep_history, ttl, attributes ' + . 'FROM `' . $this->prefix . 'feed`'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchById($id) { $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?'; $stm = $this->bd->prepare($sql); diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 297d24c96..11807fc32 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -13,8 +13,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $this->bd->commit(); } try { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); Minz_Log::warning('SQL ALTER GUID case sensitivity...'); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); @@ -139,6 +138,24 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } + public function selectAll() { + $sql = 'SELECT id, name, attributes FROM `' . $this->prefix . 'tag`'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + + public function selectEntryTag() { + $sql = 'SELECT id_tag, id_entry FROM `' . $this->prefix . 'entrytag`'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } + public function searchById($id) { $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE id=?'; $stm = $this->bd->prepare($sql); diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 0cf163bae..6292cc09f 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -1,21 +1,19 @@ db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - - $userPDO = new Minz_ModelPdo($username); + public function createUser($new_user_language = null, $insertDefaultFeeds = false) { + require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); $currentLanguage = Minz_Translate::language(); try { - Minz_Translate::reset($new_user_language); + if ($new_user_language != null) { + Minz_Translate::reset($new_user_language); + } $ok = false; - $bd_prefix_user = $db['prefix'] . $username . '_'; if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL - $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, $bd_prefix_user, _t('gen.short.default_category')); - $stm = $userPDO->bd->prepare($sql); + $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, $this->prefix, _t('gen.short.default_category')); + $stm = $this->bd->prepare($sql); $ok = $stm && $stm->execute(); } else { //E.g. SQLite global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS; @@ -23,8 +21,8 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS); $ok = !empty($instructions); foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $bd_prefix_user, _t('gen.short.default_category')); - $stm = $userPDO->bd->prepare($sql); + $sql = sprintf($instruction, $this->prefix, _t('gen.short.default_category')); + $stm = $this->bd->prepare($sql); $ok &= ($stm && $stm->execute()); } } @@ -32,8 +30,8 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { if ($ok && $insertDefaultFeeds) { $default_feeds = FreshRSS_Context::$system_conf->default_feeds; foreach ($default_feeds as $feed) { - $sql = sprintf(SQL_INSERT_FEED, $bd_prefix_user); - $stm = $userPDO->bd->prepare($sql); + $sql = sprintf(SQL_INSERT_FEED, $this->prefix); + $stm = $this->bd->prepare($sql); $parameters = array( ':url' => $feed['url'], ':name' => $feed['name'], @@ -44,7 +42,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { } } } catch (Exception $e) { - Minz_Log::error('Error while creating user: ' . $e->getMessage()); + Minz_Log::error('Error while creating database for user: ' . $e->getMessage()); } Minz_Translate::reset($currentLanguage); @@ -53,30 +51,43 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { return true; } else { $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error: ' . $info[2]); + Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); return false; } } - public function deleteUser($username) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + public function deleteUser() { + if (defined('STDERR')) { + fwrite(STDERR, 'Deleting SQL data for user “' . $this->current_user . "”…\n"); + } - if ($db['type'] === 'sqlite') { - return unlink(USERS_PATH . '/' . $username . '/db.sqlite'); - } else { - $userPDO = new Minz_ModelPdo($username); + require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); - $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); - $stm = $userPDO->bd->prepare($sql); - if ($stm && $stm->execute()) { - return true; - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error : ' . $info[2]); - return false; + $ok = false; + if (defined('SQL_DROP_TABLES')) { //E.g. MySQL + $sql = sprintf(SQL_DROP_TABLES, $this->prefix); + $stm = $this->bd->prepare($sql); + $ok = $stm && $stm->execute(); + } else { //E.g. SQLite + global $SQL_DROP_TABLES; + if (is_array($SQL_DROP_TABLES)) { + $instructions = $SQL_DROP_TABLES; + $ok = !empty($instructions); + foreach ($instructions as $instruction) { + $sql = sprintf($instruction, $this->prefix); + $stm = $this->bd->prepare($sql); + $ok &= ($stm && $stm->execute()); + } } } + + if ($ok) { + return true; + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); + return false; + } } public static function exists($username) { diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php index d1dbfc192..88de84358 100644 --- a/app/SQL/install.sql.sqlite.php +++ b/app/SQL/install.sql.sqlite.php @@ -91,7 +91,7 @@ $SQL_CREATE_TABLE_TAGS = array( );', 'CREATE TABLE IF NOT EXISTS `entrytag` ( `id_tag` SMALLINT, - `id_entry` SMALLINT, + `id_entry` BIGINT, PRIMARY KEY (`id_tag`,`id_entry`), FOREIGN KEY (`id_tag`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`id_entry`) REFERENCES `entry` (`id`) ON DELETE CASCADE ON UPDATE CASCADE @@ -105,4 +105,12 @@ define( VALUES(:url, 1, :name, :website, :description, 86400);' ); -define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `entrytag`, `tag`, `entrytmp`, `entry`, `feed`, `category`'); +global $SQL_DROP_TABLES; +$SQL_DROP_TABLES = [ + 'DROP TABLE IF EXISTS `entrytag`', + 'DROP TABLE IF EXISTS `tag`', + 'DROP TABLE IF EXISTS `entrytmp`', + 'DROP TABLE IF EXISTS `entry`', + 'DROP TABLE IF EXISTS `feed`', + 'DROP TABLE IF EXISTS `category`', +]; diff --git a/cli/README.md b/cli/README.md index e9e336439..35c9bad9b 100644 --- a/cli/README.md +++ b/cli/README.md @@ -35,7 +35,7 @@ cd /usr/share/FreshRSS ./cli/prepare.php # Ensure the needed directories in ./data/ -./cli/do-install.php --default_user admin ( --auth_type form --environment production --base_url https://rss.example.net --language en --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss ) +./cli/do-install.php --default_user admin [ --auth_type form --environment production --base_url https://rss.example.net --language en --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss ] # --auth_type can be: 'form' (default), 'http_auth' (using the Web server access control), 'none' (dangerous) # --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL) # --base_url should be a public (routable) URL if possible, and is used for push (WebSub), for some API functions (e.g. favicons), and external URLs in FreshRSS. @@ -47,26 +47,20 @@ cd /usr/share/FreshRSS ./cli/reconfigure.php # Same parameters as for do-install.php. Used to update an existing installation. -./cli/create-user.php --user username ( --password 'password' --api_password 'api_password' --language en --email user@example.net --token 'longRandomString' --no_default_feeds --purge_after_months 3 --feed_min_articles_default 50 --feed_ttl_default 3600 --since_hours_posts_per_rss 168 --min_posts_per_rss 2 --max_posts_per_rss 400 ) +./cli/create-user.php --user username [ --password 'password' --api_password 'api_password' --language en --email user@example.net --token 'longRandomString' --no_default_feeds --purge_after_months 3 --feed_min_articles_default 50 --feed_ttl_default 3600 --since_hours_posts_per_rss 168 --min_posts_per_rss 2 --max_posts_per_rss 400 ] # --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/) -./cli/update-user.php --user username ( ... ) +./cli/update-user.php --user username [ ... ] # Same options as create-user.php, except --no_default_feeds which is only available for create-user.php +./cli/actualize-user.php --user username +# Fetch feeds for the specified user + ./cli/delete-user.php --user username ./cli/list-users.php # Return a list of users, with the default/admin user first -./cli/actualize-user.php --user username - -./cli/import-for-user.php --user username --filename /path/to/file.ext -# The extension of the file { .json, .opml, .xml, .zip } is used to detect the type of import - -./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml - -./cli/export-zip-for-user.php --user username ( --max-feed-entries 100 ) > /path/to/file.zip - ./cli/user-info.php -h --user username # -h is to use a human-readable format # --user can be a username, or '*' to loop on all users @@ -74,6 +68,20 @@ cd /usr/share/FreshRSS # 3) the date/time of last user action, 4) the size occupied, # and the number of: 5) categories, 6) feeds, 7) read articles, 8) unread articles, 9) favourites, and 10) tags +./cli/import-for-user.php --user username --filename /path/to/file.ext +# The extension of the file { .json, .opml, .xml, .zip } is used to detect the type of import + +./cli/export-sqlite-for-user.php --user username --filename /path/to/db.sqlite +# Export the user’s database to a new SQLite file. + +./cli/import-sqlite-for-user.php --user username [ --force-overwrite ] --filename /path/to/db.sqlite +# Import the user’s database from an SQLite file. +# --force-overwrite will clear the target user database before import (import only works on an empty user database) + +./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml + +./cli/export-zip-for-user.php --user username [ --max-feed-entries 100 ] > /path/to/file.zip + ./cli/db-optimize.php --user username # Optimize database (reduces the size) for a given user (perform `OPTIMIZE TABLE` in MySQL, `VACUUM` in SQLite) ``` diff --git a/cli/export-sqlite-for-user.php b/cli/export-sqlite-for-user.php new file mode 100755 index 000000000..027d13f38 --- /dev/null +++ b/cli/export-sqlite-for-user.php @@ -0,0 +1,28 @@ +#!/usr/bin/php +dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_EXPORT); + +done($ok); diff --git a/cli/import-sqlite-for-user.php b/cli/import-sqlite-for-user.php new file mode 100755 index 000000000..f0e54e2fa --- /dev/null +++ b/cli/import-sqlite-for-user.php @@ -0,0 +1,34 @@ +#!/usr/bin/php +dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_IMPORT, $clearFirst); +if (!$ok) { + echo 'If you would like to clear the user database first, use the option --force-overwrite', "\n"; +} +invalidateHttpCache($username); + +done($ok); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 14510c983..4d5e47da9 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -17,7 +17,6 @@ class Minz_ModelPdo { private static $sharedBd = null; private static $sharedPrefix; private static $sharedCurrentUser; - protected static $sharedDbType; /** * $bd variable représentant la base de données @@ -27,18 +26,21 @@ class Minz_ModelPdo { protected $current_user; protected $prefix; - public function dbType() { - return self::$sharedDbType; - } - /** * Créé la connexion à la base de données à l'aide des variables * HOST, BASE, USER et PASS définies dans le fichier de configuration */ - public function __construct($currentUser = null) { + public function __construct($currentUser = null, $currentPrefix = null, $currentDb = null) { if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser'); } + if ($currentPrefix !== null) { + $this->prefix = $currentPrefix; + } + if ($currentDb != null) { + $this->bd = $currentDb; + return; + } if (self::$useSharedBd && self::$sharedBd != null && ($currentUser == null || $currentUser === self::$sharedCurrentUser)) { $this->bd = self::$sharedBd; @@ -65,6 +67,7 @@ class Minz_ModelPdo { $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; $this->prefix = $db['prefix'] . $currentUser . '_'; $this->bd = new MinzPDOMySql($string, $db['user'], $db['password'], $driver_options); + $this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); break; case 'sqlite': $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); @@ -89,7 +92,6 @@ class Minz_ModelPdo { break; } self::$sharedBd = $this->bd; - self::$sharedDbType = $db['type']; self::$sharedPrefix = $this->prefix; } catch (Exception $e) { throw new Minz_PDOConnectionException( @@ -114,17 +116,12 @@ class Minz_ModelPdo { public static function clean() { self::$sharedBd = null; + self::$sharedCurrentUser = ''; self::$sharedPrefix = ''; } - - public function disableBuffering() { - if ((self::$sharedDbType === 'mysql') && defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) { - $this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); - } - } } -class MinzPDO extends PDO { +abstract class MinzPDO extends PDO { private static function check($statement) { if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { invalidateHttpCache(); @@ -135,6 +132,8 @@ class MinzPDO extends PDO { return $statement; } + abstract public function dbType(); + public function prepare($statement, $driver_options = array()) { MinzPDO::check($statement); $statement = $this->compatibility($statement); @@ -155,18 +154,30 @@ class MinzPDO extends PDO { } class MinzPDOMySql extends MinzPDO { + public function dbType() { + return 'mysql'; + } + public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } class MinzPDOSQLite extends MinzPDO { + public function dbType() { + return 'sqlite'; + } + public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } class MinzPDOPGSQL extends MinzPDO { + public function dbType() { + return 'pgsql'; + } + protected function compatibility($statement) { return str_replace(array('`', ' LIKE '), array('"', ' ILIKE '), $statement); } -- cgit v1.2.3 From e3e5954394f4523850c78e80e496f1b916622677 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 29 Sep 2019 16:22:50 +0200 Subject: PDO refactoring for code simplification (#2522) * PDO refactor * Automatic prefix when using the syntax `_tableName` * Uniformity: MySQL is now PDO::ATTR_EMULATE_PREPARES = false just like SQLite and PostgreSQL, with consequences such as only one statement per query * Use PDO methods exec(), query(), prepare() + execute() in a more efficient way * Remove auto-update SQL code for versions older than FreshRSS 1.5 (3 years old) * The name of the default category is set in PHP instead of in the DB (simplies SQL and allows changing the name according to the FreshRSS language) * Rename `->bd` to `->pdo` (less of a frenshism, and more informative) * Fix some requests, which were not compatible with MySQL prepared statements * Whitespace * Fix syntax for PostgreSQL sequences + MySQL install * Minor formatting * Fix lastInsertId for PostgreSQL * Use PHP 5.6+ const Take advantage of https://github.com/FreshRSS/FreshRSS/pull/2527 https://www.php.net/manual/en/migration56.new-features.php * A bit of forgotten PHP 5.6 simplification for cURL * Forgotten $s * Mini fix custom user config https://github.com/FreshRSS/FreshRSS/pull/2490/files#r326290346 * More work on install.php but not finished * install.php working * More cleaning of PDO in install * Even more simplification Take advantage of PDO->exec() to run multiple statements * Disallow changing the name of the default category https://github.com/FreshRSS/FreshRSS/pull/2522#discussion_r326967724 --- app/Controllers/configureController.php | 3 +- app/Controllers/feedController.php | 2 +- app/Controllers/userController.php | 13 +- app/Models/Category.php | 7 +- app/Models/CategoryDAO.php | 104 ++++---- app/Models/DatabaseDAO.php | 92 ++++--- app/Models/DatabaseDAOPGSQL.php | 28 +-- app/Models/DatabaseDAOSQLite.php | 26 +- app/Models/Entry.php | 12 +- app/Models/EntryDAO.php | 341 ++++++++++---------------- app/Models/EntryDAOPGSQL.php | 24 +- app/Models/EntryDAOSQLite.php | 72 +++--- app/Models/Feed.php | 17 +- app/Models/FeedDAO.php | 154 ++++++------ app/Models/FeedDAOSQLite.php | 2 +- app/Models/StatsDAO.php | 47 ++-- app/Models/StatsDAOPGSQL.php | 5 +- app/Models/StatsDAOSQLite.php | 5 +- app/Models/TagDAO.php | 106 ++++----- app/Models/TagDAOSQLite.php | 2 +- app/Models/UserDAO.php | 57 +---- app/SQL/install.sql.mysql.php | 89 +++---- app/SQL/install.sql.pgsql.php | 93 ++++---- app/SQL/install.sql.sqlite.php | 88 +++---- app/install.php | 409 ++++++++++++++------------------ app/views/helpers/category/update.phtml | 5 +- cli/do-install.php | 7 +- lib/Minz/ModelPdo.php | 132 ++++++----- lib/favicons.php | 19 +- lib/lib_install.php | 79 ++---- lib/lib_rss.php | 11 +- p/api/fever.php | 4 +- p/api/greader.php | 20 +- p/f.php | 10 +- 34 files changed, 885 insertions(+), 1200 deletions(-) (limited to 'app/Models/UserDAO.php') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index b02ad02e4..85ca9da39 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -167,8 +167,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * tab and up. */ public function shortcutAction() { - global $SHORTCUT_KEYS; - $this->view->list_keys = $SHORTCUT_KEYS; + $this->view->list_keys = SHORTCUT_KEYS; if (Minz_Request::isPost()) { FreshRSS_Context::$user_conf->shortcuts = validateShortcutList(Minz_Request::param('shortcuts')); diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index c2c0cce37..ea07d96e4 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -429,7 +429,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->updateLastUpdate($feed->id(), false, $mtime); if ($needFeedCacheRefresh) { - $feedDAO->updateCachedValue($feed->id()); + $feedDAO->updateCachedValues($feed->id()); } if ($entryDAO->inTransaction()) { $entryDAO->commit(); diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index ab8dfb0b2..6afc91b4e 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -211,16 +211,15 @@ class FreshRSS_user_Controller extends Minz_ActionController { } } - public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain, $userConfigOverride = array(), $insertDefaultFeeds = true) { - $userConfig = array(); + public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain = '', $userConfigOverride = [], $insertDefaultFeeds = true) { + $userConfig = []; $customUserConfigPath = join_path(DATA_PATH, 'config-user.custom.php'); if (file_exists($customUserConfigPath)) { $customUserConfig = include($customUserConfigPath); - } - - if (is_array($customUserConfig)) { - $userConfig = $customUserConfig; + if (is_array($customUserConfig)) { + $userConfig = $customUserConfig; + } } if (is_array($userConfigOverride)) { @@ -249,7 +248,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { } if ($ok) { $newUserDAO = FreshRSS_Factory::createUserDao($new_user_name); - $ok &= $newUserDAO->createUser($userConfig['language'], $insertDefaultFeeds); + $ok &= $newUserDAO->createUser($insertDefaultFeeds); $ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain); } return $ok; diff --git a/app/Models/Category.php b/app/Models/Category.php index fa711aa66..29c0e586b 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -68,8 +68,11 @@ class FreshRSS_Category extends Minz_Model { return $this->hasFeedsWithError; } - public function _id($value) { - $this->id = $value; + public function _id($id) { + $this->id = $id; + if ($id == FreshRSS_CategoryDAO::DEFAULTCATEGORYID) { + $this->_name(_t('gen.short.default_category')); + } } public function _name($value) { $this->name = trim($value); diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index b0fcb5033..2bae5ef83 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -5,10 +5,10 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable const DEFAULTCATEGORYID = 1; public function addCategory($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) ' + $sql = 'INSERT INTO `_category`(name) ' . 'SELECT * FROM (SELECT TRIM(?)) c2 ' //TRIM() to provide a type hint as text for PostgreSQL - . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = TRIM(?))'; //No tag of the same name - $stm = $this->bd->prepare($sql); + . 'WHERE NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = TRIM(?))'; //No tag of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'); $values = array( @@ -17,7 +17,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'category_id_seq"'); + return $this->pdo->lastInsertId('`_category_id_seq`'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error addCategory: ' . $info[2]); @@ -39,9 +39,9 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function updateCategory($id, $valuesTmp) { - $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=? ' - . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = ?)'; //No tag of the same name - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_category` SET name=? WHERE id=? ' + . 'AND NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = ?)'; //No tag of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'); $values = array( @@ -63,12 +63,10 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($id <= self::DEFAULTCATEGORYID) { return false; } - $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - if ($stm && $stm->execute($values)) { + $sql = 'DELETE FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -78,21 +76,18 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function selectAll() { - $sql = 'SELECT id, name FROM `' . $this->prefix . 'category`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT id, name FROM `_category`'; + $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { yield $row; } } public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - $stm->execute($values); + $sql = 'SELECT * FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); @@ -103,18 +98,15 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } } public function searchByName($name) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_category` WHERE name=:name'; + $stm = $this->pdo->prepare($sql); if ($stm == false) { return false; } - - $values = array($name); - - $stm->execute($values); + $stm->bindParam(':name', $name); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); - if (isset($cat[0])) { return $cat[0]; } else { @@ -126,26 +118,26 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($prePopulateFeeds) { $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads`, f.ttl ') - . 'FROM `' . $this->prefix . 'category` c ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' + . 'FROM `_category` c ' + . 'LEFT OUTER JOIN `_feed` f ON f.category=c.id ' . 'WHERE f.priority >= :priority_normal ' . 'GROUP BY f.id, c_id ' . 'ORDER BY c.name, f.name'; - $stm = $this->bd->prepare($sql); - $stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL)); + $stm = $this->pdo->prepare($sql); + $stm->bindValue(':priority_normal', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT); + $stm->execute(); return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); } else { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT * FROM `_category` ORDER BY name'; + $stm = $this->pdo->query($sql); return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC)); } } public function getDefault() { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=' . self::DEFAULTCATEGORYID; - $stm = $this->bd->prepare($sql); - + $sql = 'SELECT * FROM `_category` WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindValue(':id', self::DEFAULTCATEGORYID, PDO::PARAM_INT); $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $cat = self::daoToCategory($res); @@ -167,12 +159,12 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $cat = new FreshRSS_Category(_t('gen.short.default_category')); $cat->_id(self::DEFAULTCATEGORYID); - $sql = 'INSERT INTO `' . $this->prefix . 'category`(id, name) VALUES(?, ?)'; - if ($this->bd->dbType() === 'pgsql') { + $sql = 'INSERT INTO `_category`(id, name) VALUES(?, ?)'; + if ($this->pdo->dbType() === 'pgsql') { //Force call to nextval() - $sql .= ' RETURNING nextval(\'"' . $this->prefix . 'category_id_seq"\');'; + $sql .= " RETURNING nextval('`_category_id_seq`');"; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array( $cat->id(), @@ -180,7 +172,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'category_id_seq"'); + return $this->pdo->lastInsertId('`_category_id_seq`'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error check default category: ' . json_encode($info)); @@ -191,31 +183,27 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function count() { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT COUNT(*) AS count FROM `_category`'; + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); - return $res[0]['count']; } public function countFeed($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $stm->execute($values); + $sql = 'SELECT COUNT(*) AS count FROM `_feed` WHERE category=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); - return $res[0]['count']; } public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $stm->execute($values); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id WHERE category=:id AND e.is_read=0'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); - return $res[0]['count']; } diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php index ec84da664..8046e1b5a 100644 --- a/app/Models/DatabaseDAO.php +++ b/app/Models/DatabaseDAO.php @@ -14,19 +14,44 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { //https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html const LENGTH_INDEX_UNICODE = 191; + public function create() { + require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + $db = FreshRSS_Context::$system_conf->db; + + try { + $sql = sprintf(SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']); + return $this->pdo->exec($sql) !== false; + } catch (PDOException $e) { + $_SESSION['bd_error'] = $e->getMessage(); + syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage()); + return false; + } + } + + public function testConnection() { + try { + $sql = 'SELECT 1'; + $stm = $this->pdo->query($sql); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return $res != false; + } catch (PDOException $e) { + $_SESSION['bd_error'] = $e->getMessage(); + syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage()); + return false; + } + } + public function tablesAreCorrect() { - $sql = 'SHOW TABLES'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query('SHOW TABLES'); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - $this->prefix . 'category' => false, - $this->prefix . 'feed' => false, - $this->prefix . 'entry' => false, - $this->prefix . 'entrytmp' => false, - $this->prefix . 'tag' => false, - $this->prefix . 'entrytag' => false, + $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; @@ -36,10 +61,8 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { } public function getSchema($table) { - $sql = 'DESC ' . $this->prefix . $table; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $sql = 'DESC `_' . $table . '`'; + $stm = $this->pdo->query($sql); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -119,9 +142,9 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $values = array($db['base']); if (!$all) { $sql .= ' AND table_name LIKE ?'; - $values[] = $this->prefix . '%'; + $values[] = $this->pdo->prefix() . '%'; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; @@ -132,29 +155,23 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'); foreach ($tables as $table) { - $sql = 'OPTIMIZE TABLE `' . $this->prefix . $table . '`'; //MySQL - $stm = $this->bd->prepare($sql); - $ok &= $stm != false; - if ($stm) { - $ok &= $stm->execute(); - } + $sql = 'OPTIMIZE TABLE `_' . $table . '`'; //MySQL + $ok &= ($this->pdo->exec($sql) !== false); } return $ok; } public function ensureCaseInsensitiveGuids() { $ok = true; - if ($this->bd->dbType() === 'mysql') { + if ($this->pdo->dbType() === 'mysql') { include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - if (defined('SQL_UPDATE_GUID_LATIN1_BIN')) { //FreshRSS 1.12 - try { - $sql = sprintf(SQL_UPDATE_GUID_LATIN1_BIN, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm->execute(); - } catch (Exception $e) { - $ok = false; - Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage()); - } + + $ok = false; + try { + $ok = $this->pdo->exec(SQL_UPDATE_GUID_LATIN1_BIN) !== false; //FreshRSS 1.12 + } catch (Exception $e) { + $ok = false; + Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage()); } } return $ok; @@ -195,7 +212,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { $error = 'Error: SQLite import file is not readable: ' . $filename; } elseif ($clearFirst) { $userDAO->deleteUser(); - if ($this->bd->dbType() === 'sqlite') { + if ($this->pdo->dbType() === 'sqlite') { //We cannot just delete the .sqlite file otherwise PDO gets buggy. //SQLite is the only one with database-level optimization, instead of at table level. $this->optimize(); @@ -219,18 +236,17 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { try { $sqlite = new MinzPDOSQLite('sqlite:' . $filename); - $sqlite->exec('PRAGMA foreign_keys = ON;'); } catch (Exception $e) { $error = 'Error while initialising SQLite copy: ' . $e->getMessage(); return self::stdError($error); } Minz_ModelPdo::clean(); - $userDAOSQLite = new FreshRSS_UserDAO('', '', $sqlite); - $categoryDAOSQLite = new FreshRSS_CategoryDAO('', '', $sqlite); - $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', '', $sqlite); - $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', '', $sqlite); - $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', '', $sqlite); + $userDAOSQLite = new FreshRSS_UserDAO('', $sqlite); + $categoryDAOSQLite = new FreshRSS_CategoryDAO('', $sqlite); + $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', $sqlite); + $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', $sqlite); + $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', $sqlite); switch ($mode) { case self::SQLITE_EXPORT: diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php index 8582b5719..60aceacc2 100644 --- a/app/Models/DatabaseDAOPGSQL.php +++ b/app/Models/DatabaseDAOPGSQL.php @@ -13,18 +13,18 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { $db = FreshRSS_Context::$system_conf->db; $dbowner = $db['user']; $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=?'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($dbowner); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - $this->prefix . 'category' => false, - $this->prefix . 'feed' => false, - $this->prefix . 'entry' => false, - $this->prefix . 'entrytmp' => false, - $this->prefix . 'tag' => false, - $this->prefix . 'entrytag' => false, + $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; @@ -35,8 +35,8 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { public function getSchema($table) { $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->bd->prepare($sql); - $stm->execute(array($this->prefix . $table)); + $stm = $this->pdo->prepare($sql); + $stm->execute(array($this->pdo->prefix() . $table)); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -53,7 +53,7 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { $db = FreshRSS_Context::$system_conf->db; $sql = 'SELECT pg_size_pretty(pg_database_size(?))'; $values = array($db['base']); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; @@ -64,12 +64,8 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'); foreach ($tables as $table) { - $sql = 'VACUUM `' . $this->prefix . $table . '`'; - $stm = $this->bd->prepare($sql); - $ok &= $stm != false; - if ($stm) { - $ok &= $stm->execute(); - } + $sql = 'VACUUM `_' . $table . '`'; + $ok &= ($this->pdo->exec($sql) !== false); } return $ok; } diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php index a93a209b2..11900979e 100644 --- a/app/Models/DatabaseDAOSQLite.php +++ b/app/Models/DatabaseDAOSQLite.php @@ -6,17 +6,16 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { public function tablesAreCorrect() { $sql = 'SELECT name FROM sqlite_master WHERE type="table"'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $tables = array( - 'category' => false, - 'feed' => false, - 'entry' => false, - 'entrytmp' => false, - 'tag' => false, - 'entrytag' => false, + $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[$value['name']] = true; @@ -27,9 +26,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { public function getSchema($table) { $sql = 'PRAGMA table_info(' . $table . ')'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $stm = $this->pdo->query($sql); return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -61,11 +58,6 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { } public function optimize() { - $sql = 'VACUUM'; - $stm = $this->bd->prepare($sql); - if ($stm) { - return $stm->execute(); - } - return false; + return $this->exec('VACUUM') !== false; } } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 3bb977283..d90f828bc 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -327,7 +327,7 @@ class FreshRSS_Entry extends Minz_Model { } $ch = curl_init(); - curl_setopt_array($ch, array( + curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_REFERER => SimplePie_Misc::url_remove_credentials($url), CURLOPT_HTTPHEADER => array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), @@ -337,13 +337,9 @@ class FreshRSS_Entry extends Minz_Model { //CURLOPT_FAILONERROR => true; CURLOPT_MAXREDIRS => 4, CURLOPT_RETURNTRANSFER => true, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_ENCODING => '', //Enable all encodings + ]); curl_setopt_array($ch, $system_conf->curl_options); if (isset($attributes['ssl_verify'])) { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $attributes['ssl_verify'] ? 2 : 0); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b13c83d67..fbf160041 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -18,106 +18,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return 'hex(' . $x . ')'; } - //TODO: Move the database auto-updates to DatabaseDAO - protected function addColumn($name) { - Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name); - $hasTransaction = false; - try { - $stm = null; - if ($name === 'lastSeen') { //v1.1.1 - if (!$this->bd->inTransaction()) { - $this->bd->beginTransaction(); - $hasTransaction = true; - } - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN `lastSeen` INT(11) DEFAULT 0'); - if ($stm && $stm->execute()) { - $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 - if ($stm && $stm->execute()) { - if ($hasTransaction) { - $this->bd->commit(); - } - return true; - } - } - if ($hasTransaction) { - $this->bd->rollBack(); - } - } elseif ($name === 'hash') { //v1.1.1 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)'); - return $stm && $stm->execute(); - } - } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::addColumn error: ' . $e->getMessage()); - if ($hasTransaction) { - $this->bd->rollBack(); - } - } - return false; - } - - private $triedUpdateToUtf8mb4 = false; - - //TODO: Move the database auto-updates to DatabaseDAO - protected function updateToUtf8mb4() { - if ($this->triedUpdateToUtf8mb4) { - return false; - } - $this->triedUpdateToUtf8mb4 = true; - $db = FreshRSS_Context::$system_conf->db; - if ($this->bd->dbType() === 'mysql') { - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - if (defined('SQL_UPDATE_UTF8MB4')) { - Minz_Log::warning('Updating MySQL to UTF8MB4...'); //v1.5.0 - $hadTransaction = $this->bd->inTransaction(); - if ($hadTransaction) { - $this->bd->commit(); - } - $ok = false; - try { - $sql = sprintf(SQL_UPDATE_UTF8MB4, $this->prefix, $db['base']); - $stm = $this->bd->prepare($sql); - $ok = $stm->execute(); - } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::updateToUtf8mb4 error: ' . $e->getMessage()); - } - if ($hadTransaction) { - $this->bd->beginTransaction(); - //NB: Transaction not starting. Why? (tested on PHP 7.0.8-0ubuntu and MySQL 5.7.13-0ubuntu) - } - return $ok; - } - } - return false; - } - //TODO: Move the database auto-updates to DatabaseDAO protected function createEntryTempTable() { $ok = false; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if ($hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } try { - require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL CREATE TABLE entrytmp...'); - if (defined('SQL_CREATE_TABLE_ENTRYTMP')) { - $sql = sprintf(SQL_CREATE_TABLE_ENTRYTMP, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLE_ENTRYTMP; - $ok = !empty($SQL_CREATE_TABLE_ENTRYTMP); - foreach ($SQL_CREATE_TABLE_ENTRYTMP as $instruction) { - $sql = sprintf($instruction, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } + $ok = $this->pdo->exec(SQL_CREATE_TABLE_ENTRYTMP) !== false; } catch (Exception $e) { Minz_Log::error('FreshRSS_EntryDAO::createEntryTempTable error: ' . $e->getMessage()); } if ($hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } return $ok; } @@ -125,14 +41,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { //TODO: Move the database auto-updates to DatabaseDAO protected function autoUpdateDb($errorInfo) { if (isset($errorInfo[0])) { - if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR) { - //autoAddColumn - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($errorInfo[2], $column) !== false) { - return $this->addColumn($column); - } - } - } elseif ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) { + if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) { if (stripos($errorInfo[2], 'tag') !== false) { $tagDAO = FreshRSS_Factory::createTagDao(); return $tagDAO->createTagTable(); //v1.12.0 @@ -141,11 +50,6 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } } - if (isset($errorInfo[1])) { - if ($errorInfo[1] == FreshRSS_DatabaseDAO::ER_TRUNCATED_WRONG_VALUE_FOR_FIELD) { - return $this->updateToUtf8mb4(); //v1.5.0 - } - } return false; } @@ -153,7 +57,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addEntry($valuesTmp, $useTmpTable = true) { if ($this->addEntryPrepared == null) { - $sql = 'INSERT INTO `' . $this->prefix . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, ' + $sql = 'INSERT INTO `_' . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' . 'VALUES(:id, :guid, :title, :author, ' @@ -161,7 +65,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . ', :link, :date, :last_seen, ' . $this->sqlHexDecode(':hash') . ', :is_read, :is_favorite, :id_feed, :tags)'; - $this->addEntryPrepared = $this->bd->prepare($sql); + $this->addEntryPrepared = $this->pdo->prepare($sql); } if ($this->addEntryPrepared) { $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']); @@ -212,22 +116,26 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function commitNewEntries() { - $sql = 'SET @rank=(SELECT MAX(id) - COUNT(*) FROM `' . $this->prefix . 'entrytmp`); ' . //MySQL-specific - 'INSERT IGNORE INTO `' . $this->prefix . 'entry` - ( - id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - ) ' . - 'SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - FROM `' . $this->prefix . 'entrytmp` - ORDER BY date; ' . - 'DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= @rank;'; - $hadTransaction = $this->bd->inTransaction(); + $sql = <<<'SQL' +SET @rank=(SELECT MAX(id) - COUNT(*) FROM `_entrytmp`); + +INSERT IGNORE INTO `_entry` ( + id, guid, title, author, content_bin, link, date, `lastSeen`, + hash, is_read, is_favorite, id_feed, tags +) +SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags +FROM `_entrytmp` +ORDER BY date; + +DELETE FROM `_entrytmp` WHERE id <= @rank;'; +SQL; + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } @@ -240,7 +148,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } if ($this->updateEntryPrepared === null) { - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET title=:title, author=:author, ' . ($this->isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content') . ', link=:link, date=:date, `lastSeen`=:last_seen, ' @@ -248,7 +156,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=:is_read, ') . 'tags=:tags ' . 'WHERE id_feed=:id_feed AND guid=:guid'; - $this->updateEntryPrepared = $this->bd->prepare($sql); + $this->updateEntryPrepared = $this->pdo->prepare($sql); } $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760); @@ -309,12 +217,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return 0; } FreshRSS_UserDAO::touch(); - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_favorite=? ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; $values = array($is_favorite ? 1 : 0); $values = array_merge($values, $ids); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { @@ -336,11 +244,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { * @return boolean */ protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + $sql = 'UPDATE `_feed` f ' . 'LEFT OUTER JOIN (' . 'SELECT e.id_feed, ' . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' + . 'FROM `_entry` e ' . 'WHERE e.is_read=0 ' . 'GROUP BY e.id_feed' . ') x ON x.id_feed=f.id ' @@ -359,7 +267,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql .= ' f.category=?'; $values[] = $catId; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return true; } else { @@ -393,12 +301,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $affected; } - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_read=? ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; $values = array($is_read ? 1 : 0); $values = array_merge($values, $ids); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markRead: ' . $info[2]); @@ -410,12 +318,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $affected; } else { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id ' . 'SET e.is_read=?,' . 'f.`cache_nbUnreads`=f.`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' . 'WHERE e.id=? AND e.is_read=?'; $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { @@ -454,7 +362,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id ' . 'SET e.is_read=? ' . 'WHERE e.is_read <> ? AND e.id <= ?'; if ($onlyFavorites) { @@ -466,7 +374,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); @@ -497,14 +405,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id ' . 'SET e.is_read=? ' . 'WHERE f.category=? AND e.is_read <> ? AND e.id <= ?'; $values = array($is_read ? 1 : 0, $id, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); @@ -534,39 +442,39 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $idMax = time() . '000000'; Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); } - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_read=? ' . 'WHERE id_feed=? AND is_read <> ? AND id <= ?'; $values = array($is_read ? 1 : 0, $id_feed, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed: ' . $info[2] . ' with SQL: ' . $sql . $search); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } $affected = $stm->rowCount(); if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_feed` ' . 'SET `cache_nbUnreads`=`cache_nbUnreads`-' . $affected - . ' WHERE id=?'; - $values = array($id_feed); - $stm = $this->bd->prepare($sql); + . ' WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id_feed, PDO::PARAM_INT); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed cache: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } @@ -583,7 +491,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { Minz_Log::debug('Calling markReadTag(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id ' + $sql = 'UPDATE `_entry` e INNER JOIN `_entrytag` et ON et.id_entry = e.id ' . 'SET e.is_read = ? ' . 'WHERE ' . ($id == '' ? '' : 'et.id_tag = ? AND ') @@ -597,7 +505,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); @@ -611,18 +519,20 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function cleanOldEntries($id_feed, $date_min, $keep = 15) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after - $sql = 'DELETE FROM `' . $this->prefix . 'entry` ' - . 'WHERE id_feed=:id_feed AND id<=:id_max ' + $sql = 'DELETE FROM `_entry` ' + . 'WHERE id_feed=:id_feed1 AND id<=:id_max ' . 'AND is_favorite=0 ' //Do not remove favourites - . 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance - . 'AND id NOT IN (SELECT id_entry FROM `' . $this->prefix . 'entrytag`) ' //Do not purge tagged entries - . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' - $stm = $this->bd->prepare($sql); + . 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `_entry` e3 WHERE e3.id_feed=:id_feed2) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance + . 'AND id NOT IN (SELECT id_entry FROM `_entrytag`) ' //Do not purge tagged entries + . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `_entry` e2 WHERE e2.id_feed=:id_feed3 ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' + $stm = $this->pdo->prepare($sql); if ($stm) { $id_max = intval($date_min) . '000000'; - $stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT); $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); + $stm->bindParam(':id_feed1', $id_feed, PDO::PARAM_INT); + $stm->bindParam(':id_feed2', $id_feed, PDO::PARAM_INT); + $stm->bindParam(':id_feed3', $id_feed, PDO::PARAM_INT); $stm->bindParam(':keep', $keep, PDO::PARAM_INT); } @@ -642,9 +552,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, `lastSeen`, ' . $this->sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + . 'FROM `_entry`'; + $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { yield $row; } @@ -655,15 +564,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; - $stm = $this->bd->prepare($sql); - - $values = array( - $id_feed, - $guid, - ); - - $stm->execute($values); + . '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); $entries = self::daoToEntries($res); return isset($entries[0]) ? $entries[0] : null; @@ -673,22 +578,21 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - $stm->execute($values); + . '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); $entries = self::daoToEntries($res); return isset($entries[0]) ? $entries[0] : null; } public function searchIdByGuid($id_feed, $guid) { - $sql = 'SELECT id FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; - $stm = $this->bd->prepare($sql); - $values = array($id_feed, $guid); - $stm->execute($values); + $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; } @@ -872,7 +776,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $where .= '1=1 '; break; case 'ST': //Starred or tagged - $where .= 'e.is_favorite=1 OR EXISTS (SELECT et2.id_tag FROM `' . $this->prefix . 'entrytag` et2 WHERE et2.id_entry = e.id) '; + $where .= 'e.is_favorite=1 OR EXISTS (SELECT et2.id_tag FROM `_entrytag` et2 WHERE et2.id_entry = e.id) '; break; default: throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); @@ -883,9 +787,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return array(array_merge($values, $searchValues), 'SELECT ' . ($type === 'T' ? 'DISTINCT ' : '') - . 'e.id FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . ($type === 't' || $type === 'T' ? 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id ' : '') + . 'e.id FROM `_entry` e ' + . 'INNER JOIN `_feed` f ON e.id_feed = f.id ' + . ($type === 't' || $type === 'T' ? 'INNER JOIN `_entrytag` et ON et.id_entry = e.id ' : '') . 'WHERE ' . $where . $search . 'ORDER BY e.id ' . $order @@ -898,13 +802,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags ' - . 'FROM `' . $this->prefix . 'entry` e0 ' + . 'FROM `_entry` e0 ' . 'INNER JOIN (' . $sql . ') e2 ON e2.id=e0.id ' . 'ORDER BY e0.id ' . $order; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm; } else { @@ -931,11 +835,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry` ' + . 'FROM `_entry` ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) ' . 'ORDER BY id ' . $order; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($ids); return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); } @@ -943,7 +847,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); return $stm->fetchAll(PDO::FETCH_COLUMN, 0); @@ -954,8 +858,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return array(); } $guids = array_unique($guids); - $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hex_hash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hex_hash FROM `_entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $stm = $this->pdo->prepare($sql); $values = array($id_feed); $values = array_merge($values, $guids); if ($stm && $stm->execute($values)) { @@ -980,8 +884,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (count($guids) < 1) { return 0; } - $sql = 'UPDATE `' . $this->prefix . 'entry` SET `lastSeen`=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_entry` SET `lastSeen`=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $stm = $this->pdo->prepare($sql); if ($mtime <= 0) { $mtime = time(); } @@ -1001,68 +905,67 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countUnreadRead() { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0' - . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0 AND e.is_read=0'; - $stm = $this->bd->prepare($sql); - if ($stm == false) { + $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) { return false; } - $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); rsort($res); $all = empty($res[0]) ? 0 : $res[0]; $unread = empty($res[1]) ? 0 : $res[1]; return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); } + public function count($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e'; + $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e'; if ($minPriority !== null) { - $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql .= ' INNER JOIN `_feed` f ON e.id_feed=f.id'; $sql .= ' WHERE f.priority > ' . intval($minPriority); } - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return isset($res[0]) ? $res[0] : 0; } + public function countNotRead($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e'; + $sql = 'SELECT COUNT(e.id) AS count FROM `_entry` e'; if ($minPriority !== null) { - $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql .= ' INNER JOIN `_feed` f ON e.id_feed=f.id'; } $sql .= ' WHERE e.is_read=0'; if ($minPriority !== null) { $sql .= ' AND f.priority > ' . intval($minPriority); } - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } public function countUnreadReadFavorites() { - $sql = <<prefix}entry` AS e1 - JOIN `{$this->prefix}feed` AS f1 ON e1.id_feed = f1.id - WHERE e1.is_favorite = 1 - AND f1.priority >= :priority_normal - UNION - SELECT COUNT(e2.id) AS c - , 2 AS o - FROM `{$this->prefix}entry` AS e2 - JOIN `{$this->prefix}feed` AS f2 ON e2.id_feed = f2.id - WHERE e2.is_favorite = 1 - AND e2.is_read = 0 - AND f2.priority >= :priority_normal - ) u + $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 + WHERE e1.is_favorite = 1 + 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 + WHERE e2.is_favorite = 1 + AND e2.is_read = 0 + AND f2.priority >= :priority_normal2 + ) u ORDER BY o SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL)); + $stm = $this->pdo->prepare($sql); + //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 : $res[0]; diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php index e90aa8332..9afea279f 100644 --- a/app/Models/EntryDAOPGSQL.php +++ b/app/Models/EntryDAOPGSQL.php @@ -35,25 +35,27 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite { public function commitNewEntries() { $sql = 'DO $$ DECLARE -maxrank bigint := (SELECT MAX(id) FROM `' . $this->prefix . 'entrytmp`); -rank bigint := (SELECT maxrank - COUNT(*) FROM `' . $this->prefix . 'entrytmp`); +maxrank bigint := (SELECT MAX(id) FROM `_entrytmp`); +rank bigint := (SELECT maxrank - COUNT(*) FROM `_entrytmp`); BEGIN - INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) - (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags - FROM `' . $this->prefix . 'entrytmp` AS etmp + INSERT INTO `_entry` + (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) + (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, + link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags + FROM `_entrytmp` AS etmp WHERE NOT EXISTS ( - SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal + SELECT 1 FROM `_entry` AS ereal WHERE (etmp.id = ereal.id) OR (etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid)) ORDER BY date); - DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank; + DELETE FROM `_entrytmp` WHERE id <= maxrank; END $$;'; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index f53685e35..d9abefc4b 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -15,27 +15,19 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='tag'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'tag') === false) { $tagDAO = FreshRSS_Factory::createTagDao(); return $tagDAO->createTagTable(); //v1.12.0 } } - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entrytmp'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='entrytmp'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'entrytmp') === false) { return $this->createEntryTempTable(); //v1.7.0 } } - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) { - $showCreate = $tableInfo->fetchColumn(); - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($showCreate, $column) === false) { - return $this->addColumn($column); - } - } - } return false; } @@ -44,27 +36,27 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { 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 - FROM `' . $this->prefix . 'entrytmp` + FROM `_entrytmp` ORDER BY date; -INSERT OR IGNORE INTO `' . $this->prefix . 'entry` +INSERT OR IGNORE INTO `_entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) SELECT rowid + (SELECT MAX(id) - COUNT(*) FROM `tmp`) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `tmp` ORDER BY date; -DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`); +DELETE FROM `_entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`); DROP TABLE IF EXISTS `tmp`; '; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if (!$hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } - $result = $this->bd->exec($sql) !== false; + $result = $this->pdo->exec($sql) !== false; if (!$result) { - Minz_Log::error('SQL error commitNewEntries: ' . json_encode($this->bd->errorInfo())); + Minz_Log::error('SQL error commitNewEntries: ' . json_encode($this->pdo->errorInfo())); } if (!$hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } return $result; } @@ -74,10 +66,10 @@ DROP TABLE IF EXISTS `tmp`; } protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_feed` ' . 'SET `cache_nbUnreads`=(' - . 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0)'; + . 'SELECT COUNT(*) AS nbUnreads FROM `_entry` e ' + . 'WHERE e.id_feed=`_feed`.id AND e.is_read=0)'; $hasWhere = false; $values = array(); if ($feedId !== false) { @@ -92,7 +84,7 @@ DROP TABLE IF EXISTS `tmp`; $sql .= ' category=?'; $values[] = $catId; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return true; } else { @@ -126,30 +118,30 @@ DROP TABLE IF EXISTS `tmp`; return $affected; } } else { - $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?'; + $this->pdo->beginTransaction(); + $sql = 'UPDATE `_entry` SET is_read=? WHERE id=? AND is_read=?'; $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markRead 1: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } $affected = $stm->rowCount(); if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` SET `cache_nbUnreads`=`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' - . 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)'; + $sql = 'UPDATE `_feed` SET `cache_nbUnreads`=`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' + . 'WHERE id=(SELECT e.id_feed FROM `_entry` e WHERE e.id=?)'; $values = array($ids); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markRead 2: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } } @@ -182,17 +174,17 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read = ? WHERE is_read <> ? AND id <= ?'; + $sql = 'UPDATE `_entry` SET is_read = ? WHERE is_read <> ? AND id <= ?'; if ($onlyFavorites) { $sql .= ' AND is_favorite=1'; } elseif ($priorityMin >= 0) { - $sql .= ' AND id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.priority > ' . intval($priorityMin) . ')'; + $sql .= ' AND id_feed IN (SELECT f.id FROM `_feed` f WHERE f.priority > ' . intval($priorityMin) . ')'; } $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); @@ -223,15 +215,15 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` ' + $sql = 'UPDATE `_entry` ' . 'SET is_read = ? ' . 'WHERE is_read <> ? AND id <= ? AND ' - . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)'; + . 'id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category=?)'; $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax, $id); list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); @@ -257,10 +249,10 @@ DROP TABLE IF EXISTS `tmp`; Minz_Log::debug('Calling markReadTag(0) is deprecated!'); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' + $sql = 'UPDATE `_entry` e ' . 'SET e.is_read = ? ' . 'WHERE e.is_read <> ? AND e.id <= ? AND ' - . 'e.id IN (SELECT et.id_entry FROM `' . $this->prefix . 'entrytag` et ' + . 'e.id IN (SELECT et.id_entry FROM `_entrytag` et ' . ($id == '' ? '' : 'WHERE et.id = ?') . ')'; $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax); @@ -270,7 +262,7 @@ DROP TABLE IF EXISTS `tmp`; list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state); - $stm = $this->bd->prepare($sql . $search); + $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 89989236c..8aee9d62f 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -153,18 +153,17 @@ class FreshRSS_Feed extends Minz_Model { return $this->nbNotRead; } public function faviconPrepare() { - global $favicons_dir; require_once(LIB_PATH . '/favicons.php'); $url = $this->website; if ($url == '') { $url = $this->url; } - $txt = $favicons_dir . $this->hash() . '.txt'; + $txt = FAVICONS_DIR . $this->hash() . '.txt'; if (!file_exists($txt)) { file_put_contents($txt, $url); } if (FreshRSS_Context::$isCli) { - $ico = $favicons_dir . $this->hash() . '.ico'; + $ico = FAVICONS_DIR . $this->hash() . '.ico'; $ico_mtime = @filemtime($ico); $txt_mtime = @filemtime($txt); if ($txt_mtime != false && @@ -701,7 +700,7 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents($hubFilename, json_encode($hubJson)); } $ch = curl_init(); - curl_setopt_array($ch, array( + curl_setopt_array($ch, [ CURLOPT_URL => $hubJson['hub'], CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query(array( @@ -712,13 +711,9 @@ class FreshRSS_Feed extends Minz_Model { )), CURLOPT_USERAGENT => FRESHRSS_USERAGENT, CURLOPT_MAXREDIRS => 10, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_ENCODING => '', //Enable all encodings + ]); $response = curl_exec($ch); $info = curl_getinfo($ch); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 1dad4a834..fc914eefc 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -3,14 +3,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { protected function addColumn($name) { - Minz_Log::warning('FreshRSS_FeedDAO::addColumn: ' . $name); + Minz_Log::warning(__method__ . ': ' . $name); try { if ($name === 'attributes') { //v1.11.0 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN attributes TEXT'); - return $stm && $stm->execute(); + return $this->pdo->exec('ALTER TABLE `_feed` ADD COLUMN attributes TEXT') !== false; } } catch (Exception $e) { - Minz_Log::error('FreshRSS_FeedDAO::addColumn error: ' . $e->getMessage()); + Minz_Log::error(__method__ . ' error: ' . $e->getMessage()); } return false; } @@ -30,7 +29,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addFeed($valuesTmp) { $sql = ' - INSERT INTO `' . $this->prefix . 'feed` + INSERT INTO `_feed` ( url, category, @@ -48,7 +47,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $valuesTmp['url'] = safe_ascii($valuesTmp['url']); $valuesTmp['website'] = safe_ascii($valuesTmp['website']); @@ -73,7 +72,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'feed_id_seq"'); + return $this->pdo->lastInsertId('`_feed_id_seq`'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { @@ -141,8 +140,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } $set = substr($set, 0, -2); - $sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET ' . $set . ' WHERE id=?'; + $stm = $this->pdo->prepare($sql); foreach ($valuesTmp as $v) { $values[] = $v; @@ -173,7 +172,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateLastUpdate($id, $inError = false, $mtime = 0) { //See also updateCachedValue() - $sql = 'UPDATE `' . $this->prefix . 'feed` ' + $sql = 'UPDATE `_feed` ' . 'SET `lastUpdate`=?, error=? ' . 'WHERE id=?'; $values = array( @@ -181,7 +180,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $inError ? 1 : 0, $id, ); - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); @@ -199,8 +198,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $newCat = $catDAO->getDefault(); } - $sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?'; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET category=? WHERE category=?'; + $stm = $this->pdo->prepare($sql); $values = array( $newCat->id(), @@ -217,8 +216,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function deleteFeed($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_feed` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); @@ -231,8 +230,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } public function deleteFeedByCategory($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_feed` WHERE category=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); @@ -248,17 +247,16 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function selectAll() { $sql = 'SELECT id, url, category, name, website, description, `lastUpdate`, priority, ' . '`pathEntries`, `httpAuth`, error, keep_history, ttl, attributes ' - . 'FROM `' . $this->prefix . 'feed`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + . 'FROM `_feed`'; + $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { yield $row; } } public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); @@ -273,8 +271,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } public function searchByUrl($url) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE url=?'; + $stm = $this->pdo->prepare($sql); $values = array($url); @@ -290,25 +288,21 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function listFeedsIds() { - $sql = 'SELECT id FROM `' . $this->prefix . 'feed`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT id FROM `_feed`'; + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_COLUMN, 0); } public function listFeeds() { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - + $sql = 'SELECT * FROM `_feed` ORDER BY name'; + $stm = $this->pdo->query($sql); return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } public function arrayFeedCategoryNames() { //For API - $sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f ' - . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT f.id, f.name, c.name as c_name FROM `_feed` f ' + . 'INNER JOIN `_category` c ON c.id = f.category'; + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $feedCategoryNames = array(); foreach ($res as $line) { @@ -326,13 +320,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function listFeedsOrderUpdate($defaultCacheDuration = 3600, $limit = 0) { $this->updateTTL(); $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl, attributes ' - . 'FROM `' . $this->prefix . 'feed` ' + . 'FROM `_feed` ' . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl >= ' . FreshRSS_Feed::TTL_DEFAULT - . ' AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ') + . ' AND `lastUpdate` < (' . (time() + 60) + . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ') . 'ORDER BY `lastUpdate` ' . ($limit < 1 ? '' : 'LIMIT ' . intval($limit)); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $stm = $this->pdo->query($sql); + if ($stm !== false) { return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); @@ -345,8 +340,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function listByCategory($cat) { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_feed` WHERE category=? ORDER BY name'; + $stm = $this->pdo->prepare($sql); $values = array($cat); @@ -356,8 +351,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countEntries($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -366,8 +361,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=? AND is_read=0'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -375,62 +370,51 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $res[0]['count']; } - public function updateCachedValue($id) { //For multiple feeds, call updateCachedValues() - $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE - . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0) ' - . 'WHERE id=?'; - $values = array($id); - $stm = $this->bd->prepare($sql); - - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCachedValue: ' . $info[2]); - return false; + public function updateCachedValues($id = null) { + //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE + $sql = 'UPDATE `_feed` ' + . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `_entry` e1 WHERE e1.id_feed=`_feed`.id),' + . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `_entry` e2 WHERE e2.id_feed=`_feed`.id AND e2.is_read=0)' + . ($id != null ? ' WHERE id=:id' : ''); + $stm = $this->pdo->prepare($sql); + if ($id != null) { + $stm->bindParam(':id', $id, PDO::PARAM_INT); } - } - public function updateCachedValues() { //For one single feed, call updateCachedValue($id) - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)'; - $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCachedValues: ' . $info[2]); + Minz_Log::error('SQL error updateCachedValue: ' . $info[2]); return false; } } public function truncate($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $this->bd->beginTransaction(); - if (!($stm && $stm->execute($values))) { + $sql = 'DELETE FROM `_entry` WHERE id_feed=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); + $this->pdo->beginTransaction(); + if (!($stm && $stm->execute())) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } $affected = $stm->rowCount(); - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=?'; - $values = array($id); - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` ' + . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=:id'; + $stm = $this->pdo->prepare($sql); + $stm->bindParam(':id', $id, PDO::PARAM_INT); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); - $this->bd->rollBack(); + $this->pdo->rollBack(); return false; } - $this->bd->commit(); + $this->pdo->commit(); return $affected; } @@ -479,19 +463,15 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateTTL() { - $sql = <<prefix}feed` - SET ttl = :new_value - WHERE ttl = :old_value -SQL; - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_feed` SET ttl=:new_value WHERE ttl=:old_value'; + $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql); - $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3 - $stm = $this->bd->prepare($sql2); - if (!($stm && $stm->execute())) { + $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 = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2); } diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index 3c203b378..c56447df6 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -3,7 +3,7 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("PRAGMA table_info('feed')")) { + if ($tableInfo = $this->pdo->query("PRAGMA table_info('feed')")) { $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1); foreach (array('attributes') as $column) { if (!in_array($column, $columns)) { diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index 67ada73f7..cbfa79c61 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -45,13 +45,11 @@ SELECT COUNT(1) AS total, COUNT(1) - SUM(e.is_read) AS count_unreads, SUM(e.is_read) AS count_reads, SUM(e.is_favorite) AS count_favorites -FROM `{$this->prefix}entry` AS e -, `{$this->prefix}feed` AS f +FROM `_entry` AS e, `_feed` AS f WHERE e.id_feed = f.id {$filter} SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]; @@ -73,13 +71,12 @@ SQL; $sql = <<prefix}entry` +FROM `_entry` WHERE date >= {$oldest} AND date < {$midnight} GROUP BY day ORDER BY day ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); foreach ($res as $value) { @@ -143,14 +140,13 @@ SQL; $sql = <<prefix}entry` AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); $repartition = array(); @@ -207,11 +203,10 @@ SQL; SELECT COUNT(1) AS count , MIN(date) AS date_min , MAX(date) AS date_max -FROM `{$this->prefix}entry` AS e +FROM `_entry` AS e {$restrict} SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetch(PDO::FETCH_NAMED); $date_min = new \DateTime(); $date_min->setTimestamp($res['date_min']); @@ -251,14 +246,12 @@ SQL; $sql = <<prefix}category` AS c, -`{$this->prefix}feed` AS f +FROM `_category` AS c, `_feed` AS f WHERE c.id = f.category GROUP BY label ORDER BY data DESC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res; @@ -274,16 +267,13 @@ SQL; $sql = <<prefix}category` AS c, -`{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_category` AS c, `_feed` AS f, `_entry` AS e WHERE c.id = f.category AND f.id = e.id_feed GROUP BY label ORDER BY data DESC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res; @@ -300,17 +290,14 @@ SELECT f.id AS id , MAX(f.name) AS name , MAX(c.name) AS category , COUNT(e.id) AS count -FROM `{$this->prefix}category` AS c, -`{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_category` AS c, `_feed` AS f, `_entry` AS e WHERE c.id = f.category AND f.id = e.id_feed GROUP BY f.id ORDER BY count DESC LIMIT 10 SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_ASSOC); } @@ -325,14 +312,12 @@ SELECT MAX(f.id) as id , MAX(f.name) AS name , MAX(date) AS last_date , COUNT(*) AS nb_articles -FROM `{$this->prefix}feed` AS f, -`{$this->prefix}entry` AS e +FROM `_feed` AS f, `_entry` AS e WHERE f.id = e.id_feed GROUP BY f.id ORDER BY name SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); return $stm->fetchAll(PDO::FETCH_ASSOC); } diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php index 1effbb64b..4a66068cb 100644 --- a/app/Models/StatsDAOPGSQL.php +++ b/app/Models/StatsDAOPGSQL.php @@ -47,14 +47,13 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO { $sql = <<prefix}entry" AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); foreach ($res as $value) { diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php index 6cfc20463..f96f8f479 100644 --- a/app/Models/StatsDAOSQLite.php +++ b/app/Models/StatsDAOSQLite.php @@ -15,14 +15,13 @@ class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO { $sql = <<prefix}entry` AS e +FROM `_entry` AS e {$restrict} GROUP BY period ORDER BY period ASC SQL; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $stm = $this->pdo->query($sql); $res = $stm->fetchAll(PDO::FETCH_NAMED); $repartition = array(); diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 11807fc32..8af3d5e34 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -8,36 +8,24 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function createTagTable() { $ok = false; - $hadTransaction = $this->bd->inTransaction(); + $hadTransaction = $this->pdo->inTransaction(); if ($hadTransaction) { - $this->bd->commit(); + $this->pdo->commit(); } try { - require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL ALTER GUID case sensitivity...'); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); $databaseDAO->ensureCaseInsensitiveGuids(); Minz_Log::warning('SQL CREATE TABLE tag...'); - if (defined('SQL_CREATE_TABLE_TAGS')) { - $sql = sprintf(SQL_CREATE_TABLE_TAGS, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLE_TAGS; - $ok = !empty($SQL_CREATE_TABLE_TAGS); - foreach ($SQL_CREATE_TABLE_TAGS as $instruction) { - $sql = sprintf($instruction, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } + $ok = $this->pdo->exec(SQL_CREATE_TABLE_TAGS) !== false; } catch (Exception $e) { Minz_Log::error('FreshRSS_EntryDAO::createTagTable error: ' . $e->getMessage()); } if ($hadTransaction) { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } return $ok; } @@ -54,10 +42,10 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function addTag($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'tag`(name, attributes) ' - . 'SELECT * FROM (SELECT TRIM(?), TRIM(?)) t2 ' //TRIM() to provide a type hint as text for PostgreSQL - . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = TRIM(?))'; //No category of the same name - $stm = $this->bd->prepare($sql); + $sql = 'INSERT INTO `_tag`(name, attributes) ' + . 'SELECT * FROM (SELECT TRIM(?) as name, TRIM(?) as attributes) t2 ' //TRIM() gives a text type hint to PostgreSQL + . 'WHERE NOT EXISTS (SELECT 1 FROM `_category` WHERE name = TRIM(?))'; //No category of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8'); $values = array( @@ -67,7 +55,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId('"' . $this->prefix . 'tag_id_seq"'); + return $this->pdo->lastInsertId('`_tag_id_seq`'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error addTag: ' . $info[2]); @@ -88,9 +76,9 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function updateTag($id, $valuesTmp) { - $sql = 'UPDATE `' . $this->prefix . 'tag` SET name=?, attributes=? WHERE id=? ' - . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = ?)'; //No category of the same name - $stm = $this->bd->prepare($sql); + $sql = 'UPDATE `_tag` SET name=?, attributes=? WHERE id=? ' + . 'AND NOT EXISTS (SELECT 1 FROM `_category` WHERE name = ?)'; //No category of the same name + $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8'); $values = array( @@ -124,8 +112,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($id <= 0) { return false; } - $sql = 'DELETE FROM `' . $this->prefix . 'tag` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'DELETE FROM `_tag` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); @@ -139,26 +127,24 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function selectAll() { - $sql = 'SELECT id, name, attributes FROM `' . $this->prefix . 'tag`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT id, name, attributes FROM `_tag`'; + $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { yield $row; } } public function selectEntryTag() { - $sql = 'SELECT id_tag, id_entry FROM `' . $this->prefix . 'entrytag`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); + $sql = 'SELECT id_tag, id_entry FROM `_entrytag`'; + $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { yield $row; } } public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE id=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_tag` WHERE id=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -167,8 +153,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function searchByName($name) { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE name=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT * FROM `_tag` WHERE name=?'; + $stm = $this->pdo->prepare($sql); $values = array($name); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -179,17 +165,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function listTags($precounts = false) { if ($precounts) { $sql = 'SELECT t.id, t.name, count(e.id) AS unreads ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entry` e ON et.id_entry = e.id AND e.is_read = 0 ' + . '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'; } else { - $sql = 'SELECT * FROM `' . $this->prefix . 'tag` ORDER BY name'; + $sql = 'SELECT * FROM `_tag` ORDER BY name'; } - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $stm = $this->pdo->query($sql); + if ($stm !== false) { return self::daoToTag($stm->fetchAll(PDO::FETCH_ASSOC)); } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); @@ -202,9 +188,9 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function count() { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'tag`'; - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { + $sql = 'SELECT COUNT(*) AS count FROM `_tag`'; + $stm = $this->pdo->query($sql); + if ($stm !== false) { $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } else { @@ -218,8 +204,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countEntries($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` WHERE id_tag=?'; - $stm = $this->bd->prepare($sql); + $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` WHERE id_tag=?'; + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -227,10 +213,10 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` et ' - . 'INNER JOIN `' . $this->prefix . 'entry` e ON et.id_entry=e.id ' + $sql = 'SELECT COUNT(*) AS count FROM `_entrytag` et ' + . 'INNER JOIN `_entry` e ON et.id_entry=e.id ' . 'WHERE et.id_tag=? AND e.is_read=0'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id); $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_ASSOC); @@ -239,11 +225,11 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function tagEntry($id_tag, $id_entry, $checked = true) { if ($checked) { - $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `' . $this->prefix . 'entrytag`(id_tag, id_entry) VALUES(?, ?)'; + $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `_entrytag`(id_tag, id_entry) VALUES(?, ?)'; } else { - $sql = 'DELETE FROM `' . $this->prefix . 'entrytag` WHERE id_tag=? AND id_entry=?'; + $sql = 'DELETE FROM `_entrytag` WHERE id_tag=? AND id_entry=?'; } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id_tag, $id_entry); if ($stm && $stm->execute($values)) { @@ -257,11 +243,11 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function getTagsForEntry($id_entry) { $sql = 'SELECT t.id, t.name, et.id_entry IS NOT NULL as checked ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id AND et.id_entry=? ' + . 'FROM `_tag` t ' + . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id AND et.id_entry=? ' . 'ORDER BY t.name'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $values = array($id_entry); if ($stm && $stm->execute($values)) { @@ -283,8 +269,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function getTagsForEntries($entries) { $sql = 'SELECT et.id_entry, et.id_tag, t.name ' - . 'FROM `' . $this->prefix . 'tag` t ' - . 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id'; + . 'FROM `_tag` t ' + . 'INNER JOIN `_entrytag` et ON et.id_tag = t.id'; $values = array(); if (is_array($entries) && count($entries) > 0) { @@ -303,7 +289,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } } - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->fetchAll(PDO::FETCH_ASSOC); diff --git a/app/Models/TagDAOSQLite.php b/app/Models/TagDAOSQLite.php index b1deb6c65..ca0fce7ca 100644 --- a/app/Models/TagDAOSQLite.php +++ b/app/Models/TagDAOSQLite.php @@ -7,7 +7,7 @@ class FreshRSS_TagDAOSQLite extends FreshRSS_TagDAO { } protected function autoUpdateDb($errorInfo) { - if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) { + if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='tag'")) { $showCreate = $tableInfo->fetchColumn(); if (stripos($showCreate, 'tag') === false) { return $this->createTagTable(); //v1.12.0 diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 6292cc09f..7580de06e 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -1,43 +1,22 @@ bd->dbType() . '.php'); - - $currentLanguage = Minz_Translate::language(); + public function createUser($insertDefaultFeeds = false) { + require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); try { - if ($new_user_language != null) { - Minz_Translate::reset($new_user_language); - } - $ok = false; - if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL - $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, $this->prefix, _t('gen.short.default_category')); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { //E.g. SQLite - global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS; - if (is_array($SQL_CREATE_TABLES)) { - $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS); - $ok = !empty($instructions); - foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $this->prefix, _t('gen.short.default_category')); - $stm = $this->bd->prepare($sql); - $ok &= ($stm && $stm->execute()); - } - } - } + $sql = SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS; + $ok = $this->pdo->exec($sql) !== false; //Note: Only exec() can take multiple statements safely. if ($ok && $insertDefaultFeeds) { $default_feeds = FreshRSS_Context::$system_conf->default_feeds; + $stm = $this->pdo->prepare(SQL_INSERT_FEED); foreach ($default_feeds as $feed) { - $sql = sprintf(SQL_INSERT_FEED, $this->prefix); - $stm = $this->bd->prepare($sql); - $parameters = array( + $parameters = [ ':url' => $feed['url'], ':name' => $feed['name'], ':website' => $feed['website'], ':description' => $feed['description'], - ); + ]; $ok &= ($stm && $stm->execute($parameters)); } } @@ -45,8 +24,6 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { Minz_Log::error('Error while creating database for user: ' . $e->getMessage()); } - Minz_Translate::reset($currentLanguage); - if ($ok) { return true; } else { @@ -61,25 +38,9 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { fwrite(STDERR, 'Deleting SQL data for user “' . $this->current_user . "”…\n"); } - require_once(APP_PATH . '/SQL/install.sql.' . $this->bd->dbType() . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); - $ok = false; - if (defined('SQL_DROP_TABLES')) { //E.g. MySQL - $sql = sprintf(SQL_DROP_TABLES, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok = $stm && $stm->execute(); - } else { //E.g. SQLite - global $SQL_DROP_TABLES; - if (is_array($SQL_DROP_TABLES)) { - $instructions = $SQL_DROP_TABLES; - $ok = !empty($instructions); - foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $this->prefix); - $stm = $this->bd->prepare($sql); - $ok &= ($stm && $stm->execute()); - } - } - } + $ok = $this->pdo->exec(SQL_DROP_TABLES) !== false; if ($ok) { return true; diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index 0f396f701..328e8acc0 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -1,20 +1,22 @@ ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . '; -ALTER TABLE `%1$scategory` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -OPTIMIZE TABLE `%1$scategory`; - -ALTER TABLE `%1$sfeed` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -UPDATE `%1$sfeed` SET name=SUBSTRING(name,1,' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') WHERE LENGTH(name) > ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . '; -ALTER TABLE `%1$sfeed` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -ALTER TABLE `%1$sfeed` MODIFY `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -OPTIMIZE TABLE `%1$sfeed`; - -ALTER TABLE `%1$sentry` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -ALTER TABLE `%1$sentry` MODIFY `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; -ALTER TABLE `%1$sentry` MODIFY `author` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -ALTER TABLE `%1$sentry` MODIFY `tags` VARCHAR(1023) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -OPTIMIZE TABLE `%1$sentry`; -'); - -define('SQL_UPDATE_GUID_LATIN1_BIN', ' -- v1.12 -ALTER TABLE `%1$sentrytmp` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -ALTER TABLE `%1$sentry` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -'); +const SQL_UPDATE_GUID_LATIN1_BIN = <<<'SQL' +ALTER TABLE `_entrytmp` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; -- v1.12 +ALTER TABLE `_entry` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL; +SQL; diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php index ef08ca940..dfacc38e7 100644 --- a/app/SQL/install.sql.pgsql.php +++ b/app/SQL/install.sql.pgsql.php @@ -1,14 +1,15 @@ title; - $_SESSION['old_entries'] = param('old_entries', $user_default_config->old_entries); - $_SESSION['auth_type'] = param('auth_type', 'form'); - if (FreshRSS_user_Controller::checkUsername(param('default_user', ''))) { - $_SESSION['default_user'] = param('default_user', ''); - } - - $password_plain = param('passwordPlain', false); - if ($password_plain !== false && cryptAvailable()) { - $_SESSION['passwordHash'] = FreshRSS_user_Controller::hashPassword($password_plain); - } - - if (empty($_SESSION['old_entries']) || - empty($_SESSION['auth_type']) || - empty($_SESSION['default_user'])) { - return false; - } - - if ($_SESSION['auth_type'] === 'form' && empty($_SESSION['passwordHash'])) { - return false; - } - - $_SESSION['salt'] = generateSalt(); - if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { - $_SESSION['old_entries'] = $user_default_config->old_entries; - } - - $token = ''; - - $config_array = array( - 'language' => $_SESSION['language'], - 'theme' => $user_default_config->theme, - 'old_entries' => $_SESSION['old_entries'], - 'passwordHash' => $_SESSION['passwordHash'], - 'token' => $token, - ); - - // Create default user files but first, we delete previous data to - // avoid access right problems. - $user_dir = join_path(USERS_PATH, $_SESSION['default_user']); - $user_config_path = join_path($user_dir, 'config.php'); - - recursive_unlink($user_dir); - mkdir($user_dir); - file_put_contents($user_config_path, " $_SESSION['salt'], + $config_array = [ + 'salt' => generateSalt(), 'base_url' => $base_url, - 'title' => $_SESSION['title'], - 'default_user' => $_SESSION['default_user'], - 'auth_type' => $_SESSION['auth_type'], - 'db' => array( + 'default_user' => 'admin', + 'db' => [ 'type' => $_SESSION['bd_type'], 'host' => $_SESSION['bd_host'], 'user' => $_SESSION['bd_user'], 'password' => $_SESSION['bd_password'], 'base' => $_SESSION['bd_base'], 'prefix' => $_SESSION['bd_prefix'], - 'pdo_options' => array(), - ), + 'pdo_options' => [], + ], 'pubsubhubbub_enabled' => server_is_public($base_url), - ); + ]; + if (!empty($_SESSION['title'])) { + $config_array['title'] = $_SESSION['title']; + } + if (!empty($_SESSION['auth_type'])) { + $config_array['auth_type'] = $_SESSION['auth_type']; + } + + @unlink(DATA_PATH . '/config.php'); //To avoid access-rights problems + file_put_contents(DATA_PATH . '/config.php', "title; + $_SESSION['old_entries'] = param('old_entries', $user_default_config->old_entries); + $_SESSION['auth_type'] = param('auth_type', 'form'); + if (FreshRSS_user_Controller::checkUsername(param('default_user', ''))) { + $_SESSION['default_user'] = param('default_user', ''); + } + + if (empty($_SESSION['old_entries']) || + empty($_SESSION['auth_type']) || + empty($_SESSION['default_user'])) { + return false; + } + + $password_plain = param('passwordPlain', false); + if ($_SESSION['auth_type'] === 'form' && $password_plain == '') { + return false; + } + + Minz_Configuration::register('system', DATA_PATH . '/config.php', FRESHRSS_PATH . '/config.default.php'); + FreshRSS_Context::$system_conf = Minz_Configuration::get('system'); + Minz_Translate::init($_SESSION['language']); + + FreshRSS_Context::$system_conf->default_user = $_SESSION['default_user']; + FreshRSS_Context::$system_conf->save(); + + if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { + $_SESSION['old_entries'] = $user_default_config->old_entries; + } + + // Create default user files but first, we delete previous data to + // avoid access right problems. + recursive_unlink(USERS_PATH . '/' . $_SESSION['default_user']); + + $ok = false; + try { + $ok = FreshRSS_user_Controller::createUser( + $_SESSION['default_user'], + '', //TODO: Add e-mail + $password_plain, + '', + [ + 'language' => $_SESSION['language'], + 'old_entries' => $_SESSION['old_entries'], + ] + ); + } catch (Exception $e) { + $_SESSION['bd_error'] = $e->getMessage(); + $ok = false; + } + if (!$ok) { + return false; + } + + header('Location: index.php?step=4'); + } +} /*** VÉRIFICATIONS ***/ function checkStep() { @@ -297,29 +291,6 @@ function freshrss_already_installed() { } function checkStep2() { - $conf = !empty($_SESSION['old_entries']) && - !empty($_SESSION['default_user']); - - $form = ( - isset($_SESSION['auth_type']) && - ($_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash'])) - ); - - $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; - if ($defaultUser === null) { - $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; - } - $data = is_writable(join_path(USERS_PATH, $defaultUser, 'config.php')); - - return array( - 'conf' => $conf ? 'ok' : 'ko', - 'form' => $form ? 'ok' : 'ko', - 'data' => $data ? 'ok' : 'ko', - 'all' => $conf && $form && $data ? 'ok' : 'ko' - ); -} - -function checkStep3() { $conf = is_writable(join_path(DATA_PATH, 'config.php')); $bd = isset($_SESSION['bd_type']) && @@ -331,61 +302,35 @@ function checkStep3() { isset($_SESSION['bd_error']); $conn = empty($_SESSION['bd_error']); - return array( + return [ 'bd' => $bd ? 'ok' : 'ko', 'conn' => $conn ? 'ok' : 'ko', 'conf' => $conf ? 'ok' : 'ko', - 'all' => $bd && $conn && $conf ? 'ok' : 'ko' - ); + 'all' => $bd && $conn && $conf ? 'ok' : 'ko', + ]; } -function checkDbUser(&$dbOptions) { - $ok = false; - $str = $dbOptions['dsn']; - $driver_options = $dbOptions['options']; - try { - $c = new PDO($str, $dbOptions['user'], $dbOptions['password'], $driver_options); - if (defined('SQL_CREATE_TABLES')) { - $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, - $dbOptions['prefix_user'], _t('gen.short.default_category')); - $stm = $c->prepare($sql); - $ok = $stm && $stm->execute(); - } else { - global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS; - $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS); - $ok = !empty($instructions); - foreach ($instructions as $instruction) { - $sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category')); - $stm = $c->prepare($sql); - $ok &= $stm && $stm->execute(); - } - } +function checkStep3() { + $conf = !empty($_SESSION['old_entries']) && + !empty($_SESSION['default_user']); - Minz_Configuration::register( - 'system', - join_path(DATA_PATH, 'config.php'), - join_path(FRESHRSS_PATH, 'config.default.php') - ); - $system_conf = Minz_Configuration::get('system'); - $default_feeds = $system_conf->default_feeds; - foreach ($default_feeds as $feed) { - $sql = sprintf(SQL_INSERT_FEED, $dbOptions['prefix_user']); - $stm = $c->prepare($sql); - $parameters = array( - ':url' => $feed['url'], - ':name' => $feed['name'], - ':website' => $feed['website'], - ':description' => $feed['description'], - ); - $ok &= ($stm && $stm->execute($parameters)); - } - } catch (PDOException $e) { - $ok = false; - $dbOptions['error'] = $e->getMessage(); + $form = isset($_SESSION['auth_type']); + + $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; + if ($defaultUser === null) { + $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; } - return $ok; + $data = is_writable(join_path(USERS_PATH, $defaultUser, 'config.php')); + + return [ + 'conf' => $conf ? 'ok' : 'ko', + 'form' => $form ? 'ok' : 'ko', + 'data' => $data ? 'ok' : 'ko', + 'all' => $conf && $form && $data ? 'ok' : 'ko', + ]; } + /*** AFFICHAGE ***/ function printStep0() { $actual = Minz_Translate::language(); @@ -544,83 +489,15 @@ function printStep1() { } function printStep2() { - $user_default_config = Minz_Configuration::get('default_user'); -?> - -

- -

- - -
- - -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
-
- tabindex="5" /> - -
- - -
-
- -
-
- - - - - -
-
-
- - +

- +

-
+
@@ -685,6 +562,74 @@ function printStep3() {
+
+
+ + + + + +
+
+
+ + +

+ +

+ + +
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ tabindex="5" /> + +
+ + +
+
+
@@ -763,8 +708,8 @@ case 5:
  • -
  • -
  • +
  • +
  • diff --git a/app/views/helpers/category/update.phtml b/app/views/helpers/category/update.phtml index a2ee3e2ef..b64bd786a 100644 --- a/app/views/helpers/category/update.phtml +++ b/app/views/helpers/category/update.phtml @@ -11,7 +11,10 @@
    - + category->id() == FreshRSS_CategoryDAO::DEFAULTCATEGORYID ? 'disabled="disabled"' : ''; + ?> />
    diff --git a/cli/do-install.php b/cli/do-install.php index fd5aa4a3c..dea5d235e 100755 --- a/cli/do-install.php +++ b/cli/do-install.php @@ -3,7 +3,7 @@ require(__DIR__ . '/_cli.php'); if (!file_exists(DATA_PATH . '/do-install.txt')) { - fail('FreshRSS looks to be already installed! Please use `./cli/reconfigure.php` instead.'); + fail('FreshRSS seems to be already installed! Please use `./cli/reconfigure.php` instead.'); } $params = array( @@ -82,10 +82,9 @@ if (file_put_contents(join_path(DATA_PATH, 'config.php'), fail('FreshRSS could not write configuration file!: ' . join_path(DATA_PATH, 'config.php')); } -$config['db']['default_user'] = $config['default_user']; -if (!checkDb($config['db'])) { +if (!checkDb()) { @unlink(join_path(DATA_PATH, 'config.php')); - fail('FreshRSS database error: ' . (empty($config['db']['error']) ? 'Unknown error' : $config['db']['error'])); + fail('FreshRSS database error: ' . (empty($_SESSION['bd_error']) ? 'Unknown error' : $_SESSION['bd_error'])); } echo '• Remember to create the default user: ', $config['default_user'] , "\n", diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 4d5e47da9..3fabb73c8 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -6,45 +6,35 @@ /** * La classe Model_sql représente le modèle interragissant avec les bases de données - * Seul la connexion MySQL est prise en charge pour le moment */ class Minz_ModelPdo { /** * Partage la connexion à la base de données entre toutes les instances. */ - public static $useSharedBd = true; - private static $sharedBd = null; + public static $usesSharedPdo = true; + private static $sharedPdo = null; private static $sharedPrefix; private static $sharedCurrentUser; - /** - * $bd variable représentant la base de données - */ - protected $bd; - + protected $pdo; protected $current_user; - protected $prefix; /** * Créé la connexion à la base de données à l'aide des variables * HOST, BASE, USER et PASS définies dans le fichier de configuration */ - public function __construct($currentUser = null, $currentPrefix = null, $currentDb = null) { + public function __construct($currentUser = null, $currentPdo = null) { if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser'); } - if ($currentPrefix !== null) { - $this->prefix = $currentPrefix; - } - if ($currentDb != null) { - $this->bd = $currentDb; + if ($currentPdo != null) { + $this->pdo = $currentPdo; return; } - if (self::$useSharedBd && self::$sharedBd != null && - ($currentUser == null || $currentUser === self::$sharedCurrentUser)) { - $this->bd = self::$sharedBd; - $this->prefix = self::$sharedPrefix; + if (self::$usesSharedPdo && self::$sharedPdo != null && + ($currentUser == '' || $currentUser === self::$sharedCurrentUser)) { + $this->pdo = self::$sharedPdo; $this->current_user = self::$sharedCurrentUser; return; } @@ -54,35 +44,39 @@ class Minz_ModelPdo { $conf = Minz_Configuration::get('system'); $db = $conf->db; - $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); + $driver_options = isset($db['pdo_options']) && is_array($db['pdo_options']) ? $db['pdo_options'] : []; $dbServer = parse_url('db://' . $db['host']); + $dsn = ''; try { switch ($db['type']) { case 'mysql': - $string = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';dbname=' . $db['base'] . ';charset=utf8mb4'; + $dsn = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';charset=utf8mb4'; + if (!empty($db['base'])) { + $dsn .= ';dbname=' . $db['base']; + } if (!empty($dbServer['port'])) { - $string .= ';port=' . $dbServer['port']; + $dsn .= ';port=' . $dbServer['port']; } $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; - $this->prefix = $db['prefix'] . $currentUser . '_'; - $this->bd = new MinzPDOMySql($string, $db['user'], $db['password'], $driver_options); - $this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + $this->pdo = new MinzPDOMySql($dsn, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix($db['prefix'] . $currentUser . '_'); break; case 'sqlite': - $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); - $this->prefix = ''; - $this->bd = new MinzPDOSQLite($string, $db['user'], $db['password'], $driver_options); - $this->bd->exec('PRAGMA foreign_keys = ON;'); + $dsn = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); + $this->pdo = new MinzPDOSQLite($dsn, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix(''); break; case 'pgsql': - $string = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';dbname=' . $db['base']; + $dsn = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']); + if (!empty($db['base'])) { + $dsn .= ';dbname=' . $db['base']; + } if (!empty($dbServer['port'])) { - $string .= ';port=' . $dbServer['port']; + $dsn .= ';port=' . $dbServer['port']; } - $this->prefix = $db['prefix'] . $currentUser . '_'; - $this->bd = new MinzPDOPGSQL($string, $db['user'], $db['password'], $driver_options); - $this->bd->exec("SET NAMES 'UTF8';"); + $this->pdo = new MinzPDOPGSQL($dsn, $db['user'], $db['password'], $driver_options); + $this->pdo->setPrefix($db['prefix'] . $currentUser . '_'); break; default: throw new Minz_PDOConnectionException( @@ -91,69 +85,86 @@ class Minz_ModelPdo { ); break; } - self::$sharedBd = $this->bd; - self::$sharedPrefix = $this->prefix; + self::$sharedPdo = $this->pdo; } catch (Exception $e) { throw new Minz_PDOConnectionException( - $string, + $dsn, $db['user'], Minz_Exception::ERROR ); } } public function beginTransaction() { - $this->bd->beginTransaction(); + $this->pdo->beginTransaction(); } public function inTransaction() { - return $this->bd->inTransaction(); + return $this->pdo->inTransaction(); } public function commit() { - $this->bd->commit(); + $this->pdo->commit(); } public function rollBack() { - $this->bd->rollBack(); + $this->pdo->rollBack(); } public static function clean() { - self::$sharedBd = null; + self::$sharedPdo = null; self::$sharedCurrentUser = ''; - self::$sharedPrefix = ''; } } abstract class MinzPDO extends PDO { - private static function check($statement) { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + abstract public function dbType(); + + private $prefix = ''; + public function prefix() { return $this->prefix; } + public function setPrefix($prefix) { $this->prefix = $prefix; } + + private function autoPrefix($sql) { + return str_replace('`_', '`' . $this->prefix, $sql); + } + + protected function preSql($statement) { if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { invalidateHttpCache(); } + return $this->autoPrefix($statement); } - protected function compatibility($statement) { - return $statement; + public function lastInsertId($name = null) { + if ($name != null) { + $name = $this->preSql($name); + } + return parent::lastInsertId($name); } - abstract public function dbType(); - public function prepare($statement, $driver_options = array()) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::prepare($statement, $driver_options); } public function exec($statement) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::exec($statement); } public function query($statement) { - MinzPDO::check($statement); - $statement = $this->compatibility($statement); + $statement = $this->preSql($statement); return parent::query($statement); } } class MinzPDOMySql extends MinzPDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + } + public function dbType() { return 'mysql'; } @@ -164,6 +175,11 @@ class MinzPDOMySql extends MinzPDO { } class MinzPDOSQLite extends MinzPDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->exec('PRAGMA foreign_keys = ON;'); + } + public function dbType() { return 'sqlite'; } @@ -174,11 +190,17 @@ class MinzPDOSQLite extends MinzPDO { } class MinzPDOPGSQL extends MinzPDO { + public function __construct($dsn, $username = null, $passwd = null, $options = null) { + parent::__construct($dsn, $username, $passwd, $options); + $this->exec("SET NAMES 'UTF8';"); + } + public function dbType() { return 'pgsql'; } - protected function compatibility($statement) { + protected function preSql($statement) { + $statement = parent::preSql($statement); return str_replace(array('`', ' LIKE '), array('"', ' ILIKE '), $statement); } } diff --git a/lib/favicons.php b/lib/favicons.php index 7a2d1187e..bc82b57b9 100644 --- a/lib/favicons.php +++ b/lib/favicons.php @@ -1,6 +1,6 @@ true, CURLOPT_TIMEOUT => 15, CURLOPT_USERAGENT => FRESHRSS_USERAGENT, CURLOPT_MAXREDIRS => 10, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_ENCODING => '', //Enable all encodings + ]); curl_setopt_array($ch, $curlOptions); $response = curl_exec($ch); $info = curl_getinfo($ch); @@ -89,7 +85,6 @@ function searchFavicon(&$url) { } function download_favicon($url, $dest) { - global $default_favicon; $url = trim($url); $favicon = searchFavicon($url); if ($favicon == '') { @@ -109,5 +104,5 @@ function download_favicon($url, $dest) { } } return ($favicon != '' && file_put_contents($dest, $favicon)) || - @copy($default_favicon, $dest); + @copy(DEFAULT_FAVICON, $dest); } diff --git a/lib/lib_install.php b/lib/lib_install.php index 17defccf6..ed361eb39 100644 --- a/lib/lib_install.php +++ b/lib/lib_install.php @@ -78,69 +78,24 @@ function generateSalt() { return sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); } -function checkDb(&$dbOptions) { - $dsn = ''; - $driver_options = null; - prepareSyslog(); - try { - switch ($dbOptions['type']) { - case 'mysql': - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4' - ); - try { // on ouvre une connexion juste pour créer la base si elle n'existe pas - $dsn = 'mysql:host=' . $dbOptions['host'] . ';'; - $c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options); - $sql = sprintf(SQL_CREATE_DB, $dbOptions['base']); - $res = $c->query($sql); - } catch (PDOException $e) { - syslog(LOG_DEBUG, 'FreshRSS MySQL warning: ' . $e->getMessage()); - } - // on écrase la précédente connexion en sélectionnant la nouvelle BDD - $dsn = 'mysql:host=' . $dbOptions['host'] . ';dbname=' . $dbOptions['base']; - break; - case 'sqlite': - include_once(APP_PATH . '/SQL/install.sql.sqlite.php'); - $path = join_path(USERS_PATH, $dbOptions['default_user']); - if (!is_dir($path)) { - mkdir($path); - } - $dsn = 'sqlite:' . join_path($path, 'db.sqlite'); - $driver_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - break; - case 'pgsql': - include_once(APP_PATH . '/SQL/install.sql.pgsql.php'); - $driver_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - try { // on ouvre une connexion juste pour créer la base si elle n'existe pas - $dsn = 'pgsql:host=' . $dbOptions['host'] . ';dbname=postgres'; - $c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options); - $sql = sprintf(SQL_CREATE_DB, $dbOptions['base']); - $res = $c->query($sql); - } catch (PDOException $e) { - syslog(LOG_DEBUG, 'FreshRSS PostgreSQL warning: ' . $e->getMessage()); - } - // on écrase la précédente connexion en sélectionnant la nouvelle BDD - $dsn = 'pgsql:host=' . $dbOptions['host'] . ';dbname=' . $dbOptions['base']; - break; - default: - return false; - } - - $c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options); - $res = $c->query('SELECT 1'); - } catch (PDOException $e) { - $dsn = ''; - syslog(LOG_DEBUG, 'FreshRSS SQL warning: ' . $e->getMessage()); - $dbOptions['error'] = $e->getMessage(); +function checkDb() { + $conf = FreshRSS_Context::$system_conf; + $db = $conf->db; + if (empty($db['pdo_options'])) { + $db['pdo_options'] = []; } - $dbOptions['dsn'] = $dsn; - $dbOptions['options'] = $driver_options; - return $dsn != ''; + $db['pdo_options'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; + $dbBase = isset($db['base']) ? $db['base'] : ''; + + $db['base'] = ''; //First connection without database name to create the database + Minz_ModelPdo::$usesSharedPdo = false; + $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); + $databaseDAO->create(); + + $db['base'] = $dbBase; //New connection with the database name + $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); + Minz_ModelPdo::$usesSharedPdo = true; + return $databaseDAO->testConnection(); } function deleteInstall() { diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 1bba60c36..854126b54 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -535,17 +535,16 @@ function _i($icon, $url_only = false) { } -$SHORTCUT_KEYS = array( //No const for < PHP 5.6 compatibility +const SHORTCUT_KEYS = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Backspace', 'Delete', 'End', 'Enter', 'Escape', 'Home', 'Insert', 'PageDown', 'PageUp', 'Space', 'Tab', - ); + ]; function validateShortcutList($shortcuts) { - global $SHORTCUT_KEYS; $legacy = array( 'down' => 'ArrowDown', 'left' => 'ArrowLeft', 'page_down' => 'PageDown', 'page_up' => 'PageUp', 'right' => 'ArrowRight', 'up' => 'ArrowUp', @@ -554,17 +553,17 @@ function validateShortcutList($shortcuts) { $shortcuts_ok = array(); foreach ($shortcuts as $key => $value) { - if (in_array($value, $SHORTCUT_KEYS)) { + if (in_array($value, SHORTCUT_KEYS)) { $shortcuts_ok[$key] = $value; } elseif (isset($legacy[$value])) { $shortcuts_ok[$key] = $legacy[$value]; } else { //Case-insensitive search if ($upper === null) { - $upper = array_map('strtoupper', $SHORTCUT_KEYS); + $upper = array_map('strtoupper', SHORTCUT_KEYS); } $i = array_search(strtoupper($value), $upper); if ($i !== false) { - $shortcuts_ok[$key] = $SHORTCUT_KEYS[$i]; + $shortcuts_ok[$key] = SHORTCUT_KEYS[$i]; } } } diff --git a/p/api/fever.php b/p/api/fever.php index b81646928..30b85dafd 100644 --- a/p/api/fever.php +++ b/p/api/fever.php @@ -95,7 +95,7 @@ class FeverDAO extends Minz_ModelPdo $sql = 'SELECT id, guid, title, author, ' . ($entryDAO->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed ' - . 'FROM `' . $this->prefix . 'entry` WHERE'; + . 'FROM `_entry` WHERE'; if (!empty($entry_ids)) { $bindEntryIds = $this->bindParamArray('id', $entry_ids, $values); @@ -120,7 +120,7 @@ class FeverDAO extends Minz_ModelPdo $sql .= $order; $sql .= ' LIMIT 50'; - $stm = $this->bd->prepare($sql); + $stm = $this->pdo->prepare($sql); $stm->execute($values); $result = $stm->fetchAll(PDO::FETCH_ASSOC); diff --git a/p/api/greader.php b/p/api/greader.php index b6777796a..77e498524 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -76,12 +76,6 @@ function multiplePosts($name) { //https://bugs.php.net/bug.php?id=51633 return $result; } -class MyPDO extends Minz_ModelPdo { - function prepare($sql) { - return $this->bd->prepare(str_replace('%_', $this->prefix, $sql)); - } -} - function debugInfo() { if (function_exists('getallheaders')) { $ALL_HEADERS = getallheaders(); @@ -239,9 +233,8 @@ function userInfo() { //https://github.com/theoldreader/api#user-info function tagList() { header('Content-Type: application/json; charset=UTF-8'); - $pdo = new MyPDO(); - $stm = $pdo->prepare('SELECT c.name FROM `%_category` c'); - $stm->execute(); + $model = new Minz_ModelPdo(); + $stm = $model->pdo->query('SELECT c.name FROM `_category` c'); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); $tags = array( @@ -277,10 +270,11 @@ function tagList() { function subscriptionList() { header('Content-Type: application/json; charset=UTF-8'); - $pdo = new MyPDO(); - $stm = $pdo->prepare('SELECT f.id, f.name, f.url, f.website, c.id as c_id, c.name as c_name FROM `%_feed` f - INNER JOIN `%_category` c ON c.id = f.category AND f.priority >= :priority_normal'); - $stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL)); + $model = new Minz_ModelPdo(); + $stm = $model->pdo->prepare('SELECT f.id, f.name, f.url, f.website, c.id as c_id, c.name as c_name FROM `_feed` f + INNER JOIN `_category` c ON c.id = f.category AND f.priority >= :priority_normal'); + $stm->bindValue(':priority_normal', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $salt = FreshRSS_Context::$system_conf->salt; diff --git a/p/f.php b/p/f.php index b68109cd5..9947539c3 100644 --- a/p/f.php +++ b/p/f.php @@ -5,13 +5,11 @@ require(LIB_PATH . '/favicons.php'); require(LIB_PATH . '/http-conditional.php'); function show_default_favicon($cacheSeconds = 3600) { - global $default_favicon; - header('Content-Disposition: inline; filename="default_favicon.ico"'); - $default_mtime = @filemtime($default_favicon); + $default_mtime = @filemtime(DEFAULT_FAVICON); if (!httpConditional($default_mtime, $cacheSeconds, 2)) { - readfile($default_favicon); + readfile(DEFAULT_FAVICON); } } @@ -20,8 +18,8 @@ if (!ctype_xdigit($id)) { $id = '0'; } -$txt = $favicons_dir . $id . '.txt'; -$ico = $favicons_dir . $id . '.ico'; +$txt = FAVICONS_DIR . $id . '.txt'; +$ico = FAVICONS_DIR . $id . '.ico'; $ico_mtime = @filemtime($ico); $txt_mtime = @filemtime($txt); -- cgit v1.2.3 From be4c942cb3bd2b41dc2e01eb437dfd4f2f73ab4a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 6 Oct 2019 00:14:19 +0200 Subject: SQL allow recreating existing user (#2555) * SQL allow recreating existing user Taking advantage of https://github.com/FreshRSS/FreshRSS/pull/2554 In a case when FreshRSS data is lost, but database data still intact (in particular MySQL or PostgreSQL), this patch allows recreating previous users without error * Better error retrieval Especially when error occur during the prepare statement --- app/Models/CategoryDAO.php | 8 ++++---- app/Models/EntryDAO.php | 36 ++++++++++++++++++------------------ app/Models/EntryDAOSQLite.php | 12 ++++++------ app/Models/FeedDAO.php | 24 ++++++++++++------------ app/Models/TagDAO.php | 16 ++++++++-------- app/Models/UserDAO.php | 4 ++-- app/SQL/install.sql.mysql.php | 11 ++++++----- app/SQL/install.sql.pgsql.php | 8 +++++--- app/SQL/install.sql.sqlite.php | 10 ++++++---- 9 files changed, 67 insertions(+), 62 deletions(-) (limited to 'app/Models/UserDAO.php') diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 2bae5ef83..dd49b542d 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -19,7 +19,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($stm && $stm->execute($values)) { return $this->pdo->lastInsertId('`_category_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error addCategory: ' . $info[2]); return false; } @@ -53,7 +53,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCategory: ' . $info[2]); return false; } @@ -69,7 +69,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($stm && $stm->execute()) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteCategory: ' . $info[2]); return false; } @@ -174,7 +174,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($stm && $stm->execute($values)) { return $this->pdo->lastInsertId('`_category_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error check default category: ' . json_encode($info)); return false; } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b7ce89094..cf896eafd 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -28,9 +28,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { try { require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL CREATE TABLE entrytmp...'); - $ok = $this->pdo->exec(SQL_CREATE_TABLE_ENTRYTMP) !== false; - } catch (Exception $e) { - Minz_Log::error('FreshRSS_EntryDAO::createEntryTempTable error: ' . $e->getMessage()); + $ok = $this->pdo->exec(SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_INDEX_ENTRY_1) !== false; + } catch (Exception $ex) { + Minz_Log::error(__method__ . ' error: ' . $ex->getMessage()); } if ($hadTransaction) { $this->pdo->beginTransaction(); @@ -129,7 +129,7 @@ SQL; if ($this->addEntryPrepared && $this->addEntryPrepared->execute()) { return true; } else { - $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); + $info = $this->addEntryPrepared == null ? $this->pdo->errorInfo() : $this->addEntryPrepared->errorInfo(); if ($this->autoUpdateDb($info)) { $this->addEntryPrepared = null; return $this->addEntry($valuesTmp); @@ -215,7 +215,7 @@ SQL; if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute()) { return true; } else { - $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo(); + $info = $this->updateEntryPrepared == null ? $this->pdo->errorInfo() : $this->updateEntryPrepared->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateEntry($valuesTmp); } @@ -252,7 +252,7 @@ SQL; if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markFavorite: ' . $info[2]); return false; } @@ -297,7 +297,7 @@ SQL; if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } @@ -334,7 +334,7 @@ SQL; $values = array_merge($values, $ids); $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } @@ -353,7 +353,7 @@ SQL; if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } @@ -402,7 +402,7 @@ SQL; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } @@ -440,7 +440,7 @@ SQL; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } @@ -479,7 +479,7 @@ SQL; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed: ' . $info[2] . ' with SQL: ' . $sql . $search); $this->pdo->rollBack(); return false; @@ -493,7 +493,7 @@ SQL; $stm = $this->pdo->prepare($sql); $stm->bindParam(':id', $id_feed, PDO::PARAM_INT); if (!($stm && $stm->execute())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed cache: ' . $info[2]); $this->pdo->rollBack(); return false; @@ -533,7 +533,7 @@ SQL; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); return false; } @@ -565,7 +565,7 @@ SQL; if ($stm && $stm->execute()) { return $stm->rowCount(); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->cleanOldEntries($id_feed, $date_min, $keep); } @@ -838,7 +838,7 @@ SQL; if ($stm && $stm->execute($values)) { return $stm; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error listWhereRaw: ' . $info[2]); return false; } @@ -896,7 +896,7 @@ SQL; } return $result; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listHashForFeedGuids($id_feed, $guids); } @@ -920,7 +920,7 @@ SQL; if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateLastSeen($id_feed, $guids); } diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index d9abefc4b..12e8f27e1 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -88,7 +88,7 @@ DROP TABLE IF EXISTS `tmp`; if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } @@ -123,7 +123,7 @@ DROP TABLE IF EXISTS `tmp`; $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead 1: ' . $info[2]); $this->pdo->rollBack(); return false; @@ -135,7 +135,7 @@ DROP TABLE IF EXISTS `tmp`; $values = array($ids); $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markRead 2: ' . $info[2]); $this->pdo->rollBack(); return false; @@ -186,7 +186,7 @@ DROP TABLE IF EXISTS `tmp`; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } @@ -225,7 +225,7 @@ DROP TABLE IF EXISTS `tmp`; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } @@ -264,7 +264,7 @@ DROP TABLE IF EXISTS `tmp`; $stm = $this->pdo->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error markReadTag: ' . $info[2]); return false; } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index fc914eefc..d4a91c145 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -74,7 +74,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $this->pdo->lastInsertId('`_feed_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->addFeed($valuesTmp); } @@ -151,7 +151,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->updateFeed($id, $valuesTmp); } @@ -185,7 +185,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateLastUpdate: ' . $info[2]); return false; } @@ -209,7 +209,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error changeCategory: ' . $info[2]); return false; } @@ -224,7 +224,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteFeed: ' . $info[2]); return false; } @@ -238,7 +238,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]); return false; } @@ -330,7 +330,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm !== false) { return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listFeedsOrderUpdate($defaultCacheDuration); } @@ -384,7 +384,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute()) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateCachedValue: ' . $info[2]); return false; } @@ -396,7 +396,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $stm->bindParam(':id', $id, PDO::PARAM_INT); $this->pdo->beginTransaction(); if (!($stm && $stm->execute())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); $this->pdo->rollBack(); return false; @@ -408,7 +408,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $stm = $this->pdo->prepare($sql); $stm->bindParam(':id', $id, PDO::PARAM_INT); if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); $this->pdo->rollBack(); return false; @@ -466,13 +466,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'UPDATE `_feed` SET ttl=:new_value WHERE ttl=:old_value'; $stm = $this->pdo->prepare($sql); if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql); $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 = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2); } } else { diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 8af3d5e34..9c0f591c9 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -57,7 +57,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $this->pdo->lastInsertId('`_tag_id_seq`'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error addTag: ' . $info[2]); return false; } @@ -91,7 +91,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error updateTag: ' . $info[2]); return false; } @@ -120,7 +120,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error deleteTag: ' . $info[2]); return false; } @@ -178,7 +178,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm !== false) { return self::daoToTag($stm->fetchAll(PDO::FETCH_ASSOC)); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->listTags($precounts); } @@ -194,7 +194,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->count(); } @@ -235,7 +235,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error tagEntry: ' . $info[2]); return false; } @@ -258,7 +258,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $lines; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->getTagsForEntry($id_entry); } @@ -294,7 +294,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($stm && $stm->execute($values)) { return $stm->fetchAll(PDO::FETCH_ASSOC); } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { return $this->getTagsForEntries($entries); } diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 7580de06e..8e7e977d0 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -27,7 +27,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { if ($ok) { return true; } else { - $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = empty($stm) ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); return false; } @@ -45,7 +45,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { if ($ok) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error(__METHOD__ . ' error: ' . $info[2]); return false; } diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index a5db1b216..87b5d1989 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -57,14 +57,18 @@ CREATE TABLE IF NOT EXISTS `_entry` ( UNIQUE KEY (`id_feed`,`guid`), -- v0.7 INDEX (`is_favorite`), -- v0.7 INDEX (`is_read`), -- v0.7 - INDEX `entry_lastSeen_index` (`lastSeen`) -- v1.1.1 - -- INDEX `entry_feed_read_index` (`id_feed`,`is_read`) -- v1.7 Located futher down + INDEX `entry_lastSeen_index` (`lastSeen`), -- v1.1.1 + INDEX `entry_feed_read_index` (`id_feed`,`is_read`) -- v1.7 ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = INNODB; INSERT IGNORE INTO `_category` (id, name) VALUES(1, "Uncategorized"); SQL; +const SQL_CREATE_INDEX_ENTRY_1 = <<<'SQL' +CREATE INDEX `entry_feed_read_index` ON `_entry` (`id_feed`,`is_read`); -- v1.7 +SQL; + const SQL_CREATE_TABLE_ENTRYTMP = <<<'SQL' CREATE TABLE IF NOT EXISTS `_entrytmp` ( -- v1.7 `id` BIGINT NOT NULL, @@ -86,9 +90,6 @@ CREATE TABLE IF NOT EXISTS `_entrytmp` ( -- v1.7 INDEX (`date`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = INNODB; - --- v1.7 Located here to be auto-added -CREATE INDEX `entry_feed_read_index` ON `_entry`(`id_feed`,`is_read`); SQL; const SQL_CREATE_TABLE_TAGS = <<<'SQL' diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php index b37ff9f90..d77fe917b 100644 --- a/app/SQL/install.sql.pgsql.php +++ b/app/SQL/install.sql.pgsql.php @@ -52,6 +52,7 @@ CREATE TABLE IF NOT EXISTS `_entry` ( CREATE INDEX IF NOT EXISTS `_is_favorite_index` ON `_entry` ("is_favorite"); CREATE INDEX IF NOT EXISTS `_is_read_index` ON `_entry` ("is_read"); CREATE INDEX IF NOT EXISTS `_entry_lastSeen_index` ON `_entry` ("lastSeen"); +CREATE INDEX IF NOT EXISTS `_entry_feed_read_index` ON `_entry` ("id_feed","is_read"); -- v1.7 INSERT INTO `_category` (id, name) SELECT 1, 'Uncategorized' @@ -59,6 +60,10 @@ INSERT INTO `_category` (id, name) RETURNING nextval('`_category_id_seq`'); SQL; +const SQL_CREATE_INDEX_ENTRY_1 = <<<'SQL' +CREATE INDEX IF NOT EXISTS `_entry_feed_read_index` ON `_entry` ("id_feed","is_read"); -- v1.7 +SQL; + const SQL_CREATE_TABLE_ENTRYTMP = <<<'SQL' CREATE TABLE IF NOT EXISTS `_entrytmp` ( -- v1.7 "id" BIGINT NOT NULL PRIMARY KEY, @@ -78,9 +83,6 @@ CREATE TABLE IF NOT EXISTS `_entrytmp` ( -- v1.7 UNIQUE ("id_feed","guid") ); CREATE INDEX IF NOT EXISTS `_entrytmp_date_index` ON `_entrytmp` ("date"); - --- v1.7 -CREATE INDEX IF NOT EXISTS `_entry_feed_read_index` ON `_entry` ("id_feed","is_read"); SQL; const SQL_CREATE_TABLE_TAGS = <<<'SQL' diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php index 81370ce56..d3a5e0697 100644 --- a/app/SQL/install.sql.sqlite.php +++ b/app/SQL/install.sql.sqlite.php @@ -55,10 +55,15 @@ CREATE TABLE IF NOT EXISTS `entry` ( CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `entry`(`is_favorite`); CREATE INDEX IF NOT EXISTS entry_is_read_index ON `entry`(`is_read`); CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `entry`(`lastSeen`); -- //v1.1.1 +CREATE INDEX IF NOT EXISTS entry_feed_read_index ON `entry`(`id_feed`,`is_read`); -- v1.7 INSERT OR IGNORE INTO `category` (id, name) VALUES(1, "Uncategorized"); SQL; +const SQL_CREATE_INDEX_ENTRY_1 = <<<'SQL' +CREATE INDEX IF NOT EXISTS entry_feed_read_index ON `entry`(`id_feed`,`is_read`); -- v1.7 +SQL; + const SQL_CREATE_TABLE_ENTRYTMP = <<<'SQL' CREATE TABLE IF NOT EXISTS `entrytmp` ( -- v1.7 `id` BIGINT NOT NULL, @@ -79,9 +84,6 @@ CREATE TABLE IF NOT EXISTS `entrytmp` ( -- v1.7 UNIQUE (`id_feed`,`guid`) ); CREATE INDEX IF NOT EXISTS entrytmp_date_index ON `entrytmp`(`date`); - --- v1.7 -CREATE INDEX IF NOT EXISTS `entry_feed_read_index` ON `entry`(`id_feed`,`is_read`); SQL; const SQL_CREATE_TABLE_TAGS = <<<'SQL' @@ -98,7 +100,7 @@ CREATE TABLE IF NOT EXISTS `entrytag` ( FOREIGN KEY (`id_tag`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`id_entry`) REFERENCES `entry` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ); -CREATE INDEX entrytag_id_entry_index ON `entrytag` (`id_entry`); +CREATE INDEX IF NOT EXISTS entrytag_id_entry_index ON `entrytag` (`id_entry`); SQL; const SQL_INSERT_FEED = <<<'SQL' -- cgit v1.2.3 From cc0db9af4f980829faa4bf0960617807b32fb4fa Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Wed, 23 Oct 2019 00:52:15 +0200 Subject: Feature/new archiving (#2335) * Change archiving config page layout I've changed some wording and moved actions into a maintenance section. * Update purge action Now we have more control on the purge action. The configuration allows us to choose what to keep and what to discard in a more precise way. At the moment, the configuration applies for all feeds. * Add purge configuration on feed level Now the extend purge configuration is available on feed level. It is stored as attributes and will be used in the purge action. * Update purge action Now the purge action uses the feed configuration if it exists and defaults on user configuration if not. * Add empty option in period list * Fix configuration warnings * Add archiving configuration on categories See #2369 * Add user info back * Add explanations in UI * Fixes for SQLite + error + misc. * Fix invalid feed reference * Short array syntax Only for new code, so far * Fix prefix error * Query performance, default values Work in progress * Fix default values and confirm before leaving Form cancel and confirm changes before leaving were broken. And start taking advantage of the short echo syntax `` as we have moved to PHP 5.4+ * More work * Tuning SQL * Fix MariaDB + performance issue * SQL performance * Fix SQLite bug * Fix some attributes JSON encoding bugs Especially for SQLite export/import * More uniform, fix bugs More uniform between global, category, feed settings * Drop special cases for old articles during refresh Instead will use lastSeen date with the new archiving logic. This was generating problems anyway https://github.com/FreshRSS/FreshRSS/issues/2154 * Draft drop index keep_history Not needed anymore * MySQL typo Now properly tested with MySQL, PostgreSQL, SQLite * More work for legacy values Important to avoid overriding user's preference and risking deleting data erroneously * Fix PHP 7.3 / 7.4 warnings @aledeg "Trying to use values of type null, bool, int, float or resource as an array (such as $null["key"]) will now generate a notice. " https://php.net/migration74.incompatible * Reintroduce min articles and take care of legacy parameters * A few changes forgotten * Draft of migration + DROP of feed.keep_history * Fix several errors And give up using const for SQL to allow multiple database types (and we cannot redefine a const) * Add keep_min to categories + factorise archiving logic * Legacy fix * Fix bug yield from * Minor: Use JSON_UNESCAPED_SLASHE for attributes And make more uniform * Fix sign and missing variable * Fine tune the logic --- app/Controllers/configureController.php | 43 +++++++++- app/Controllers/entryController.php | 20 +---- app/Controllers/feedController.php | 24 +----- app/Controllers/subscriptionController.php | 58 +++++++++++++- app/Models/Category.php | 24 ++++++ app/Models/CategoryDAO.php | 122 ++++++++++++++++++++++++++--- app/Models/CategoryDAOSQLite.php | 17 ++++ app/Models/ConfigurationSetter.php | 10 --- app/Models/Context.php | 19 +++++ app/Models/DatabaseDAO.php | 12 +-- app/Models/DatabaseDAOSQLite.php | 2 +- app/Models/EntryDAO.php | 65 ++++++++++----- app/Models/Factory.php | 8 +- app/Models/Feed.php | 38 ++++++--- app/Models/FeedDAO.php | 20 ++--- app/Models/FeedDAOSQLite.php | 2 +- app/Models/Tag.php | 2 +- app/Models/TagDAO.php | 14 +++- app/Models/UserDAO.php | 11 ++- app/SQL/install.sql.mysql.php | 21 +++-- app/SQL/install.sql.pgsql.php | 19 +++-- app/SQL/install.sql.sqlite.php | 17 ++-- app/i18n/cz/conf.php | 12 ++- app/i18n/cz/gen.php | 7 ++ app/i18n/cz/sub.php | 3 +- app/i18n/de/conf.php | 12 ++- app/i18n/de/gen.php | 7 ++ app/i18n/de/sub.php | 3 +- app/i18n/en/conf.php | 12 ++- app/i18n/en/gen.php | 7 ++ app/i18n/en/sub.php | 3 +- app/i18n/es/conf.php | 12 ++- app/i18n/es/gen.php | 7 ++ app/i18n/es/sub.php | 3 +- app/i18n/fr/conf.php | 12 ++- app/i18n/fr/gen.php | 7 ++ app/i18n/fr/sub.php | 3 +- app/i18n/he/conf.php | 12 ++- app/i18n/he/gen.php | 7 ++ app/i18n/he/sub.php | 3 +- app/i18n/it/conf.php | 12 ++- app/i18n/it/gen.php | 7 ++ app/i18n/it/sub.php | 3 +- app/i18n/kr/conf.php | 12 ++- app/i18n/kr/gen.php | 7 ++ app/i18n/kr/sub.php | 3 +- app/i18n/nl/conf.php | 16 +++- app/i18n/nl/gen.php | 7 ++ app/i18n/nl/sub.php | 3 +- app/i18n/oc/conf.php | 11 ++- app/i18n/oc/gen.php | 7 ++ app/i18n/oc/sub.php | 3 +- app/i18n/pt-br/conf.php | 12 ++- app/i18n/pt-br/gen.php | 7 ++ app/i18n/pt-br/sub.php | 3 +- app/i18n/ru/conf.php | 14 +++- app/i18n/ru/gen.php | 7 ++ app/i18n/ru/sub.php | 3 +- app/i18n/sk/conf.php | 2 +- app/i18n/sk/sub.php | 2 +- app/i18n/tr/conf.php | 12 ++- app/i18n/tr/gen.php | 7 ++ app/i18n/tr/sub.php | 3 +- app/i18n/zh-cn/conf.php | 12 ++- app/i18n/zh-cn/gen.php | 7 ++ app/i18n/zh-cn/sub.php | 3 +- app/install.php | 20 +---- app/views/configure/archiving.phtml | 110 ++++++++++++++++++++------ app/views/helpers/category/update.phtml | 116 +++++++++++++++++++++++++++ app/views/helpers/feed/update.phtml | 113 ++++++++++++++++++++++++-- cli/_update-or-create-user.php | 4 +- config-user.default.php | 10 ++- lib/Minz/Request.php | 6 ++ lib/lib_rss.php | 6 +- p/scripts/category.js | 23 ++++++ p/scripts/extra.js | 38 ++++++--- p/themes/base-theme/template.css | 3 + phpcs.xml | 5 -- 78 files changed, 1062 insertions(+), 277 deletions(-) create mode 100644 app/Models/CategoryDAOSQLite.php (limited to 'app/Models/UserDAO.php') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 85ca9da39..b38d3289a 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -196,9 +196,31 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ public function archivingAction() { if (Minz_Request::isPost()) { - FreshRSS_Context::$user_conf->old_entries = Minz_Request::param('old_entries', 3); - FreshRSS_Context::$user_conf->keep_history_default = Minz_Request::param('keep_history_default', 0); + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace('1', Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', FreshRSS_Feed::TTL_DEFAULT); + FreshRSS_Context::$user_conf->archiving = [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => Minz_Request::param('keep_min_default', 0), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]; + FreshRSS_Context::$user_conf->keep_history_default = null; //Legacy < FreshRSS 1.15 + FreshRSS_Context::$user_conf->old_entries = null; //Legacy < FreshRSS 1.15 FreshRSS_Context::$user_conf->save(); invalidateHttpCache(); @@ -206,7 +228,20 @@ class FreshRSS_configure_Controller extends Minz_ActionController { array('c' => 'configure', 'a' => 'archiving')); } - Minz_View::prependTitle(_t('conf.archiving.title') . ' · '); + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + $keepPeriod = FreshRSS_Context::$user_conf->archiving['keep_period']; + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $keepPeriod, $matches)) { + $volatile = [ + 'enable_keep_period' => true, + 'keep_period_count' => $matches['count'], + 'keep_period_unit' => str_replace($matches['count'], 1, $keepPeriod), + ]; + } + FreshRSS_Context::$user_conf->volatile = $volatile; $entryDAO = FreshRSS_Factory::createEntryDao(); $this->view->nb_total = $entryDAO->count(); @@ -217,6 +252,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { if (FreshRSS_Auth::hasAccess('admin')) { $this->view->size_total = $databaseDAO->size(true); } + + Minz_View::prependTitle(_t('conf.archiving.title') . ' · '); } /** diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index 0215128f4..7881cb3ec 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -181,32 +181,20 @@ class FreshRSS_entry_Controller extends Minz_ActionController { public function purgeAction() { @set_time_limit(300); - $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - $entryDAO = FreshRSS_Factory::createEntryDao(); $feedDAO = FreshRSS_Factory::createFeedDao(); $feeds = $feedDAO->listFeeds(); $nb_total = 0; invalidateHttpCache(); - foreach ($feeds as $feed) { - $feed_history = $feed->keepHistory(); - if (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) { - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } + $feedDAO->beginTransaction(); - if ($feed_history >= 0) { - $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, $feed_history); - if ($nb > 0) { - $nb_total += $nb; - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']'); - } - } + foreach ($feeds as $feed) { + $nb_total += $feed->cleanOldEntries(); } $feedDAO->updateCachedValues(); + $feedDAO->commit(); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); $databaseDAO->minorDbMaintenance(); diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index ea07d96e4..aabeb80ff 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -267,10 +267,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $maxFeeds = 10; } - // Calculate date of oldest entries we accept in DB. - $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - // WebSub (PubSubHubbub) support $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled; $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration. @@ -323,12 +319,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } - $feed_history = $feed->keepHistory(); - if ($isNewFeed) { - $feed_history = FreshRSS_Feed::KEEP_HISTORY_INFINITE; - } elseif (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) { - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } $needFeedCacheRefresh = false; // We want chronological order and SimplePie uses reverse order. @@ -376,15 +366,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } $entryDAO->updateEntry($entry->toArray()); } - } elseif ($feed_history == 0 && $entry_date < $date_min) { - // This entry should not be added considering configuration and date. - $oldGuids[] = $entry->guid(); } else { $id = uTimeString(); $entry->_id($id); - if ($entry_date < $date_min) { - $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read - } $entry->applyFilterActions(); @@ -413,17 +397,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->updateLastSeen($feed->id(), $oldGuids, $mtime); } - if ($feed_history >= 0 && mt_rand(0, 30) === 1) { - // TODO: move this function in web cron when available (see entry::purge) - // Remove old entries once in 30. + if (mt_rand(0, 30) === 1) { // Remove old entries once in 30. if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } - - $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10)); + $nb = $feed->cleanOldEntries(); if ($nb > 0) { $needFeedCacheRefresh = true; - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']'); } } diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php index f6d5e9457..f9497f0be 100644 --- a/app/Controllers/subscriptionController.php +++ b/app/Controllers/subscriptionController.php @@ -121,6 +121,32 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $feed->_attributes('timeout', null); } + if (Minz_Request::paramBoolean('use_default_purge_options')) { + $feed->_attributes('archiving', null); + } else { + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + $feed->_attributes('archiving', [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => intval(Minz_Request::param('keep_min', 0)), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]); + } + $feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', ''))); $values = array( @@ -132,7 +158,6 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { 'pathEntries' => Minz_Request::param('path_entries', ''), 'priority' => intval(Minz_Request::param('priority', FreshRSS_Feed::PRIORITY_MAIN_STREAM)), 'httpAuth' => $httpAuth, - 'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)), 'ttl' => $ttl * ($mute ? -1 : 1), 'attributes' => $feed->attributes(), ); @@ -165,9 +190,36 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $this->view->category = $category; if (Minz_Request::isPost()) { - $values = array( + if (Minz_Request::paramBoolean('use_default_purge_options')) { + $category->_attributes('archiving', null); + } else { + if (!Minz_Request::paramBoolean('enable_keep_max')) { + $keepMax = false; + } elseif (!$keepMax = Minz_Request::param('keep_max')) { + $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT; + } + if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) { + $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD; + if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) { + $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit')); + } + } else { + $keepPeriod = false; + } + $category->_attributes('archiving', [ + 'keep_period' => $keepPeriod, + 'keep_max' => $keepMax, + 'keep_min' => intval(Minz_Request::param('keep_min', 0)), + 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'), + 'keep_labels' => Minz_Request::paramBoolean('keep_labels'), + 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'), + ]); + } + + $values = [ 'name' => Minz_Request::param('name', ''), - ); + 'attributes' => $category->attributes(), + ]; invalidateHttpCache(); diff --git a/app/Models/Category.php b/app/Models/Category.php index 29c0e586b..a0ee1ddaa 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -8,6 +8,7 @@ class FreshRSS_Category extends Minz_Model { private $feeds = null; private $hasFeedsWithError = false; private $isDefault = false; + private $attributes = []; public function __construct($name = '', $feeds = null) { $this->_name($name); @@ -68,6 +69,14 @@ class FreshRSS_Category extends Minz_Model { return $this->hasFeedsWithError; } + public function attributes($key = '') { + if ($key == '') { + return $this->attributes; + } else { + return isset($this->attributes[$key]) ? $this->attributes[$key] : null; + } + } + public function _id($id) { $this->id = $id; if ($id == FreshRSS_CategoryDAO::DEFAULTCATEGORYID) { @@ -87,4 +96,19 @@ class FreshRSS_Category extends Minz_Model { $this->feeds = $values; } + + public function _attributes($key, $value) { + if ($key == '') { + if (is_string($value)) { + $value = json_decode($value, true); + } + if (is_array($value)) { + $this->attributes = $value; + } + } elseif ($value === null) { + unset($this->attributes[$key]); + } else { + $this->attributes[$key] = $value; + } + } } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index dd49b542d..1b8717e83 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -4,15 +4,81 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable const DEFAULTCATEGORYID = 1; + protected function addColumn($name) { + Minz_Log::warning(__method__ . ': ' . $name); + try { + if ('attributes' === $name) { //v1.15.0 + $ok = $this->pdo->exec('ALTER TABLE `_category` ADD COLUMN attributes TEXT') !== false; + + $stm = $this->pdo->query('SELECT * FROM `_feed`'); + $feeds = $stm->fetchAll(PDO::FETCH_ASSOC); + + $stm = $this->pdo->prepare('UPDATE `_feed` SET attributes = :attributes WHERE id = :id'); + foreach ($feeds as $feed) { + if (empty($feed['keep_history']) || empty($feed['id'])) { + continue; + } + $keepHistory = $feed['keep_history']; + $attributes = empty($feed['attributes']) ? [] : json_decode($feed['attributes'], true); + if (is_string($attributes)) { //Legacy risk of double-encoding + $attributes = json_decode($attributes, true); + } + if (!is_array($attributes)) { + $attributes = []; + } + if ($keepHistory > 0) { + $attributes['archiving']['keep_min'] = intval($keepHistory); + } elseif ($keepHistory == -1) { //Infinite + $attributes['archiving']['keep_period'] = false; + $attributes['archiving']['keep_max'] = false; + $attributes['archiving']['keep_min'] = false; + } else { + continue; + } + $stm->bindValue(':id', $feed['id'], PDO::PARAM_INT); + $stm->bindValue(':attributes', json_encode($attributes, JSON_UNESCAPED_SLASHES)); + $stm->execute(); + } + + if ($this->pdo->dbType() !== 'sqlite') { //SQLite does not support DROP COLUMN + $this->pdo->exec('ALTER TABLE `_feed` DROP COLUMN keep_history'); + } else { + $this->pdo->exec('DROP INDEX IF EXISTS feed_keep_history_index'); //SQLite at least drop index + } + return $ok; + } + } catch (Exception $e) { + Minz_Log::error(__method__ . ': ' . $e->getMessage()); + } + return false; + } + + protected function autoUpdateDb($errorInfo) { + if (isset($errorInfo[0])) { + if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) { + foreach (['attributes'] as $column) { + if (stripos($errorInfo[2], $column) !== false) { + return $this->addColumn($column); + } + } + } + } + return false; + } + public function addCategory($valuesTmp) { - $sql = 'INSERT INTO `_category`(name) ' - . 'SELECT * FROM (SELECT TRIM(?)) c2 ' //TRIM() to provide a type hint as text for PostgreSQL + $sql = 'INSERT INTO `_category`(name, attributes) ' + . 'SELECT * FROM (SELECT TRIM(?), ?) c2 ' //TRIM() to provide a type hint as text for PostgreSQL . 'WHERE NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = TRIM(?))'; //No tag of the same name $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, '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), $valuesTmp['name'], ); @@ -20,7 +86,10 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable return $this->pdo->lastInsertId('`_category_id_seq`'); } else { $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); - Minz_Log::error('SQL error addCategory: ' . $info[2]); + if ($this->autoUpdateDb($info)) { + return $this->addCategory($valuesTmp); + } + Minz_Log::error('SQL error addCategory: ' . json_encode($info)); return false; } } @@ -39,13 +108,17 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function updateCategory($id, $valuesTmp) { - $sql = 'UPDATE `_category` SET name=? WHERE id=? ' + $sql = 'UPDATE `_category` SET name=?, attributes=? WHERE id=? ' . 'AND NOT EXISTS (SELECT 1 FROM `_tag` WHERE name = ?)'; //No tag of the same name $stm = $this->pdo->prepare($sql); $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, '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'], ); @@ -54,7 +127,10 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable return $stm->rowCount(); } else { $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); - Minz_Log::error('SQL error updateCategory: ' . $info[2]); + if ($this->autoUpdateDb($info)) { + return $this->updateCategory($valuesTmp); + } + Minz_Log::error('SQL error updateCategory: ' . json_encode($info)); return false; } } @@ -70,16 +146,27 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable return $stm->rowCount(); } else { $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); - Minz_Log::error('SQL error deleteCategory: ' . $info[2]); + Minz_Log::error('SQL error deleteCategory: ' . json_encode($info)); return false; } } public function selectAll() { - $sql = 'SELECT id, name FROM `_category`'; + $sql = 'SELECT id, name, attributes FROM `_category`'; $stm = $this->pdo->query($sql); - while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { - yield $row; + if ($stm != false) { + while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { + yield $row; + } + } else { + $info = $this->pdo->errorInfo(); + if ($this->autoUpdateDb($info)) { + foreach ($this->selectAll() as $category) { // `yield from` requires PHP 7+ + yield $category; + } + } + Minz_Log::error(__method__ . ' error: ' . json_encode($info)); + return false; } } @@ -116,7 +203,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable public function listCategories($prePopulateFeeds = true, $details = false) { if ($prePopulateFeeds) { - $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' + $sql = 'SELECT c.id AS c_id, c.name AS c_name, c.attributes AS c_attributes, ' . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads`, f.ttl ') . 'FROM `_category` c ' . 'LEFT OUTER JOIN `_feed` f ON f.category=c.id ' @@ -124,9 +211,17 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable . 'GROUP BY f.id, c_id ' . 'ORDER BY c.name, f.name'; $stm = $this->pdo->prepare($sql); - $stm->bindValue(':priority_normal', FreshRSS_Feed::PRIORITY_NORMAL, PDO::PARAM_INT); - $stm->execute(); - return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); + $values = [ ':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL ]; + if ($stm && $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)); + return false; + } } else { $sql = 'SELECT * FROM `_category` ORDER BY name'; $stm = $this->pdo->query($sql); @@ -282,6 +377,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $dao['name'] ); $cat->_id($dao['id']); + $cat->_attributes('', isset($dao['attributes']) ? $dao['attributes'] : ''); $cat->_isDefault(static::DEFAULTCATEGORYID === intval($dao['id'])); $list[$key] = $cat; } diff --git a/app/Models/CategoryDAOSQLite.php b/app/Models/CategoryDAOSQLite.php new file mode 100644 index 000000000..e32545c90 --- /dev/null +++ b/app/Models/CategoryDAOSQLite.php @@ -0,0 +1,17 @@ +pdo->query("PRAGMA table_info('category')")) { + $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1); + foreach (['attributes'] as $column) { + if (!in_array($column, $columns)) { + return $this->addColumn($column); + } + } + } + return false; + } + +} diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php index 963d37e2b..b1d271f41 100644 --- a/app/Models/ConfigurationSetter.php +++ b/app/Models/ConfigurationSetter.php @@ -79,11 +79,6 @@ class FreshRSS_ConfigurationSetter { $data['html5_notif_timeout'] = $value >= 0 ? $value : 0; } - private function _keep_history_default(&$data, $value) { - $value = intval($value); - $data['keep_history_default'] = $value >= FreshRSS_Feed::KEEP_HISTORY_INFINITE ? $value : 0; - } - // It works for system config too! private function _language(&$data, $value) { $value = strtolower($value); @@ -94,11 +89,6 @@ class FreshRSS_ConfigurationSetter { $data['language'] = $value; } - private function _old_entries(&$data, $value) { - $value = intval($value); - $data['old_entries'] = $value > 0 ? $value : 3; - } - private function _passwordHash(&$data, $value) { $data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } diff --git a/app/Models/Context.php b/app/Models/Context.php index 95dc47c8c..878b72c69 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -51,6 +51,25 @@ class FreshRSS_Context { // Init configuration. self::$system_conf = Minz_Configuration::get('system'); self::$user_conf = Minz_Configuration::get('user'); + + //Legacy + $oldEntries = (int)FreshRSS_Context::$user_conf->param('old_entries', 0); + if ($oldEntries > 0) { //Freshrss < 1.15 + $archiving['keep_period'] = 'P' . $oldEntries . 'M'; + } + + $keepMin = (int)FreshRSS_Context::$user_conf->param('keep_history_default', -5); + if ($keepMin != 0 && $keepMin > -5) { //Freshrss < 1.15 + $archiving = FreshRSS_Context::$user_conf->archiving; + if ($keepMin > 0) { + $archiving['keep_min'] = $keepMin; + } elseif ($keepMin == -1) { //Infinite + $archiving['keep_period'] = false; + $archiving['keep_max'] = false; + $archiving['keep_min'] = false; + } + FreshRSS_Context::$user_conf->archiving = $archiving; + } } /** diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php index f6cd2f756..a36b469b1 100644 --- a/app/Models/DatabaseDAO.php +++ b/app/Models/DatabaseDAO.php @@ -15,11 +15,11 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { const LENGTH_INDEX_UNICODE = 191; public function create() { - require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); $db = FreshRSS_Context::$system_conf->db; try { - $sql = sprintf(SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']); + $sql = sprintf($SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']); return $this->pdo->exec($sql) !== false; } catch (PDOException $e) { $_SESSION['bd_error'] = $e->getMessage(); @@ -86,7 +86,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function feedIsCorrect() { return $this->checkTable('feed', array( 'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate', - 'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', 'attributes', + 'priority', 'pathEntries', 'httpAuth', 'error', 'ttl', 'attributes', 'cache_nbEntries', 'cache_nbUnreads', )); } @@ -164,11 +164,11 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { public function ensureCaseInsensitiveGuids() { $ok = true; if ($this->pdo->dbType() === 'mysql') { - include_once(APP_PATH . '/SQL/install.sql.mysql.php'); + include(APP_PATH . '/SQL/install.sql.mysql.php'); $ok = false; try { - $ok = $this->pdo->exec(SQL_UPDATE_GUID_LATIN1_BIN) !== false; //FreshRSS 1.12 + $ok = $this->pdo->exec($SQL_UPDATE_GUID_LATIN1_BIN) !== false; //FreshRSS 1.12 } catch (Exception $e) { $ok = false; Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage()); @@ -243,7 +243,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { Minz_ModelPdo::clean(); $userDAOSQLite = new FreshRSS_UserDAO('', $sqlite); - $categoryDAOSQLite = new FreshRSS_CategoryDAO('', $sqlite); + $categoryDAOSQLite = new FreshRSS_CategoryDAOSQLite('', $sqlite); $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', $sqlite); $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', $sqlite); $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', $sqlite); diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php index b1473ab09..413e7ee09 100644 --- a/app/Models/DatabaseDAOSQLite.php +++ b/app/Models/DatabaseDAOSQLite.php @@ -66,6 +66,6 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { } public function optimize() { - return $this->exec('VACUUM') !== false; + return $this->pdo->exec('VACUUM') !== false; } } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 5ff3a5b70..6a8a25b3e 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -26,9 +26,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $this->pdo->commit(); } try { - require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL CREATE TABLE entrytmp...'); - $ok = $this->pdo->exec(SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_INDEX_ENTRY_1) !== false; + $ok = $this->pdo->exec($SQL_CREATE_TABLE_ENTRYTMP . $SQL_CREATE_INDEX_ENTRY_1) !== false; } catch (Exception $ex) { Minz_Log::error(__method__ . ' error: ' . $ex->getMessage()); } @@ -544,32 +544,57 @@ SQL; return $affected; } - public function cleanOldEntries($id_feed, $date_min, $keep = 15) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after - $sql = 'DELETE FROM `_entry` ' - . 'WHERE id_feed=:id_feed1 AND id<=:id_max ' - . 'AND is_favorite=0 ' //Do not remove favourites - . 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `_entry` e3 WHERE e3.id_feed=:id_feed2) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance - . 'AND id NOT IN (SELECT id_entry FROM `_entrytag`) ' //Do not purge tagged entries - . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `_entry` e2 WHERE e2.id_feed=:id_feed3 ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' - $stm = $this->pdo->prepare($sql); + public function cleanOldEntries($id_feed, $options = []) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after + $sql = 'DELETE FROM `_entry` WHERE id_feed = :id_feed1'; //No alias for MySQL / MariaDB + $params = []; + $params[':id_feed1'] = $id_feed; - if ($stm) { - $id_max = intval($date_min) . '000000'; - $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); - $stm->bindParam(':id_feed1', $id_feed, PDO::PARAM_INT); - $stm->bindParam(':id_feed2', $id_feed, PDO::PARAM_INT); - $stm->bindParam(':id_feed3', $id_feed, PDO::PARAM_INT); - $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + //==Exclusions== + if (!empty($options['keep_favourites'])) { + $sql .= ' AND is_favorite = 0'; + } + if (!empty($options['keep_unreads'])) { + $sql .= ' AND is_read = 1'; + } + if (!empty($options['keep_labels'])) { + $sql .= ' AND NOT EXISTS (SELECT 1 FROM `_entrytag` WHERE id_entry = id)'; + } + if (!empty($options['keep_min']) && $options['keep_min'] > 0) { + $sql .= ' AND `lastSeen` < (SELECT e2.`lastSeen` FROM `_entry` e2 WHERE e2.id_feed = :id_feed2' + . ' ORDER BY e2.`lastSeen` DESC LIMIT 1 OFFSET :keep_min)'; + $params[':id_feed2'] = $id_feed; + $params[':keep_min'] = (int)$options['keep_min']; } + //Keep at least the articles seen at the last refresh + $sql .= ' AND `lastSeen` < (SELECT MAX(e3.`lastSeen`) FROM `_entry` e3 WHERE e3.id_feed = :id_feed3)'; + $params[':id_feed3'] = $id_feed; + + //==Inclusions== + $sql .= ' AND (1=0'; + if (!empty($options['keep_period'])) { + $sql .= ' OR `lastSeen` < :max_last_seen'; + $now = new DateTime('now'); + $now->sub(new DateInterval($options['keep_period'])); + $params[':max_last_seen'] = $now->format('U'); + } + if (!empty($options['keep_max']) && $options['keep_max'] > 0) { + $sql .= ' OR `lastSeen` <= (SELECT e4.`lastSeen` FROM `_entry` e4 WHERE e4.id_feed = :id_feed4' + . ' ORDER BY e4.`lastSeen` DESC LIMIT 1 OFFSET :keep_max)'; + $params[':id_feed4'] = $id_feed; + $params[':keep_max'] = (int)$options['keep_max']; + } + $sql .= ')'; + + $stm = $this->pdo->prepare($sql); - if ($stm && $stm->execute()) { + if ($stm && $stm->execute($params)) { return $stm->rowCount(); } else { $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { - return $this->cleanOldEntries($id_feed, $date_min, $keep); + return $this->cleanOldEntries($id_feed, $options); } - Minz_Log::error('SQL error cleanOldEntries: ' . $info[2]); + Minz_Log::error(__method__ . ' error:' . json_encode($info)); return false; } } diff --git a/app/Models/Factory.php b/app/Models/Factory.php index 6f2ca2217..69885c205 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -7,7 +7,13 @@ class FreshRSS_Factory { } public static function createCategoryDao($username = null) { - return new FreshRSS_CategoryDAO($username); + $conf = Minz_Configuration::get('system'); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_CategoryDAOSQLite($username); + default: + return new FreshRSS_CategoryDAO($username); + } } public static function createFeedDao($username = null) { diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 8aee9d62f..0a45a1f4c 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -7,8 +7,8 @@ class FreshRSS_Feed extends Minz_Model { const TTL_DEFAULT = 0; - const KEEP_HISTORY_DEFAULT = -2; - const KEEP_HISTORY_INFINITE = -1; + const ARCHIVING_RETENTION_COUNT_LIMIT = 10000; + const ARCHIVING_RETENTION_PERIOD = 'P3M'; private $id = 0; private $url; @@ -24,9 +24,8 @@ class FreshRSS_Feed extends Minz_Model { private $pathEntries = ''; private $httpAuth = ''; private $error = false; - private $keep_history = self::KEEP_HISTORY_DEFAULT; private $ttl = self::TTL_DEFAULT; - private $attributes = array(); + private $attributes = []; private $mute = false; private $hash = null; private $lockPath = ''; @@ -110,9 +109,6 @@ class FreshRSS_Feed extends Minz_Model { public function inError() { return $this->error; } - public function keepHistory() { - return $this->keep_history; - } public function ttl() { return $this->ttl; } @@ -230,12 +226,6 @@ class FreshRSS_Feed extends Minz_Model { public function _error($value) { $this->error = (bool)$value; } - public function _keepHistory($value) { - $value = intval($value); - $value = min($value, 1000000); - $value = max($value, self::KEEP_HISTORY_DEFAULT); - $this->keep_history = $value; - } public function _ttl($value) { $value = intval($value); $value = min($value, 100000000); @@ -469,6 +459,28 @@ class FreshRSS_Feed extends Minz_Model { $this->entries = $entries; } + public function cleanOldEntries() { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after + $archiving = $this->attributes('archiving'); + if ($archiving == null) { + $catDAO = FreshRSS_Factory::createCategoryDao(); + $category = $catDAO->searchById($this->category()); + $archiving = $category == null ? null : $category->attributes('archiving'); + if ($archiving == null) { + $archiving = FreshRSS_Context::$user_conf->archiving; + } + } + if (is_array($archiving)) { + $entryDAO = FreshRSS_Factory::createEntryDao(); + $nb = $entryDAO->cleanOldEntries($this->id(), $archiving); + if ($nb > 0) { + $needFeedCacheRefresh = true; + Minz_Log::debug($nb . ' entries cleaned in feed [' . $this->url(false) . '] with: ' . json_encode($archiving)); + } + return $nb; + } + return false; + } + protected function cacheFilename() { return CACHE_PATH . '/' . md5($this->url) . '.spc'; } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index d4a91c145..fa0001df7 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -17,7 +17,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { protected function autoUpdateDb($errorInfo) { if (isset($errorInfo[0])) { if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) { - foreach (array('attributes') as $column) { + foreach (['attributes'] as $column) { if (stripos($errorInfo[2], $column) !== false) { return $this->addColumn($column); } @@ -41,12 +41,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { `pathEntries`, `httpAuth`, error, - keep_history, ttl, attributes ) VALUES - (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; $stm = $this->pdo->prepare($sql); $valuesTmp['url'] = safe_ascii($valuesTmp['url']); @@ -54,6 +53,9 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (!isset($valuesTmp['pathEntries'])) { $valuesTmp['pathEntries'] = ''; } + if (!isset($valuesTmp['attributes'])) { + $valuesTmp['attributes'] = []; + } $values = array( substr($valuesTmp['url'], 0, 511), @@ -66,9 +68,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { mb_strcut($valuesTmp['pathEntries'], 0, 511, 'UTF-8'), base64_encode($valuesTmp['httpAuth']), isset($valuesTmp['error']) ? intval($valuesTmp['error']) : 0, - isset($valuesTmp['keep_history']) ? intval($valuesTmp['keep_history']) : FreshRSS_Feed::KEEP_HISTORY_DEFAULT, isset($valuesTmp['ttl']) ? intval($valuesTmp['ttl']) : FreshRSS_Feed::TTL_DEFAULT, - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), ); if ($stm && $stm->execute($values)) { @@ -135,7 +136,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($key === 'httpAuth') { $valuesTmp[$key] = base64_encode($v); } elseif ($key === 'attributes') { - $valuesTmp[$key] = json_encode($v); + $valuesTmp[$key] = is_string($valuesTmp[$key]) ? $valuesTmp[$key] : json_encode($valuesTmp[$key], JSON_UNESCAPED_SLASHES); } } $set = substr($set, 0, -2); @@ -246,7 +247,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function selectAll() { $sql = 'SELECT id, url, category, name, website, description, `lastUpdate`, priority, ' - . '`pathEntries`, `httpAuth`, error, keep_history, ttl, attributes ' + . '`pathEntries`, `httpAuth`, error, ttl, attributes ' . 'FROM `_feed`'; $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { @@ -319,7 +320,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { */ public function listFeedsOrderUpdate($defaultCacheDuration = 3600, $limit = 0) { $this->updateTTL(); - $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl, attributes ' + $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes ' . 'FROM `_feed` ' . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl >= ' . FreshRSS_Feed::TTL_DEFAULT . ' AND `lastUpdate` < (' . (time() + 60) @@ -407,7 +408,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=:id'; $stm = $this->pdo->prepare($sql); $stm->bindParam(':id', $id, PDO::PARAM_INT); - if (!($stm && $stm->execute($values))) { + if (!($stm && $stm->execute())) { $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); Minz_Log::error('SQL error truncate: ' . $info[2]); $this->pdo->rollBack(); @@ -448,7 +449,6 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : ''); $myFeed->_error(isset($dao['error']) ? $dao['error'] : 0); - $myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : FreshRSS_Feed::KEEP_HISTORY_DEFAULT); $myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : FreshRSS_Feed::TTL_DEFAULT); $myFeed->_attributes('', isset($dao['attributes']) ? $dao['attributes'] : ''); $myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0); diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index c56447df6..0f685867a 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -5,7 +5,7 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { protected function autoUpdateDb($errorInfo) { if ($tableInfo = $this->pdo->query("PRAGMA table_info('feed')")) { $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1); - foreach (array('attributes') as $column) { + foreach (['attributes'] as $column) { if (!in_array($column, $columns)) { return $this->addColumn($column); } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 3eb989cc1..0d50e356c 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -3,7 +3,7 @@ class FreshRSS_Tag extends Minz_Model { private $id = 0; private $name; - private $attributes = array(); + private $attributes = []; private $nbEntries = -1; private $nbUnread = -1; diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 9c0f591c9..5882eee76 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -13,14 +13,14 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $this->pdo->commit(); } try { - require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); Minz_Log::warning('SQL ALTER GUID case sensitivity...'); $databaseDAO = FreshRSS_Factory::createDatabaseDAO(); $databaseDAO->ensureCaseInsensitiveGuids(); Minz_Log::warning('SQL CREATE TABLE tag...'); - $ok = $this->pdo->exec(SQL_CREATE_TABLE_TAGS) !== false; + $ok = $this->pdo->exec($SQL_CREATE_TABLE_TAGS) !== false; } catch (Exception $e) { Minz_Log::error('FreshRSS_EntryDAO::createTagTable error: ' . $e->getMessage()); } @@ -48,9 +48,12 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $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'], - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $valuesTmp['name'], ); @@ -81,9 +84,12 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $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'], - isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '', + is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES), $id, $valuesTmp['name'], ); diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 8e7e977d0..4e824cf01 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -2,14 +2,14 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { public function createUser($insertDefaultFeeds = false) { - require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); try { - $sql = SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS; + $sql = $SQL_CREATE_TABLES . $SQL_CREATE_TABLE_ENTRYTMP . $SQL_CREATE_TABLE_TAGS; $ok = $this->pdo->exec($sql) !== false; //Note: Only exec() can take multiple statements safely. if ($ok && $insertDefaultFeeds) { $default_feeds = FreshRSS_Context::$system_conf->default_feeds; - $stm = $this->pdo->prepare(SQL_INSERT_FEED); + $stm = $this->pdo->prepare($SQL_INSERT_FEED); foreach ($default_feeds as $feed) { $parameters = [ ':url' => $feed['url'], @@ -38,9 +38,8 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { fwrite(STDERR, 'Deleting SQL data for user “' . $this->current_user . "”…\n"); } - require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); - - $ok = $this->pdo->exec(SQL_DROP_TABLES) !== false; + require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php'); + $ok = $this->pdo->exec($SQL_DROP_TABLES) !== false; if ($ok) { return true; diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index 87b5d1989..1eabfae8b 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -1,12 +1,13 @@ array( '_' => 'Archivace', - 'advanced' => 'Pokročilé', 'delete_after' => 'Smazat články starší než', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Více možností je dostupných v nastavení jednotlivých kanálů', - 'keep_history_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Optimalizovat databázi', 'optimize_help' => 'Občasná údržba zmenší velikost databáze', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Vyčistit nyní', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivace', 'ttl' => 'Neaktualizovat častěji než', ), diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php index c6dabd555..de1456187 100644 --- a/app/i18n/cz/gen.php +++ b/app/i18n/cz/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Žádné nové články', 'previous' => 'Předchozí', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index b2bdf416b..eaaff9acd 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Kategorie', 'add' => 'Přidat kategorii', + 'archiving' => 'Archivace', 'empty' => 'Vyprázdit kategorii', 'information' => 'Informace', 'new' => 'Nová kategorie', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informace', - 'keep_history' => 'Zachovat tento minimální počet článků', + 'keep_min' => 'Zachovat tento minimální počet článků', 'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nejsou označeny žádné kanály.', diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php index 99225da9c..89bbfc10e 100644 --- a/app/i18n/de/conf.php +++ b/app/i18n/de/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivierung', - 'advanced' => 'Erweitert', 'delete_after' => 'Entferne Artikel nach', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Weitere Optionen sind in den Einstellungen der individuellen Feeds verfügbar.', - 'keep_history_by_feed' => 'Minimale Anzahl an Artikeln, die pro Feed behalten werden', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimale Anzahl an Artikeln, die pro Feed behalten werden', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Datenbank optimieren', 'optimize_help' => 'Sollte gelegentlich durchgeführt werden, um die Größe der Datenbank zu reduzieren.', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Jetzt bereinigen', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivierung', 'ttl' => 'Aktualisiere automatisch nicht öfter als', ), diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php index 6cc791d5e..e2dd2a251 100644 --- a/app/i18n/de/gen.php +++ b/app/i18n/de/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Es gibt keine weiteren Artikel', 'previous' => 'Vorherige', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index abc01b954..1227b5559 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Kategorie', 'add' => 'Eine Kategorie hinzufügen', + 'archiving' => 'Archivierung', 'empty' => 'Leere Kategorie', 'information' => 'Information', 'new' => 'Neue Kategorie', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Information', - 'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird', + 'keep_min' => 'Minimale Anzahl an Artikeln, die behalten wird', 'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie %s eingefügt.', 'mute' => 'Stumm schalten', 'no_selected' => 'Kein Feed ausgewählt.', diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php index 1078c736c..2d4e06550 100644 --- a/app/i18n/en/conf.php +++ b/app/i18n/en/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archiving', - 'advanced' => 'Advanced', 'delete_after' => 'Remove articles after', + 'exception' => 'Purge exception', 'help' => 'More options are available in the individual feed settings', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_favourites' => 'Never delete favourites', + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_labels' => 'Never delete labels', + 'keep_unreads' => 'Never delete unreads', + 'maintenance' => 'Maintenance', 'optimize' => 'Optimise database', 'optimize_help' => 'Do occasionally to reduce the size of the database', + 'policy' => 'Purge policy', + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', 'purge_now' => 'Purge now', + 'keep_max' => 'Maximum number of articles to keep', + 'keep_period' => 'Maximum age of articles to keep', 'title' => 'Archiving', 'ttl' => 'Do not automatically refresh more often than', ), diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php index a6ddcbb60..fc1bd587a 100644 --- a/app/i18n/en/gen.php +++ b/app/i18n/en/gen.php @@ -163,6 +163,13 @@ return array( 'nothing_to_load' => 'There are no more articles', 'previous' => 'Previous', ), + 'period' => array( + 'days' => 'days', + 'hours' => 'hours', + 'months' => 'months', + 'weeks' => 'weeks', + 'years' => 'years', + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index fde01f9df..04ca793ec 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Category', 'add' => 'Add a category', + 'archiving' => 'Archiving', 'empty' => 'Empty category', 'information' => 'Information', 'new' => 'New category', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', ), 'information' => 'Information', - 'keep_history' => 'Minimum number of articles to keep', + 'keep_min' => 'Minimum number of articles to keep', 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', 'mute' => 'mute', 'no_selected' => 'No feed selected.', diff --git a/app/i18n/es/conf.php b/app/i18n/es/conf.php index 6aaad8d13..7a93a87de 100755 --- a/app/i18n/es/conf.php +++ b/app/i18n/es/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivo', - 'advanced' => 'Avanzado', 'delete_after' => 'Eliminar artículos tras', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Hay más opciones disponibles en los ajustes de la fuente', - 'keep_history_by_feed' => 'Número mínimo de artículos a conservar por fuente', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Número mínimo de artículos a conservar por fuente', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Optimizar la base de datos', 'optimize_help' => 'Ejecuta la optimización de vez en cuando para reducir el tamaño de la base de datos', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Limpiar ahora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivo', 'ttl' => 'No actualizar automáticamente más de', ), diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php index 4affecc51..538ddc8fe 100755 --- a/app/i18n/es/gen.php +++ b/app/i18n/es/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'No hay más artículos', 'previous' => 'Anterior', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php index 7d33c59fa..96be76c6c 100755 --- a/app/i18n/es/sub.php +++ b/app/i18n/es/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Categoría', 'add' => 'Añadir a la categoría', + 'archiving' => 'Archivo', 'empty' => 'Vaciar categoría', 'information' => 'Información', 'new' => 'Nueva categoría', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Información', - 'keep_history' => 'Número mínimo de artículos a conservar', + 'keep_min' => 'Número mínimo de artículos a conservar', 'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'No hay funentes seleccionadas.', diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php index dcd623b5a..020c94085 100644 --- a/app/i18n/fr/conf.php +++ b/app/i18n/fr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archivage', - 'advanced' => 'Avancé', 'delete_after' => 'Supprimer les articles après', + 'exception' => 'Exception de nettoyage', 'help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.', - 'keep_history_by_feed' => 'Nombre minimum d’articles à conserver par flux', + 'keep_favourites' => 'Ne jamais supprimer les articles favoris', + 'keep_min_by_feed' => 'Nombre minimum d’articles à conserver par flux', + 'keep_labels' => 'Ne jamais supprimer les articles étiquetés', + 'keep_unreads' => 'Ne jamais supprimer les articles non lus', + 'maintenance' => 'Maintenance', 'optimize' => 'Optimiser la base de données', 'optimize_help' => 'À faire de temps en temps pour réduire la taille de la BDD', + 'policy' => 'Politique de nettoyage', + 'policy_warning' => 'Si aucune politique de nettoyage n’est sélectionnée, tous les articles seront conservés.', 'purge_now' => 'Purger maintenant', + 'keep_max' => 'Nombre maximum d’articles à conserver', + 'keep_period' => 'Âge maximum des articles à conserver', 'title' => 'Archivage', 'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que', ), diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php index 01b66d316..a6875dd05 100644 --- a/app/i18n/fr/gen.php +++ b/app/i18n/fr/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Fin des articles', 'previous' => 'Précédent', ), + 'period' => array( + 'days' => 'jours', + 'hours' => 'heures', + 'months' => 'mois', + 'weeks' => 'semaines', + 'years' => 'années', + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index df44150c2..d09a19e5a 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Catégorie', 'add' => 'Ajouter une catégorie', + 'archiving' => 'Archivage', 'empty' => 'Catégorie vide', 'information' => 'Informations', 'new' => 'Nouvelle catégorie', @@ -40,7 +41,7 @@ return array( 'help' => 'Écrivez une recherche par ligne.', ), 'information' => 'Informations', - 'keep_history' => 'Nombre minimum d’articles à conserver', + 'keep_min' => 'Nombre minimum d’articles à conserver', 'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans %s.', 'mute' => 'muet', 'no_selected' => 'Aucun flux sélectionné.', diff --git a/app/i18n/he/conf.php b/app/i18n/he/conf.php index 7e764b944..b987f21f4 100644 --- a/app/i18n/he/conf.php +++ b/app/i18n/he/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'ארכוב', - 'advanced' => 'מתקדם', 'delete_after' => 'מחיקת מאמרים לאחר', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'אפשרויות נוספות זמינות בזרמים ספציפיים', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'מיטוב בסיס הנתונים', 'optimize_help' => 'ביצוע לעיתים קרובות על מנת למטב את בסיס הנתונים', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'ניקוי עכשיו', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'ארכוב', 'ttl' => 'אין לרענן אוטומטית יותר מ', ), diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php index 158f11e5b..34e6d77de 100644 --- a/app/i18n/he/gen.php +++ b/app/i18n/he/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'אין מאמרים נוספים', 'previous' => 'הקודם', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php index 8a629defb..15965d9e2 100644 --- a/app/i18n/he/sub.php +++ b/app/i18n/he/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'קטגוריה', 'add' => 'הוספת קטגוריה', + 'archiving' => 'ארכוב', 'empty' => 'Empty category', //TODO - Translation 'information' => 'מידע', 'new' => 'קטגוריה חדשה', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'מידע', - 'keep_history' => 'מסםר מינימלי של מאמרים לשמור', + 'keep_min' => 'מסםר מינימלי של מאמרים לשמור', 'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'אף הזנה לא נבחרה.', diff --git a/app/i18n/it/conf.php b/app/i18n/it/conf.php index f06302c72..4bdaad33d 100644 --- a/app/i18n/it/conf.php +++ b/app/i18n/it/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Archiviazione', - 'advanced' => 'Avanzate', 'delete_after' => 'Rimuovi articoli dopo', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Altre opzioni sono disponibili nelle impostazioni dei singoli feed', - 'keep_history_by_feed' => 'Numero minimo di articoli da mantenere per feed', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Numero minimo di articoli da mantenere per feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Ottimizza database', 'optimize_help' => 'Da fare occasionalmente per ridurre le dimensioni del database', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Cancella ora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archiviazione', 'ttl' => 'Non effettuare aggiornamenti per più di', ), diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php index 604cc6941..50d4b4e6c 100644 --- a/app/i18n/it/gen.php +++ b/app/i18n/it/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Non ci sono altri articoli', 'previous' => 'Precedente', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index 50738d9e3..22cd36986 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Aggiungi una categoria', + 'archiving' => 'Archiviazione', 'empty' => 'Categoria vuota', 'information' => 'Informazioni', 'new' => 'Nuova categoria', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informazioni', - 'keep_history' => 'Numero minimo di articoli da mantenere', + 'keep_min' => 'Numero minimo di articoli da mantenere', 'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nessun feed selezionato.', diff --git a/app/i18n/kr/conf.php b/app/i18n/kr/conf.php index 397d57418..1e77d0098 100644 --- a/app/i18n/kr/conf.php +++ b/app/i18n/kr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => '보관', - 'advanced' => '고급 설정', 'delete_after' => '다음 기간보다 오래된 글 삭제', + 'exception' => 'Purge exception', //TODO - Translation 'help' => '더 자세한 옵션은 개별 피드 설정에 있습니다', - 'keep_history_by_feed' => '피드별 최소 유지 글 개수', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => '피드별 최소 유지 글 개수', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => '데이터베이스 최적화', 'optimize_help' => '데이터베이스 크기를 줄이기 위해 가끔씩 수행해주세요', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => '지금 삭제', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => '보관', 'ttl' => '다음 시간이 지나기 전에 새로고침 금지', ), diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php index 55fea3d66..fdc95d431 100644 --- a/app/i18n/kr/gen.php +++ b/app/i18n/kr/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => '더 이상 글이 없습니다', 'previous' => '이전', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php index f8eccfa27..2586395f2 100644 --- a/app/i18n/kr/sub.php +++ b/app/i18n/kr/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => '카테고리', 'add' => '카테고리 추가', + 'archiving' => '보관', 'empty' => '빈 카테고리', 'information' => '정보', 'new' => '새 카테고리', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => '정보', - 'keep_history' => '최소 유지 글 개수', + 'keep_min' => '최소 유지 글 개수', 'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 %s 아래로 분류됩니다.', 'mute' => '무기한 새로고침 금지', 'no_selected' => '선택된 피드가 없습니다.', diff --git a/app/i18n/nl/conf.php b/app/i18n/nl/conf.php index ec219d051..22302ccc0 100644 --- a/app/i18n/nl/conf.php +++ b/app/i18n/nl/conf.php @@ -1,15 +1,23 @@ array( '_' => 'Archivering', - 'advanced' => 'Geavanceerd', 'delete_after' => 'Verwijder artikelen na', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Meer opties zijn beschikbaar in de persoonlijke stroom instellingen', - 'keep_history_by_feed' => 'Minimum aantal te behouden artikelen in de feed', - 'optimize' => 'Optimaliseer database', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimum aantal te behouden artikelen in de feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation + 'optimize' => 'Optimaliseer database', //TODO - Translation 'optimize_help' => 'Doe dit zo af en toe om de omvang van de database te verkleinen', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Schoon nu op', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archivering', 'ttl' => 'Vernieuw niet automatisch meer dan', ), diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php index 0dcb3010a..4854e806e 100644 --- a/app/i18n/nl/gen.php +++ b/app/i18n/nl/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Er zijn geen artikelen meer', 'previous' => 'Vorige', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'email' => 'Email', 'Known' => 'Known-gebaseerde sites', diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index 8ceb5aa28..6b498132f 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Categorie', 'add' => 'Voeg categorie toe', + 'archiving' => 'Archiveren', 'empty' => 'Lege categorie', 'information' => 'Informatie', 'new' => 'Nieuwe categorie', @@ -40,7 +41,7 @@ return array( 'help' => 'Voer één zoekfilter per lijn in.', ), 'information' => 'Informatie', - 'keep_history' => 'Minimum aantal artikelen om te houden', + 'keep_min' => 'Minimum aantal artikelen om te houden', 'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder %s.', 'mute' => 'demp', 'no_selected' => 'Geen feed geselecteerd.', diff --git a/app/i18n/oc/conf.php b/app/i18n/oc/conf.php index 76c41911e..e8de3b089 100644 --- a/app/i18n/oc/conf.php +++ b/app/i18n/oc/conf.php @@ -5,11 +5,20 @@ return array( '_' => 'Archius', 'advanced' => 'Avançat', 'delete_after' => 'Levar los articles aprèp', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Mai d’opcions son disponiblas dins la configuracion individuala dels fluxes', - 'keep_history_by_feed' => 'Nombre minimum d’articles de servar per flux', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Nombre minimum d’articles de servar per flux', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Optimizar la basa de donada', 'optimize_help' => 'De far de temps en temps per redusir la talha de la basa de donadas', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Purgar ara', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Archius', 'ttl' => 'Actualizar pas automaticament mai sovent que', ), diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php index 7ab56368f..928377997 100644 --- a/app/i18n/oc/gen.php +++ b/app/i18n/oc/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'I a pas mai d’articles', 'previous' => 'Precedent', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index eae9dff29..0f465d7ca 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -12,6 +12,7 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Ajustar una categoria', + 'archiving' => 'Archivar', 'empty' => 'Categoria voida', 'information' => 'Informacions', 'new' => 'Nòva categoria', @@ -39,7 +40,7 @@ return array( 'help' => 'Escrivètz una recèrca per linha.', ), 'information' => 'Informacions', - 'keep_history' => 'Nombre minimum d’articles de servar', + 'keep_min' => 'Nombre minimum d’articles de servar', 'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins %s.', 'mute' => 'mut', 'no_selected' => 'Cap de flux pas seleccionat.', diff --git a/app/i18n/pt-br/conf.php b/app/i18n/pt-br/conf.php index eb067e58a..5e43cc373 100644 --- a/app/i18n/pt-br/conf.php +++ b/app/i18n/pt-br/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Arquivar', - 'advanced' => 'Avançado', 'delete_after' => 'Remover artigos depois', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Mais opções estão disponíveis nas configurações individuais do feed', - 'keep_history_by_feed' => 'Número mínimo de artigos para deixar no feed', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Número mínimo de artigos para deixar no feed', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Otimizar banco de dados', 'optimize_help' => 'Faça ocasionalmente para reduzir o tamanho do banco de dados', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Purge agora', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Arquivar', 'ttl' => 'Não atualize automaticamente mais frequente que', ), diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php index b327937d5..0e7f367ee 100644 --- a/app/i18n/pt-br/gen.php +++ b/app/i18n/pt-br/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Não há mais artigos', 'previous' => 'Anterior', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php index d4bea33c4..c4c28bd6c 100644 --- a/app/i18n/pt-br/sub.php +++ b/app/i18n/pt-br/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Categoria', 'add' => 'Adicionar uma categoria', + 'archiving' => 'Arquivar', 'empty' => 'Categoria vazia', 'information' => 'Informações', 'new' => 'Nova categoria', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Informações', - 'keep_history' => 'Número mínimo de artigos para manter', + 'keep_min' => 'Número mínimo de artigos para manter', 'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como %s.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Nenhum feed selecionado.', diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php index af6f3b5f6..7a80587f8 100644 --- a/app/i18n/ru/conf.php +++ b/app/i18n/ru/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Архивация', - 'advanced' => 'Продвинутые настройки', 'delete_after' => 'Удалять статьи после', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Каждую подписку можно настроить более гибко', - 'keep_history_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Minimum number of articles to keep by feed', //TODO - Translation + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Оптимизировать базу данных', - 'optimize_help' => 'To do occasionally to reduce the size of the database', //TODO - Translation + 'optimize_help' => 'To do occasionally to reduce the size of the database', //TODO - Translation + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Очистить сейчас', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Архивация', 'ttl' => 'Не обновлять чаще чем', ), diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php index cc1d7e00e..5200a7005 100644 --- a/app/i18n/ru/gen.php +++ b/app/i18n/ru/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'There are no more articles', //TODO - Translation 'previous' => 'Previous', //TODO - Translation ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index a2c4e4690..f4bda385d 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Category', //TODO - Translation 'add' => 'Add a category', //TODO - Translation + 'archiving' => 'Archivage', //TODO - Translation 'empty' => 'Empty category', //TODO - Translation 'information' => 'Information', //TODO - Translation 'new' => 'New category', //TODO - Translation @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Information', //TODO - Translation - 'keep_history' => 'Minimum number of articles to keep', //TODO - Translation + 'keep_min' => 'Minimum number of articles to keep', //TODO - Translation 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', //TODO - Translation 'mute' => 'mute', //TODO - Translation 'no_selected' => 'No feed selected.', //TODO - Translation diff --git a/app/i18n/sk/conf.php b/app/i18n/sk/conf.php index f704fd4be..2e2289b79 100644 --- a/app/i18n/sk/conf.php +++ b/app/i18n/sk/conf.php @@ -6,7 +6,7 @@ return array( 'advanced' => 'Pokročilé', 'delete_after' => 'Vymazať články po', 'help' => 'Viac možností nájdete v nastaveniach kanála', - 'keep_history_by_feed' => 'Minimálny počet článkov kanála na zachovanie', + 'keep_min_by_feed' => 'Minimálny počet článkov kanála na zachovanie', 'optimize' => 'Optimalizovať databázu', 'optimize_help' => 'Občas vykonajte na zmenšenie veľkosti databázy', 'purge_now' => 'Vyčistiť teraz', diff --git a/app/i18n/sk/sub.php b/app/i18n/sk/sub.php index 4dcd09f57..2167e1817 100644 --- a/app/i18n/sk/sub.php +++ b/app/i18n/sk/sub.php @@ -40,7 +40,7 @@ return array( 'help' => 'Napíšte jeden výraz hľadania na riadok.', ), 'information' => 'Informácia', - 'keep_history' => 'Minimálny počet článkov na uchovanie', + 'keep_min' => 'Minimálny počet článkov na uchovanie', 'moved_category_deleted' => 'Keď vymažete kategóriu, jej kanály sa automaticky zaradia pod %s.', 'mute' => 'stíšiť', 'no_selected' => 'Nevybrali ste kanál.', diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php index 2bf1e8a6a..c8ea78efa 100644 --- a/app/i18n/tr/conf.php +++ b/app/i18n/tr/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => 'Arşiv', - 'advanced' => 'Gelişmiş', 'delete_after' => 'Makelelerin tutulacağı süre', + 'exception' => 'Purge exception', //TODO - Translation 'help' => 'Akış ayarlarında daha çok ayar bulabilirsiniz', - 'keep_history_by_feed' => 'Akışta en az tutulacak makale sayısı', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => 'Akışta en az tutulacak makale sayısı', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => 'Veritabanı optimize et', 'optimize_help' => 'Bu işlem bazen veritabanı boyutunu düşürmeye yardımcı olur', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => 'Şimdi temizle', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => 'Arşiv', 'ttl' => 'Şu süreden sık otomatik yenileme yapma', ), diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php index 5e361affb..ccc5b9ee6 100644 --- a/app/i18n/tr/gen.php +++ b/app/i18n/tr/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => 'Başka makale yok', 'previous' => 'Önceki', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 858d15758..f6f40d3f7 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => 'Kategori', 'add' => 'Kategori ekle', + 'archiving' => 'Arşiv', 'empty' => 'Boş kategori', 'information' => 'Bilgi', 'new' => 'Yeni kategori', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => 'Bilgi', - 'keep_history' => 'En az tutulacak makale sayısı', + 'keep_min' => 'En az tutulacak makale sayısı', 'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar %s içerisine yerleşir.', 'mute' => 'mute', //TODO - Translation 'no_selected' => 'Hiçbir akış seçilmedi.', diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php index 2960cd6b1..a7404bc58 100644 --- a/app/i18n/zh-cn/conf.php +++ b/app/i18n/zh-cn/conf.php @@ -3,13 +3,21 @@ return array( 'archiving' => array( '_' => '存档', - 'advanced' => '高级', 'delete_after' => '文章保留', + 'exception' => 'Purge exception', //TODO - Translation 'help' => '详细选项位于单独的 RSS 源设置', - 'keep_history_by_feed' => '至少保存的文章数', + 'keep_favourites' => 'Never delete favourites', //TODO - Translation + 'keep_min_by_feed' => '至少保存的文章数', + 'keep_labels' => 'Never delete labels', //TODO - Translation + 'keep_unreads' => 'Never delete unreads', //TODO - Translation + 'maintenance' => 'Maintenance', //TODO - Translation 'optimize' => '优化数据库', 'optimize_help' => '偶尔执行优化可以减少数据库大小', + 'policy' => 'Purge policy', //TODO - Translation + 'policy_warning' => 'If no purge policy is selected, every article will be kept.', //TODO - Translation 'purge_now' => '立即清除', + 'keep_max' => 'Maximum number of articles to keep', //TODO - Translation + 'keep_period' => 'Maximum age of articles to keep', //TODO - Translation 'title' => '存档', 'ttl' => '最小自动更新时间', ), diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php index 7ae156573..31817260e 100644 --- a/app/i18n/zh-cn/gen.php +++ b/app/i18n/zh-cn/gen.php @@ -162,6 +162,13 @@ return array( 'nothing_to_load' => '没有更多文章了', 'previous' => '上一页', ), + 'period' => array( + 'days' => 'days', //TODO - Translation + 'hours' => 'hours', //TODO - Translation + 'months' => 'months', //TODO - Translation + 'weeks' => 'weeks', //TODO - Translation + 'years' => 'years', //TODO - Translation + ), 'share' => array( 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index bf517756b..f6f3a0f7a 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -13,6 +13,7 @@ return array( 'category' => array( '_' => '分类', 'add' => '添加分类', + 'archiving' => '存档', 'empty' => '空分类', 'information' => '信息', 'new' => '新分类', @@ -40,7 +41,7 @@ return array( 'help' => 'Write one search filter per line.', //TODO - Translation ), 'information' => '信息', - 'keep_history' => '至少保存的文章数', + 'keep_min' => '至少保存的文章数', 'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到 %s', 'mute' => '暂停', 'no_selected' => '未选择 RSS 源。', diff --git a/app/install.php b/app/install.php index 366fb0cfc..f8bc6dd4e 100644 --- a/app/install.php +++ b/app/install.php @@ -86,7 +86,6 @@ function saveStep1() { // Then, we set $_SESSION vars $_SESSION['title'] = $system_conf->title; $_SESSION['auth_type'] = $system_conf->auth_type; - $_SESSION['old_entries'] = $user_conf->old_entries; $_SESSION['default_user'] = $current_user; $_SESSION['passwordHash'] = $user_conf->passwordHash; @@ -184,14 +183,12 @@ function saveStep3() { if (!empty($_POST)) { $system_default_config = Minz_Configuration::get('default_system'); $_SESSION['title'] = $system_default_config->title; - $_SESSION['old_entries'] = param('old_entries', $user_default_config->old_entries); $_SESSION['auth_type'] = param('auth_type', 'form'); if (FreshRSS_user_Controller::checkUsername(param('default_user', ''))) { $_SESSION['default_user'] = param('default_user', ''); } - if (empty($_SESSION['old_entries']) || - empty($_SESSION['auth_type']) || + if (empty($_SESSION['auth_type']) || empty($_SESSION['default_user'])) { return false; } @@ -208,10 +205,6 @@ function saveStep3() { FreshRSS_Context::$system_conf->default_user = $_SESSION['default_user']; FreshRSS_Context::$system_conf->save(); - if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { - $_SESSION['old_entries'] = $user_default_config->old_entries; - } - // Create default user files but first, we delete previous data to // avoid access right problems. recursive_unlink(USERS_PATH . '/' . $_SESSION['default_user']); @@ -225,7 +218,6 @@ function saveStep3() { '', [ 'language' => $_SESSION['language'], - 'old_entries' => $_SESSION['old_entries'], ] ); } catch (Exception $e) { @@ -317,8 +309,7 @@ function checkStep2() { } function checkStep3() { - $conf = !empty($_SESSION['old_entries']) && - !empty($_SESSION['default_user']); + $conf = !empty($_SESSION['default_user']); $form = isset($_SESSION['auth_type']); @@ -593,13 +584,6 @@ function printStep3() { -
    - -
    - -
    -
    -
    diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml index 09be55fd9..0387a2b96 100644 --- a/app/views/configure/archiving.phtml +++ b/app/views/configure/archiving.phtml @@ -8,23 +8,6 @@

    -
    - -
    - -   -
    -
    -
    - -
    - () -
    -
    @@ -47,6 +30,76 @@
    +

    + +

    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    @@ -55,9 +108,9 @@
    -
    + + -
    @@ -66,21 +119,30 @@
    -
    -
    - size_total); ?> +
    + -
    +
    + +
    -
    + + +
    + +
    + size_total); ?> +
    +
    +
    diff --git a/app/views/helpers/category/update.phtml b/app/views/helpers/category/update.phtml index b64bd786a..31482f163 100644 --- a/app/views/helpers/category/update.phtml +++ b/app/views/helpers/category/update.phtml @@ -33,5 +33,121 @@
    + + + category->attributes('archiving'); + if (empty($archiving)) { + $archiving = [ 'default' => true ]; + } else { + $archiving['default'] = false; + } + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + if (!empty($archiving['keep_period'])) { + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $archiving['keep_period'], $matches)) { + $volatile['enable_keep_period'] = true; + $volatile['keep_period_count'] = $matches['count']; + $volatile['keep_period_unit'] = str_replace($matches['count'], '1', $archiving['keep_period']); + } + } + //Defaults + if (!isset($archiving['keep_max'])) { + $archiving['keep_max'] = false; + } + if (!isset($archiving['keep_favourites'])) { + $archiving['keep_favourites'] = true; + } + if (!isset($archiving['keep_labels'])) { + $archiving['keep_labels'] = true; + } + if (!isset($archiving['keep_unreads'])) { + $archiving['keep_unreads'] = false; + } + if (!isset($archiving['keep_min'])) { + $archiving['keep_min'] = 50; + } + ?> + +

    + +

    + +
    + +
    + +
    +
    + + + + + + +
    +
    + + +
    +
    diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index 620806d7b..84461ed03 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -78,6 +78,7 @@
    +
    + feed->attributes('archiving'); + if (empty($archiving)) { + $archiving = [ 'default' => true ]; + } else { + $archiving['default'] = false; + } + $volatile = [ + 'enable_keep_period' => false, + 'keep_period_count' => '3', + 'keep_period_unit' => 'P1M', + ]; + if (!empty($archiving['keep_period'])) { + if (preg_match('/^PT?(?P\d+)[YMWDH]$/', $archiving['keep_period'], $matches)) { + $volatile['enable_keep_period'] = true; + $volatile['keep_period_count'] = $matches['count']; + $volatile['keep_period_unit'] = str_replace($matches['count'], '1', $archiving['keep_period']); + } + } + //Defaults + if (!isset($archiving['keep_max'])) { + $archiving['keep_max'] = false; + } + if (!isset($archiving['keep_min'])) { + $archiving['keep_min'] = 50; + } + if (!isset($archiving['keep_favourites'])) { + $archiving['keep_favourites'] = true; + } + if (!isset($archiving['keep_labels'])) { + $archiving['keep_labels'] = true; + } + if (!isset($archiving['keep_unreads'])) { + $archiving['keep_unreads'] = false; + } + ?> + +

    + +

    +
    - +
    - +
    + + + + + + +
    @@ -143,6 +243,7 @@
    +
    diff --git a/cli/_update-or-create-user.php b/cli/_update-or-create-user.php index eda597f19..43b86a4a9 100644 --- a/cli/_update-or-create-user.php +++ b/cli/_update-or-create-user.php @@ -45,8 +45,8 @@ $values = array( 'language' => strParam('language'), 'mail_login' => strParam('email'), 'token' => strParam('token'), - 'old_entries' => intParam('purge_after_months'), - 'keep_history_default' => intParam('feed_min_articles_default'), + 'old_entries' => intParam('purge_after_months'), //TODO: Update with new mechanism + 'keep_history_default' => intParam('feed_min_articles_default'), //TODO: Update with new mechanism 'ttl_default' => intParam('feed_ttl_default'), 'since_hours_posts_per_rss' => intParam('since_hours_posts_per_rss'), 'min_posts_per_rss' => intParam('min_posts_per_rss'), diff --git a/config-user.default.php b/config-user.default.php index 950bef045..5b49d689a 100644 --- a/config-user.default.php +++ b/config-user.default.php @@ -5,8 +5,14 @@ # override. return array ( 'language' => 'en', - 'old_entries' => 3, - 'keep_history_default' => 50, + 'archiving' => [ + 'keep_period' => 'P3M', + 'keep_max' => 200, + 'keep_min' => 50, + 'keep_favourites' => true, + 'keep_labels' => true, + 'keep_unreads' => false, + ], 'ttl_default' => 3600, 'mail_login' => '', 'email_validation_token' => '', diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 01feece52..9235f873a 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -52,6 +52,12 @@ class Minz_Request { } return null; } + public static function paramBoolean($key) { + if (null === $value = self::paramTernary($key)) { + return false; + } + return $value; + } public static function defaultControllerName() { return self::$default_controller_name; } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 854126b54..2a230e6f8 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -300,7 +300,11 @@ function invalidateHttpCache($username = '') { Minz_Session::_param('touch', uTimeString()); $username = Minz_Session::param('currentUser', '_'); } - return touch(join_path(DATA_PATH, 'users', $username, 'log.txt')); + $ok = @touch(DATA_PATH . '/users/' . $username . '/log.txt'); + if (!$ok) { + //TODO: Display notification error on front-end + } + return $ok; } function listUsers() { diff --git a/p/scripts/category.js b/p/scripts/category.js index c01b1fdd7..c5d36e900 100644 --- a/p/scripts/category.js +++ b/p/scripts/category.js @@ -137,11 +137,34 @@ function init_draggable() { }; } +function archiving() { + const slider = document.getElementById('slider'); + slider.addEventListener('change', function (e) { + if (e.target.id === 'use_default_purge_options') { + slider.querySelectorAll('.archiving').forEach(function (element) { + element.hidden = e.target.checked; + }); + } + }); + slider.addEventListener('click', function (e) { + if (e.target.closest('button[type=reset]')) { + const archiving = document.getElementById('use_default_purge_options'); + if (archiving) { + slider.querySelectorAll('.archiving').forEach(function (element) { + element.hidden = archiving.getAttribute('data-leave-validation') == 1; + }); + } + } + }); +} + if (document.readyState && document.readyState !== 'loading') { init_draggable(); + archiving(); } else if (document.addEventListener) { document.addEventListener('DOMContentLoaded', function () { init_draggable(); + archiving(); }, false); } // @license-end diff --git a/p/scripts/extra.js b/p/scripts/extra.js index bba2e8e2b..1fd8a19de 100644 --- a/p/scripts/extra.js +++ b/p/scripts/extra.js @@ -184,12 +184,32 @@ function init_slider_observers() { }; closer.onclick = function (ev) { - closer.classList.remove('active'); - slider.classList.remove('active'); - return false; + if (data_leave_validation() || confirm(context.i18n.confirmation_default)) { + slider.querySelectorAll('form').forEach(function (f) { f.reset(); }); + closer.classList.remove('active'); + slider.classList.remove('active'); + return true; + } else { + return false; + } }; } +function data_leave_validation() { + const ds = document.querySelectorAll('[data-leave-validation]'); + for (let i = ds.length - 1; i >= 0; i--) { + const input = ds[i]; + if (input.type === 'checkbox' || input.type === 'radio') { + if (input.checked != input.getAttribute('data-leave-validation')) { + return false; + } + } else if (input.value != input.getAttribute('data-leave-validation')) { + return false; + } + } + return true; +} + function init_configuration_alert() { window.onsubmit = function (e) { window.hasSubmit = true; @@ -198,16 +218,8 @@ function init_configuration_alert() { if (window.hasSubmit) { return; } - const ds = document.querySelectorAll('[data-leave-validation]'); - for (let i = ds.length - 1; i >= 0; i--) { - const input = ds[i]; - if (input.type === 'checkbox' || input.type === 'radio') { - if (input.checked != input.getAttribute('data-leave-validation')) { - return false; - } - } else if (input.value != input.getAttribute('data-leave-validation')) { - return false; - } + if (!data_leave_validation()) { + return false; } }; } diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index 889d33c4e..2d76c9b4d 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -101,6 +101,9 @@ label { input { width: 180px; } +input[type=number] { + width: 6em; +} textarea, input[type="file"], diff --git a/phpcs.xml b/phpcs.xml index c69f53ea4..fba5624a8 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,8 +3,6 @@ Created with the PHP Coding Standard Generator. https://edorian.github.com/php-coding-standard-generator/ - ./static - ./vendor ./lib/SimplePie/ ./lib/PHPMailer/ ./lib/http-conditional.php @@ -28,9 +26,6 @@ ./app/install.php ./tests/app/ - - ./app/SQL/install.sql.mysql.php - ./app/SQL/install.sql.pgsql.php -- cgit v1.2.3